Compare commits

..

13 Commits

Author SHA1 Message Date
Mr Forest
a8381e875b feat: change all occurrences when a object name is changed (#1252) 2022-11-02 00:17:38 +08:00
Ke Wang
4c81fd7d16 feat: fix generating wrong x.509 private key file header (#1253)
According to the [official x509 documentation](https://pkg.go.dev/crypto/x509#MarshalPKCS1PrivateKey), the private key generated using `x509.MarshalPKCS1PrivateKey` starts with `-----BEGIN RSA PRIVATE KEY-----` instead of `-----BEGIN PRIVATE KEY-----`. Otherwise, it will not be parsed by most tools (like OpenSSL, [jwt.io](https://jwt.io/), etc.) because it does not conform to the specification.
2022-11-01 22:19:38 +08:00
xAmast
25ee4226d3 feat: clear the session of a signin but non-existent user (#1246) 2022-10-29 20:18:02 +08:00
Bingchang Chen
9d5b019243 fix: nil error if init data is empty (#1247) 2022-10-29 20:04:43 +08:00
Mr Forest
6bb7b545b4 feat: restrict DingTalk user log in who is under the DingTalk Org(which ClientId belong) (#1241)
* feat: fix bug in GetAcceptLanguage()

* feat: add appName when logging in with DingTalk

* fix review problems

* format code

* delete useless printf

* modify display name

Co-authored-by: Gucheng Wang <nomeguy@qq.com>
2022-10-28 22:14:05 +08:00
wenxuan70
25d56ee8d5 feat: allow captcha to be enabled when logging in (#1211)
* Fix bug in GetAcceptLanguage()

* feat: allow captcha to be enabled when logging in

* feat: when the login password is wrong, enable captcha

* feat: Restrict captcha from frontend

* fix: modify CaptchaModal component

* fix: modify the words of i18n

* Update data.json

Co-authored-by: Gucheng Wang <nomeguy@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-10-28 13:38:14 +08:00
Chell
7e5952c804 fix: login / signin frontend router (#1244)
* fix: go to link

* fix: remove gotologin

* fix: redirect to login page

* fix: redirect to login page

* remove comments

* fix: formats

* fix: formats

* Update Setting.js

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-10-28 02:23:57 +08:00
Yaodong Yu
80bf29d79a feat: fix showing wrong error message: "Please sign in first" (#1245) 2022-10-27 23:50:45 +08:00
Yaodong Yu
971e53dfd8 fix: fix duplicated user bug in user list page (#1243)
* fix: user list repititon errer

* Update UserListPage.js

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-10-27 22:51:05 +08:00
Bingchang Chen
654b903d7a feat: fix multi-platform docker image (#1242) 2022-10-26 23:31:00 +08:00
jakiuncle
2f72e6971b fix: make the app list in homepage have the same height (#1239)
* fix: make the app list in homepage have the same height

* fix: make the app list in homepage have the same height

* Update SingleCard.js

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-10-25 19:27:24 +08:00
Mr Forest
d4b587b93e feat: fix bug in GetAcceptLanguage() (#1237)
Co-authored-by: Gucheng Wang <nomeguy@qq.com>
2022-10-25 10:50:10 +08:00
Gucheng Wang
ac7a510949 Fix go.mod 2022-10-23 16:14:49 +08:00
51 changed files with 985 additions and 200 deletions

View File

@@ -14,6 +14,9 @@ RUN ./build.sh
FROM alpine:latest AS STANDARD FROM alpine:latest AS STANDARD
LABEL MAINTAINER="https://casdoor.org/" LABEL MAINTAINER="https://casdoor.org/"
ARG USER=casdoor ARG USER=casdoor
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN sed -i 's/https/http/' /etc/apk/repositories RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add --update sudo RUN apk add --update sudo
@@ -28,7 +31,7 @@ RUN adduser -D $USER -u 1000 \
USER 1000 USER 1000
WORKDIR / 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/swagger ./swagger
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf 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 COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
@@ -46,12 +49,15 @@ RUN apt update \
FROM db AS ALLINONE FROM db AS ALLINONE
LABEL MAINTAINER="https://casdoor.org/" LABEL MAINTAINER="https://casdoor.org/"
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN apt update RUN apt update
RUN apt install -y ca-certificates && update-ca-certificates RUN apt install -y ca-certificates && update-ca-certificates
WORKDIR / 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/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf

View File

@@ -8,4 +8,5 @@ else
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct" echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
export GOPROXY="https://goproxy.cn,direct" export GOPROXY="https://goproxy.cn,direct"
fi 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 .

View File

@@ -14,6 +14,8 @@
package captcha package captcha
import "fmt"
type CaptchaProvider interface { type CaptchaProvider interface {
VerifyCaptcha(token, clientSecret string) (bool, error) VerifyCaptcha(token, clientSecret string) (bool, error)
} }
@@ -32,3 +34,12 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
} }
return nil 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)
}

View File

@@ -64,6 +64,10 @@ type RequestForm struct {
RelayState string `json:"relayState"` RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"` SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"` SamlResponse string `json:"samlResponse"`
CaptchaType string `json:"captchaType"`
CaptchaToken string `json:"captchaToken"`
ClientSecret string `json:"clientSecret"`
} }
type Response struct { type Response struct {
@@ -241,8 +245,7 @@ func (c *ApiController) Logout() {
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication() application := c.GetSessionApplication()
c.SetSessionUsername("") c.ClearUserSession()
c.SetSessionData(nil)
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" { if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user) c.ResponseOk(user)

View File

@@ -23,6 +23,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/casdoor/casdoor/captcha"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
@@ -251,6 +253,25 @@ func (c *ApiController) Login() {
return return
} }
} else { } 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 password := form.Password
user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage()) user, msg = object.CheckUserPassword(form.Organization, form.Username, password, c.GetAcceptLanguage())
} }

View File

@@ -63,8 +63,7 @@ func (c *ApiController) GetSessionUsername() string {
if sessionData != nil && if sessionData != nil &&
sessionData.ExpireTime != 0 && sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() { sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("") c.ClearUserSession()
c.SetSessionData(nil)
return "" return ""
} }
@@ -85,13 +84,17 @@ func (c *ApiController) GetSessionApplication() *object.Application {
return application return application
} }
func (c *ApiController) ClearUserSession() {
c.SetSessionUsername("")
c.SetSessionData(nil)
}
func (c *ApiController) GetSessionOidc() (string, string) { func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData() sessionData := c.GetSessionData()
if sessionData != nil && if sessionData != nil &&
sessionData.ExpireTime != 0 && sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() { sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("") c.ClearUserSession()
c.SetSessionData(nil)
return "", "" return "", ""
} }
scopeValue := c.GetSession("scope") scopeValue := c.GetSession("scope")

View File

@@ -56,6 +56,9 @@ func (c *ApiController) T(error string) string {
// GetAcceptLanguage ... // GetAcceptLanguage ...
func (c *ApiController) GetAcceptLanguage() string { func (c *ApiController) GetAcceptLanguage() string {
lang := c.Ctx.Request.Header.Get("Accept-Language") lang := c.Ctx.Request.Header.Get("Accept-Language")
if lang == "" {
lang = "en"
}
return lang[0:2] return lang[0:2]
} }
@@ -80,7 +83,7 @@ func (c *ApiController) SetTokenErrorHttpStatus() {
func (c *ApiController) RequireSignedIn() (string, bool) { func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername() userId := c.GetSessionUsername()
if userId == "" { if userId == "" {
c.ResponseError(c.T("LoginErr.SignInFirst")) c.ResponseError(c.T("LoginErr.LoginFirst"), "Please login first")
return "", false return "", false
} }
return userId, true return userId, true
@@ -95,6 +98,7 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
user := object.GetUser(userId) user := object.GetUser(userId)
if user == nil { if user == nil {
c.ClearUserSession()
c.ResponseError(fmt.Sprintf(c.T("UserErr.DoNotExist"), userId)) c.ResponseError(fmt.Sprintf(c.T("UserErr.DoNotExist"), userId))
return nil, false return nil, false
} }

5
go.mod
View File

@@ -4,11 +4,10 @@ go 1.16
require ( require (
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b 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/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
github.com/aws/aws-sdk-go v1.44.4 github.com/aws/aws-sdk-go v1.44.4
github.com/beego/beego v1.12.11 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/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.30.1 github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v3 v3.0.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 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // 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/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect
xorm.io/core v0.7.2 xorm.io/core v0.7.2

4
go.sum
View File

@@ -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 h1:MWKcnpavb7iAIS0m6uuEq6pHKkYvGNw/5umIUKqL7jM=
github.com/beego/beego v1.12.11/go.mod h1:QURFL1HldOcCZAxnc1cZ7wrplsYR5dKPHFjmk6WkLAs= 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/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/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 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= 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 h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= 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.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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=

View File

@@ -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 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 SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -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 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 SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -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 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 SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -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 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 SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -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 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 SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -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 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 SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -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 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 SessionOutdated = Session outdated, please login again
SignOutFirst = Please sign out first before signing in SignOutFirst = Please sign out first before signing in
SignInFirst = Please sign in first
UserDoNotExist = The user: %s/%s doesn't exist UserDoNotExist = The user: %s/%s doesn't exist
UserIsForbidden = The user is forbidden to sign in, please contact the administrator UserIsForbidden = The user is forbidden to sign in, please contact the administrator
UnknownAuthentication = Unknown authentication type (not password or provider), form = %s UnknownAuthentication = Unknown authentication type (not password or provider), form = %s

View File

@@ -51,7 +51,6 @@ OldUser = 提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定:
ProviderCanNotSignUp = 提供商账户: %s 与用户名: %s (%s) 不存在且 不允许通过 %s 注册新账户, 请使用其他方式注册 ProviderCanNotSignUp = 提供商账户: %s 与用户名: %s (%s) 不存在且 不允许通过 %s 注册新账户, 请使用其他方式注册
SignOutFirst = 请在登录前登出 SignOutFirst = 请在登录前登出
SessionOutdated = Session已过期请重新登陆 SessionOutdated = Session已过期请重新登陆
SignInFirst = 请先登出
UserDoNotExist = 用户不存在: %s/%s UserDoNotExist = 用户不存在: %s/%s
UserIsForbidden = 该用户被禁止登陆,请联系管理员 UserIsForbidden = 该用户被禁止登陆,请联系管理员
UnknownAuthentication = 未知的认证类型 (非密码或提供商认证), form = %s UnknownAuthentication = 未知的认证类型 (非密码或提供商认证), form = %s

View File

@@ -15,9 +15,12 @@
package idp package idp
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@@ -167,7 +170,10 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
Email: dtUserInfo.Email, Email: dtUserInfo.Email,
AvatarUrl: dtUserInfo.AvatarUrl, AvatarUrl: dtUserInfo.AvatarUrl,
} }
isUserInOrg, err := idp.isUserInOrg(userInfo.UnionId)
if !isUserInOrg {
return nil, err
}
return &userInfo, nil return &userInfo, nil
} }
@@ -194,3 +200,62 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
return data, nil 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
}

View File

@@ -270,6 +270,13 @@ func UpdateApplication(id string, application *Application) bool {
application.Name = name application.Name = name
} }
if name != application.Name {
err := applicationChangeTrigger(name, application.Name)
if err != nil {
return false
}
}
for _, providerItem := range application.Providers { for _, providerItem := range application.Providers {
providerItem.Provider = nil providerItem.Provider = nil
} }
@@ -400,3 +407,55 @@ func ExtendManagedAccountsWithUser(user *User) *User {
return 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()
}

View File

@@ -114,6 +114,12 @@ func UpdateCert(id string, cert *Cert) bool {
return false 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) affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -161,3 +167,22 @@ func getCertByApplication(application *Application) *Cert {
func GetDefaultCert() *Cert { func GetDefaultCert() *Cert {
return getCert("admin", "cert-built-in") 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()
}

View File

@@ -340,3 +340,20 @@ func CheckUsername(username string, lang string) string {
return "" 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
}

View File

@@ -143,7 +143,7 @@ func initBuiltInApplication() {
EnablePassword: true, EnablePassword: true,
EnableSignUp: true, EnableSignUp: true,
Providers: []*ProviderItem{ 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{ SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"}, {Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},

View File

@@ -56,12 +56,40 @@ func readInitDataFromFile(filePath string) *InitData {
s := util.ReadStringFromPath(filePath) 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) err := util.JsonToStruct(s, data)
if err != nil { if err != nil {
panic(err) 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 return data
} }

View File

@@ -92,6 +92,12 @@ func UpdateModel(id string, modelObj *Model) bool {
return false return false
} }
if name != modelObj.Name {
err := modelChangeTrigger(name, modelObj.Name)
if err != nil {
return false
}
}
// check model grammar // check model grammar
_, err := model.NewModelFromString(modelObj.ModelText) _, err := model.NewModelFromString(modelObj.ModelText)
if err != nil { if err != nil {
@@ -127,3 +133,22 @@ func DeleteModel(model *Model) bool {
func (model *Model) GetId() string { func (model *Model) GetId() string {
return fmt.Sprintf("%s/%s", model.Owner, model.Name) 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()
}

View File

@@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/casdoor/casdoor/cred" "github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
@@ -134,15 +135,10 @@ func UpdateOrganization(id string, organization *Organization) bool {
} }
if name != organization.Name { if name != organization.Name {
go func() { err := organizationChangeTrigger(name, organization.Name)
application := new(Application) if err != nil {
application.Organization = organization.Name return false
_, _ = adapter.Engine.Where("organization=?", name).Update(application) }
user := new(User)
user.Owner = organization.Name
_, _ = adapter.Engine.Where("owner=?", name).Update(user)
}()
} }
if organization.MasterPassword != "" && organization.MasterPassword != "***" { if organization.MasterPassword != "" && organization.MasterPassword != "***" {
@@ -252,3 +248,148 @@ func GetDefaultApplication(id string) (*Application, error) {
return defaultApplication, nil 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()
}

View File

@@ -175,6 +175,13 @@ func UpdateProvider(id string, provider *Provider) bool {
return false 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() session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if provider.ClientSecret == "***" { if provider.ClientSecret == "***" {
session = session.Omit("client_secret") session = session.Omit("client_secret")
@@ -262,3 +269,41 @@ func GetCaptchaProviderByApplication(applicationId, isCurrentProvider, lang stri
} }
return nil, nil 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()
}

View File

@@ -21,6 +21,7 @@ type ProviderItem struct {
CanUnlink bool `json:"canUnlink"` CanUnlink bool `json:"canUnlink"`
Prompted bool `json:"prompted"` Prompted bool `json:"prompted"`
AlertType string `json:"alertType"` AlertType string `json:"alertType"`
Rule string `json:"rule"`
Provider *Provider `json:"provider"` Provider *Provider `json:"provider"`
} }

View File

@@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
@@ -94,6 +95,13 @@ func UpdateRole(id string, role *Role) bool {
return false 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) affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -133,3 +141,54 @@ func GetRolesByUser(userId string) []*Role {
return roles 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()
}

View File

@@ -37,7 +37,7 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
// Encode private key to PKCS#1 ASN.1 PEM. // Encode private key to PKCS#1 ASN.1 PEM.
privateKeyPem := pem.EncodeToMemory( privateKeyPem := pem.EncodeToMemory(
&pem.Block{ &pem.Block{
Type: "PRIVATE KEY", Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key), Bytes: x509.MarshalPKCS1PrivateKey(key),
}, },
) )

View File

@@ -380,6 +380,13 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
return false return false
} }
if name != user.Name {
err := userChangeTrigger(name, user.Name)
if err != nil {
return false
}
}
if user.Password == "***" { if user.Password == "***" {
user.Password = oldUser.Password user.Password = oldUser.Password
} }
@@ -416,6 +423,13 @@ func UpdateUserForAllFields(id string, user *User) bool {
return false return false
} }
if name != user.Name {
err := userChangeTrigger(name, user.Name)
if err != nil {
return false
}
}
user.UpdateUserHash() user.UpdateUserHash()
if user.Avatar != oldUser.Avatar && user.Avatar != "" { if user.Avatar != oldUser.Avatar && user.Avatar != "" {
@@ -567,3 +581,62 @@ func ExtendUserWithRolesAndPermissions(user *User) {
user.Roles = GetRolesByUser(user.GetId()) user.Roles = GetRolesByUser(user.GetId())
user.Permissions = GetPermissionsByUser(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()
}

View File

@@ -221,10 +221,9 @@ class App extends Component {
if (res.status === "ok") { if (res.status === "ok") {
account = res.data; account = res.data;
account.organization = res.data2; account.organization = res.data2;
this.setLanguage(account); this.setLanguage(account);
} else { } else {
if (res.msg !== "Please sign in first") { if (res.data !== "Please login first") {
Setting.showMessage("error", `Failed to sign in: ${res.msg}`); Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
} }
} }

View File

@@ -39,7 +39,7 @@ class ProviderTable extends React.Component {
} }
addRow(table) { 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) { if (table === undefined) {
table = []; table = [];
} }
@@ -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"), title: i18next.t("general:Action"),
key: "action", key: "action",

View File

@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import React from "react";
import {Link, useHistory} from "react-router-dom";
import {Tag, Tooltip, message} from "antd"; import {Tag, Tooltip, message} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons"; import {QuestionCircleTwoTone} from "@ant-design/icons";
import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n"; import "./i18n";
import i18next from "i18next"; import i18next from "i18next";
@@ -22,7 +23,6 @@ import copy from "copy-to-clipboard";
import {authConfig} from "./auth/Auth"; import {authConfig} from "./auth/Auth";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import * as Conf from "./Conf"; import * as Conf from "./Conf";
import {Link} from "react-router-dom";
import * as path from "path-browserify"; import * as path from "path-browserify";
export const ServerUrl = ""; export const ServerUrl = "";
@@ -743,26 +743,31 @@ export function renderLogo(application) {
} }
} }
export function goToLogin(ths, application) { export function getLoginLink(application) {
let url;
if (application === null) { if (application === null) {
return; 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");
if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) { } else if (authConfig.appName === application.name) {
const link = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize"); url = "/login";
goToLink(link); } else if (application.signinUrl === "") {
return; url = path.join(application.homepageUrl, "login");
}
if (authConfig.appName === application.name) {
goToLinkSoft(ths, "/login");
} else { } else {
if (application.signinUrl === "") { url = application.signinUrl;
goToLink(path.join(application.homepageUrl, "login"));
} else {
goToLink(application.signinUrl);
}
} }
return url;
}
export function renderLoginLink(application, text) {
const url = getLoginLink(application);
return renderLink(url, text, null);
}
export function redirectToLoginPage(application) {
const loginLink = getLoginLink(application);
const history = useHistory();
history.push(loginLink);
} }
function renderLink(url, text, onClick) { function renderLink(url, text, onClick) {

View File

@@ -164,7 +164,7 @@ class SignupTable extends React.Component {
}, },
}, },
{ {
title: i18next.t("application:rule"), title: i18next.t("application:Rule"),
dataIndex: "rule", dataIndex: "rule",
key: "rule", key: "rule",
width: "155px", width: "155px",

View File

@@ -352,7 +352,7 @@ class UserListPage extends BaseListPage {
return ( return (
<div> <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={() => ( title={() => (
<div> <div>
{i18next.t("general:Users")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Users")}&nbsp;&nbsp;&nbsp;&nbsp;

View File

@@ -166,7 +166,7 @@ class ForgetPage extends React.Component {
values.userOwner = this.state.application?.organizationObj.name; values.userOwner = this.state.application?.organizationObj.name;
UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => { UserBackend.setPassword(values.userOwner, values.username, "", values?.newPassword).then(res => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.goToLogin(this, this.state.application); Setting.redirectToLoginPage(this.state.application);
} else { } else {
Setting.showMessage("error", i18next.t(`signup:${res.msg}`)); Setting.showMessage("error", i18next.t(`signup:${res.msg}`));
} }

View File

@@ -13,7 +13,6 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Link} from "react-router-dom";
import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd"; import {Button, Checkbox, Col, Form, Input, Result, Row, Spin, Tabs} from "antd";
import {LockOutlined, UserOutlined} from "@ant-design/icons"; import {LockOutlined, UserOutlined} from "@ant-design/icons";
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend"; import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
@@ -30,6 +29,7 @@ import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput"; import {CountDownInput} from "../common/CountDownInput";
import SelectLanguageBox from "../SelectLanguageBox"; import SelectLanguageBox from "../SelectLanguageBox";
import {withTranslation} from "react-i18next"; import {withTranslation} from "react-i18next";
import {CaptchaModal} from "../common/CaptchaModal";
const {TabPane} = Tabs; const {TabPane} = Tabs;
@@ -49,6 +49,9 @@ class LoginPage extends React.Component {
validEmail: false, validEmail: false,
validPhone: false, validPhone: false,
loginMethod: "password", loginMethod: "password",
enableCaptchaModal: false,
openCaptchaModal: false,
verifyCaptcha: undefined,
}; };
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) { if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
@@ -69,6 +72,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() { getApplicationLogin() {
const oAuthParams = Util.getOAuthGetParameters(); const oAuthParams = Util.getOAuthGetParameters();
AuthBackend.getApplicationLogin(oAuthParams) AuthBackend.getApplicationLogin(oAuthParams)
@@ -226,6 +241,23 @@ class LoginPage extends React.Component {
return; 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 // here we are supposed to determine whether Casdoor is working as an OAuth server or CAS server
if (this.state.type === "cas") { if (this.state.type === "cas") {
// CAS // CAS
@@ -240,6 +272,8 @@ class LoginPage extends React.Component {
} }
Util.showMessage("success", msg); Util.showMessage("success", msg);
this.setState({openCaptchaModal: false});
if (casParams.service !== "") { if (casParams.service !== "") {
const st = res.data; const st = res.data;
const newUrl = new URL(casParams.service); const newUrl = new URL(casParams.service);
@@ -247,6 +281,7 @@ class LoginPage extends React.Component {
window.location.href = newUrl.toString(); window.location.href = newUrl.toString();
} }
} else { } else {
this.setState({openCaptchaModal: false});
Util.showMessage("error", `Failed to log in: ${res.msg}`); Util.showMessage("error", `Failed to log in: ${res.msg}`);
} }
}); });
@@ -259,6 +294,7 @@ class LoginPage extends React.Component {
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
const responseType = values["type"]; const responseType = values["type"];
if (responseType === "login") { if (responseType === "login") {
Util.showMessage("success", "Logged in successfully"); Util.showMessage("success", "Logged in successfully");
@@ -276,6 +312,7 @@ class LoginPage extends React.Component {
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`); Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
} }
} else { } else {
this.setState({openCaptchaModal: false});
Util.showMessage("error", `Failed to log in: ${res.msg}`); Util.showMessage("error", `Failed to log in: ${res.msg}`);
} }
}); });
@@ -302,13 +339,11 @@ class LoginPage extends React.Component {
title="Sign Up Error" title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"} subTitle={"The application does not allow to sign up new account"}
extra={[ extra={[
<Link key="login" onClick={() => { <Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}>
Setting.goToLogin(this, application); {
}}> i18next.t("login:Sign In")
<Button type="primary" key="signin"> }
Sign In </Button>,
</Button>
</Link>,
]} ]}
> >
</Result> </Result>
@@ -421,6 +456,9 @@ class LoginPage extends React.Component {
i18next.t("login:Sign In") i18next.t("login:Sign In")
} }
</Button> </Button>
{
this.renderCaptchaModal(application)
}
{ {
this.renderFooter(application) this.renderFooter(application)
} }
@@ -463,16 +501,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) { renderFooter(application) {
if (this.state.mode === "signup") { if (this.state.mode === "signup") {
return ( return (
<div style={{float: "right"}}> <div style={{float: "right"}}>
{i18next.t("signup:Have account?")}&nbsp; {i18next.t("signup:Have account?")}&nbsp;
<Link onClick={() => { {
Setting.goToLogin(this, application); Setting.renderLoginLink(application, i18next.t("signup:sign in now"))
}}> }
{i18next.t("signup:sign in now")}
</Link>
</div> </div>
); );
} else { } else {

View File

@@ -190,7 +190,7 @@ class PromptPage extends React.Component {
if (redirectUrl !== "" && redirectUrl !== null) { if (redirectUrl !== "" && redirectUrl !== null) {
Setting.goToLink(redirectUrl); Setting.goToLink(redirectUrl);
} else { } else {
Setting.goToLogin(this, this.getApplicationObj()); Setting.redirectToLoginPage(this.getApplicationObj());
} }
} else { } else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`); Setting.showMessage("error", `Failed to log out: ${res.msg}`);
@@ -234,10 +234,10 @@ class PromptPage extends React.Component {
title="Sign Up Error" title="Sign Up Error"
subTitle={"You are unexpected to see this prompt page"} subTitle={"You are unexpected to see this prompt page"}
extra={[ extra={[
<Button type="primary" key="signin" onClick={() => { <Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}>
Setting.goToLogin(this, application); {
}}> i18next.t("login:Sign In")
Sign In }
</Button>, </Button>,
]} ]}
> >

View File

@@ -69,7 +69,7 @@ class ResultPage extends React.Component {
if (linkInStorage !== null && linkInStorage !== "") { if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage); Setting.goToLink(linkInStorage);
} else { } else {
Setting.goToLogin(this, application); Setting.redirectToLoginPage(application);
} }
}}> }}>
{i18next.t("login:Sign In")} {i18next.t("login:Sign In")}

View File

@@ -541,10 +541,10 @@ class SignupPage extends React.Component {
title="Sign Up Error" title="Sign Up Error"
subTitle={"The application does not allow to sign up new account"} subTitle={"The application does not allow to sign up new account"}
extra={[ extra={[
<Button type="primary" key="signin" onClick={() => { <Button type="primary" key="signin" onClick={() => Setting.redirectToLoginPage(application)}>
Setting.goToLogin(this, application); {
}}> i18next.t("login:Sign In")
Sign In }
</Button>, </Button>,
]} ]}
> >
@@ -600,7 +600,7 @@ class SignupPage extends React.Component {
if (linkInStorage !== null && linkInStorage !== "") { if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage); Setting.goToLink(linkInStorage);
} else { } else {
Setting.goToLogin(this, application); Setting.redirectToLoginPage(application);
} }
}}> }}>
{i18next.t("signup:sign in now")} {i18next.t("signup:sign in now")}

View File

@@ -62,10 +62,10 @@ class SingleCard extends React.Component {
<Card <Card
hoverable hoverable
cover={ 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)} 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} /> <Meta title={title} description={desc} />
<br /> <br />

View 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>
);
};

View File

@@ -12,13 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import {Button, Col, Input, Modal, Row} from "antd"; import {Button} from "antd";
import React from "react"; import React from "react";
import i18next from "i18next"; import i18next from "i18next";
import * as UserBackend from "../backend/UserBackend"; import {CaptchaModal} from "./CaptchaModal";
import * as ProviderBackend from "../backend/ProviderBackend"; import * as ProviderBackend from "../backend/ProviderBackend";
import {SafetyOutlined} from "@ant-design/icons"; import * as UserBackend from "../backend/UserBackend";
import {CaptchaWidget} from "./CaptchaWidget";
export const CaptchaPreview = ({ export const CaptchaPreview = ({
provider, provider,
@@ -33,37 +32,9 @@ export const CaptchaPreview = ({
clientId2, clientId2,
clientSecret2, clientSecret2,
}) => { }) => {
const [visible, setVisible] = React.useState(false); const [open, setOpen] = 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 clickPreview = () => { const clickPreview = () => {
setVisible(true);
provider.name = name; provider.name = name;
provider.clientId = clientId; provider.clientId = clientId;
provider.type = captchaType; provider.type = captchaType;
@@ -71,64 +42,10 @@ export const CaptchaPreview = ({
if (clientSecret !== "***") { if (clientSecret !== "***") {
provider.clientSecret = clientSecret; provider.clientSecret = clientSecret;
ProviderBackend.updateProvider(owner, providerName, provider).then(() => { ProviderBackend.updateProvider(owner, providerName, provider).then(() => {
getCaptchaFromBackend(); setOpen(true);
}); });
} else { } else {
getCaptchaFromBackend(); setOpen(true);
}
};
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>
);
} }
}; };
@@ -146,6 +63,16 @@ export const CaptchaPreview = ({
return false; return false;
}; };
const onOk = (captchaType, captchaToken, secret) => {
UserBackend.verifyCaptcha(captchaType, captchaToken, secret).then(() => {
setOpen(false);
});
};
const onCancel = () => {
setOpen(false);
};
return ( return (
<React.Fragment> <React.Fragment>
<Button <Button
@@ -156,20 +83,20 @@ export const CaptchaPreview = ({
> >
{i18next.t("general:Preview")} {i18next.t("general:Preview")}
</Button> </Button>
<Modal <CaptchaModal
closable={false} owner={owner}
maskClosable={false} name={name}
destroyOnClose={true} captchaType={captchaType}
title={i18next.t("general:Captcha")} subType={subType}
visible={visible} clientId={clientId}
okText={i18next.t("user:OK")} clientId2={clientId2}
cancelText={i18next.t("user:Cancel")} clientSecret={clientSecret}
onOk={handleOk} clientSecret2={clientSecret2}
onCancel={handleCancel} open={open}
width={348} onOk={onOk}
> onCancel={onCancel}
{renderCheck()} canCancel={true}
</Modal> />
</React.Fragment> </React.Fragment>
); );
}; };

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Token läuft ab - Tooltip", "Token expire - Tooltip": "Token läuft ab - Tooltip",
"Token format": "Token-Format", "Token format": "Token-Format",
"Token format - Tooltip": "Token-Format - Tooltip", "Token format - Tooltip": "Token-Format - Tooltip",
"rule": "rule" "Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Bitgröße", "Bit size": "Bitgröße",
@@ -695,6 +697,7 @@
"Old Password": "Altes Passwort", "Old Password": "Altes Passwort",
"Password": "Passwort", "Password": "Passwort",
"Password Set": "Passwort setzen", "Password Set": "Passwort setzen",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Eigenschaften", "Properties": "Eigenschaften",
"Re-enter New": "Neu erneut eingeben", "Re-enter New": "Neu erneut eingeben",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Token expire - Tooltip", "Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format", "Token format": "Token format",
"Token format - Tooltip": "Token format - Tooltip", "Token format - Tooltip": "Token format - Tooltip",
"rule": "rule" "Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Bit size", "Bit size": "Bit size",
@@ -695,6 +697,7 @@
"Old Password": "Old Password", "Old Password": "Old Password",
"Password": "Password", "Password": "Password",
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",

View File

@@ -45,7 +45,9 @@
"Token expire - Tooltip": "Expiración del Token - Tooltip", "Token expire - Tooltip": "Expiración del Token - Tooltip",
"Token format": "Formato del Token", "Token format": "Formato del Token",
"Token format - Tooltip": "Formato del Token - Tooltip", "Token format - Tooltip": "Formato del Token - Tooltip",
"rule": "rule" "Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Tamaño del Bit", "Bit size": "Tamaño del Bit",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Expiration du jeton - Info-bulle", "Token expire - Tooltip": "Expiration du jeton - Info-bulle",
"Token format": "Format du jeton", "Token format": "Format du jeton",
"Token format - Tooltip": "Format du jeton - infobulle", "Token format - Tooltip": "Format du jeton - infobulle",
"rule": "rule" "Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Taille du bit", "Bit size": "Taille du bit",
@@ -695,6 +697,7 @@
"Old Password": "Ancien mot de passe", "Old Password": "Ancien mot de passe",
"Password": "Mot de passe", "Password": "Mot de passe",
"Password Set": "Mot de passe défini", "Password Set": "Mot de passe défini",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Propriétés", "Properties": "Propriétés",
"Re-enter New": "Nouvelle entrée", "Re-enter New": "Nouvelle entrée",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "トークンの有効期限 - ツールチップ", "Token expire - Tooltip": "トークンの有効期限 - ツールチップ",
"Token format": "トークンのフォーマット", "Token format": "トークンのフォーマット",
"Token format - Tooltip": "トークンフォーマット - ツールチップ", "Token format - Tooltip": "トークンフォーマット - ツールチップ",
"rule": "rule" "Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "ビットサイズ", "Bit size": "ビットサイズ",
@@ -695,6 +697,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...", "Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Token expire - Tooltip", "Token expire - Tooltip": "Token expire - Tooltip",
"Token format": "Token format", "Token format": "Token format",
"Token format - Tooltip": "Token format - Tooltip", "Token format - Tooltip": "Token format - Tooltip",
"rule": "rule" "Rule": "Rule",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Bit size", "Bit size": "Bit size",
@@ -695,6 +697,7 @@
"Old Password": "Old Password", "Old Password": "Old Password",
"Password": "Password", "Password": "Password",
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Истек токен - Подсказка", "Token expire - Tooltip": "Истек токен - Подсказка",
"Token format": "Формат токена", "Token format": "Формат токена",
"Token format - Tooltip": "Формат токена - Подсказка", "Token format - Tooltip": "Формат токена - Подсказка",
"rule": "правило" "Rule": "правило",
"None": "None",
"Always": "Always"
}, },
"cert": { "cert": {
"Bit size": "Размер бита", "Bit size": "Размер бита",
@@ -695,6 +697,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...", "Reset Email...": "Reset Email...",

View File

@@ -68,7 +68,9 @@
"Token expire - Tooltip": "Access Token过期时间", "Token expire - Tooltip": "Access Token过期时间",
"Token format": "Access Token格式", "Token format": "Access Token格式",
"Token format - Tooltip": "Access Token格式", "Token format - Tooltip": "Access Token格式",
"rule": "规则" "Rule": "规则",
"None": "关闭",
"Always": "始终开启"
}, },
"cert": { "cert": {
"Bit size": "位大小", "Bit size": "位大小",
@@ -695,6 +697,7 @@
"Old Password": "旧密码", "Old Password": "旧密码",
"Password": "密码", "Password": "密码",
"Password Set": "密码已设置", "Password Set": "密码已设置",
"Please select avatar from resources": "从资源中选择...",
"Properties": "属性", "Properties": "属性",
"Re-enter New": "重复新密码", "Re-enter New": "重复新密码",
"Reset Email...": "重置邮箱...", "Reset Email...": "重置邮箱...",