mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-16 10:43:35 +08:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
262aeba7e2 | |||
61c2fd5412 | |||
d542208eb8 | |||
f818200c95 | |||
5bc2e91344 | |||
295f732b18 | |||
770ae47471 | |||
2ce4f96355 | |||
07ed834b27 | |||
8d686411ee | |||
ce722897f1 | |||
a8381e875b | |||
4c81fd7d16 | |||
25ee4226d3 | |||
9d5b019243 | |||
6bb7b545b4 | |||
25d56ee8d5 | |||
7e5952c804 | |||
80bf29d79a | |||
971e53dfd8 | |||
654b903d7a | |||
2f72e6971b | |||
d4b587b93e | |||
ac7a510949 |
10
Dockerfile
10
Dockerfile
@ -14,6 +14,9 @@ RUN ./build.sh
|
||||
FROM alpine:latest AS STANDARD
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
ARG USER=casdoor
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
||||
|
||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||
RUN apk add --update sudo
|
||||
@ -28,7 +31,7 @@ RUN adduser -D $USER -u 1000 \
|
||||
|
||||
USER 1000
|
||||
WORKDIR /
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server ./server
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
|
||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
|
||||
@ -46,12 +49,15 @@ RUN apt update \
|
||||
|
||||
FROM db AS ALLINONE
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
||||
|
||||
RUN apt update
|
||||
RUN apt install -y ca-certificates && update-ca-certificates
|
||||
|
||||
WORKDIR /
|
||||
COPY --from=BACK /go/src/casdoor/server ./server
|
||||
COPY --from=BACK /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
||||
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
||||
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
|
@ -142,7 +142,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", subOwner, subName)
|
||||
user := object.GetUser(userId)
|
||||
if user != nil && user.IsAdmin && subOwner == objOwner {
|
||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin" && subOwner == objName)) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
3
build.sh
3
build.sh
@ -8,4 +8,5 @@ else
|
||||
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
||||
export GOPROXY="https://goproxy.cn,direct"
|
||||
fi
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
package captcha
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CaptchaProvider interface {
|
||||
VerifyCaptcha(token, clientSecret string) (bool, error)
|
||||
}
|
||||
@ -32,3 +34,12 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyCaptchaByCaptchaType(captchaType, token, clientSecret string) (bool, error) {
|
||||
provider := GetCaptchaProvider(captchaType)
|
||||
if provider == nil {
|
||||
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
|
||||
}
|
||||
|
||||
return provider.VerifyCaptcha(token, clientSecret)
|
||||
}
|
||||
|
@ -64,6 +64,10 @@ type RequestForm struct {
|
||||
RelayState string `json:"relayState"`
|
||||
SamlRequest string `json:"samlRequest"`
|
||||
SamlResponse string `json:"samlResponse"`
|
||||
|
||||
CaptchaType string `json:"captchaType"`
|
||||
CaptchaToken string `json:"captchaToken"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
@ -241,8 +245,7 @@ func (c *ApiController) Logout() {
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
application := c.GetSessionApplication()
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
c.ClearUserSession()
|
||||
|
||||
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
|
||||
c.ResponseOk(user)
|
||||
|
@ -23,6 +23,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/captcha"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -251,6 +253,25 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||
return
|
||||
}
|
||||
|
||||
if object.CheckToEnableCaptcha(application) {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
password := form.Password
|
||||
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
|
||||
}
|
||||
|
@ -63,8 +63,7 @@ func (c *ApiController) GetSessionUsername() string {
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
c.ClearUserSession()
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -85,13 +84,17 @@ func (c *ApiController) GetSessionApplication() *object.Application {
|
||||
return application
|
||||
}
|
||||
|
||||
func (c *ApiController) ClearUserSession() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
c.ClearUserSession()
|
||||
return "", ""
|
||||
}
|
||||
scopeValue := c.GetSession("scope")
|
||||
|
@ -48,6 +48,30 @@ func (c *ApiController) GetProviders() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalProviders
|
||||
// @Title GetGlobalProviders
|
||||
// @Tag Provider API
|
||||
// @Description get Global providers
|
||||
// @Success 200 {array} object.Provider The Response object
|
||||
// @router /get-global-providers [get]
|
||||
func (c *ApiController) GetGlobalProviders() {
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
if limit == "" || page == "" {
|
||||
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders())
|
||||
c.ServeJSON()
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalProviderCount(field, value)))
|
||||
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder))
|
||||
c.ResponseOk(providers, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetProvider
|
||||
// @Title GetProvider
|
||||
// @Tag Provider API
|
||||
|
@ -56,6 +56,9 @@ func (c *ApiController) T(error string) string {
|
||||
// GetAcceptLanguage ...
|
||||
func (c *ApiController) GetAcceptLanguage() string {
|
||||
lang := c.Ctx.Request.Header.Get("Accept-Language")
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
return lang[0:2]
|
||||
}
|
||||
|
||||
@ -80,7 +83,7 @@ func (c *ApiController) SetTokenErrorHttpStatus() {
|
||||
func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
c.ResponseError(c.T("LoginErr.SignInFirst"))
|
||||
c.ResponseError(c.T("LoginErr.LoginFirst"), "Please login first")
|
||||
return "", false
|
||||
}
|
||||
return userId, true
|
||||
@ -95,6 +98,7 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ClearUserSession()
|
||||
c.ResponseError(fmt.Sprintf(c.T("UserErr.DoNotExist"), userId))
|
||||
return nil, false
|
||||
}
|
||||
|
5
go.mod
5
go.mod
@ -4,11 +4,10 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||
github.com/Unknwon/goconfig v1.0.0 // indirect
|
||||
github.com/Unknwon/goconfig v1.0.0
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||
github.com/aws/aws-sdk-go v1.44.4
|
||||
github.com/beego/beego v1.12.11
|
||||
github.com/beego/i18n v0.0.0-20161101132742-e9308947f407
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casbin/xorm-adapter/v3 v3.0.1
|
||||
@ -49,7 +48,7 @@ require (
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
xorm.io/core v0.7.2
|
||||
|
4
go.sum
4
go.sum
@ -84,8 +84,6 @@ github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod
|
||||
github.com/beego/beego v1.12.11 h1:MWKcnpavb7iAIS0m6uuEq6pHKkYvGNw/5umIUKqL7jM=
|
||||
github.com/beego/beego v1.12.11/go.mod h1:QURFL1HldOcCZAxnc1cZ7wrplsYR5dKPHFjmk6WkLAs=
|
||||
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
|
||||
github.com/beego/i18n v0.0.0-20161101132742-e9308947f407 h1:WtJfx5HqASTQp7HfiZldnin8KQV2futplF3duGp5PGc=
|
||||
github.com/beego/i18n v0.0.0-20161101132742-e9308947f407/go.mod h1:KLeFCpAMq2+50NkXC8iiJxLLiiTfTqrGtKEVm+2fk7s=
|
||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
@ -761,8 +759,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
|
@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
|
||||
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
|
||||
SessionOutdated = Session outdated, please login again
|
||||
SignOutFirst = Please sign out first before signing in
|
||||
SignInFirst = Please sign in first
|
||||
UserDoNotExist = The user: %s/%s doesn't exist
|
||||
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
|
||||
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s
|
||||
|
@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
|
||||
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
|
||||
SessionOutdated = Session outdated, please login again
|
||||
SignOutFirst = Please sign out first before signing in
|
||||
SignInFirst = Please sign in first
|
||||
UserDoNotExist = The user: %s/%s doesn't exist
|
||||
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
|
||||
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s
|
||||
|
@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
|
||||
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
|
||||
SessionOutdated = Session outdated, please login again
|
||||
SignOutFirst = Please sign out first before signing in
|
||||
SignInFirst = Please sign in first
|
||||
UserDoNotExist = The user: %s/%s doesn't exist
|
||||
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
|
||||
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s
|
||||
|
@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
|
||||
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
|
||||
SessionOutdated = Session outdated, please login again
|
||||
SignOutFirst = Please sign out first before signing in
|
||||
SignInFirst = Please sign in first
|
||||
UserDoNotExist = The user: %s/%s doesn't exist
|
||||
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
|
||||
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s
|
||||
|
@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
|
||||
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
|
||||
SessionOutdated = Session outdated, please login again
|
||||
SignOutFirst = Please sign out first before signing in
|
||||
SignInFirst = Please sign in first
|
||||
UserDoNotExist = The user: %s/%s doesn't exist
|
||||
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
|
||||
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s
|
||||
|
@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
|
||||
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
|
||||
SessionOutdated = Session outdated, please login again
|
||||
SignOutFirst = Please sign out first before signing in
|
||||
SignInFirst = Please sign in first
|
||||
UserDoNotExist = The user: %s/%s doesn't exist
|
||||
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
|
||||
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s
|
||||
|
@ -51,7 +51,6 @@ OldUser = The account for provider: %s and username: %s (%s) is already linked t
|
||||
ProviderCanNotSignUp = The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up
|
||||
SessionOutdated = Session outdated, please login again
|
||||
SignOutFirst = Please sign out first before signing in
|
||||
SignInFirst = Please sign in first
|
||||
UserDoNotExist = The user: %s/%s doesn't exist
|
||||
UserIsForbidden = The user is forbidden to sign in, please contact the administrator
|
||||
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s
|
||||
|
@ -51,7 +51,6 @@ OldUser = 提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定:
|
||||
ProviderCanNotSignUp = 提供商账户: %s 与用户名: %s (%s) 不存在且 不允许通过 %s 注册新账户, 请使用其他方式注册
|
||||
SignOutFirst = 请在登录前登出
|
||||
SessionOutdated = Session已过期,请重新登陆
|
||||
SignInFirst = 请先登出
|
||||
UserDoNotExist = 用户不存在: %s/%s
|
||||
UserIsForbidden = 该用户被禁止登陆,请联系管理员
|
||||
UnknownAuthentication = 未知的认证类型 (非密码或提供商认证), form = %s
|
||||
|
@ -15,9 +15,12 @@
|
||||
package idp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -167,7 +170,10 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
||||
Email: dtUserInfo.Email,
|
||||
AvatarUrl: dtUserInfo.AvatarUrl,
|
||||
}
|
||||
|
||||
isUserInOrg, err := idp.isUserInOrg(userInfo.UnionId)
|
||||
if !isUserInOrg {
|
||||
return nil, err
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
@ -194,3 +200,62 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) getInnerAppAccessToken() string {
|
||||
appKey := idp.Config.ClientID
|
||||
appSecret := idp.Config.ClientSecret
|
||||
body := make(map[string]string)
|
||||
body["appKey"] = appKey
|
||||
body["appSecret"] = appSecret
|
||||
bodyData, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
reader := bytes.NewReader(bodyData)
|
||||
request, err := http.NewRequest("POST", "https://api.dingtalk.com/v1.0/oauth2/accessToken", reader)
|
||||
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
resp, err := idp.Client.Do(request)
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
var data struct {
|
||||
ExpireIn int `json:"expireIn"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
}
|
||||
err = json.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
return data.AccessToken
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) isUserInOrg(unionId string) (bool, error) {
|
||||
body := make(map[string]string)
|
||||
body["unionid"] = unionId
|
||||
bodyData, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
reader := bytes.NewReader(bodyData)
|
||||
accessToken := idp.getInnerAppAccessToken()
|
||||
request, _ := http.NewRequest("POST", "https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token="+accessToken, reader)
|
||||
request.Header.Set("Content-Type", "application/json;charset=UTF-8")
|
||||
resp, err := idp.Client.Do(request)
|
||||
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
var data struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMessage string `json:"errmsg"`
|
||||
}
|
||||
err = json.Unmarshal(respBytes, &data)
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
if data.ErrCode == 60121 {
|
||||
return false, fmt.Errorf("the user is not found in the organization where clientId and clientSecret belong")
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
@ -270,6 +270,13 @@ func UpdateApplication(id string, application *Application) bool {
|
||||
application.Name = name
|
||||
}
|
||||
|
||||
if name != application.Name {
|
||||
err := applicationChangeTrigger(name, application.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
@ -400,3 +407,55 @@ func ExtendManagedAccountsWithUser(user *User) *User {
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
func applicationChangeTrigger(oldName string, newName string) error {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
organization := new(Organization)
|
||||
organization.DefaultApplication = newName
|
||||
_, err = session.Where("default_application=?", oldName).Update(organization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := new(User)
|
||||
user.SignupApplication = newName
|
||||
_, err = session.Where("signup_application=?", oldName).Update(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resource := new(Resource)
|
||||
resource.Application = newName
|
||||
_, err = session.Where("application=?", oldName).Update(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var permissions []*Permission
|
||||
err = adapter.Engine.Find(&permissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(permissions); i++ {
|
||||
permissionResoureces := permissions[i].Resources
|
||||
for j := 0; j < len(permissionResoureces); j++ {
|
||||
if permissionResoureces[j] == oldName {
|
||||
permissionResoureces[j] = newName
|
||||
}
|
||||
}
|
||||
permissions[i].Resources = permissionResoureces
|
||||
_, err = session.Where("name=?", permissions[i].Name).Update(permissions[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
@ -114,6 +114,12 @@ func UpdateCert(id string, cert *Cert) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name != cert.Name {
|
||||
err := certChangeTrigger(name, cert.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -161,3 +167,22 @@ func getCertByApplication(application *Application) *Cert {
|
||||
func GetDefaultCert() *Cert {
|
||||
return getCert("admin", "cert-built-in")
|
||||
}
|
||||
|
||||
func certChangeTrigger(oldName string, newName string) error {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
application := new(Application)
|
||||
application.Cert = newName
|
||||
_, err = session.Where("cert=?", oldName).Update(application)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
@ -340,3 +340,20 @@ func CheckUsername(username string, lang string) string {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckToEnableCaptcha(application *Application) bool {
|
||||
if len(application.Providers) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
if providerItem.Provider == nil {
|
||||
continue
|
||||
}
|
||||
if providerItem.Provider.Category == "Captcha" && providerItem.Provider.Type == "Default" {
|
||||
return providerItem.Rule == "Always"
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func initBuiltInApplication() {
|
||||
EnablePassword: true,
|
||||
EnableSignUp: true,
|
||||
Providers: []*ProviderItem{
|
||||
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Provider: nil},
|
||||
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Rule: "None", Provider: nil},
|
||||
},
|
||||
SignupItems: []*SignupItem{
|
||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||
|
@ -56,12 +56,40 @@ func readInitDataFromFile(filePath string) *InitData {
|
||||
|
||||
s := util.ReadStringFromPath(filePath)
|
||||
|
||||
data := &InitData{}
|
||||
data := &InitData{
|
||||
Organizations: []*Organization{},
|
||||
Applications: []*Application{},
|
||||
Users: []*User{},
|
||||
Certs: []*Cert{},
|
||||
Providers: []*Provider{},
|
||||
Ldaps: []*Ldap{},
|
||||
}
|
||||
err := util.JsonToStruct(s, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// transform nil slice to empty slice
|
||||
for _, organization := range data.Organizations {
|
||||
if organization.Tags == nil {
|
||||
organization.Tags = []string{}
|
||||
}
|
||||
}
|
||||
for _, application := range data.Applications {
|
||||
if application.Providers == nil {
|
||||
application.Providers = []*ProviderItem{}
|
||||
}
|
||||
if application.SignupItems == nil {
|
||||
application.SignupItems = []*SignupItem{}
|
||||
}
|
||||
if application.GrantTypes == nil {
|
||||
application.GrantTypes = []string{}
|
||||
}
|
||||
if application.RedirectUris == nil {
|
||||
application.RedirectUris = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,12 @@ func UpdateModel(id string, modelObj *Model) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name != modelObj.Name {
|
||||
err := modelChangeTrigger(name, modelObj.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// check model grammar
|
||||
_, err := model.NewModelFromString(modelObj.ModelText)
|
||||
if err != nil {
|
||||
@ -127,3 +133,22 @@ func DeleteModel(model *Model) bool {
|
||||
func (model *Model) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", model.Owner, model.Name)
|
||||
}
|
||||
|
||||
func modelChangeTrigger(oldName string, newName string) error {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permission := new(Permission)
|
||||
permission.Model = newName
|
||||
_, err = session.Where("model=?", oldName).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/cred"
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
@ -134,15 +135,10 @@ func UpdateOrganization(id string, organization *Organization) bool {
|
||||
}
|
||||
|
||||
if name != organization.Name {
|
||||
go func() {
|
||||
application := new(Application)
|
||||
application.Organization = organization.Name
|
||||
_, _ = adapter.Engine.Where("organization=?", name).Update(application)
|
||||
|
||||
user := new(User)
|
||||
user.Owner = organization.Name
|
||||
_, _ = adapter.Engine.Where("owner=?", name).Update(user)
|
||||
}()
|
||||
err := organizationChangeTrigger(name, organization.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
|
||||
@ -252,3 +248,148 @@ func GetDefaultApplication(id string) (*Application, error) {
|
||||
|
||||
return defaultApplication, nil
|
||||
}
|
||||
|
||||
func organizationChangeTrigger(oldName string, newName string) error {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
application := new(Application)
|
||||
application.Organization = newName
|
||||
_, err = session.Where("organization=?", oldName).Update(application)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := new(User)
|
||||
user.Owner = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
role := new(Role)
|
||||
_, err = adapter.Engine.Where("owner=?", oldName).Get(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, u := range role.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
role.Users[i] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
for i, u := range role.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
role.Roles[i] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
role.Owner = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
permission := new(Permission)
|
||||
_, err = adapter.Engine.Where("owner=?", oldName).Get(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, u := range permission.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
permission.Users[i] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
for i, u := range permission.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[0] == oldName {
|
||||
split[0] = newName
|
||||
permission.Roles[i] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
permission.Owner = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
casbinAdapter := new(CasbinAdapter)
|
||||
casbinAdapter.Owner = newName
|
||||
casbinAdapter.Organization = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(casbinAdapter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ldap := new(Ldap)
|
||||
ldap.Owner = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(ldap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
model := new(Model)
|
||||
model.Owner = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(model)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
payment := new(Payment)
|
||||
payment.Organization = newName
|
||||
_, err = session.Where("organization=?", oldName).Update(payment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
record := new(Record)
|
||||
record.Owner = newName
|
||||
record.Organization = newName
|
||||
_, err = session.Where("organization=?", oldName).Update(record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resource := new(Resource)
|
||||
resource.Owner = newName
|
||||
_, err = session.Where("owner=?", oldName).Update(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
syncer := new(Syncer)
|
||||
syncer.Organization = newName
|
||||
_, err = session.Where("organization=?", oldName).Update(syncer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
token := new(Token)
|
||||
token.Organization = newName
|
||||
_, err = session.Where("organization=?", oldName).Update(token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
webhook := new(Webhook)
|
||||
webhook.Organization = newName
|
||||
_, err = session.Where("organization=?", oldName).Update(webhook)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ type Provider struct {
|
||||
IntranetEndpoint string `xorm:"varchar(100)" json:"intranetEndpoint"`
|
||||
Domain string `xorm:"varchar(100)" json:"domain"`
|
||||
Bucket string `xorm:"varchar(100)" json:"bucket"`
|
||||
PathPrefix string `xorm:"varchar(100)" json:"pathPrefix"`
|
||||
|
||||
Metadata string `xorm:"mediumtext" json:"metadata"`
|
||||
IdP string `xorm:"mediumtext" json:"idP"`
|
||||
@ -101,6 +102,16 @@ func GetProviderCount(owner, field, value string) int {
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetGlobalProviderCount(field, value string) int {
|
||||
session := GetSession("", -1, -1, field, value, "", "")
|
||||
count, err := session.Count(&Provider{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetProviders(owner string) []*Provider {
|
||||
providers := []*Provider{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner})
|
||||
@ -111,8 +122,18 @@ func GetProviders(owner string) []*Provider {
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider {
|
||||
func GetGlobalProviders() []*Provider {
|
||||
providers := []*Provider{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&providers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider {
|
||||
var providers []*Provider
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&providers)
|
||||
if err != nil {
|
||||
@ -122,6 +143,17 @@ func GetPaginationProviders(owner string, offset, limit int, field, value, sortF
|
||||
return providers
|
||||
}
|
||||
|
||||
func GetPaginationGlobalProviders(offset, limit int, field, value, sortField, sortOrder string) []*Provider {
|
||||
var providers []*Provider
|
||||
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&providers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func getProvider(owner string, name string) *Provider {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
@ -175,6 +207,13 @@ func UpdateProvider(id string, provider *Provider) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name != provider.Name {
|
||||
err := providerChangeTrigger(name, provider.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
|
||||
if provider.ClientSecret == "***" {
|
||||
session = session.Omit("client_secret")
|
||||
@ -262,3 +301,41 @@ func GetCaptchaProviderByApplication(applicationId, isCurrentProvider, lang stri
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func providerChangeTrigger(oldName string, newName string) error {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var applications []*Application
|
||||
err = adapter.Engine.Find(&applications)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < len(applications); i++ {
|
||||
providers := applications[i].Providers
|
||||
for j := 0; j < len(providers); j++ {
|
||||
if providers[j].Name == oldName {
|
||||
providers[j].Name = newName
|
||||
}
|
||||
}
|
||||
applications[i].Providers = providers
|
||||
_, err = session.Where("name=?", applications[i].Name).Update(applications[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resource := new(Resource)
|
||||
resource.Provider = newName
|
||||
_, err = session.Where("provider=?", oldName).Update(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ type ProviderItem struct {
|
||||
CanUnlink bool `json:"canUnlink"`
|
||||
Prompted bool `json:"prompted"`
|
||||
AlertType string `json:"alertType"`
|
||||
Rule string `json:"rule"`
|
||||
Provider *Provider `json:"provider"`
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
@ -94,6 +95,13 @@ func UpdateRole(id string, role *Role) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name != role.Name {
|
||||
err := roleChangeTrigger(name, role.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -133,3 +141,54 @@ func GetRolesByUser(userId string) []*Role {
|
||||
|
||||
return roles
|
||||
}
|
||||
|
||||
func roleChangeTrigger(oldName string, newName string) error {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var roles []*Role
|
||||
err = adapter.Engine.Find(&roles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
for j, u := range role.Roles {
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
role.Roles[j] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", role.Name).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var permissions []*Permission
|
||||
err = adapter.Engine.Find(&permissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Roles {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
permission.Roles[j] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", permission.Name).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ func escapePath(path string) string {
|
||||
}
|
||||
|
||||
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
|
||||
escapedPath := escapePath(fullFilePath)
|
||||
escapedPath := util.UrlJoin(provider.PathPrefix, escapePath(fullFilePath))
|
||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
||||
|
||||
host := ""
|
||||
@ -70,7 +70,7 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
||||
host = util.UrlJoin(provider.Domain, "/files")
|
||||
}
|
||||
if provider.Type == "Azure Blob" {
|
||||
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
|
||||
host = util.UrlJoin(host, provider.Bucket)
|
||||
}
|
||||
|
||||
fileUrl := util.UrlJoin(host, escapePath(objectKey))
|
||||
|
@ -37,7 +37,16 @@ func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return syncer.getOriginalUsersFromMap(results), nil
|
||||
// Memory leak problem handling
|
||||
// https://github.com/casdoor/casdoor/issues/1256
|
||||
users := syncer.getOriginalUsersFromMap(results)
|
||||
for _, m := range results {
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser, error) {
|
||||
|
@ -37,7 +37,7 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
|
||||
// Encode private key to PKCS#1 ASN.1 PEM.
|
||||
privateKeyPem := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
||||
},
|
||||
)
|
||||
|
@ -380,6 +380,13 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
|
||||
return false
|
||||
}
|
||||
|
||||
if name != user.Name {
|
||||
err := userChangeTrigger(name, user.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if user.Password == "***" {
|
||||
user.Password = oldUser.Password
|
||||
}
|
||||
@ -416,6 +423,13 @@ func UpdateUserForAllFields(id string, user *User) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if name != user.Name {
|
||||
err := userChangeTrigger(name, user.Name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
user.UpdateUserHash()
|
||||
|
||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||
@ -567,3 +581,62 @@ func ExtendUserWithRolesAndPermissions(user *User) {
|
||||
user.Roles = GetRolesByUser(user.GetId())
|
||||
user.Permissions = GetPermissionsByUser(user.GetId())
|
||||
}
|
||||
|
||||
func userChangeTrigger(oldName string, newName string) error {
|
||||
session := adapter.Engine.NewSession()
|
||||
defer session.Close()
|
||||
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var roles []*Role
|
||||
err = adapter.Engine.Find(&roles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
for j, u := range role.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
role.Users[j] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", role.Name).Update(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var permissions []*Permission
|
||||
err = adapter.Engine.Find(&permissions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, permission := range permissions {
|
||||
for j, u := range permission.Users {
|
||||
// u = organization/username
|
||||
split := strings.Split(u, "/")
|
||||
if split[1] == oldName {
|
||||
split[1] = newName
|
||||
permission.Users[j] = split[0] + "/" + split[1]
|
||||
}
|
||||
}
|
||||
_, err = session.Where("name=?", permission.Name).Update(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
resource := new(Resource)
|
||||
resource.User = newName
|
||||
_, err = session.Where("user=?", oldName).Update(resource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
|
||||
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
|
||||
beego.Router("/api/get-global-providers", &controllers.ApiController{}, "GET:GetGlobalProviders")
|
||||
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
|
||||
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
|
||||
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
|
||||
|
@ -120,7 +120,7 @@ class AccountTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:visible"),
|
||||
title: i18next.t("organization:Visible"),
|
||||
dataIndex: "visible",
|
||||
key: "visible",
|
||||
width: "120px",
|
||||
@ -133,7 +133,7 @@ class AccountTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("organization:viewRule"),
|
||||
title: i18next.t("organization:View rule"),
|
||||
dataIndex: "viewRule",
|
||||
key: "viewRule",
|
||||
width: "155px",
|
||||
@ -160,7 +160,7 @@ class AccountTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("organization:modifyRule"),
|
||||
title: i18next.t("organization:Modify rule"),
|
||||
dataIndex: "modifyRule",
|
||||
key: "modifyRule",
|
||||
width: "155px",
|
||||
|
@ -59,7 +59,7 @@ class AdapterEditPage extends React.Component {
|
||||
}
|
||||
|
||||
getOrganizations() {
|
||||
OrganizationBackend.getOrganizations(this.state.organizationName)
|
||||
OrganizationBackend.getOrganizations("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
organizations: (res.msg === undefined) ? res : [],
|
||||
@ -195,7 +195,7 @@ class AdapterEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {this.updateadapterField("organization", value);})}>
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {this.updateAdapterField("organization", value);})}>
|
||||
{
|
||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||
}
|
||||
|
@ -70,21 +70,6 @@ class AdapterListPage extends BaseListPage {
|
||||
|
||||
renderTable(adapters) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
@ -101,6 +86,21 @@ class AdapterListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
|
@ -221,10 +221,9 @@ class App extends Component {
|
||||
if (res.status === "ok") {
|
||||
account = res.data;
|
||||
account.organization = res.data2;
|
||||
|
||||
this.setLanguage(account);
|
||||
} else {
|
||||
if (res.msg !== "Please sign in first") {
|
||||
if (res.data !== "Please login first") {
|
||||
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
|
||||
}
|
||||
}
|
||||
@ -421,13 +420,6 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/providers">
|
||||
<Link to="/providers">
|
||||
{i18next.t("general:Providers")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/applications">
|
||||
<Link to="/applications">
|
||||
@ -438,6 +430,13 @@ class App extends Component {
|
||||
}
|
||||
|
||||
if (Setting.isLocalAdminUser(this.state.account)) {
|
||||
res.push(
|
||||
<Menu.Item key="/providers">
|
||||
<Link to="/providers">
|
||||
{i18next.t("general:Providers")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/resources">
|
||||
<Link to="/resources">
|
||||
@ -567,6 +566,7 @@ class App extends Component {
|
||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/applications/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||
|
@ -123,7 +123,7 @@
|
||||
|
||||
.login-form {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.login-content {
|
||||
|
@ -91,6 +91,7 @@ class ApplicationEditPage extends React.Component {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
owner: props.account.owner,
|
||||
applicationName: props.match.params.applicationName,
|
||||
application: null,
|
||||
organizations: [],
|
||||
@ -141,12 +142,21 @@ class ApplicationEditPage extends React.Component {
|
||||
}
|
||||
|
||||
getProviders() {
|
||||
ProviderBackend.getProviders("admin")
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
providers: res,
|
||||
if (Setting.isAdminUser(this.props.account)) {
|
||||
ProviderBackend.getGlobalProviders()
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
providers: res,
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ProviderBackend.getProviders(this.state.owner)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
providers: res,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getSamlMetadata() {
|
||||
|
@ -63,21 +63,6 @@ class ModelListPage extends BaseListPage {
|
||||
|
||||
renderTable(models) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
@ -94,6 +79,21 @@ class ModelListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
|
@ -76,6 +76,38 @@ class PaymentListPage extends BaseListPage {
|
||||
|
||||
renderTable(payments) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "180px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/payments/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Provider"),
|
||||
dataIndex: "provider",
|
||||
key: "provider",
|
||||
width: "150px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("provider"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/providers/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
@ -106,22 +138,7 @@ class PaymentListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
width: "180px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("name"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/payments/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
@ -140,22 +157,6 @@ class PaymentListPage extends BaseListPage {
|
||||
// sorter: true,
|
||||
// ...this.getColumnSearchProps('displayName'),
|
||||
// },
|
||||
{
|
||||
title: i18next.t("general:Provider"),
|
||||
dataIndex: "provider",
|
||||
key: "provider",
|
||||
width: "150px",
|
||||
fixed: "left",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("provider"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/providers/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("payment:Type"),
|
||||
dataIndex: "type",
|
||||
|
@ -77,21 +77,7 @@ class PermissionListPage extends BaseListPage {
|
||||
|
||||
renderTable(permissions) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
// https://github.com/ant-design/ant-design/issues/22184
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
@ -108,6 +94,21 @@ class PermissionListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
|
@ -32,6 +32,7 @@ class ProviderEditPage extends React.Component {
|
||||
this.state = {
|
||||
classes: props,
|
||||
providerName: props.match.params.providerName,
|
||||
owner: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
|
||||
provider: null,
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
};
|
||||
@ -42,7 +43,7 @@ class ProviderEditPage extends React.Component {
|
||||
}
|
||||
|
||||
getProvider() {
|
||||
ProviderBackend.getProvider("admin", this.state.providerName)
|
||||
ProviderBackend.getProvider(this.state.owner, this.state.providerName)
|
||||
.then((provider) => {
|
||||
this.setState({
|
||||
provider: provider,
|
||||
@ -469,6 +470,16 @@ class ProviderEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:The prefix path of the file - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.pathPrefix} onChange={e => {
|
||||
this.updateProviderField("pathPrefix", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
|
@ -23,10 +23,25 @@ import i18next from "i18next";
|
||||
import BaseListPage from "./BaseListPage";
|
||||
|
||||
class ProviderListPage extends BaseListPage {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
owner: Setting.isAdminUser(props.account) ? "admin" : props.account.organization.name,
|
||||
data: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
loading: false,
|
||||
searchText: "",
|
||||
searchedColumn: "",
|
||||
};
|
||||
}
|
||||
newProvider() {
|
||||
const randomName = Setting.getRandomName();
|
||||
return {
|
||||
owner: "admin", // this.props.account.providername,
|
||||
owner: this.state.owner,
|
||||
name: `provider_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
displayName: `New Provider - ${randomName}`,
|
||||
@ -46,7 +61,7 @@ class ProviderListPage extends BaseListPage {
|
||||
const newProvider = this.newProvider();
|
||||
ProviderBackend.addProvider(newProvider)
|
||||
.then((res) => {
|
||||
this.props.history.push({pathname: `/providers/${newProvider.name}`, mode: "add"});
|
||||
this.props.history.push({pathname: `/providers/${newProvider.owner}/${newProvider.name}`, mode: "add"});
|
||||
}
|
||||
)
|
||||
.catch(error => {
|
||||
@ -177,7 +192,7 @@ class ProviderListPage extends BaseListPage {
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/providers/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||
<Popconfirm
|
||||
title={`Sure to delete provider: ${record.name} ?`}
|
||||
onConfirm={() => this.deleteProvider(index)}
|
||||
@ -224,7 +239,8 @@ class ProviderListPage extends BaseListPage {
|
||||
value = params.type;
|
||||
}
|
||||
this.setState({loading: true});
|
||||
ProviderBackend.getProviders("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
(Setting.isAdminUser(this.props.account) ? ProviderBackend.getGlobalProviders(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||
: ProviderBackend.getProviders(this.state.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
this.setState({
|
||||
|
@ -39,7 +39,7 @@ class ProviderTable extends React.Component {
|
||||
}
|
||||
|
||||
addRow(table) {
|
||||
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, alertType: "None"};
|
||||
const row = {name: Setting.getNewRowNameForTable(table, "Please select a provider"), canSignUp: true, canSignIn: true, canUnlink: true, alertType: "None", rule: "None"};
|
||||
if (table === undefined) {
|
||||
table = [];
|
||||
}
|
||||
@ -105,7 +105,7 @@ class ProviderTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:canSignUp"),
|
||||
title: i18next.t("provider:Can signup"),
|
||||
dataIndex: "canSignUp",
|
||||
key: "canSignUp",
|
||||
width: "120px",
|
||||
@ -122,7 +122,7 @@ class ProviderTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:canSignIn"),
|
||||
title: i18next.t("provider:Can signin"),
|
||||
dataIndex: "canSignIn",
|
||||
key: "canSignIn",
|
||||
width: "120px",
|
||||
@ -139,7 +139,7 @@ class ProviderTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:canUnlink"),
|
||||
title: i18next.t("provider:Can unlink"),
|
||||
dataIndex: "canUnlink",
|
||||
key: "canUnlink",
|
||||
width: "120px",
|
||||
@ -156,7 +156,7 @@ class ProviderTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:prompted"),
|
||||
title: i18next.t("provider:Prompted"),
|
||||
dataIndex: "prompted",
|
||||
key: "prompted",
|
||||
width: "120px",
|
||||
@ -193,6 +193,28 @@ class ProviderTable extends React.Component {
|
||||
// )
|
||||
// }
|
||||
// },
|
||||
{
|
||||
title: i18next.t("application:Rule"),
|
||||
dataIndex: "rule",
|
||||
key: "rule",
|
||||
width: "100px",
|
||||
render: (text, record, index) => {
|
||||
if (record.provider?.category !== "Captcha") {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Select virtual={false} style={{width: "100%"}}
|
||||
value={text}
|
||||
defaultValue="None"
|
||||
onChange={value => {
|
||||
this.updateField(table, index, "rule", value);
|
||||
}} >
|
||||
<Option key="None" value="None">{i18next.t("application:None")}</Option>
|
||||
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
|
||||
</Select>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
key: "action",
|
||||
|
@ -173,7 +173,7 @@ class RoleEditPage extends React.Component {
|
||||
this.updateRoleField("domains", value);
|
||||
})}>
|
||||
{
|
||||
this.state.role.domains.map((domain, index) => <Option key={index} value={domain}>{domain}</Option>)
|
||||
this.state.role.domains?.map((domain, index) => <Option key={index} value={domain}>{domain}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
|
@ -65,21 +65,6 @@ class RoleListPage extends BaseListPage {
|
||||
|
||||
renderTable(roles) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
@ -96,6 +81,21 @@ class RoleListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "owner",
|
||||
key: "owner",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("owner"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
|
@ -12,9 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Tag, Tooltip, message} from "antd";
|
||||
import {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||
import React from "react";
|
||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||
import "./i18n";
|
||||
import i18next from "i18next";
|
||||
@ -22,7 +23,6 @@ import copy from "copy-to-clipboard";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import {Helmet} from "react-helmet";
|
||||
import * as Conf from "./Conf";
|
||||
import {Link} from "react-router-dom";
|
||||
import * as path from "path-browserify";
|
||||
|
||||
export const ServerUrl = "";
|
||||
@ -743,26 +743,30 @@ export function renderLogo(application) {
|
||||
}
|
||||
}
|
||||
|
||||
export function goToLogin(ths, application) {
|
||||
export function getLoginLink(application) {
|
||||
let url;
|
||||
if (application === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
|
||||
const link = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
|
||||
goToLink(link);
|
||||
return;
|
||||
}
|
||||
|
||||
if (authConfig.appName === application.name) {
|
||||
goToLinkSoft(ths, "/login");
|
||||
url = null;
|
||||
} else if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
|
||||
url = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
|
||||
} else if (authConfig.appName === application.name) {
|
||||
url = "/login";
|
||||
} else if (application.signinUrl === "") {
|
||||
url = path.join(application.homepageUrl, "/login");
|
||||
} else {
|
||||
if (application.signinUrl === "") {
|
||||
goToLink(path.join(application.homepageUrl, "login"));
|
||||
} else {
|
||||
goToLink(application.signinUrl);
|
||||
}
|
||||
url = application.signinUrl;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export function renderLoginLink(application, text) {
|
||||
const url = getLoginLink(application);
|
||||
return renderLink(url, text, null);
|
||||
}
|
||||
|
||||
export function redirectToLoginPage(application, history) {
|
||||
const loginLink = getLoginLink(application);
|
||||
history.push(loginLink);
|
||||
}
|
||||
|
||||
function renderLink(url, text, onClick) {
|
||||
|
@ -104,7 +104,7 @@ class SignupTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:visible"),
|
||||
title: i18next.t("provider:Visible"),
|
||||
dataIndex: "visible",
|
||||
key: "visible",
|
||||
width: "120px",
|
||||
@ -126,7 +126,7 @@ class SignupTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:required"),
|
||||
title: i18next.t("provider:Required"),
|
||||
dataIndex: "required",
|
||||
key: "required",
|
||||
width: "120px",
|
||||
@ -143,7 +143,7 @@ class SignupTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:prompted"),
|
||||
title: i18next.t("provider:Prompted"),
|
||||
dataIndex: "prompted",
|
||||
key: "prompted",
|
||||
width: "120px",
|
||||
@ -164,7 +164,7 @@ class SignupTable extends React.Component {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("application:rule"),
|
||||
title: i18next.t("application:Rule"),
|
||||
dataIndex: "rule",
|
||||
key: "rule",
|
||||
width: "155px",
|
||||
|
@ -88,21 +88,6 @@ class SyncerListPage extends BaseListPage {
|
||||
|
||||
renderTable(syncers) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
@ -119,6 +104,21 @@ class SyncerListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
|
@ -90,37 +90,66 @@ class SystemInfo extends React.Component {
|
||||
</div> : i18next.t("system:Get Memory Usage Failed")
|
||||
);
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col span={6}></Col>
|
||||
<Col span={12}>
|
||||
<Row gutter={[10, 10]}>
|
||||
<Col span={12}>
|
||||
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : CPUInfo}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : MemInfo}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
||||
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
|
||||
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
|
||||
<br />
|
||||
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
|
||||
<br />
|
||||
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
|
||||
<br />
|
||||
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}></Col>
|
||||
</Row>
|
||||
);
|
||||
if (!Setting.isMobile()) {
|
||||
return (
|
||||
<Row>
|
||||
<Col span={6}></Col>
|
||||
<Col span={12}>
|
||||
<Row gutter={[10, 10]}>
|
||||
<Col span={12}>
|
||||
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : CPUInfo}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : MemInfo}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider />
|
||||
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
||||
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
|
||||
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
|
||||
<br />
|
||||
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
|
||||
<br />
|
||||
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
|
||||
<br />
|
||||
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}></Col>
|
||||
</Row>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Row gutter={[16, 0]}>
|
||||
<Col span={24}>
|
||||
<Card title={i18next.t("system:CPU Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : CPUInfo}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Card title={i18next.t("system:Memory Usage")} bordered={true} style={{textAlign: "center", width: "100%"}}>
|
||||
{this.state.loading ? <Spin size="large" /> : MemInfo}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
||||
<div>{i18next.t("system:An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS")}</div>
|
||||
GitHub: <a href="https://github.com/casdoor/casdoor">casdoor</a>
|
||||
<br />
|
||||
{i18next.t("system:Version")}: <a href={this.state.href}>{this.state.latestVersion}</a>
|
||||
<br />
|
||||
{i18next.t("system:Official Website")}: <a href="https://casdoor.org/">casdoor.org</a>
|
||||
<br />
|
||||
{i18next.t("system:Community")}: <a href="https://casdoor.org/#:~:text=Casdoor%20API-,Community,-GitHub">contact us</a>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ import {Link} from "react-router-dom";
|
||||
import {Button, Popconfirm, Switch, Table, Upload} from "antd";
|
||||
import {UploadOutlined} from "@ant-design/icons";
|
||||
import moment from "moment";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
import * as UserBackend from "./backend/UserBackend";
|
||||
import i18next from "i18next";
|
||||
@ -28,6 +29,7 @@ class UserListPage extends BaseListPage {
|
||||
this.state = {
|
||||
classes: props,
|
||||
organizationName: props.match.params.organizationName,
|
||||
organization: null,
|
||||
data: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
@ -271,6 +273,15 @@ class UserListPage extends BaseListPage {
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("tag"),
|
||||
render: (text, record, index) => {
|
||||
const tagMap = {};
|
||||
this.state.organization?.tags?.map((tag, index) => {
|
||||
const tokens = tag.split("|");
|
||||
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
|
||||
tagMap[tokens[0]] = displayValue;
|
||||
});
|
||||
return tagMap[text];
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("user:Is admin"),
|
||||
@ -352,7 +363,7 @@ class UserListPage extends BaseListPage {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={paginationProps}
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Users")}
|
||||
@ -387,6 +398,11 @@ class UserListPage extends BaseListPage {
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
|
||||
const users = res.data;
|
||||
if (users.length > 0) {
|
||||
this.getOrganization(users[0].owner);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -403,10 +419,24 @@ class UserListPage extends BaseListPage {
|
||||
searchText: params.searchText,
|
||||
searchedColumn: params.searchedColumn,
|
||||
});
|
||||
|
||||
const users = res.data;
|
||||
if (users.length > 0) {
|
||||
this.getOrganization(users[0].owner);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getOrganization(organizationName) {
|
||||
OrganizationBackend.getOrganization("admin", organizationName)
|
||||
.then((organization) => {
|
||||
this.setState({
|
||||
organization: organization,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default UserListPage;
|
||||
|
@ -67,21 +67,6 @@ class WebhookListPage extends BaseListPage {
|
||||
|
||||
renderTable(webhooks) {
|
||||
const columns = [
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: "name",
|
||||
@ -98,6 +83,21 @@ class WebhookListPage extends BaseListPage {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/organizations/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: "createdTime",
|
||||
|
@ -23,6 +23,7 @@ import {CountDownInput} from "../common/CountDownInput";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import {CheckCircleOutlined, KeyOutlined, LockOutlined, SolutionOutlined, UserOutlined} from "@ant-design/icons";
|
||||
import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import {withRouter} from "react-router-dom";
|
||||
|
||||
const {Step} = Steps;
|
||||
const {Option} = Select;
|
||||
@ -166,7 +167,7 @@ class ForgetPage extends React.Component {
|
||||
values.userOwner = this.state.application?.organizationObj.name;
|
||||
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
|
||||
if (res.status === "ok") {
|
||||
Setting.goToLogin(this, this.state.application);
|
||||
Setting.redirectToLoginPage(this.state.application, this.props.history);
|
||||
} else {
|
||||
Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
|
||||
}
|
||||
@ -489,7 +490,7 @@ class ForgetPage extends React.Component {
|
||||
return (
|
||||
<div className="loginBackground" style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${application.formBackgroundUrl})`}}>
|
||||
<CustomGithubCorner />
|
||||
<div className="login-content forget-content">
|
||||
<div className="login-content forget-content" style={{padding: Setting.isMobile() ? "0" : null, boxShadow: Setting.isMobile() ? "none" : null}}>
|
||||
<Row>
|
||||
<Col span={24} style={{justifyContent: "center"}}>
|
||||
<Row>
|
||||
@ -550,4 +551,4 @@ class ForgetPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default ForgetPage;
|
||||
export default withRouter(ForgetPage);
|
||||
|
@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd";
|
||||
import {LockOutlined, UserOutlined} from "@ant-design/icons";
|
||||
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
|
||||
@ -30,6 +29,8 @@ import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import {CountDownInput} from "../common/CountDownInput";
|
||||
import SelectLanguageBox from "../SelectLanguageBox";
|
||||
import {withTranslation} from "react-i18next";
|
||||
import {CaptchaModal} from "../common/CaptchaModal";
|
||||
import {withRouter} from "react-router-dom";
|
||||
|
||||
const {TabPane} = Tabs;
|
||||
|
||||
@ -49,6 +50,9 @@ class LoginPage extends React.Component {
|
||||
validEmail: false,
|
||||
validPhone: false,
|
||||
loginMethod: "password",
|
||||
enableCaptchaModal: false,
|
||||
openCaptchaModal: false,
|
||||
verifyCaptcha: undefined,
|
||||
};
|
||||
|
||||
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
|
||||
@ -69,6 +73,18 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
if (this.state.application && !prevState.application) {
|
||||
const defaultCaptchaProviderItems = this.getDefaultCaptchaProviderItems(this.state.application);
|
||||
|
||||
if (!defaultCaptchaProviderItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({enableCaptchaModal: defaultCaptchaProviderItems.some(providerItem => providerItem.rule === "Always")});
|
||||
}
|
||||
}
|
||||
|
||||
getApplicationLogin() {
|
||||
const oAuthParams = Util.getOAuthGetParameters();
|
||||
AuthBackend.getApplicationLogin(oAuthParams)
|
||||
@ -226,6 +242,23 @@ class LoginPage extends React.Component {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.loginMethod === "password" && this.state.enableCaptchaModal) {
|
||||
this.setState({
|
||||
openCaptchaModal: true,
|
||||
verifyCaptcha: (captchaType, captchaToken, secret) => {
|
||||
values["captchaType"] = captchaType;
|
||||
values["captchaToken"] = captchaToken;
|
||||
values["clientSecret"] = secret;
|
||||
|
||||
this.login(values);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.login(values);
|
||||
}
|
||||
}
|
||||
|
||||
login(values) {
|
||||
// here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
|
||||
if (this.state.type === "cas") {
|
||||
// CAS
|
||||
@ -240,6 +273,8 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
Util.showMessage("success", msg);
|
||||
|
||||
this.setState({openCaptchaModal: false});
|
||||
|
||||
if (casParams.service !== "") {
|
||||
const st = res.data;
|
||||
const newUrl = new URL(casParams.service);
|
||||
@ -247,6 +282,7 @@ class LoginPage extends React.Component {
|
||||
window.location.href = newUrl.toString();
|
||||
}
|
||||
} else {
|
||||
this.setState({openCaptchaModal: false});
|
||||
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
@ -259,6 +295,7 @@ class LoginPage extends React.Component {
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const responseType = values["type"];
|
||||
|
||||
if (responseType === "login") {
|
||||
Util.showMessage("success", "Logged in successfully");
|
||||
|
||||
@ -276,6 +313,7 @@ class LoginPage extends React.Component {
|
||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||
}
|
||||
} else {
|
||||
this.setState({openCaptchaModal: false});
|
||||
Util.showMessage("error", `Failed to log in: ${res.msg}`);
|
||||
}
|
||||
});
|
||||
@ -302,13 +340,11 @@ class LoginPage extends React.Component {
|
||||
title="Sign Up Error"
|
||||
subTitle={"The application does not allow to sign up new account"}
|
||||
extra={[
|
||||
<Link key="login" onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
}}>
|
||||
<Button type="primary" key="signin">
|
||||
Sign In
|
||||
</Button>
|
||||
</Link>,
|
||||
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
|
||||
{
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
</Result>
|
||||
@ -421,6 +457,9 @@ class LoginPage extends React.Component {
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
</Button>
|
||||
{
|
||||
this.renderCaptchaModal(application)
|
||||
}
|
||||
{
|
||||
this.renderFooter(application)
|
||||
}
|
||||
@ -463,16 +502,54 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultCaptchaProviderItems(application) {
|
||||
const providers = application?.providers;
|
||||
|
||||
if (providers === undefined || providers === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return providers.filter(providerItem => {
|
||||
if (providerItem.provider === undefined || providerItem.provider === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return providerItem.provider.category === "Captcha" && providerItem.provider.type === "Default";
|
||||
});
|
||||
}
|
||||
|
||||
renderCaptchaModal(application) {
|
||||
if (!this.state.enableCaptchaModal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const provider = this.getDefaultCaptchaProviderItems(application)
|
||||
.filter(providerItem => providerItem.rule === "Always")
|
||||
.map(providerItem => providerItem.provider)[0];
|
||||
|
||||
return <CaptchaModal
|
||||
owner={provider.owner}
|
||||
name={provider.name}
|
||||
captchaType={provider.type}
|
||||
subType={provider.subType}
|
||||
clientId={provider.clientId}
|
||||
clientId2={provider.clientId2}
|
||||
clientSecret={provider.clientSecret}
|
||||
clientSecret2={provider.clientSecret2}
|
||||
open={this.state.openCaptchaModal}
|
||||
onOk={(captchaType, captchaToken, secret) => this.state.verifyCaptcha?.(captchaType, captchaToken, secret)}
|
||||
canCancel={false}
|
||||
/>;
|
||||
}
|
||||
|
||||
renderFooter(application) {
|
||||
if (this.state.mode === "signup") {
|
||||
return (
|
||||
<div style={{float: "right"}}>
|
||||
{i18next.t("signup:Have account?")}
|
||||
<Link onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
}}>
|
||||
{i18next.t("signup:sign in now")}
|
||||
</Link>
|
||||
{
|
||||
Setting.renderLoginLink(application, i18next.t("signup:sign in now"))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
@ -708,7 +785,6 @@ class LoginPage extends React.Component {
|
||||
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
|
||||
{Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
|
||||
<div className="login-panel">
|
||||
<SelectLanguageBox id="language-box-corner" style={{top: "50px"}} />
|
||||
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
</div>
|
||||
@ -724,6 +800,7 @@ class LoginPage extends React.Component {
|
||||
{/* {*/}
|
||||
{/* this.state.clientId !== null ? "Redirect" : null*/}
|
||||
{/* }*/}
|
||||
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||
{
|
||||
this.renderSignedInBox()
|
||||
}
|
||||
@ -740,4 +817,4 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation()(LoginPage);
|
||||
export default withTranslation()(withRouter(LoginPage));
|
||||
|
@ -22,6 +22,7 @@ import i18next from "i18next";
|
||||
import AffiliationSelect from "../common/AffiliationSelect";
|
||||
import OAuthWidget from "../common/OAuthWidget";
|
||||
import SelectRegionBox from "../SelectRegionBox";
|
||||
import {withRouter} from "react-router-dom";
|
||||
|
||||
class PromptPage extends React.Component {
|
||||
constructor(props) {
|
||||
@ -190,7 +191,7 @@ class PromptPage extends React.Component {
|
||||
if (redirectUrl !== "" && redirectUrl !== null) {
|
||||
Setting.goToLink(redirectUrl);
|
||||
} else {
|
||||
Setting.goToLogin(this, this.getApplicationObj());
|
||||
Setting.redirectToLoginPage(this.getApplicationObj(), this.props.history);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
||||
@ -234,10 +235,10 @@ class PromptPage extends React.Component {
|
||||
title="Sign Up Error"
|
||||
subTitle={"You are unexpected to see this prompt page"}
|
||||
extra={[
|
||||
<Button type="primary" key="signin" onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
}}>
|
||||
Sign In
|
||||
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
|
||||
{
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
@ -272,4 +273,4 @@ class PromptPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default PromptPage;
|
||||
export default withRouter(PromptPage);
|
||||
|
@ -69,7 +69,7 @@ class ResultPage extends React.Component {
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
} else {
|
||||
Setting.goToLogin(this, application);
|
||||
Setting.redirectToLoginPage(application);
|
||||
}
|
||||
}}>
|
||||
{i18next.t("login:Sign In")}
|
||||
|
@ -26,6 +26,7 @@ import {CountDownInput} from "../common/CountDownInput";
|
||||
import SelectRegionBox from "../SelectRegionBox";
|
||||
import CustomGithubCorner from "../CustomGithubCorner";
|
||||
import SelectLanguageBox from "../SelectLanguageBox";
|
||||
import {withRouter} from "react-router-dom";
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {
|
||||
@ -541,10 +542,10 @@ class SignupPage extends React.Component {
|
||||
title="Sign Up Error"
|
||||
subTitle={"The application does not allow to sign up new account"}
|
||||
extra={[
|
||||
<Button type="primary" key="signin" onClick={() => {
|
||||
Setting.goToLogin(this, application);
|
||||
}}>
|
||||
Sign In
|
||||
<Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application, this.props.history)}>
|
||||
{
|
||||
i18next.t("login:Sign In")
|
||||
}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
@ -562,7 +563,7 @@ class SignupPage extends React.Component {
|
||||
application: application.name,
|
||||
organization: application.organization,
|
||||
}}
|
||||
style={{width: !Setting.isMobile() ? "400px" : "250px"}}
|
||||
style={{width: !Setting.isMobile() ? "400px" : "300px"}}
|
||||
size="large"
|
||||
>
|
||||
<Form.Item
|
||||
@ -600,7 +601,7 @@ class SignupPage extends React.Component {
|
||||
if (linkInStorage !== null && linkInStorage !== "") {
|
||||
Setting.goToLink(linkInStorage);
|
||||
} else {
|
||||
Setting.goToLogin(this, application);
|
||||
Setting.redirectToLoginPage(application, this.props.history);
|
||||
}
|
||||
}}>
|
||||
{i18next.t("signup:sign in now")}
|
||||
@ -633,7 +634,6 @@ class SignupPage extends React.Component {
|
||||
<div className="login-content" style={{margin: this.parseOffset(application.formOffset)}}>
|
||||
{Setting.inIframe() ? null : <div dangerouslySetInnerHTML={{__html: application.formCss}} />}
|
||||
<div className="login-panel" >
|
||||
<SelectLanguageBox id="language-box-corner" style={{top: "50px"}} />
|
||||
<div className="side-image" style={{display: application.formOffset !== 4 ? "none" : null}}>
|
||||
<div dangerouslySetInnerHTML={{__html: application.formSideHtml}} />
|
||||
</div>
|
||||
@ -645,6 +645,7 @@ class SignupPage extends React.Component {
|
||||
{
|
||||
Setting.renderLogo(application)
|
||||
}
|
||||
<SelectLanguageBox id="language-box-corner" style={{top: "55px", right: "5px", position: "absolute"}} />
|
||||
{
|
||||
this.renderForm(application)
|
||||
}
|
||||
@ -660,4 +661,4 @@ class SignupPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
export default SignupPage;
|
||||
export default withRouter(SignupPage);
|
||||
|
@ -24,6 +24,16 @@ export function getProviders(owner, page = "", pageSize = "", field = "", value
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getGlobalProviders(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-global-providers?p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Accept-Language": Setting.getAcceptLanguage(),
|
||||
},
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getProvider(owner, name) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-provider?id=${owner}/${encodeURIComponent(name)}`, {
|
||||
method: "GET",
|
||||
|
@ -62,10 +62,10 @@ class SingleCard extends React.Component {
|
||||
<Card
|
||||
hoverable
|
||||
cover={
|
||||
<img alt="logo" src={logo} style={{width: "100%", objectFit: "scale-down"}} />
|
||||
<img alt="logo" src={logo} style={{width: "100%", height: "200px", padding: "20px", objectFit: "scale-down"}} />
|
||||
}
|
||||
onClick={() => Setting.goToLinkSoft(this, silentSigninLink)}
|
||||
style={isSingle ? {width: "320px"} : {width: "100%"}}
|
||||
style={isSingle ? {width: "320px", height: "100%"} : {width: "100%", height: "100%"}}
|
||||
>
|
||||
<Meta title={title} description={desc} />
|
||||
<br />
|
||||
|
159
web/src/common/CaptchaModal.js
Normal file
159
web/src/common/CaptchaModal.js
Normal file
@ -0,0 +1,159 @@
|
||||
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Button, Col, Input, Modal, Row} from "antd";
|
||||
import i18next from "i18next";
|
||||
import React, {useEffect} from "react";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import {CaptchaWidget} from "./CaptchaWidget";
|
||||
import {SafetyOutlined} from "@ant-design/icons";
|
||||
|
||||
export const CaptchaModal = ({
|
||||
owner,
|
||||
name,
|
||||
captchaType,
|
||||
subType,
|
||||
clientId,
|
||||
clientId2,
|
||||
clientSecret,
|
||||
clientSecret2,
|
||||
open,
|
||||
onOk,
|
||||
onCancel,
|
||||
canCancel,
|
||||
}) => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||
const [captchaToken, setCaptchaToken] = React.useState("");
|
||||
const [secret, setSecret] = React.useState(clientSecret);
|
||||
const [secret2, setSecret2] = React.useState(clientSecret2);
|
||||
|
||||
useEffect(() => {
|
||||
setVisible(() => {
|
||||
if (open) {
|
||||
getCaptchaFromBackend();
|
||||
} else {
|
||||
cleanUp();
|
||||
}
|
||||
return open;
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
const handleOk = () => {
|
||||
onOk?.(captchaType, captchaToken, secret);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel?.();
|
||||
};
|
||||
|
||||
const cleanUp = () => {
|
||||
setCaptchaToken("");
|
||||
};
|
||||
|
||||
const getCaptchaFromBackend = () => {
|
||||
UserBackend.getCaptcha(owner, name, true).then((res) => {
|
||||
if (captchaType === "Default") {
|
||||
setSecret(res.captchaId);
|
||||
setCaptchaImg(res.captchaImage);
|
||||
} else {
|
||||
setSecret(res.clientSecret);
|
||||
setSecret2(res.clientSecret2);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const renderDefaultCaptcha = () => {
|
||||
return (
|
||||
<Col>
|
||||
<Row
|
||||
style={{
|
||||
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
|
||||
backgroundRepeat: "no-repeat",
|
||||
height: "80px",
|
||||
width: "200px",
|
||||
borderRadius: "5px",
|
||||
border: "1px solid #ccc",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
/>
|
||||
<Row>
|
||||
<Input
|
||||
autoFocus
|
||||
value={captchaToken}
|
||||
prefix={<SafetyOutlined />}
|
||||
placeholder={i18next.t("general:Captcha")}
|
||||
onPressEnter={handleOk}
|
||||
onChange={(e) => setCaptchaToken(e.target.value)}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
const onSubmit = (token) => {
|
||||
setCaptchaToken(token);
|
||||
};
|
||||
|
||||
const renderCheck = () => {
|
||||
if (captchaType === "Default") {
|
||||
return renderDefaultCaptcha();
|
||||
} else {
|
||||
return (
|
||||
<Col>
|
||||
<Row>
|
||||
<CaptchaWidget
|
||||
captchaType={captchaType}
|
||||
subType={subType}
|
||||
siteKey={clientId}
|
||||
clientSecret={secret}
|
||||
onChange={onSubmit}
|
||||
clientId2={clientId2}
|
||||
clientSecret2={secret2}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooter = () => {
|
||||
if (canCancel) {
|
||||
return [
|
||||
<Button key="cancel" onClick={handleCancel}>{i18next.t("user:Cancel")}</Button>,
|
||||
<Button key="ok" type="primary" onClick={handleOk}>{i18next.t("user:OK")}</Button>,
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
<Button key="ok" type="primary" onClick={handleOk}>{i18next.t("user:OK")}</Button>,
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Modal
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
destroyOnClose={true}
|
||||
title={i18next.t("general:Captcha")}
|
||||
visible={visible}
|
||||
width={348}
|
||||
footer={renderFooter()}
|
||||
>
|
||||
{renderCheck()}
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
@ -12,13 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import {Button, Col, Input, Modal, Row} from "antd";
|
||||
import {Button} from "antd";
|
||||
import React from "react";
|
||||
import i18next from "i18next";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
import {CaptchaModal} from "./CaptchaModal";
|
||||
import * as ProviderBackend from "../backend/ProviderBackend";
|
||||
import {SafetyOutlined} from "@ant-design/icons";
|
||||
import {CaptchaWidget} from "./CaptchaWidget";
|
||||
import * as UserBackend from "../backend/UserBackend";
|
||||
|
||||
export const CaptchaPreview = ({
|
||||
provider,
|
||||
@ -33,37 +32,9 @@ export const CaptchaPreview = ({
|
||||
clientId2,
|
||||
clientSecret2,
|
||||
}) => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
const [captchaImg, setCaptchaImg] = React.useState("");
|
||||
const [captchaToken, setCaptchaToken] = React.useState("");
|
||||
const [secret, setSecret] = React.useState(clientSecret);
|
||||
const [secret2, setSecret2] = React.useState(clientSecret2);
|
||||
|
||||
const handleOk = () => {
|
||||
UserBackend.verifyCaptcha(captchaType, captchaToken, secret).then(() => {
|
||||
setCaptchaToken("");
|
||||
setVisible(false);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const getCaptchaFromBackend = () => {
|
||||
UserBackend.getCaptcha(owner, name, true).then((res) => {
|
||||
if (captchaType === "Default") {
|
||||
setSecret(res.captchaId);
|
||||
setCaptchaImg(res.captchaImage);
|
||||
} else {
|
||||
setSecret(res.clientSecret);
|
||||
setSecret2(res.clientSecret2);
|
||||
}
|
||||
});
|
||||
};
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const clickPreview = () => {
|
||||
setVisible(true);
|
||||
provider.name = name;
|
||||
provider.clientId = clientId;
|
||||
provider.type = captchaType;
|
||||
@ -71,64 +42,10 @@ export const CaptchaPreview = ({
|
||||
if (clientSecret !== "***") {
|
||||
provider.clientSecret = clientSecret;
|
||||
ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
|
||||
getCaptchaFromBackend();
|
||||
setOpen(true);
|
||||
});
|
||||
} else {
|
||||
getCaptchaFromBackend();
|
||||
}
|
||||
};
|
||||
|
||||
const renderDefaultCaptcha = () => {
|
||||
return (
|
||||
<Col>
|
||||
<Row
|
||||
style={{
|
||||
backgroundImage: `url('data:image/png;base64,${captchaImg}')`,
|
||||
backgroundRepeat: "no-repeat",
|
||||
height: "80px",
|
||||
width: "200px",
|
||||
borderRadius: "5px",
|
||||
border: "1px solid #ccc",
|
||||
marginBottom: 10,
|
||||
}}
|
||||
/>
|
||||
<Row>
|
||||
<Input
|
||||
autoFocus
|
||||
value={captchaToken}
|
||||
prefix={<SafetyOutlined />}
|
||||
placeholder={i18next.t("general:Captcha")}
|
||||
onPressEnter={handleOk}
|
||||
onChange={(e) => setCaptchaToken(e.target.value)}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
const onSubmit = (token) => {
|
||||
setCaptchaToken(token);
|
||||
};
|
||||
|
||||
const renderCheck = () => {
|
||||
if (captchaType === "Default") {
|
||||
return renderDefaultCaptcha();
|
||||
} else {
|
||||
return (
|
||||
<Col>
|
||||
<Row>
|
||||
<CaptchaWidget
|
||||
captchaType={captchaType}
|
||||
subType={subType}
|
||||
siteKey={clientId}
|
||||
clientSecret={secret}
|
||||
onChange={onSubmit}
|
||||
clientId2={clientId2}
|
||||
clientSecret2={secret2}
|
||||
/>
|
||||
</Row>
|
||||
</Col>
|
||||
);
|
||||
setOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
@ -146,6 +63,16 @@ export const CaptchaPreview = ({
|
||||
return false;
|
||||
};
|
||||
|
||||
const onOk = (captchaType, captchaToken, secret) => {
|
||||
UserBackend.verifyCaptcha(captchaType, captchaToken, secret).then(() => {
|
||||
setOpen(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Button
|
||||
@ -156,20 +83,20 @@ export const CaptchaPreview = ({
|
||||
>
|
||||
{i18next.t("general:Preview")}
|
||||
</Button>
|
||||
<Modal
|
||||
closable={false}
|
||||
maskClosable={false}
|
||||
destroyOnClose={true}
|
||||
title={i18next.t("general:Captcha")}
|
||||
visible={visible}
|
||||
okText={i18next.t("user:OK")}
|
||||
cancelText={i18next.t("user:Cancel")}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
width={348}
|
||||
>
|
||||
{renderCheck()}
|
||||
</Modal>
|
||||
<CaptchaModal
|
||||
owner={owner}
|
||||
name={name}
|
||||
captchaType={captchaType}
|
||||
subType={subType}
|
||||
clientId={clientId}
|
||||
clientId2={clientId2}
|
||||
clientSecret={clientSecret}
|
||||
clientSecret2={clientSecret2}
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
onCancel={onCancel}
|
||||
canCancel={true}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
@ -13,6 +13,7 @@
|
||||
"Sync": "Sync"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
"Auto signin": "Auto signin",
|
||||
"Auto signin - Tooltip": "Auto signin - Tooltip",
|
||||
"Background URL": "Background URL",
|
||||
@ -43,6 +44,7 @@
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Passwort AN",
|
||||
"Password ON - Tooltip": "Whether to allow password login",
|
||||
"Please select a HTML file": "Bitte wählen Sie eine HTML-Datei",
|
||||
@ -53,6 +55,7 @@
|
||||
"Refresh token expire": "Aktualisierungs-Token läuft ab",
|
||||
"Refresh token expire - Tooltip": "Aktualisierungs-Token läuft ab - Tooltip",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -67,8 +70,7 @@
|
||||
"Token expire": "Token läuft ab",
|
||||
"Token expire - Tooltip": "Token läuft ab - Tooltip",
|
||||
"Token format": "Token-Format",
|
||||
"Token format - Tooltip": "Token-Format - Tooltip",
|
||||
"rule": "rule"
|
||||
"Token format - Tooltip": "Token-Format - Tooltip"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Bitgröße",
|
||||
@ -309,15 +311,16 @@
|
||||
"Favicon": "Févicon",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Weiche Löschung",
|
||||
"Soft deletion - Tooltip": "Weiche Löschung - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website-URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@ -450,6 +453,9 @@
|
||||
"Bucket": "Eimer",
|
||||
"Bucket - Tooltip": "Storage bucket name",
|
||||
"Can not parse Metadata": "Metadaten können nicht analysiert werden",
|
||||
"Can signin": "Can signin",
|
||||
"Can signup": "Can signup",
|
||||
"Can unlink": "Can unlink",
|
||||
"Category": "Kategorie",
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Channel No.": "Channel No.",
|
||||
@ -489,14 +495,17 @@
|
||||
"New Provider": "New Provider",
|
||||
"Parse": "Parse",
|
||||
"Parse Metadata successfully": "Metadaten erfolgreich analysieren",
|
||||
"Path prefix": "Path prefix",
|
||||
"Port": "Port",
|
||||
"Port - Tooltip": "Unique string-style identifier",
|
||||
"Prompted": "Prompted",
|
||||
"Provider URL": "Provider-URL",
|
||||
"Provider URL - Tooltip": "Unique string-style identifier",
|
||||
"Region ID": "Region ID",
|
||||
"Region ID - Tooltip": "Region ID - Tooltip",
|
||||
"Region endpoint for Internet": "Region Endpunkt für Internet",
|
||||
"Region endpoint for Intranet": "Region Endpunkt für Intranet",
|
||||
"Required": "Required",
|
||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||
@ -533,19 +542,15 @@
|
||||
"Test Connection": "Test Smtp Connection",
|
||||
"Test Email": "Test email config",
|
||||
"Test Email - Tooltip": "Email Address",
|
||||
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Typ",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"UserInfo URL": "UserInfo URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
|
||||
"alertType": "alarmtyp",
|
||||
"canSignIn": "canSignIn",
|
||||
"canSignUp": "canSignUp",
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "gefragt",
|
||||
"required": "benötigt",
|
||||
"visible": "sichtbar"
|
||||
"Visible": "Visible",
|
||||
"alertType": "alarmtyp"
|
||||
},
|
||||
"record": {
|
||||
"Is Triggered": "Wird ausgelöst"
|
||||
@ -695,6 +700,7 @@
|
||||
"Old Password": "Altes Passwort",
|
||||
"Password": "Passwort",
|
||||
"Password Set": "Passwort setzen",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Eigenschaften",
|
||||
"Re-enter New": "Neu erneut eingeben",
|
||||
"Reset Email...": "Reset Email...",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"Sync": "Sync"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
"Auto signin": "Auto signin",
|
||||
"Auto signin - Tooltip": "Auto signin - Tooltip",
|
||||
"Background URL": "Background URL",
|
||||
@ -43,6 +44,7 @@
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Password ON",
|
||||
"Password ON - Tooltip": "Password ON - Tooltip",
|
||||
"Please select a HTML file": "Please select a HTML file",
|
||||
@ -53,6 +55,7 @@
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -67,8 +70,7 @@
|
||||
"Token expire": "Token expire",
|
||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||
"Token format": "Token format",
|
||||
"Token format - Tooltip": "Token format - Tooltip",
|
||||
"rule": "rule"
|
||||
"Token format - Tooltip": "Token format - Tooltip"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Bit size",
|
||||
@ -309,15 +311,16 @@
|
||||
"Favicon": "Favicon",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Soft deletion",
|
||||
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Website URL - Tooltip",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
"Website URL - Tooltip": "Website URL - Tooltip"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@ -450,6 +453,9 @@
|
||||
"Bucket": "Bucket",
|
||||
"Bucket - Tooltip": "Bucket - Tooltip",
|
||||
"Can not parse Metadata": "Can not parse Metadata",
|
||||
"Can signin": "Can signin",
|
||||
"Can signup": "Can signup",
|
||||
"Can unlink": "Can unlink",
|
||||
"Category": "Category",
|
||||
"Category - Tooltip": "Category - Tooltip",
|
||||
"Channel No.": "Channel No.",
|
||||
@ -489,14 +495,17 @@
|
||||
"New Provider": "New Provider",
|
||||
"Parse": "Parse",
|
||||
"Parse Metadata successfully": "Parse Metadata successfully",
|
||||
"Path prefix": "Path prefix",
|
||||
"Port": "Port",
|
||||
"Port - Tooltip": "Port - Tooltip",
|
||||
"Prompted": "Prompted",
|
||||
"Provider URL": "Provider URL",
|
||||
"Provider URL - Tooltip": "Provider URL - Tooltip",
|
||||
"Region ID": "Region ID",
|
||||
"Region ID - Tooltip": "Region ID - Tooltip",
|
||||
"Region endpoint for Internet": "Region endpoint for Internet",
|
||||
"Region endpoint for Intranet": "Region endpoint for Intranet",
|
||||
"Required": "Required",
|
||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||
@ -533,19 +542,15 @@
|
||||
"Test Connection": "Test Connection",
|
||||
"Test Email": "Test Email",
|
||||
"Test Email - Tooltip": "Test Email - Tooltip",
|
||||
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Type - Tooltip",
|
||||
"UserInfo URL": "UserInfo URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
|
||||
"alertType": "alertType",
|
||||
"canSignIn": "canSignIn",
|
||||
"canSignUp": "canSignUp",
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "prompted",
|
||||
"required": "required",
|
||||
"visible": "visible"
|
||||
"Visible": "Visible",
|
||||
"alertType": "alertType"
|
||||
},
|
||||
"record": {
|
||||
"Is Triggered": "Is Triggered"
|
||||
@ -695,6 +700,7 @@
|
||||
"Old Password": "Old Password",
|
||||
"Password": "Password",
|
||||
"Password Set": "Password Set",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Properties",
|
||||
"Re-enter New": "Re-enter New",
|
||||
"Reset Email...": "Reset Email...",
|
||||
|
@ -45,7 +45,9 @@
|
||||
"Token expire - Tooltip": "Expiración del Token - Tooltip",
|
||||
"Token format": "Formato del Token",
|
||||
"Token format - Tooltip": "Formato del Token - Tooltip",
|
||||
"rule": "rule"
|
||||
"Rule": "Rule",
|
||||
"None": "None",
|
||||
"Always": "Always"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Tamaño del Bit",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"Sync": "Sync"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
"Auto signin": "Auto signin",
|
||||
"Auto signin - Tooltip": "Auto signin - Tooltip",
|
||||
"Background URL": "Background URL",
|
||||
@ -43,6 +44,7 @@
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Mot de passe activé",
|
||||
"Password ON - Tooltip": "Whether to allow password login",
|
||||
"Please select a HTML file": "Veuillez sélectionner un fichier HTML",
|
||||
@ -53,6 +55,7 @@
|
||||
"Refresh token expire": "Expiration du jeton d'actualisation",
|
||||
"Refresh token expire - Tooltip": "Expiration du jeton d'actualisation - infobulle",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -67,8 +70,7 @@
|
||||
"Token expire": "Expiration du jeton",
|
||||
"Token expire - Tooltip": "Expiration du jeton - Info-bulle",
|
||||
"Token format": "Format du jeton",
|
||||
"Token format - Tooltip": "Format du jeton - infobulle",
|
||||
"rule": "rule"
|
||||
"Token format - Tooltip": "Format du jeton - infobulle"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Taille du bit",
|
||||
@ -309,15 +311,16 @@
|
||||
"Favicon": "Favicon",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Suppression du logiciel",
|
||||
"Soft deletion - Tooltip": "Suppression de soft - infobulle",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "URL du site web",
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@ -450,6 +453,9 @@
|
||||
"Bucket": "Seau",
|
||||
"Bucket - Tooltip": "Storage bucket name",
|
||||
"Can not parse Metadata": "Impossible d'analyser les métadonnées",
|
||||
"Can signin": "Can signin",
|
||||
"Can signup": "Can signup",
|
||||
"Can unlink": "Can unlink",
|
||||
"Category": "Catégorie",
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Channel No.": "Channel No.",
|
||||
@ -489,14 +495,17 @@
|
||||
"New Provider": "New Provider",
|
||||
"Parse": "Parse",
|
||||
"Parse Metadata successfully": "Analyse des métadonnées réussie",
|
||||
"Path prefix": "Path prefix",
|
||||
"Port": "Port",
|
||||
"Port - Tooltip": "Unique string-style identifier",
|
||||
"Prompted": "Prompted",
|
||||
"Provider URL": "URL du fournisseur",
|
||||
"Provider URL - Tooltip": "Unique string-style identifier",
|
||||
"Region ID": "ID de la région",
|
||||
"Region ID - Tooltip": "ID de région - infobulle",
|
||||
"Region endpoint for Internet": "Point de terminaison de la région pour Internet",
|
||||
"Region endpoint for Intranet": "Point de terminaison de la région pour Intranet",
|
||||
"Required": "Required",
|
||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||
@ -533,19 +542,15 @@
|
||||
"Test Connection": "Test Smtp Connection",
|
||||
"Test Email": "Test email config",
|
||||
"Test Email - Tooltip": "Email Address",
|
||||
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Type de texte",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"UserInfo URL": "UserInfo URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
|
||||
"alertType": "Type d'alerte",
|
||||
"canSignIn": "canSignIn",
|
||||
"canSignUp": "canSignUp",
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "invitée",
|
||||
"required": "Obligatoire",
|
||||
"visible": "Visible"
|
||||
"Visible": "Visible",
|
||||
"alertType": "Type d'alerte"
|
||||
},
|
||||
"record": {
|
||||
"Is Triggered": "Est déclenché"
|
||||
@ -695,6 +700,7 @@
|
||||
"Old Password": "Ancien mot de passe",
|
||||
"Password": "Mot de passe",
|
||||
"Password Set": "Mot de passe défini",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Propriétés",
|
||||
"Re-enter New": "Nouvelle entrée",
|
||||
"Reset Email...": "Reset Email...",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"Sync": "Sync"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
"Auto signin": "Auto signin",
|
||||
"Auto signin - Tooltip": "Auto signin - Tooltip",
|
||||
"Background URL": "Background URL",
|
||||
@ -43,6 +44,7 @@
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "パスワードON",
|
||||
"Password ON - Tooltip": "Whether to allow password login",
|
||||
"Please select a HTML file": "HTMLファイルを選択してください",
|
||||
@ -53,6 +55,7 @@
|
||||
"Refresh token expire": "トークンの更新の期限が切れます",
|
||||
"Refresh token expire - Tooltip": "トークンの有効期限を更新する - ツールチップ",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -67,8 +70,7 @@
|
||||
"Token expire": "トークンの有効期限",
|
||||
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
|
||||
"Token format": "トークンのフォーマット",
|
||||
"Token format - Tooltip": "トークンフォーマット - ツールチップ",
|
||||
"rule": "rule"
|
||||
"Token format - Tooltip": "トークンフォーマット - ツールチップ"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "ビットサイズ",
|
||||
@ -309,15 +311,16 @@
|
||||
"Favicon": "ファビコン",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "ソフト削除",
|
||||
"Soft deletion - Tooltip": "ソフト削除 - ツールチップ",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@ -450,6 +453,9 @@
|
||||
"Bucket": "バケツ入りバケツ",
|
||||
"Bucket - Tooltip": "Storage bucket name",
|
||||
"Can not parse Metadata": "メタデータをパースできません",
|
||||
"Can signin": "Can signin",
|
||||
"Can signup": "Can signup",
|
||||
"Can unlink": "Can unlink",
|
||||
"Category": "カテゴリ",
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Channel No.": "Channel No.",
|
||||
@ -489,14 +495,17 @@
|
||||
"New Provider": "New Provider",
|
||||
"Parse": "Parse",
|
||||
"Parse Metadata successfully": "メタデータの解析に成功",
|
||||
"Path prefix": "Path prefix",
|
||||
"Port": "ポート",
|
||||
"Port - Tooltip": "Unique string-style identifier",
|
||||
"Prompted": "Prompted",
|
||||
"Provider URL": "プロバイダー URL",
|
||||
"Provider URL - Tooltip": "Unique string-style identifier",
|
||||
"Region ID": "地域ID",
|
||||
"Region ID - Tooltip": "リージョンID - ツールチップ",
|
||||
"Region endpoint for Internet": "インターネットのリージョンエンドポイント",
|
||||
"Region endpoint for Intranet": "イントラネットのリージョンエンドポイント",
|
||||
"Required": "Required",
|
||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||
@ -533,19 +542,15 @@
|
||||
"Test Connection": "Test Smtp Connection",
|
||||
"Test Email": "Test email config",
|
||||
"Test Email - Tooltip": "Email Address",
|
||||
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "タイプ",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"UserInfo URL": "UserInfo URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
|
||||
"alertType": "alertType",
|
||||
"canSignIn": "canSignIn",
|
||||
"canSignUp": "canSignUp",
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "プロンプトされた",
|
||||
"required": "必須",
|
||||
"visible": "表示"
|
||||
"Visible": "Visible",
|
||||
"alertType": "alertType"
|
||||
},
|
||||
"record": {
|
||||
"Is Triggered": "トリガーされます"
|
||||
@ -695,6 +700,7 @@
|
||||
"Old Password": "古いパスワード",
|
||||
"Password": "パスワード",
|
||||
"Password Set": "パスワード設定",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "プロパティー",
|
||||
"Re-enter New": "新しい入力",
|
||||
"Reset Email...": "Reset Email...",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"Sync": "Sync"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
"Auto signin": "Auto signin",
|
||||
"Auto signin - Tooltip": "Auto signin - Tooltip",
|
||||
"Background URL": "Background URL",
|
||||
@ -43,6 +44,7 @@
|
||||
"Grant types - Tooltip": "Grant types - Tooltip",
|
||||
"Left": "Left",
|
||||
"New Application": "New Application",
|
||||
"None": "None",
|
||||
"Password ON": "Password ON",
|
||||
"Password ON - Tooltip": "Whether to allow password login",
|
||||
"Please select a HTML file": "Please select a HTML file",
|
||||
@ -53,6 +55,7 @@
|
||||
"Refresh token expire": "Refresh token expire",
|
||||
"Refresh token expire - Tooltip": "Refresh token expire - Tooltip",
|
||||
"Right": "Right",
|
||||
"Rule": "Rule",
|
||||
"SAML metadata": "SAML metadata",
|
||||
"SAML metadata - Tooltip": "SAML metadata - Tooltip",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML metadata URL copied to clipboard successfully",
|
||||
@ -67,8 +70,7 @@
|
||||
"Token expire": "Token expire",
|
||||
"Token expire - Tooltip": "Token expire - Tooltip",
|
||||
"Token format": "Token format",
|
||||
"Token format - Tooltip": "Token format - Tooltip",
|
||||
"rule": "rule"
|
||||
"Token format - Tooltip": "Token format - Tooltip"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Bit size",
|
||||
@ -309,15 +311,16 @@
|
||||
"Favicon": "Favicon",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Soft deletion",
|
||||
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "Website URL",
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@ -450,6 +453,9 @@
|
||||
"Bucket": "Bucket",
|
||||
"Bucket - Tooltip": "Storage bucket name",
|
||||
"Can not parse Metadata": "Can not parse Metadata",
|
||||
"Can signin": "Can signin",
|
||||
"Can signup": "Can signup",
|
||||
"Can unlink": "Can unlink",
|
||||
"Category": "Category",
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Channel No.": "Channel No.",
|
||||
@ -489,14 +495,17 @@
|
||||
"New Provider": "New Provider",
|
||||
"Parse": "Parse",
|
||||
"Parse Metadata successfully": "Parse Metadata successfully",
|
||||
"Path prefix": "Path prefix",
|
||||
"Port": "Port",
|
||||
"Port - Tooltip": "Unique string-style identifier",
|
||||
"Prompted": "Prompted",
|
||||
"Provider URL": "Provider URL",
|
||||
"Provider URL - Tooltip": "Unique string-style identifier",
|
||||
"Region ID": "Region ID",
|
||||
"Region ID - Tooltip": "Region ID - Tooltip",
|
||||
"Region endpoint for Internet": "Region endpoint for Internet",
|
||||
"Region endpoint for Intranet": "Region endpoint for Intranet",
|
||||
"Required": "Required",
|
||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||
@ -533,19 +542,15 @@
|
||||
"Test Connection": "Test Smtp Connection",
|
||||
"Test Email": "Test email config",
|
||||
"Test Email - Tooltip": "Email Address",
|
||||
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"UserInfo URL": "UserInfo URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
|
||||
"alertType": "alertType",
|
||||
"canSignIn": "canSignIn",
|
||||
"canSignUp": "canSignUp",
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "prompted",
|
||||
"required": "required",
|
||||
"visible": "visible"
|
||||
"Visible": "Visible",
|
||||
"alertType": "alertType"
|
||||
},
|
||||
"record": {
|
||||
"Is Triggered": "Is Triggered"
|
||||
@ -695,6 +700,7 @@
|
||||
"Old Password": "Old Password",
|
||||
"Password": "Password",
|
||||
"Password Set": "Password Set",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Properties",
|
||||
"Re-enter New": "Re-enter New",
|
||||
"Reset Email...": "Reset Email...",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"Sync": "Sync"
|
||||
},
|
||||
"application": {
|
||||
"Always": "Always",
|
||||
"Auto signin": "Auto signin",
|
||||
"Auto signin - Tooltip": "Auto signin - Tooltip",
|
||||
"Background URL": "Background URL",
|
||||
@ -43,6 +44,7 @@
|
||||
"Grant types - Tooltip": "Виды грантов - Подсказка",
|
||||
"Left": "Left",
|
||||
"New Application": "Новое приложение",
|
||||
"None": "None",
|
||||
"Password ON": "Пароль ВКЛ",
|
||||
"Password ON - Tooltip": "Whether to allow password login",
|
||||
"Please select a HTML file": "Пожалуйста, выберите HTML-файл",
|
||||
@ -53,6 +55,7 @@
|
||||
"Refresh token expire": "Срок действия обновления токена истекает",
|
||||
"Refresh token expire - Tooltip": "Срок обновления токена истекает - Подсказка",
|
||||
"Right": "Right",
|
||||
"Rule": "правило",
|
||||
"SAML metadata": "Метаданные SAML",
|
||||
"SAML metadata - Tooltip": "Метаданные SAML - Подсказка",
|
||||
"SAML metadata URL copied to clipboard successfully": "Адрес метаданных SAML скопирован в буфер обмена",
|
||||
@ -67,8 +70,7 @@
|
||||
"Token expire": "Токен истекает",
|
||||
"Token expire - Tooltip": "Истек токен - Подсказка",
|
||||
"Token format": "Формат токена",
|
||||
"Token format - Tooltip": "Формат токена - Подсказка",
|
||||
"rule": "правило"
|
||||
"Token format - Tooltip": "Формат токена - Подсказка"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "Размер бита",
|
||||
@ -309,15 +311,16 @@
|
||||
"Favicon": "Иконка",
|
||||
"Is profile public": "Is profile public",
|
||||
"Is profile public - Tooltip": "Is profile public - Tooltip",
|
||||
"Modify rule": "Modify rule",
|
||||
"New Organization": "New Organization",
|
||||
"Soft deletion": "Мягкое удаление",
|
||||
"Soft deletion - Tooltip": "Мягкое удаление - Подсказка",
|
||||
"Tags": "Tags",
|
||||
"Tags - Tooltip": "Tags - Tooltip",
|
||||
"View rule": "View rule",
|
||||
"Visible": "Visible",
|
||||
"Website URL": "URL сайта",
|
||||
"Website URL - Tooltip": "Unique string-style identifier",
|
||||
"modifyRule": "modifyRule",
|
||||
"viewRule": "viewRule"
|
||||
"Website URL - Tooltip": "Unique string-style identifier"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "Confirm your invoice information",
|
||||
@ -450,6 +453,9 @@
|
||||
"Bucket": "Ведро",
|
||||
"Bucket - Tooltip": "Storage bucket name",
|
||||
"Can not parse Metadata": "Невозможно разобрать метаданные",
|
||||
"Can signin": "Can signin",
|
||||
"Can signup": "Can signup",
|
||||
"Can unlink": "Can unlink",
|
||||
"Category": "Категория",
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Channel No.": "Channel No.",
|
||||
@ -489,14 +495,17 @@
|
||||
"New Provider": "New Provider",
|
||||
"Parse": "Parse",
|
||||
"Parse Metadata successfully": "Анализ метаданных успешно завершен",
|
||||
"Path prefix": "Path prefix",
|
||||
"Port": "Порт",
|
||||
"Port - Tooltip": "Unique string-style identifier",
|
||||
"Prompted": "Prompted",
|
||||
"Provider URL": "URL провайдера",
|
||||
"Provider URL - Tooltip": "Unique string-style identifier",
|
||||
"Region ID": "ID региона",
|
||||
"Region ID - Tooltip": "Идентификатор региона - Подсказка",
|
||||
"Region endpoint for Internet": "Конечная точка региона для Интернета",
|
||||
"Region endpoint for Intranet": "Конечная точка региона Интранета",
|
||||
"Required": "Required",
|
||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||
@ -533,19 +542,15 @@
|
||||
"Test Connection": "Test Smtp Connection",
|
||||
"Test Email": "Test email config",
|
||||
"Test Email - Tooltip": "Email Address",
|
||||
"The prefix path of the file - Tooltip": "The prefix path of the file - Tooltip",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - Tooltip",
|
||||
"Type": "Тип",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"UserInfo URL": "UserInfo URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo URL - Tooltip",
|
||||
"alertType": "тип оповещения",
|
||||
"canSignIn": "canSignIn",
|
||||
"canSignUp": "canSignUp",
|
||||
"canUnlink": "canUnlink",
|
||||
"prompted": "запрошено",
|
||||
"required": "обязательный",
|
||||
"visible": "видимый"
|
||||
"Visible": "Visible",
|
||||
"alertType": "тип оповещения"
|
||||
},
|
||||
"record": {
|
||||
"Is Triggered": "Срабатывает"
|
||||
@ -695,6 +700,7 @@
|
||||
"Old Password": "Старый пароль",
|
||||
"Password": "Пароль",
|
||||
"Password Set": "Пароль установлен",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Свойства",
|
||||
"Re-enter New": "Введите еще раз",
|
||||
"Reset Email...": "Reset Email...",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"Sync": "同步"
|
||||
},
|
||||
"application": {
|
||||
"Always": "始终开启",
|
||||
"Auto signin": "启用自动登录",
|
||||
"Auto signin - Tooltip": "当Casdoor存在已登录会话时,自动采用该会话进行应用端的登录",
|
||||
"Background URL": "背景图URL",
|
||||
@ -43,6 +44,7 @@
|
||||
"Grant types - Tooltip": "选择允许哪些OAuth协议中的Grant types",
|
||||
"Left": "居左",
|
||||
"New Application": "添加应用",
|
||||
"None": "关闭",
|
||||
"Password ON": "开启密码",
|
||||
"Password ON - Tooltip": "是否允许密码登录",
|
||||
"Please select a HTML file": "请选择一个HTML文件",
|
||||
@ -53,6 +55,7 @@
|
||||
"Refresh token expire": "Refresh Token过期",
|
||||
"Refresh token expire - Tooltip": "Refresh Token过期时间",
|
||||
"Right": "居右",
|
||||
"Rule": "规则",
|
||||
"SAML metadata": "SAML元数据",
|
||||
"SAML metadata - Tooltip": "SAML协议的元数据(Metadata)信息",
|
||||
"SAML metadata URL copied to clipboard successfully": "SAML元数据URL已成功复制到剪贴板",
|
||||
@ -67,8 +70,7 @@
|
||||
"Token expire": "Access Token过期",
|
||||
"Token expire - Tooltip": "Access Token过期时间",
|
||||
"Token format": "Access Token格式",
|
||||
"Token format - Tooltip": "Access Token格式",
|
||||
"rule": "规则"
|
||||
"Token format - Tooltip": "Access Token格式"
|
||||
},
|
||||
"cert": {
|
||||
"Bit size": "位大小",
|
||||
@ -309,15 +311,16 @@
|
||||
"Favicon": "图标",
|
||||
"Is profile public": "用户个人页公开",
|
||||
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织用户才能访问用户主页",
|
||||
"Modify rule": "修改规则",
|
||||
"New Organization": "添加组织",
|
||||
"Soft deletion": "软删除",
|
||||
"Soft deletion - Tooltip": "启用后,删除用户信息时不会在数据库彻底清除,只会标记为已删除状态",
|
||||
"Tags": "标签集合",
|
||||
"Tags - Tooltip": "可供用户选择的标签的集合",
|
||||
"View rule": "查看规则",
|
||||
"Visible": "是否可见",
|
||||
"Website URL": "网页地址",
|
||||
"Website URL - Tooltip": "网页地址",
|
||||
"modifyRule": "修改规则",
|
||||
"viewRule": "查看规则"
|
||||
"Website URL - Tooltip": "网页地址"
|
||||
},
|
||||
"payment": {
|
||||
"Confirm your invoice information": "确认您的发票信息",
|
||||
@ -450,6 +453,9 @@
|
||||
"Bucket": "存储桶",
|
||||
"Bucket - Tooltip": "Bucket名称",
|
||||
"Can not parse Metadata": "无法解析元数据",
|
||||
"Can signin": "可用于登录",
|
||||
"Can signup": "可用于注册",
|
||||
"Can unlink": "可解绑定",
|
||||
"Category": "分类",
|
||||
"Category - Tooltip": "分类",
|
||||
"Channel No.": "Channel No.",
|
||||
@ -489,14 +495,17 @@
|
||||
"New Provider": "添加提供商",
|
||||
"Parse": "Parse",
|
||||
"Parse Metadata successfully": "解析元数据成功",
|
||||
"Path prefix": "路径前缀",
|
||||
"Port": "端口",
|
||||
"Port - Tooltip": "端口号",
|
||||
"Prompted": "注册后提醒绑定",
|
||||
"Provider URL": "提供商URL",
|
||||
"Provider URL - Tooltip": "提供商URL",
|
||||
"Region ID": "地域ID",
|
||||
"Region ID - Tooltip": "地域ID",
|
||||
"Region endpoint for Internet": "地域节点 (外网)",
|
||||
"Region endpoint for Intranet": "地域节点 (内网)",
|
||||
"Required": "是否必填项",
|
||||
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip",
|
||||
@ -533,19 +542,15 @@
|
||||
"Test Connection": "测试SMTP连接",
|
||||
"Test Email": "测试Email配置",
|
||||
"Test Email - Tooltip": "邮箱地址",
|
||||
"The prefix path of the file - Tooltip": "文件的路径前缀 - 工具提示",
|
||||
"Token URL": "Token URL",
|
||||
"Token URL - Tooltip": "Token URL - 工具提示",
|
||||
"Type": "类型",
|
||||
"Type - Tooltip": "类型",
|
||||
"UserInfo URL": "UserInfo URL",
|
||||
"UserInfo URL - Tooltip": "UserInfo URL - 工具提示",
|
||||
"alertType": "警报类型",
|
||||
"canSignIn": "可用于登录",
|
||||
"canSignUp": "可用于注册",
|
||||
"canUnlink": "可解绑定",
|
||||
"prompted": "注册后提醒绑定",
|
||||
"required": "是否必填项",
|
||||
"visible": "是否可见"
|
||||
"Visible": "是否可见",
|
||||
"alertType": "警报类型"
|
||||
},
|
||||
"record": {
|
||||
"Is Triggered": "已触发"
|
||||
@ -695,6 +700,7 @@
|
||||
"Old Password": "旧密码",
|
||||
"Password": "密码",
|
||||
"Password Set": "密码已设置",
|
||||
"Please select avatar from resources": "从资源中选择...",
|
||||
"Properties": "属性",
|
||||
"Re-enter New": "重复新密码",
|
||||
"Reset Email...": "重置邮箱...",
|
||||
|
Reference in New Issue
Block a user