mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-08 09:01:00 +08:00
Compare commits
67 Commits
revert-310
...
v1.721.0
Author | SHA1 | Date | |
---|---|---|---|
347b25676f | |||
2417ff84e6 | |||
468631e654 | |||
e1dea9f697 | |||
c0f22bae43 | |||
c9635d9e2b | |||
3bd52172ea | |||
bf730050d5 | |||
5b733b7f15 | |||
034f28def9 | |||
c86ac8e6ad | |||
d647eed22a | |||
717c53f6e5 | |||
097adac871 | |||
74543b9533 | |||
110dc04179 | |||
6464bd10dc | |||
db878a890e | |||
12d6d8e6ce | |||
8ed6e4f934 | |||
ed9732caf9 | |||
0de4e7da38 | |||
a330fbc11f | |||
ed158d4981 | |||
8df965b98d | |||
2c3749820e | |||
0b17cb9746 | |||
e2ce9ad625 | |||
64491abc64 | |||
934a8947c8 | |||
943edfb48b | |||
0d02b5e768 | |||
ba8d0b5f46 | |||
973a1df6c2 | |||
05bfd3a3a3 | |||
69aa3c8a8b | |||
a1b010a406 | |||
89e92cbd47 | |||
d4c8193357 | |||
9b33800b4c | |||
ec98785172 | |||
45dd4cc344 | |||
1adb172d6b | |||
c08f2b1f3f | |||
62bb257c6d | |||
230a77e3e3 | |||
dce0a96dea | |||
65563fa0cd | |||
f2a94f671a | |||
1460a0498f | |||
adc63ea726 | |||
0b8be016c5 | |||
986dcbbda1 | |||
7d3920fb1f | |||
b794ef87ee | |||
a0d6f2125e | |||
85cbb7d074 | |||
fdc1be9452 | |||
2bd7dabd33 | |||
9b9a58e7ac | |||
38e389e8c8 | |||
ab5fcf848e | |||
b4e51b4631 | |||
45e25acc80 | |||
97dcf24a91 | |||
4c0fff66ff | |||
e7230700e0 |
@ -13,7 +13,7 @@
|
|||||||
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
<a href="https://github.com/casdoor/casdoor/releases/latest">
|
||||||
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
|
<a href="https://hub.docker.com/r/casbin/casdoor">
|
||||||
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -77,6 +77,7 @@ p, *, *, POST, /api/verify-code, *, *
|
|||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
|
p, *, *, GET, /.well-known/webfinger, *, *
|
||||||
p, *, *, *, /.well-known/jwks, *, *
|
p, *, *, *, /.well-known/jwks, *, *
|
||||||
p, *, *, GET, /api/get-saml-login, *, *
|
p, *, *, GET, /api/get-saml-login, *, *
|
||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
|
@ -26,6 +26,10 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
|
|||||||
return NewDefaultCaptchaProvider()
|
return NewDefaultCaptchaProvider()
|
||||||
case "reCAPTCHA":
|
case "reCAPTCHA":
|
||||||
return NewReCaptchaProvider()
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v2":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
|
case "reCAPTCHA v3":
|
||||||
|
return NewReCaptchaProvider()
|
||||||
case "Aliyun Captcha":
|
case "Aliyun Captcha":
|
||||||
return NewAliyunCaptchaProvider()
|
return NewAliyunCaptchaProvider()
|
||||||
case "hCaptcha":
|
case "hCaptcha":
|
||||||
|
@ -21,7 +21,9 @@ originFrontend =
|
|||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
isDemoMode = false
|
isDemoMode = false
|
||||||
batchSize = 100
|
batchSize = 100
|
||||||
|
enableErrorMask = false
|
||||||
enableGzip = true
|
enableGzip = true
|
||||||
|
inactiveTimeoutMinutes =
|
||||||
ldapServerPort = 389
|
ldapServerPort = 389
|
||||||
radiusServerPort = 1812
|
radiusServerPort = 1812
|
||||||
radiusSecret = "secret"
|
radiusSecret = "secret"
|
||||||
|
@ -200,6 +200,10 @@ func (c *ApiController) Signup() {
|
|||||||
Type: userType,
|
Type: userType,
|
||||||
Password: authForm.Password,
|
Password: authForm.Password,
|
||||||
DisplayName: authForm.Name,
|
DisplayName: authForm.Name,
|
||||||
|
Gender: authForm.Gender,
|
||||||
|
Bio: authForm.Bio,
|
||||||
|
Tag: authForm.Tag,
|
||||||
|
Education: authForm.Education,
|
||||||
Avatar: organization.DefaultAvatar,
|
Avatar: organization.DefaultAvatar,
|
||||||
Email: authForm.Email,
|
Email: authForm.Email,
|
||||||
Phone: authForm.Phone,
|
Phone: authForm.Phone,
|
||||||
@ -234,6 +238,10 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if invitation != nil && invitation.SignupGroup != "" {
|
||||||
|
user.Groups = []string{invitation.SignupGroup}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := object.AddUser(user)
|
affected, err := object.AddUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -463,6 +463,15 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
password := authForm.Password
|
password := authForm.Password
|
||||||
|
|
||||||
|
if application.OrganizationObj != nil {
|
||||||
|
password, err = util.GetUnobfuscatedPassword(application.OrganizationObj.PasswordObfuscatorType, application.OrganizationObj.PasswordObfuscatorKey, authForm.Password)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isSigninViaLdap := authForm.SigninMethod == "LDAP"
|
isSigninViaLdap := authForm.SigninMethod == "LDAP"
|
||||||
var isPasswordWithLdapEnabled bool
|
var isPasswordWithLdapEnabled bool
|
||||||
if authForm.SigninMethod == "Password" {
|
if authForm.SigninMethod == "Password" {
|
||||||
|
@ -60,7 +60,6 @@ func (c *ApiController) Unlink() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
|
||||||
return
|
return
|
||||||
|
@ -14,7 +14,11 @@
|
|||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import "github.com/casdoor/casdoor/object"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
// GetOidcDiscovery
|
// GetOidcDiscovery
|
||||||
// @Title GetOidcDiscovery
|
// @Title GetOidcDiscovery
|
||||||
@ -42,3 +46,31 @@ func (c *RootController) GetJwks() {
|
|||||||
c.Data["json"] = jwks
|
c.Data["json"] = jwks
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetWebFinger
|
||||||
|
// @Title GetWebFinger
|
||||||
|
// @Tag OIDC API
|
||||||
|
// @Param resource query string true "resource"
|
||||||
|
// @Success 200 {object} object.WebFinger
|
||||||
|
// @router /.well-known/webfinger [get]
|
||||||
|
func (c *RootController) GetWebFinger() {
|
||||||
|
resource := c.Input().Get("resource")
|
||||||
|
rels := []string{}
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
for key, value := range c.Input() {
|
||||||
|
if strings.HasPrefix(key, "rel") {
|
||||||
|
rels = append(rels, value...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webfinger, err := object.GetWebFinger(resource, rels, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = webfinger
|
||||||
|
c.Ctx.Output.ContentType("application/jrd+json")
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
@ -65,7 +65,7 @@ func (c *ApiController) GetOrganizations() {
|
|||||||
c.ResponseOk(organizations)
|
c.ResponseOk(organizations)
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
count, err := object.GetOrganizationCount(owner, field, value)
|
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -138,7 +138,7 @@ func (c *ApiController) AddOrganization() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := object.GetOrganizationCount("", "", "")
|
count, err := object.GetOrganizationCount("", "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -257,7 +257,7 @@ func (c *ApiController) UploadResource() {
|
|||||||
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 175)
|
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 450)
|
||||||
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
|
||||||
ext := filepath.Ext(filepath.Base(fullFilePath))
|
ext := filepath.Ext(filepath.Base(fullFilePath))
|
||||||
index := len(fullFilePath) - len(ext)
|
index := len(fullFilePath) - len(ext)
|
||||||
|
@ -289,6 +289,16 @@ func (c *ApiController) UpdateUser() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.MfaEmailEnabled && user.Email == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA email is enabled but email is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.MfaPhoneEnabled && user.Phone == "" {
|
||||||
|
c.ResponseError(c.T("user:MFA phone is enabled but phone number is empty"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
@ -400,6 +410,12 @@ func (c *ApiController) GetEmailAndPhone() {
|
|||||||
organization := c.Ctx.Request.Form.Get("organization")
|
organization := c.Ctx.Request.Form.Get("organization")
|
||||||
username := c.Ctx.Request.Form.Get("username")
|
username := c.Ctx.Request.Form.Get("username")
|
||||||
|
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
c.ResponseError("Error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user, err := object.GetUserByFields(organization, username)
|
user, err := object.GetUserByFields(organization, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -45,6 +45,22 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
|
|||||||
|
|
||||||
// ResponseError ...
|
// ResponseError ...
|
||||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||||
|
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||||
|
if enableErrorMask2 {
|
||||||
|
error = c.T("subscription:Error")
|
||||||
|
|
||||||
|
resp := &Response{Status: "error", Msg: error}
|
||||||
|
c.ResponseJsonData(resp, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enableErrorMask := conf.GetConfigBool("enableErrorMask")
|
||||||
|
if enableErrorMask {
|
||||||
|
if strings.HasPrefix(error, "The user: ") && strings.HasSuffix(error, " doesn't exist") || strings.HasPrefix(error, "用户: ") && strings.HasSuffix(error, "不存在") {
|
||||||
|
error = c.T("check:password or code is incorrect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp := &Response{Status: "error", Msg: error}
|
resp := &Response{Status: "error", Msg: error}
|
||||||
c.ResponseJsonData(resp, data...)
|
c.ResponseJsonData(resp, data...)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func deployStaticFiles(provider *object.Provider) {
|
func deployStaticFiles(provider *object.Provider) {
|
||||||
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
|
certificate := ""
|
||||||
|
if provider.Category == "Storage" && provider.Type == "Casdoor" {
|
||||||
|
cert, err := object.GetCert(util.GetId(provider.Owner, provider.Cert))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
certificate = cert.Certificate
|
||||||
|
}
|
||||||
|
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint, certificate, provider.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,10 @@ type AuthForm struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
FirstName string `json:"firstName"`
|
FirstName string `json:"firstName"`
|
||||||
LastName string `json:"lastName"`
|
LastName string `json:"lastName"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
Bio string `json:"bio"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Education string `json:"education"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Affiliation string `json:"affiliation"`
|
Affiliation string `json:"affiliation"`
|
||||||
|
6
go.mod
6
go.mod
@ -6,13 +6,13 @@ require (
|
|||||||
github.com/Masterminds/squirrel v1.5.3
|
github.com/Masterminds/squirrel v1.5.3
|
||||||
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.45.5
|
github.com/aws/aws-sdk-go v1.45.5
|
||||||
github.com/beego/beego v1.12.13
|
github.com/beego/beego v1.12.12
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin/v2 v2.77.2
|
github.com/casbin/casbin/v2 v2.77.2
|
||||||
github.com/casdoor/go-sms-sender v0.24.0
|
github.com/casdoor/go-sms-sender v0.24.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.0.1
|
||||||
github.com/casdoor/notify v0.45.0
|
github.com/casdoor/notify v0.45.0
|
||||||
github.com/casdoor/oss v1.6.0
|
github.com/casdoor/oss v1.8.0
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||||
github.com/casvisor/casvisor-go-sdk v1.4.0
|
github.com/casvisor/casvisor-go-sdk v1.4.0
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
@ -30,7 +30,7 @@ require (
|
|||||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||||
github.com/go-webauthn/webauthn v0.6.0
|
github.com/go-webauthn/webauthn v0.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.4.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/lestrrat-go/jwx v1.2.29
|
github.com/lestrrat-go/jwx v1.2.29
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
13
go.sum
13
go.sum
@ -1052,8 +1052,8 @@ github.com/baidubce/bce-sdk-go v0.9.156 h1:f++WfptxGmSp5acsjl4kUxHpWDDccoFqkIrQK
|
|||||||
github.com/baidubce/bce-sdk-go v0.9.156/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
github.com/baidubce/bce-sdk-go v0.9.156/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||||
github.com/beego/beego v1.12.13 h1:g39O1LGLTiPejWVqQKK/TFGrroW9BCZQz6/pf4S8IRM=
|
github.com/beego/beego v1.12.12 h1:ARY1sNVSS23N0mEQIhSqRDTyyDlx95JY0V3GogBbZbQ=
|
||||||
github.com/beego/beego v1.12.13/go.mod h1:QURFL1HldOcCZAxnc1cZ7wrplsYR5dKPHFjmk6WkLAs=
|
github.com/beego/beego v1.12.12/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/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=
|
||||||
@ -1083,6 +1083,8 @@ github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRt
|
|||||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
||||||
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.50.0 h1:bUYbz/MzJuWfLKJbJM0+U0YpYewAur+THp5TKnufWZM=
|
||||||
|
github.com/casdoor/casdoor-go-sdk v0.50.0/go.mod h1:cMnkCQJgMYpgAlgEx8reSt1AVaDIQLcJ1zk5pzBaz+4=
|
||||||
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
||||||
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
||||||
github.com/casdoor/go-sms-sender v0.24.0 h1:LNLsce3EG/87I3JS6UiajF3LlQmdIiCgebEu0IE4wSM=
|
github.com/casdoor/go-sms-sender v0.24.0 h1:LNLsce3EG/87I3JS6UiajF3LlQmdIiCgebEu0IE4wSM=
|
||||||
@ -1091,8 +1093,8 @@ github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR
|
|||||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||||
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||||
github.com/casdoor/oss v1.6.0 h1:IOWrGLJ+VO82qS796eaRnzFPPA1Sn3cotYTi7O/VIlQ=
|
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
||||||
github.com/casdoor/oss v1.6.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
|
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
||||||
github.com/casvisor/casvisor-go-sdk v1.4.0 h1:hbZEGGJ1cwdHFAxeXrMoNw6yha6Oyg2F0qQhBNCN/dg=
|
github.com/casvisor/casvisor-go-sdk v1.4.0 h1:hbZEGGJ1cwdHFAxeXrMoNw6yha6Oyg2F0qQhBNCN/dg=
|
||||||
@ -1460,8 +1462,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
|
||||||
|
@ -200,7 +200,7 @@ func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([
|
|||||||
|
|
||||||
formData.Set("sign", sign)
|
formData.Set("sign", sign)
|
||||||
|
|
||||||
resp, err := idp.Client.PostForm(targetUrl, formData)
|
resp, err := idp.Client.Post(targetUrl, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader(formData.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
2
main.go
2
main.go
@ -56,6 +56,7 @@ func main() {
|
|||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||||
|
beego.InsertFilter("*", beego.BeforeRouter, routers.TimeoutFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
||||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||||
@ -71,6 +72,7 @@ func main() {
|
|||||||
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
|
||||||
}
|
}
|
||||||
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
|
||||||
|
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
|
||||||
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
|
||||||
|
|
||||||
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
err := logs.SetLogger(logs.AdapterFile, conf.GetConfigString("logConfig"))
|
||||||
|
@ -31,15 +31,17 @@ type SigninMethod struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignupItem struct {
|
type SignupItem struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Visible bool `json:"visible"`
|
Visible bool `json:"visible"`
|
||||||
Required bool `json:"required"`
|
Required bool `json:"required"`
|
||||||
Prompted bool `json:"prompted"`
|
Prompted bool `json:"prompted"`
|
||||||
CustomCss string `json:"customCss"`
|
Type string `json:"type"`
|
||||||
Label string `json:"label"`
|
CustomCss string `json:"customCss"`
|
||||||
Placeholder string `json:"placeholder"`
|
Label string `json:"label"`
|
||||||
Regex string `json:"regex"`
|
Placeholder string `json:"placeholder"`
|
||||||
Rule string `json:"rule"`
|
Options []string `json:"options"`
|
||||||
|
Regex string `json:"regex"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SigninItem struct {
|
type SigninItem struct {
|
||||||
@ -78,24 +80,27 @@ type Application struct {
|
|||||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||||
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
EnableSamlPostBinding bool `json:"enableSamlPostBinding"`
|
||||||
|
UseEmailAsSamlNameId bool `json:"useEmailAsSamlNameId"`
|
||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(3000)" json:"signupItems"`
|
||||||
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
SigninItems []*SigninItem `xorm:"mediumtext" json:"signinItems"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||||
|
IsShared bool `json:"isShared"`
|
||||||
|
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
|
||||||
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
|
||||||
|
TokenSigningMethod string `xorm:"varchar(100)" json:"tokenSigningMethod"`
|
||||||
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
|
||||||
ExpireInHours int `json:"expireInHours"`
|
ExpireInHours int `json:"expireInHours"`
|
||||||
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
RefreshExpireInHours int `json:"refreshExpireInHours"`
|
||||||
@ -123,9 +128,9 @@ func GetApplicationCount(owner, field, value string) (int64, error) {
|
|||||||
return session.Count(&Application{})
|
return session.Count(&Application{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationApplicationCount(owner, Organization, field, value string) (int64, error) {
|
func GetOrganizationApplicationCount(owner, organization, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Application{Organization: Organization})
|
return session.Where("organization = ? or is_shared = ? ", organization, true).Count(&Application{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetApplications(owner string) ([]*Application, error) {
|
func GetApplications(owner string) ([]*Application, error) {
|
||||||
@ -140,7 +145,7 @@ func GetApplications(owner string) ([]*Application, error) {
|
|||||||
|
|
||||||
func GetOrganizationApplications(owner string, organization string) ([]*Application, error) {
|
func GetOrganizationApplications(owner string, organization string) ([]*Application, error) {
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
err := ormer.Engine.Desc("created_time").Find(&applications, &Application{Organization: organization})
|
err := ormer.Engine.Desc("created_time").Where("organization = ? or is_shared = ? ", organization, true).Find(&applications, &Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return applications, err
|
return applications, err
|
||||||
}
|
}
|
||||||
@ -162,7 +167,7 @@ func GetPaginationApplications(owner string, offset, limit int, field, value, so
|
|||||||
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Application, error) {
|
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) ([]*Application, error) {
|
||||||
applications := []*Application{}
|
applications := []*Application{}
|
||||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
err := session.Find(&applications, &Application{Organization: organization})
|
err := session.Where("organization = ? or is_shared = ? ", organization, true).Find(&applications, &Application{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return applications, err
|
return applications, err
|
||||||
}
|
}
|
||||||
@ -337,12 +342,18 @@ func getApplication(owner string, name string) (*Application, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
application := Application{Owner: owner, Name: name}
|
realApplicationName, sharedOrg := util.GetSharedOrgFromApp(name)
|
||||||
|
|
||||||
|
application := Application{Owner: owner, Name: realApplicationName}
|
||||||
existed, err := ormer.Engine.Get(&application)
|
existed, err := ormer.Engine.Get(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared && sharedOrg != "" {
|
||||||
|
application.Organization = sharedOrg
|
||||||
|
}
|
||||||
|
|
||||||
if existed {
|
if existed {
|
||||||
err = extendApplicationWithProviders(&application)
|
err = extendApplicationWithProviders(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -428,11 +439,18 @@ func GetApplicationByUserId(userId string) (application *Application, err error)
|
|||||||
|
|
||||||
func GetApplicationByClientId(clientId string) (*Application, error) {
|
func GetApplicationByClientId(clientId string) (*Application, error) {
|
||||||
application := Application{}
|
application := Application{}
|
||||||
existed, err := ormer.Engine.Where("client_id=?", clientId).Get(&application)
|
|
||||||
|
realClientId, sharedOrg := util.GetSharedOrgFromApp(clientId)
|
||||||
|
|
||||||
|
existed, err := ormer.Engine.Where("client_id=?", realClientId).Get(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared && sharedOrg != "" {
|
||||||
|
application.Organization = sharedOrg
|
||||||
|
}
|
||||||
|
|
||||||
if existed {
|
if existed {
|
||||||
err = extendApplicationWithProviders(&application)
|
err = extendApplicationWithProviders(&application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -516,7 +534,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
|||||||
|
|
||||||
providerItems := []*ProviderItem{}
|
providerItems := []*ProviderItem{}
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha") {
|
if providerItem.Provider != nil && (providerItem.Provider.Category == "OAuth" || providerItem.Provider.Category == "Web3" || providerItem.Provider.Category == "Captcha" || providerItem.Provider.Category == "SAML") {
|
||||||
providerItems = append(providerItems, providerItem)
|
providerItems = append(providerItems, providerItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -626,6 +644,10 @@ func UpdateApplication(id string, application *Application) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared == true && application.Organization != "built-in" {
|
||||||
|
return false, fmt.Errorf("only applications belonging to built-in organization can be shared")
|
||||||
|
}
|
||||||
|
|
||||||
for _, providerItem := range application.Providers {
|
for _, providerItem := range application.Providers {
|
||||||
providerItem.Provider = nil
|
providerItem.Provider = nil
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,9 @@ func GetFailedSigninConfigByUser(user *User) (int, int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
if application == nil {
|
||||||
|
return 0, 0, fmt.Errorf("the application for user %s is not found", user.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
failedSigninLimit := application.FailedSigninLimit
|
failedSigninLimit := application.FailedSigninLimit
|
||||||
if failedSigninLimit == 0 {
|
if failedSigninLimit == 0 {
|
||||||
|
@ -182,6 +182,9 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
|
|||||||
if organization.Tags == nil {
|
if organization.Tags == nil {
|
||||||
organization.Tags = []string{}
|
organization.Tags = []string{}
|
||||||
}
|
}
|
||||||
|
if organization.AccountItems == nil {
|
||||||
|
organization.AccountItems = []*AccountItem{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, application := range data.Applications {
|
for _, application := range data.Applications {
|
||||||
if application.Providers == nil {
|
if application.Providers == nil {
|
||||||
@ -275,7 +278,9 @@ func initDefinedOrganization(organization *Organization) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
organization.CreatedTime = util.GetCurrentTime()
|
organization.CreatedTime = util.GetCurrentTime()
|
||||||
organization.AccountItems = getBuiltInAccountItems()
|
if len(organization.AccountItems) == 0 {
|
||||||
|
organization.AccountItems = getBuiltInAccountItems()
|
||||||
|
}
|
||||||
|
|
||||||
_, err = AddOrganization(organization)
|
_, err = AddOrganization(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -32,6 +32,7 @@ type Ldap struct {
|
|||||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||||
Filter string `xorm:"varchar(200)" json:"filter"`
|
Filter string `xorm:"varchar(200)" json:"filter"`
|
||||||
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
FilterFields []string `xorm:"varchar(100)" json:"filterFields"`
|
||||||
|
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
|
||||||
|
|
||||||
AutoSync int `json:"autoSync"`
|
AutoSync int `json:"autoSync"`
|
||||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||||
@ -148,7 +149,7 @@ func UpdateLdap(ldap *Ldap) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
affected, err := ormer.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||||
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync").Update(ldap)
|
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group").Update(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@ -339,6 +339,10 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
|
|||||||
Ldap: syncUser.Uuid,
|
Ldap: syncUser.Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ldap.DefaultGroup != "" {
|
||||||
|
newUser.Groups = []string{ldap.DefaultGroup}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := AddUser(newUser)
|
affected, err := AddUser(newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
@ -44,6 +44,18 @@ type OidcDiscovery struct {
|
|||||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WebFinger struct {
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Links []WebFingerLink `json:"links"`
|
||||||
|
Aliases *[]string `json:"aliases,omitempty"`
|
||||||
|
Properties *map[string]string `json:"properties,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebFingerLink struct {
|
||||||
|
Rel string `json:"rel"`
|
||||||
|
Href string `json:"href"`
|
||||||
|
}
|
||||||
|
|
||||||
func isIpAddress(host string) bool {
|
func isIpAddress(host string) bool {
|
||||||
// Attempt to split the host and port, ignoring the error
|
// Attempt to split the host and port, ignoring the error
|
||||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||||
@ -112,7 +124,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
||||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||||
SubjectTypesSupported: []string{"public"},
|
SubjectTypesSupported: []string{"public"},
|
||||||
IdTokenSigningAlgValuesSupported: []string{"RS256"},
|
IdTokenSigningAlgValuesSupported: []string{"RS256", "RS512", "ES256", "ES384", "ES512"},
|
||||||
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
|
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
|
||||||
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
|
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
|
||||||
RequestParameterSupported: true,
|
RequestParameterSupported: true,
|
||||||
@ -160,3 +172,43 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
|||||||
|
|
||||||
return jwks, nil
|
return jwks, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetWebFinger(resource string, rels []string, host string) (WebFinger, error) {
|
||||||
|
wf := WebFinger{}
|
||||||
|
|
||||||
|
resourceSplit := strings.Split(resource, ":")
|
||||||
|
|
||||||
|
if len(resourceSplit) != 2 {
|
||||||
|
return wf, fmt.Errorf("invalid resource")
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceType := resourceSplit[0]
|
||||||
|
resourceValue := resourceSplit[1]
|
||||||
|
|
||||||
|
oidcDiscovery := GetOidcDiscovery(host)
|
||||||
|
|
||||||
|
switch resourceType {
|
||||||
|
case "acct":
|
||||||
|
user, err := GetUserByEmailOnly(resourceValue)
|
||||||
|
if err != nil {
|
||||||
|
return wf, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return wf, fmt.Errorf("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
wf.Subject = resource
|
||||||
|
|
||||||
|
for _, rel := range rels {
|
||||||
|
if rel == "http://openid.net/specs/connect/1.0/issuer" {
|
||||||
|
wf.Links = append(wf.Links, WebFingerLink{
|
||||||
|
Rel: "http://openid.net/specs/connect/1.0/issuer",
|
||||||
|
Href: oidcDiscovery.Issuer,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wf, nil
|
||||||
|
}
|
||||||
|
@ -56,10 +56,12 @@ type Organization struct {
|
|||||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||||
Logo string `xorm:"varchar(200)" json:"logo"`
|
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||||
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
|
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
|
||||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
Favicon string `xorm:"varchar(200)" json:"favicon"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||||
|
PasswordObfuscatorType string `xorm:"varchar(100)" json:"passwordObfuscatorType"`
|
||||||
|
PasswordObfuscatorKey string `xorm:"varchar(100)" json:"passwordObfuscatorKey"`
|
||||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||||
@ -79,9 +81,9 @@ type Organization struct {
|
|||||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) (int64, error) {
|
func GetOrganizationCount(owner, name, field, value string) (int64, error) {
|
||||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
return session.Count(&Organization{})
|
return session.Count(&Organization{Name: name})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
func GetOrganizations(owner string, name ...string) ([]*Organization, error) {
|
||||||
@ -319,6 +321,7 @@ func GetDefaultApplication(id string) (*Application, error) {
|
|||||||
if defaultApplication == nil {
|
if defaultApplication == nil {
|
||||||
return nil, fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
|
return nil, fmt.Errorf("The default application: %s does not exist", organization.DefaultApplication)
|
||||||
} else {
|
} else {
|
||||||
|
defaultApplication.Organization = organization.Name
|
||||||
return defaultApplication, nil
|
return defaultApplication, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ type Resource struct {
|
|||||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||||
FileSize int `json:"fileSize"`
|
FileSize int `json:"fileSize"`
|
||||||
Url string `xorm:"varchar(255)" json:"url"`
|
Url string `xorm:"varchar(500)" json:"url"`
|
||||||
Description string `xorm:"varchar(255)" json:"description"`
|
Description string `xorm:"varchar(255)" json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,11 @@ func NewSamlResponse(application *Application, user *User, host string, certific
|
|||||||
assertion.CreateAttr("IssueInstant", now)
|
assertion.CreateAttr("IssueInstant", now)
|
||||||
assertion.CreateElement("saml:Issuer").SetText(host)
|
assertion.CreateElement("saml:Issuer").SetText(host)
|
||||||
subject := assertion.CreateElement("saml:Subject")
|
subject := assertion.CreateElement("saml:Subject")
|
||||||
subject.CreateElement("saml:NameID").SetText(user.Name)
|
nameIDValue := user.Name
|
||||||
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIDValue = user.Email
|
||||||
|
}
|
||||||
|
subject.CreateElement("saml:NameID").SetText(nameIDValue)
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
|
||||||
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
|
||||||
@ -184,17 +188,17 @@ type NameIDFormat struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SingleSignOnService struct {
|
type SingleSignOnService struct {
|
||||||
XMLName xml.Name
|
// XMLName xml.Name
|
||||||
Binding string `xml:"Binding,attr"`
|
Binding string `xml:"Binding,attr"`
|
||||||
Location string `xml:"Location,attr"`
|
Location string `xml:"Location,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attribute struct {
|
type Attribute struct {
|
||||||
// XMLName xml.Name
|
// XMLName xml.Name
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
Name string `xml:"Name,attr"`
|
Name string `xml:"Name,attr"`
|
||||||
NameFormat string `xml:"NameFormat,attr"`
|
NameFormat string `xml:"NameFormat,attr"`
|
||||||
FriendlyName string `xml:"FriendlyName,attr"`
|
FriendlyName string `xml:"FriendlyName,attr"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
|
||||||
Values []string `xml:"AttributeValue"`
|
Values []string `xml:"AttributeValue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,7 +390,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
// NewSamlResponse11 return a saml1.1 response(not 2.0)
|
||||||
func NewSamlResponse11(user *User, requestID string, host string) (*etree.Element, error) {
|
func NewSamlResponse11(application *Application, user *User, requestID string, host string) (*etree.Element, error) {
|
||||||
samlResponse := &etree.Element{
|
samlResponse := &etree.Element{
|
||||||
Space: "samlp",
|
Space: "samlp",
|
||||||
Tag: "Response",
|
Tag: "Response",
|
||||||
@ -430,7 +434,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
// nameIdentifier inside subject
|
// nameIdentifier inside subject
|
||||||
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
|
||||||
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
|
||||||
nameIdentifier.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifier.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifier.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// subjectConfirmation inside subject
|
// subjectConfirmation inside subject
|
||||||
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
|
||||||
@ -439,7 +447,11 @@ func NewSamlResponse11(user *User, requestID string, host string) (*etree.Elemen
|
|||||||
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
|
||||||
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
|
||||||
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
|
||||||
nameIdentifierInAttribute.SetText(user.Name)
|
if application.UseEmailAsSamlNameId {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Email)
|
||||||
|
} else {
|
||||||
|
nameIdentifierInAttribute.SetText(user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
|
||||||
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
|
||||||
|
@ -30,6 +30,13 @@ import (
|
|||||||
|
|
||||||
var isCloudIntranet bool
|
var isCloudIntranet bool
|
||||||
|
|
||||||
|
const (
|
||||||
|
ProviderTypeGoogleCloudStorage = "Google Cloud Storage"
|
||||||
|
ProviderTypeTencentCloudCOS = "Tencent Cloud COS"
|
||||||
|
ProviderTypeAzureBlob = "Azure Blob"
|
||||||
|
ProviderTypeLocalFileSystem = "Local File System"
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
isCloudIntranet = conf.GetConfigBool("isCloudIntranet")
|
isCloudIntranet = conf.GetConfigBool("isCloudIntranet")
|
||||||
}
|
}
|
||||||
@ -80,27 +87,28 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
||||||
|
|
||||||
host := ""
|
host := ""
|
||||||
if provider.Type != "Local File System" {
|
if provider.Type != ProviderTypeLocalFileSystem {
|
||||||
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
// provider.Domain = "https://cdn.casbin.com/casdoor/"
|
||||||
host = util.GetUrlHost(provider.Domain)
|
host = util.GetUrlHost(provider.Domain)
|
||||||
} else {
|
} else {
|
||||||
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
|
||||||
host = util.UrlJoin(provider.Domain, "/files")
|
host = util.UrlJoin(provider.Domain, "/files")
|
||||||
}
|
}
|
||||||
if provider.Type == "Azure Blob" {
|
if provider.Type == ProviderTypeAzureBlob || provider.Type == ProviderTypeGoogleCloudStorage {
|
||||||
host = util.UrlJoin(host, provider.Bucket)
|
host = util.UrlJoin(host, provider.Bucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileUrl := ""
|
fileUrl := ""
|
||||||
if host != "" {
|
if host != "" {
|
||||||
fileUrl = util.UrlJoin(host, escapePath(objectKey))
|
// fileUrl = util.UrlJoin(host, escapePath(objectKey))
|
||||||
|
fileUrl = util.UrlJoin(host, objectKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileUrl != "" && hasTimestamp {
|
// if fileUrl != "" && hasTimestamp {
|
||||||
fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
|
// fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
|
||||||
}
|
// }
|
||||||
|
|
||||||
if provider.Type == "Tencent Cloud COS" {
|
if provider.Type == ProviderTypeTencentCloudCOS {
|
||||||
objectKey = escapePath(objectKey)
|
objectKey = escapePath(objectKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +117,18 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
|
|||||||
|
|
||||||
func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface, error) {
|
func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface, error) {
|
||||||
endpoint := getProviderEndpoint(provider)
|
endpoint := getProviderEndpoint(provider)
|
||||||
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
|
certificate := ""
|
||||||
|
if provider.Category == "Storage" && provider.Type == "Casdoor" {
|
||||||
|
cert, err := GetCert(util.GetId(provider.Owner, provider.Cert))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return nil, fmt.Errorf("no cert for %s", provider.Cert)
|
||||||
|
}
|
||||||
|
certificate = cert.Certificate
|
||||||
|
}
|
||||||
|
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint, certificate, provider.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -135,17 +154,17 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
|
|||||||
}
|
}
|
||||||
|
|
||||||
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
||||||
|
objectKeyRefined := refineObjectKey(provider, objectKey)
|
||||||
|
|
||||||
objectKeyRefined := objectKey
|
object, err := storageProvider.Put(objectKeyRefined, fileBuffer)
|
||||||
if provider.Type == "Google Cloud Storage" {
|
|
||||||
objectKeyRefined = strings.TrimPrefix(objectKeyRefined, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = storageProvider.Put(objectKeyRefined, fileBuffer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.Type == "Casdoor" {
|
||||||
|
fileUrl = object.Path
|
||||||
|
}
|
||||||
|
|
||||||
return fileUrl, objectKey, nil
|
return fileUrl, objectKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,5 +203,13 @@ func DeleteFile(provider *Provider, objectKey string, lang string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return storageProvider.Delete(objectKey)
|
objectKeyRefined := refineObjectKey(provider, objectKey)
|
||||||
|
return storageProvider.Delete(objectKeyRefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineObjectKey(provider *Provider, objectKey string) string {
|
||||||
|
if provider.Type == ProviderTypeGoogleCloudStorage {
|
||||||
|
return strings.TrimPrefix(objectKey, "/")
|
||||||
|
}
|
||||||
|
return objectKey
|
||||||
}
|
}
|
||||||
|
@ -277,12 +277,11 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
return "", "", fmt.Errorf("the application for user %s is not found", userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
samlResponse, err := NewSamlResponse11(user, request.RequestID, host)
|
samlResponse, err := NewSamlResponse11(application, user, request.RequestID, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@ -128,7 +129,7 @@ type UserWithoutThirdIdp struct {
|
|||||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||||
|
|
||||||
// ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimsShort struct {
|
type ClaimsShort struct {
|
||||||
@ -254,6 +255,8 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
|||||||
|
|
||||||
LastSigninWrongTime: user.LastSigninWrongTime,
|
LastSigninWrongTime: user.LastSigninWrongTime,
|
||||||
SigninWrongTimes: user.SigninWrongTimes,
|
SigninWrongTimes: user.SigninWrongTimes,
|
||||||
|
|
||||||
|
ManagedAccounts: user.ManagedAccounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@ -365,6 +368,10 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.IsShared {
|
||||||
|
claims.Audience = []string{application.ClientId + "-org-" + user.Owner}
|
||||||
|
}
|
||||||
|
|
||||||
var token *jwt.Token
|
var token *jwt.Token
|
||||||
var refreshToken *jwt.Token
|
var refreshToken *jwt.Token
|
||||||
|
|
||||||
@ -372,36 +379,52 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
application.TokenFormat = "JWT"
|
application.TokenFormat = "JWT"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jwtMethod jwt.SigningMethod
|
||||||
|
|
||||||
|
if application.TokenSigningMethod == "RS256" {
|
||||||
|
jwtMethod = jwt.SigningMethodRS256
|
||||||
|
} else if application.TokenSigningMethod == "RS512" {
|
||||||
|
jwtMethod = jwt.SigningMethodRS512
|
||||||
|
} else if application.TokenSigningMethod == "ES256" {
|
||||||
|
jwtMethod = jwt.SigningMethodES256
|
||||||
|
} else if application.TokenSigningMethod == "ES512" {
|
||||||
|
jwtMethod = jwt.SigningMethodES512
|
||||||
|
} else if application.TokenSigningMethod == "ES384" {
|
||||||
|
jwtMethod = jwt.SigningMethodES384
|
||||||
|
} else {
|
||||||
|
jwtMethod = jwt.SigningMethodRS256
|
||||||
|
}
|
||||||
|
|
||||||
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
|
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
|
||||||
if application.TokenFormat == "JWT" {
|
if application.TokenFormat == "JWT" {
|
||||||
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
|
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
token = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp)
|
||||||
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsWithoutThirdIdp.TokenType = "refresh-token"
|
claimsWithoutThirdIdp.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsWithoutThirdIdp)
|
||||||
} else if application.TokenFormat == "JWT-Empty" {
|
} else if application.TokenFormat == "JWT-Empty" {
|
||||||
claimsShort := getShortClaims(claims)
|
claimsShort := getShortClaims(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
token = jwt.NewWithClaims(jwtMethod, claimsShort)
|
||||||
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsShort.TokenType = "refresh-token"
|
claimsShort.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsShort)
|
||||||
} else if application.TokenFormat == "JWT-Custom" {
|
} else if application.TokenFormat == "JWT-Custom" {
|
||||||
claimsCustom := getClaimsCustom(claims, application.TokenFields)
|
claimsCustom := getClaimsCustom(claims, application.TokenFields)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsCustom)
|
token = jwt.NewWithClaims(jwtMethod, claimsCustom)
|
||||||
refreshClaims := getClaimsCustom(claims, application.TokenFields)
|
refreshClaims := getClaimsCustom(claims, application.TokenFields)
|
||||||
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
||||||
refreshClaims["TokenType"] = "refresh-token"
|
refreshClaims["TokenType"] = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, refreshClaims)
|
refreshToken = jwt.NewWithClaims(jwtMethod, refreshClaims)
|
||||||
} else if application.TokenFormat == "JWT-Standard" {
|
} else if application.TokenFormat == "JWT-Standard" {
|
||||||
claimsStandard := getStandardClaims(claims)
|
claimsStandard := getStandardClaims(claims)
|
||||||
|
|
||||||
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard)
|
token = jwt.NewWithClaims(jwtMethod, claimsStandard)
|
||||||
claimsStandard.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
claimsStandard.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
claimsStandard.TokenType = "refresh-token"
|
claimsStandard.TokenType = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard)
|
refreshToken = jwt.NewWithClaims(jwtMethod, claimsStandard)
|
||||||
} else {
|
} else {
|
||||||
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
||||||
}
|
}
|
||||||
@ -419,34 +442,57 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA private key
|
var (
|
||||||
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
tokenString string
|
||||||
|
refreshTokenString string
|
||||||
|
key interface{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if strings.Contains(application.TokenSigningMethod, "RS") || application.TokenSigningMethod == "" {
|
||||||
|
// RSA private key
|
||||||
|
key, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
} else if strings.Contains(application.TokenSigningMethod, "ES") {
|
||||||
|
// ES private key
|
||||||
|
key, err = jwt.ParseECPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
} else if strings.Contains(application.TokenSigningMethod, "Ed") {
|
||||||
|
// Ed private key
|
||||||
|
key, err = jwt.ParseEdPrivateKeyFromPEM([]byte(cert.PrivateKey))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
token.Header["kid"] = cert.Name
|
token.Header["kid"] = cert.Name
|
||||||
tokenString, err := token.SignedString(key)
|
tokenString, err = token.SignedString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
refreshTokenString, err := refreshToken.SignedString(key)
|
refreshTokenString, err = refreshToken.SignedString(key)
|
||||||
|
|
||||||
return tokenString, refreshTokenString, name, err
|
return tokenString, refreshTokenString, name, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
||||||
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
var (
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
certificate interface{}
|
||||||
}
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
if cert.Certificate == "" {
|
if cert.Certificate == "" {
|
||||||
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSA certificate
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); ok {
|
||||||
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
// RSA certificate
|
||||||
|
certificate, err = jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
} else if _, ok := token.Method.(*jwt.SigningMethodECDSA); ok {
|
||||||
|
// ES certificate
|
||||||
|
certificate, err = jwt.ParseECPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -428,22 +428,26 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if token == nil {
|
if token == nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code is invalid",
|
ErrorDescription: fmt.Sprintf("authorization code: [%s] is invalid", code),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.CodeIsUsed {
|
if token.CodeIsUsed {
|
||||||
// anti replay attacks
|
// anti replay attacks
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code has been used",
|
ErrorDescription: fmt.Sprintf("authorization code has been used for token: [%s]", token.GetId()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
if token.CodeChallenge != "" {
|
||||||
return nil, &TokenError{
|
challengeAnswer := pkceChallenge(verifier)
|
||||||
Error: InvalidGrant,
|
if challengeAnswer != token.CodeChallenge {
|
||||||
ErrorDescription: "verifier is invalid",
|
return nil, &TokenError{
|
||||||
}, nil
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("verifier is invalid, challengeAnswer: [%s], token.CodeChallenge: [%s]", challengeAnswer, token.CodeChallenge),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.ClientSecret != clientSecret {
|
if application.ClientSecret != clientSecret {
|
||||||
@ -452,13 +456,13 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if token.CodeChallenge == "" {
|
if token.CodeChallenge == "" {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidClient,
|
Error: InvalidClient,
|
||||||
ErrorDescription: "client_secret is invalid",
|
ErrorDescription: fmt.Sprintf("client_secret is invalid for application: [%s], token.CodeChallenge: empty", application.GetId()),
|
||||||
}, nil
|
}, nil
|
||||||
} else {
|
} else {
|
||||||
if clientSecret != "" {
|
if clientSecret != "" {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidClient,
|
Error: InvalidClient,
|
||||||
ErrorDescription: "client_secret is invalid",
|
ErrorDescription: fmt.Sprintf("client_secret is invalid for application: [%s], token.CodeChallenge: [%s]", application.GetId(), token.CodeChallenge),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -467,15 +471,16 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
|
|||||||
if application.Name != token.Application {
|
if application.Name != token.Application {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "the token is for wrong application (client_id)",
|
ErrorDescription: fmt.Sprintf("the token is for wrong application (client_id), application.Name: [%s], token.Application: [%s]", application.Name, token.Application),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if time.Now().Unix() > token.CodeExpireIn {
|
nowUnix := time.Now().Unix()
|
||||||
|
if nowUnix > token.CodeExpireIn {
|
||||||
// code must be used within 5 minutes
|
// code must be used within 5 minutes
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "authorization code has expired",
|
ErrorDescription: fmt.Sprintf("authorization code has expired, nowUnix: [%s], token.CodeExpireIn: [%s]", time.Unix(nowUnix, 0).Format(time.RFC3339), time.Unix(token.CodeExpireIn, 0).Format(time.RFC3339)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
return token, nil, nil
|
return token, nil, nil
|
||||||
|
@ -18,16 +18,20 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClaimsStandard struct {
|
type ClaimsStandard struct {
|
||||||
*UserShort
|
*UserShort
|
||||||
Gender string `json:"gender,omitempty"`
|
EmailVerified bool `json:"email_verified,omitempty"`
|
||||||
TokenType string `json:"tokenType,omitempty"`
|
PhoneNumber string `json:"phone_number,omitempty"`
|
||||||
Nonce string `json:"nonce,omitempty"`
|
PhoneNumberVerified bool `json:"phone_number_verified,omitempty"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Gender string `json:"gender,omitempty"`
|
||||||
Address OIDCAddress `json:"address,omitempty"`
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
Address OIDCAddress `json:"address,omitempty"`
|
||||||
|
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
@ -43,12 +47,14 @@ func getStreetAddress(user *User) string {
|
|||||||
func getStandardClaims(claims Claims) ClaimsStandard {
|
func getStandardClaims(claims Claims) ClaimsStandard {
|
||||||
res := ClaimsStandard{
|
res := ClaimsStandard{
|
||||||
UserShort: getShortUser(claims.User),
|
UserShort: getShortUser(claims.User),
|
||||||
|
EmailVerified: claims.User.EmailVerified,
|
||||||
TokenType: claims.TokenType,
|
TokenType: claims.TokenType,
|
||||||
Nonce: claims.Nonce,
|
Nonce: claims.Nonce,
|
||||||
Scope: claims.Scope,
|
Scope: claims.Scope,
|
||||||
RegisteredClaims: claims.RegisteredClaims,
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.Phone = ""
|
||||||
var scopes []string
|
var scopes []string
|
||||||
|
|
||||||
if strings.Contains(claims.Scope, ",") {
|
if strings.Contains(claims.Scope, ",") {
|
||||||
@ -62,6 +68,15 @@ func getStandardClaims(claims Claims) ClaimsStandard {
|
|||||||
res.Address = OIDCAddress{StreetAddress: getStreetAddress(claims.User)}
|
res.Address = OIDCAddress{StreetAddress: getStreetAddress(claims.User)}
|
||||||
} else if scope == "profile" {
|
} else if scope == "profile" {
|
||||||
res.Gender = claims.User.Gender
|
res.Gender = claims.User.Gender
|
||||||
|
} else if scope == "phone" && claims.User.Phone != "" {
|
||||||
|
res.PhoneNumberVerified = true
|
||||||
|
phoneNumber, ok := util.GetE164Number(claims.User.Phone, claims.User.CountryCode)
|
||||||
|
if !ok {
|
||||||
|
res.PhoneNumberVerified = false
|
||||||
|
} else {
|
||||||
|
res.PhoneNumber = phoneNumber
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -950,7 +950,17 @@ func DeleteUser(user *User) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if organization != nil && organization.EnableSoftDeletion {
|
||||||
|
user.IsDeleted = true
|
||||||
|
user.DeletedTime = util.GetCurrentTime()
|
||||||
|
return UpdateUser(user.GetId(), user, []string{"is_deleted", "deleted_time"}, false)
|
||||||
|
} else {
|
||||||
|
return deleteUser(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
func GetUserInfo(user *User, scope string, aud string, host string) (*Userinfo, error) {
|
||||||
@ -1138,7 +1148,7 @@ func (user *User) IsApplicationAdmin(application *Application) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin()
|
return (user.Owner == application.Organization && user.IsAdmin) || user.IsGlobalAdmin() || (user.IsAdmin && application.IsShared)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user *User) IsGlobalAdmin() bool {
|
func (user *User) IsGlobalAdmin() bool {
|
||||||
|
@ -271,113 +271,213 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
|
|
||||||
if oldUser.Owner != newUser.Owner {
|
if oldUser.Owner != newUser.Owner {
|
||||||
item := GetAccountItemByName("Organization", organization)
|
item := GetAccountItemByName("Organization", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Owner = oldUser.Owner
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Name != newUser.Name {
|
if oldUser.Name != newUser.Name {
|
||||||
item := GetAccountItemByName("Name", organization)
|
item := GetAccountItemByName("Name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Name = oldUser.Name
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Id != newUser.Id {
|
if oldUser.Id != newUser.Id {
|
||||||
item := GetAccountItemByName("ID", organization)
|
item := GetAccountItemByName("ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Id = oldUser.Id
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.DisplayName != newUser.DisplayName {
|
if oldUser.DisplayName != newUser.DisplayName {
|
||||||
item := GetAccountItemByName("Display name", organization)
|
item := GetAccountItemByName("Display name", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.DisplayName = oldUser.DisplayName
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Avatar != newUser.Avatar {
|
if oldUser.Avatar != newUser.Avatar {
|
||||||
item := GetAccountItemByName("Avatar", organization)
|
item := GetAccountItemByName("Avatar", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Avatar = oldUser.Avatar
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Type != newUser.Type {
|
if oldUser.Type != newUser.Type {
|
||||||
item := GetAccountItemByName("User type", organization)
|
item := GetAccountItemByName("User type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Type = oldUser.Type
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The password is *** when not modified
|
// The password is *** when not modified
|
||||||
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
if oldUser.Password != newUser.Password && newUser.Password != "***" {
|
||||||
item := GetAccountItemByName("Password", organization)
|
item := GetAccountItemByName("Password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Password = oldUser.Password
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Email != newUser.Email {
|
if oldUser.Email != newUser.Email {
|
||||||
item := GetAccountItemByName("Email", organization)
|
item := GetAccountItemByName("Email", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Email = oldUser.Email
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Phone != newUser.Phone {
|
if oldUser.Phone != newUser.Phone {
|
||||||
item := GetAccountItemByName("Phone", organization)
|
item := GetAccountItemByName("Phone", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Phone = oldUser.Phone
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.CountryCode != newUser.CountryCode {
|
if oldUser.CountryCode != newUser.CountryCode {
|
||||||
item := GetAccountItemByName("Country code", organization)
|
item := GetAccountItemByName("Country code", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.CountryCode = oldUser.CountryCode
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Region != newUser.Region {
|
if oldUser.Region != newUser.Region {
|
||||||
item := GetAccountItemByName("Country/Region", organization)
|
item := GetAccountItemByName("Country/Region", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Region = oldUser.Region
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Location != newUser.Location {
|
if oldUser.Location != newUser.Location {
|
||||||
item := GetAccountItemByName("Location", organization)
|
item := GetAccountItemByName("Location", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Location = oldUser.Location
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Affiliation != newUser.Affiliation {
|
if oldUser.Affiliation != newUser.Affiliation {
|
||||||
item := GetAccountItemByName("Affiliation", organization)
|
item := GetAccountItemByName("Affiliation", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Affiliation = oldUser.Affiliation
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Title != newUser.Title {
|
if oldUser.Title != newUser.Title {
|
||||||
item := GetAccountItemByName("Title", organization)
|
item := GetAccountItemByName("Title", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Title = oldUser.Title
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Homepage != newUser.Homepage {
|
if oldUser.Homepage != newUser.Homepage {
|
||||||
item := GetAccountItemByName("Homepage", organization)
|
item := GetAccountItemByName("Homepage", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Homepage = oldUser.Homepage
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Bio != newUser.Bio {
|
if oldUser.Bio != newUser.Bio {
|
||||||
item := GetAccountItemByName("Bio", organization)
|
item := GetAccountItemByName("Bio", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Bio = oldUser.Bio
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.Tag != newUser.Tag {
|
if oldUser.Tag != newUser.Tag {
|
||||||
item := GetAccountItemByName("Tag", organization)
|
item := GetAccountItemByName("Tag", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Tag = oldUser.Tag
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.SignupApplication != newUser.SignupApplication {
|
if oldUser.SignupApplication != newUser.SignupApplication {
|
||||||
item := GetAccountItemByName("Signup application", organization)
|
item := GetAccountItemByName("Signup application", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.SignupApplication = oldUser.SignupApplication
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Gender != newUser.Gender {
|
if oldUser.Gender != newUser.Gender {
|
||||||
item := GetAccountItemByName("Gender", organization)
|
item := GetAccountItemByName("Gender", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Gender = oldUser.Gender
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Birthday != newUser.Birthday {
|
if oldUser.Birthday != newUser.Birthday {
|
||||||
item := GetAccountItemByName("Birthday", organization)
|
item := GetAccountItemByName("Birthday", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Birthday = oldUser.Birthday
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Education != newUser.Education {
|
if oldUser.Education != newUser.Education {
|
||||||
item := GetAccountItemByName("Education", organization)
|
item := GetAccountItemByName("Education", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Education = oldUser.Education
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCard != newUser.IdCard {
|
if oldUser.IdCard != newUser.IdCard {
|
||||||
item := GetAccountItemByName("ID card", organization)
|
item := GetAccountItemByName("ID card", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCard = oldUser.IdCard
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IdCardType != newUser.IdCardType {
|
if oldUser.IdCardType != newUser.IdCardType {
|
||||||
item := GetAccountItemByName("ID card type", organization)
|
item := GetAccountItemByName("ID card type", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IdCardType = oldUser.IdCardType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
|
||||||
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
|
||||||
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
|
||||||
item := GetAccountItemByName("Properties", organization)
|
item := GetAccountItemByName("Properties", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Properties = oldUser.Properties
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
if oldUser.PreferredMfaType != newUser.PreferredMfaType {
|
||||||
item := GetAccountItemByName("Multi-factor authentication", organization)
|
item := GetAccountItemByName("Multi-factor authentication", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.PreferredMfaType = oldUser.PreferredMfaType
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Groups == nil {
|
if oldUser.Groups == nil {
|
||||||
@ -390,7 +490,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
newUserGroupsJson, _ := json.Marshal(newUser.Groups)
|
||||||
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
if string(oldUserGroupsJson) != string(newUserGroupsJson) {
|
||||||
item := GetAccountItemByName("Groups", organization)
|
item := GetAccountItemByName("Groups", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Groups = oldUser.Groups
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Address == nil {
|
if oldUser.Address == nil {
|
||||||
@ -404,65 +508,117 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
|||||||
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
newUserAddressJson, _ := json.Marshal(newUser.Address)
|
||||||
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
if string(oldUserAddressJson) != string(newUserAddressJson) {
|
||||||
item := GetAccountItemByName("Address", organization)
|
item := GetAccountItemByName("Address", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Address = oldUser.Address
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newUser.FaceIds != nil {
|
if newUser.FaceIds != nil {
|
||||||
item := GetAccountItemByName("Face ID", organization)
|
item := GetAccountItemByName("Face ID", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.FaceIds = oldUser.FaceIds
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsAdmin != newUser.IsAdmin {
|
if oldUser.IsAdmin != newUser.IsAdmin {
|
||||||
item := GetAccountItemByName("Is admin", organization)
|
item := GetAccountItemByName("Is admin", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsAdmin = oldUser.IsAdmin
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.IsForbidden != newUser.IsForbidden {
|
if oldUser.IsForbidden != newUser.IsForbidden {
|
||||||
item := GetAccountItemByName("Is forbidden", organization)
|
item := GetAccountItemByName("Is forbidden", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsForbidden = oldUser.IsForbidden
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.IsDeleted != newUser.IsDeleted {
|
if oldUser.IsDeleted != newUser.IsDeleted {
|
||||||
item := GetAccountItemByName("Is deleted", organization)
|
item := GetAccountItemByName("Is deleted", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.IsDeleted = oldUser.IsDeleted
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
if oldUser.NeedUpdatePassword != newUser.NeedUpdatePassword {
|
||||||
item := GetAccountItemByName("Need update password", organization)
|
item := GetAccountItemByName("Need update password", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.NeedUpdatePassword = oldUser.NeedUpdatePassword
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Balance != newUser.Balance {
|
if oldUser.Balance != newUser.Balance {
|
||||||
item := GetAccountItemByName("Balance", organization)
|
item := GetAccountItemByName("Balance", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Balance = oldUser.Balance
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Score != newUser.Score {
|
if oldUser.Score != newUser.Score {
|
||||||
item := GetAccountItemByName("Score", organization)
|
item := GetAccountItemByName("Score", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Score = oldUser.Score
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Karma != newUser.Karma {
|
if oldUser.Karma != newUser.Karma {
|
||||||
item := GetAccountItemByName("Karma", organization)
|
item := GetAccountItemByName("Karma", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Karma = oldUser.Karma
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Language != newUser.Language {
|
if oldUser.Language != newUser.Language {
|
||||||
item := GetAccountItemByName("Language", organization)
|
item := GetAccountItemByName("Language", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Language = oldUser.Language
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Ranking != newUser.Ranking {
|
if oldUser.Ranking != newUser.Ranking {
|
||||||
item := GetAccountItemByName("Ranking", organization)
|
item := GetAccountItemByName("Ranking", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Ranking = oldUser.Ranking
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Currency != newUser.Currency {
|
if oldUser.Currency != newUser.Currency {
|
||||||
item := GetAccountItemByName("Currency", organization)
|
item := GetAccountItemByName("Currency", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Currency = oldUser.Currency
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldUser.Hash != newUser.Hash {
|
if oldUser.Hash != newUser.Hash {
|
||||||
item := GetAccountItemByName("Hash", organization)
|
item := GetAccountItemByName("Hash", organization)
|
||||||
itemsChanged = append(itemsChanged, item)
|
if item == nil {
|
||||||
|
newUser.Hash = oldUser.Hash
|
||||||
|
} else {
|
||||||
|
itemsChanged = append(itemsChanged, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, accountItem := range itemsChanged {
|
for _, accountItem := range itemsChanged {
|
||||||
|
@ -166,19 +166,76 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterRecordIn24Hours(record *VerificationRecord) *VerificationRecord {
|
||||||
|
if record == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
if now-record.Time > 60*60*24 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||||
var record VerificationRecord
|
record := &VerificationRecord{}
|
||||||
record.Receiver = dest
|
record.Receiver = dest
|
||||||
|
|
||||||
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(&record)
|
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
record = &VerificationRecord{}
|
||||||
|
record.Receiver = dest
|
||||||
|
|
||||||
|
has, err = ormer.Engine.Desc("time").Get(record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnusedVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||||
|
record := &VerificationRecord{}
|
||||||
|
record.Receiver = dest
|
||||||
|
|
||||||
|
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record = filterRecordIn24Hours(record)
|
||||||
|
if record == nil {
|
||||||
|
has = false
|
||||||
|
}
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
|
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
|
||||||
@ -187,7 +244,9 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if record == nil {
|
if record == nil {
|
||||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet, or has already been used!")}, nil
|
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
||||||
|
} else if record.IsUsed {
|
||||||
|
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has already been used!")}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
|
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
|
||||||
@ -196,9 +255,6 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
if now-record.Time > timeoutInMinutes*60*10 {
|
|
||||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
|
||||||
}
|
|
||||||
if now-record.Time > timeoutInMinutes*60 {
|
if now-record.Time > timeoutInMinutes*60 {
|
||||||
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
|
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
|
||||||
}
|
}
|
||||||
@ -211,7 +267,7 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DisableVerificationCode(dest string) error {
|
func DisableVerificationCode(dest string) error {
|
||||||
record, err := getVerificationRecord(dest)
|
record, err := getUnusedVerificationRecord(dest)
|
||||||
if record == nil || err != nil {
|
if record == nil || err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ func getSubject(ctx *context.Context) (string, string) {
|
|||||||
return util.GetOwnerAndNameFromId(username)
|
return util.GetOwnerAndNameFromId(username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getObject(ctx *context.Context) (string, string) {
|
func getObject(ctx *context.Context) (string, string, error) {
|
||||||
method := ctx.Request.Method
|
method := ctx.Request.Method
|
||||||
path := ctx.Request.URL.Path
|
path := ctx.Request.URL.Path
|
||||||
|
|
||||||
@ -65,13 +65,13 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
if ctx.Input.Query("id") == "/" {
|
if ctx.Input.Query("id") == "/" {
|
||||||
adapterId := ctx.Input.Query("adapterId")
|
adapterId := ctx.Input.Query("adapterId")
|
||||||
if adapterId != "" {
|
if adapterId != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(adapterId)
|
return util.GetOwnerAndNameFromIdWithError(adapterId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,34 +80,34 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
owner := ctx.Input.Query("owner")
|
owner := ctx.Input.Query("owner")
|
||||||
if owner != "" {
|
if owner != "" {
|
||||||
return owner, ""
|
return owner, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ""
|
return "", "", nil
|
||||||
} else {
|
} else {
|
||||||
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
|
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
return util.GetOwnerAndNameFromIdNoCheck(id)
|
return util.GetOwnerAndNameFromIdWithError(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body := ctx.Input.RequestBody
|
body := ctx.Input.RequestBody
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name")
|
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name"), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj Object
|
var obj Object
|
||||||
err := json.Unmarshal(body, &obj)
|
err := json.Unmarshal(body, &obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// panic(err)
|
// this is not error
|
||||||
return "", ""
|
return "", "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if path == "/api/delete-resource" {
|
if path == "/api/delete-resource" {
|
||||||
@ -117,7 +117,7 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj.Owner, obj.Name
|
return obj.Owner, obj.Name, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +183,12 @@ func ApiFilter(ctx *context.Context) {
|
|||||||
|
|
||||||
objOwner, objName := "", ""
|
objOwner, objName := "", ""
|
||||||
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
|
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
|
||||||
objOwner, objName = getObject(ctx)
|
var err error
|
||||||
|
objOwner, objName, err = getObject(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
if strings.HasPrefix(urlPath, "/api/notify-payment") {
|
||||||
|
@ -16,6 +16,7 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -23,6 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func AutoSigninFilter(ctx *context.Context) {
|
func AutoSigninFilter(ctx *context.Context) {
|
||||||
|
urlPath := ctx.Request.URL.Path
|
||||||
|
if strings.HasPrefix(urlPath, "/api/login/oauth/access_token") {
|
||||||
|
return
|
||||||
|
}
|
||||||
//if getSessionUser(ctx) != "" {
|
//if getSessionUser(ctx) != "" {
|
||||||
// return
|
// return
|
||||||
//}
|
//}
|
||||||
@ -67,6 +72,17 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
accessKey := ctx.Input.Query("accessKey")
|
||||||
|
accessSecret := ctx.Input.Query("accessSecret")
|
||||||
|
if accessKey != "" && accessSecret != "" {
|
||||||
|
userId, err := getUsernameByKeys(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
setSessionUser(ctx, userId)
|
||||||
|
}
|
||||||
|
|
||||||
// "/page?clientId=123&clientSecret=456"
|
// "/page?clientId=123&clientSecret=456"
|
||||||
userId, err := getUsernameByClientIdSecret(ctx)
|
userId, err := getUsernameByClientIdSecret(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -126,7 +125,7 @@ func setSessionUser(ctx *context.Context, user string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
|
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
|
||||||
ctx.Input.CruSession.SessionReleaseIfPresent(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
||||||
@ -135,7 +134,7 @@ func setSessionExpire(ctx *context.Context, ExpireTime int64) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
ctx.Input.CruSession.SessionReleaseIfPresent(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
||||||
@ -147,7 +146,7 @@ func setSessionOidc(ctx *context.Context, scope string, aud string) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
ctx.Input.CruSession.SessionReleaseIfPresent(ctx.ResponseWriter)
|
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBearerToken(ctx *context.Context) string {
|
func parseBearerToken(ctx *context.Context) string {
|
||||||
|
@ -48,6 +48,10 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
originHostname := getHostname(origin)
|
originHostname := getHostname(origin)
|
||||||
host := removePort(ctx.Request.Host)
|
host := removePort(ctx.Request.Host)
|
||||||
|
|
||||||
|
if origin == "null" {
|
||||||
|
origin = ""
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") || strings.Contains(origin, ".chromiumapp.org") {
|
||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
return
|
return
|
||||||
|
@ -290,6 +290,7 @@ func initAPI() {
|
|||||||
|
|
||||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||||
|
beego.Router("/.well-known/webfinger", &controllers.RootController{}, "GET:GetWebFinger")
|
||||||
|
|
||||||
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
||||||
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
||||||
|
@ -43,6 +43,10 @@ func getWebBuildFolder() string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if util.FileExist(filepath.Join(frontendBaseDir, "index.html")) {
|
||||||
|
return frontendBaseDir
|
||||||
|
}
|
||||||
|
|
||||||
path = filepath.Join(frontendBaseDir, "web/build")
|
path = filepath.Join(frontendBaseDir, "web/build")
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@ -58,7 +62,7 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
|
|||||||
redirectUri := ctx.Input.Query("redirect_uri")
|
redirectUri := ctx.Input.Query("redirect_uri")
|
||||||
scope := ctx.Input.Query("scope")
|
scope := ctx.Input.Query("scope")
|
||||||
state := ctx.Input.Query("state")
|
state := ctx.Input.Query("state")
|
||||||
nonce := ""
|
nonce := ctx.Input.Query("nonce")
|
||||||
codeChallenge := ctx.Input.Query("code_challenge")
|
codeChallenge := ctx.Input.Query("code_challenge")
|
||||||
if clientId == "" || responseType != "code" || redirectUri == "" {
|
if clientId == "" || responseType != "code" || redirectUri == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
|
64
routers/timeout_filter.go
Normal file
64
routers/timeout_filter.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2024 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.
|
||||||
|
|
||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/beego/beego/context"
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
inactiveTimeoutMinutes int64
|
||||||
|
requestTimeMap sync.Map
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
inactiveTimeoutMinutes, err = conf.GetConfigInt64("inactiveTimeoutMinutes")
|
||||||
|
if err != nil {
|
||||||
|
inactiveTimeoutMinutes = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeoutLogout(ctx *context.Context, sessionId string) {
|
||||||
|
requestTimeMap.Delete(sessionId)
|
||||||
|
ctx.Input.CruSession.Set("username", "")
|
||||||
|
ctx.Input.CruSession.Set("accessToken", "")
|
||||||
|
ctx.Input.CruSession.Delete("SessionData")
|
||||||
|
responseError(ctx, fmt.Sprintf(T(ctx, "auth:Timeout for inactivity of %d minutes"), inactiveTimeoutMinutes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TimeoutFilter(ctx *context.Context) {
|
||||||
|
if inactiveTimeoutMinutes <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
owner, name := getSubject(ctx)
|
||||||
|
if owner == "anonymous" || name == "anonymous" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionId := ctx.Input.CruSession.SessionID()
|
||||||
|
currentTime := time.Now()
|
||||||
|
preRequestTime, has := requestTimeMap.Load(sessionId)
|
||||||
|
requestTimeMap.Store(sessionId, currentTime)
|
||||||
|
if has && preRequestTime.(time.Time).Add(time.Minute*time.Duration(inactiveTimeoutMinutes)).Before(currentTime) {
|
||||||
|
timeoutLogout(ctx, sessionId)
|
||||||
|
}
|
||||||
|
}
|
19
storage/casdoor.go
Normal file
19
storage/casdoor.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/oss"
|
||||||
|
"github.com/casdoor/oss/casdoor"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCasdoorStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string, cert string, content string) oss.StorageInterface {
|
||||||
|
sp := casdoor.New(&casdoor.Config{
|
||||||
|
clientId,
|
||||||
|
clientSecret,
|
||||||
|
endpoint,
|
||||||
|
cert,
|
||||||
|
region,
|
||||||
|
content,
|
||||||
|
bucket,
|
||||||
|
})
|
||||||
|
return sp
|
||||||
|
}
|
@ -16,7 +16,7 @@ package storage
|
|||||||
|
|
||||||
import "github.com/casdoor/oss"
|
import "github.com/casdoor/oss"
|
||||||
|
|
||||||
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) (oss.StorageInterface, error) {
|
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string, cert string, content string) (oss.StorageInterface, error) {
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case "Local File System":
|
case "Local File System":
|
||||||
return NewLocalFileSystemStorageProvider(), nil
|
return NewLocalFileSystemStorageProvider(), nil
|
||||||
@ -36,6 +36,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
|||||||
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint), nil
|
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint), nil
|
||||||
case "Synology":
|
case "Synology":
|
||||||
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil
|
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint), nil
|
||||||
|
case "Casdoor":
|
||||||
|
return NewCasdoorStorageProvider(providerType, clientId, clientSecret, region, bucket, endpoint, cert, content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
76
util/obfuscator.go
Normal file
76
util/obfuscator.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2024 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.
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/des"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func unPaddingPkcs7(s []byte) []byte {
|
||||||
|
length := len(s)
|
||||||
|
if length == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
unPadding := int(s[length-1])
|
||||||
|
return s[:(length - unPadding)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func decryptDesOrAes(passwordCipher string, block cipher.Block) (string, error) {
|
||||||
|
passwordCipherBytes, err := hex.DecodeString(passwordCipher)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(passwordCipherBytes) < block.BlockSize() {
|
||||||
|
return "", fmt.Errorf("the password ciphertext should contain a random hexadecimal string of length %d at the beginning", block.BlockSize()*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := passwordCipherBytes[:block.BlockSize()]
|
||||||
|
password := make([]byte, len(passwordCipherBytes)-block.BlockSize())
|
||||||
|
|
||||||
|
mode := cipher.NewCBCDecrypter(block, iv)
|
||||||
|
mode.CryptBlocks(password, passwordCipherBytes[block.BlockSize():])
|
||||||
|
|
||||||
|
return string(unPaddingPkcs7(password)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUnobfuscatedPassword(passwordObfuscatorType string, passwordObfuscatorKey string, passwordCipher string) (string, error) {
|
||||||
|
if passwordObfuscatorType == "Plain" || passwordObfuscatorType == "" {
|
||||||
|
return passwordCipher, nil
|
||||||
|
} else if passwordObfuscatorType == "DES" || passwordObfuscatorType == "AES" {
|
||||||
|
key, err := hex.DecodeString(passwordObfuscatorKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var block cipher.Block
|
||||||
|
if passwordObfuscatorType == "DES" {
|
||||||
|
block, err = des.NewCipher(key)
|
||||||
|
} else {
|
||||||
|
block, err = aes.NewCipher(key)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptDesOrAes(passwordCipher, block)
|
||||||
|
} else {
|
||||||
|
return "", fmt.Errorf("unsupported password obfuscator type: %s", passwordObfuscatorType)
|
||||||
|
}
|
||||||
|
}
|
@ -131,6 +131,15 @@ func GetOwnerAndNameFromId(id string) (string, string) {
|
|||||||
return tokens[0], tokens[1]
|
return tokens[0], tokens[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetOwnerAndNameFromIdWithError(id string) (string, string, error) {
|
||||||
|
tokens := strings.Split(id, "/")
|
||||||
|
if len(tokens) != 2 {
|
||||||
|
return "", "", errors.New("GetOwnerAndNameFromId() error, wrong token count for ID: " + id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens[0], tokens[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetOwnerFromId(id string) string {
|
func GetOwnerFromId(id string) string {
|
||||||
tokens := strings.Split(id, "/")
|
tokens := strings.Split(id, "/")
|
||||||
if len(tokens) != 2 {
|
if len(tokens) != 2 {
|
||||||
@ -154,6 +163,16 @@ func GetOwnerAndNameAndOtherFromId(id string) (string, string, string) {
|
|||||||
return tokens[0], tokens[1], tokens[2]
|
return tokens[0], tokens[1], tokens[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSharedOrgFromApp(rawName string) (name string, organization string) {
|
||||||
|
name = rawName
|
||||||
|
splitName := strings.Split(rawName, "-org-")
|
||||||
|
if len(splitName) >= 2 {
|
||||||
|
organization = splitName[len(splitName)-1]
|
||||||
|
name = splitName[0]
|
||||||
|
}
|
||||||
|
return name, organization
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateId() string {
|
func GenerateId() string {
|
||||||
return uuid.NewString()
|
return uuid.NewString()
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,9 @@ func IsPhoneValid(phone string, countryCode string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
|
func IsPhoneAllowInRegin(countryCode string, allowRegions []string) bool {
|
||||||
|
if ContainsString(allowRegions, "All") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
return ContainsString(allowRegions, countryCode)
|
return ContainsString(allowRegions, countryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
"copy-to-clipboard": "^3.3.1",
|
"copy-to-clipboard": "^3.3.1",
|
||||||
"core-js": "^3.25.0",
|
"core-js": "^3.25.0",
|
||||||
"craco-less": "^2.0.0",
|
"craco-less": "^2.0.0",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"ethers": "5.6.9",
|
"ethers": "5.6.9",
|
||||||
"face-api.js": "^0.22.2",
|
"face-api.js": "^0.22.2",
|
||||||
|
@ -56,9 +56,11 @@ class AdapterListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -344,7 +344,8 @@ class App extends Component {
|
|||||||
window.location.pathname.startsWith("/cas") ||
|
window.location.pathname.startsWith("/cas") ||
|
||||||
window.location.pathname.startsWith("/select-plan") ||
|
window.location.pathname.startsWith("/select-plan") ||
|
||||||
window.location.pathname.startsWith("/buy-plan") ||
|
window.location.pathname.startsWith("/buy-plan") ||
|
||||||
window.location.pathname.startsWith("/qrcode") ;
|
window.location.pathname.startsWith("/qrcode") ||
|
||||||
|
window.location.pathname.startsWith("/captcha");
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = ({key}) => {
|
onClick = ({key}) => {
|
||||||
@ -361,7 +362,11 @@ class App extends Component {
|
|||||||
if (this.isDoorPages()) {
|
if (this.isDoorPages()) {
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={{
|
<ConfigProvider theme={{
|
||||||
algorithm: Setting.getAlgorithm(["default"]),
|
token: {
|
||||||
|
colorPrimary: this.state.themeData.colorPrimary,
|
||||||
|
borderRadius: this.state.themeData.borderRadius,
|
||||||
|
},
|
||||||
|
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||||
}}>
|
}}>
|
||||||
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
||||||
<Layout id="parent-area">
|
<Layout id="parent-area">
|
||||||
@ -371,6 +376,7 @@ class App extends Component {
|
|||||||
<EntryPage
|
<EntryPage
|
||||||
account={this.state.account}
|
account={this.state.account}
|
||||||
theme={this.state.themeData}
|
theme={this.state.themeData}
|
||||||
|
themeAlgorithm={this.state.themeAlgorithm}
|
||||||
updateApplication={(application) => {
|
updateApplication={(application) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
application: application,
|
application: application,
|
||||||
@ -445,7 +451,6 @@ class App extends Component {
|
|||||||
setLogoutState={() => {
|
setLogoutState={() => {
|
||||||
this.setState({
|
this.setState({
|
||||||
account: null,
|
account: null,
|
||||||
themeAlgorithm: ["default"],
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -129,6 +129,15 @@ img {
|
|||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loginBackgroundDark {
|
||||||
|
flex: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: #000 no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
.ant-menu-horizontal {
|
.ant-menu-horizontal {
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
|
@ -116,7 +116,6 @@ class ApplicationEditPage extends React.Component {
|
|||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getApplication();
|
this.getApplication();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getProviders();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getApplication() {
|
getApplication() {
|
||||||
@ -145,7 +144,9 @@ class ApplicationEditPage extends React.Component {
|
|||||||
application: application,
|
application: application,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getCerts(application.organization);
|
this.getProviders(application);
|
||||||
|
|
||||||
|
this.getCerts(application);
|
||||||
|
|
||||||
this.getSamlMetadata(application.enableSamlPostBinding);
|
this.getSamlMetadata(application.enableSamlPostBinding);
|
||||||
});
|
});
|
||||||
@ -166,7 +167,11 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCerts(owner) {
|
getCerts(application) {
|
||||||
|
let owner = application.organization;
|
||||||
|
if (application.isShared) {
|
||||||
|
owner = this.props.owner;
|
||||||
|
}
|
||||||
CertBackend.getCerts(owner)
|
CertBackend.getCerts(owner)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -175,8 +180,12 @@ class ApplicationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviders() {
|
getProviders(application) {
|
||||||
ProviderBackend.getProviders(this.state.owner)
|
let owner = application.organization;
|
||||||
|
if (application.isShared) {
|
||||||
|
owner = this.props.account.owner;
|
||||||
|
}
|
||||||
|
ProviderBackend.getProviders(owner)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -263,6 +272,16 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Is shared"), i18next.t("general:Is shared - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Switch disabled={Setting.isAdminUser()} checked={this.state.application.isShared} onChange={checked => {
|
||||||
|
this.updateApplicationField("isShared", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
|
||||||
@ -388,6 +407,16 @@ class ApplicationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:Token signing method"), i18next.t("application:Token signing method - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenSigningMethod === "" ? "RS256" : this.state.application.tokenSigningMethod} onChange={(value => {this.updateApplicationField("tokenSigningMethod", value);})}
|
||||||
|
options={["RS256", "RS512", "ES256", "ES512", "ES384"].map((item) => Setting.getOption(item, item))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("application:Token fields"), i18next.t("application:Token fields - Tooltip"))} :
|
{Setting.getLabel(i18next.t("application:Token fields"), i18next.t("application:Token fields - Tooltip"))} :
|
||||||
@ -674,6 +703,16 @@ class ApplicationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}}>
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("application:Use Email as NameID"), i18next.t("application:Use Email as NameID - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1}>
|
||||||
|
<Switch checked={this.state.application.useEmailAsSamlNameId} onChange={checked => {
|
||||||
|
this.updateApplicationField("useEmailAsSamlNameId", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
{Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
|
{Setting.getLabel(i18next.t("application:Enable SAML POST binding"), i18next.t("application:Enable SAML POST binding - Tooltip"))} :
|
||||||
@ -938,6 +977,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
<SigninTable
|
<SigninTable
|
||||||
title={i18next.t("application:Signin items")}
|
title={i18next.t("application:Signin items")}
|
||||||
table={this.state.application.signinItems}
|
table={this.state.application.signinItems}
|
||||||
|
themeAlgorithm={this.state.themeAlgorithm}
|
||||||
onUpdateTable={(value) => {
|
onUpdateTable={(value) => {
|
||||||
this.updateApplicationField("signinItems", value);
|
this.updateApplicationField("signinItems", value);
|
||||||
}}
|
}}
|
||||||
@ -989,7 +1029,11 @@ class ApplicationEditPage extends React.Component {
|
|||||||
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";
|
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${redirectUri}&scope=read&state=casdoor`;
|
let clientId = this.state.application.clientId;
|
||||||
|
if (this.state.application.isShared) {
|
||||||
|
clientId += `-org-${this.props.account.owner}`;
|
||||||
|
}
|
||||||
|
const signInUrl = `/login/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=read&state=casdoor`;
|
||||||
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
|
||||||
if (!Setting.isPasswordEnabled(this.state.application)) {
|
if (!Setting.isPasswordEnabled(this.state.application)) {
|
||||||
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
|
||||||
|
@ -97,9 +97,11 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
@ -123,7 +125,7 @@ class ApplicationListPage extends BaseListPage {
|
|||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
return (
|
return (
|
||||||
<Link to={`/applications/${record.organization}/${text}`}>
|
<Link to={`/applications/${record.organization}/${text}`}>
|
||||||
{text}
|
{Setting.getApplicationDisplayName(record)}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
116
web/src/CaptchaPage.js
Normal file
116
web/src/CaptchaPage.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2021 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 React from "react";
|
||||||
|
import {CaptchaModal} from "./common/modal/CaptchaModal";
|
||||||
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
|
||||||
|
class CaptchaPage extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
this.state = {
|
||||||
|
owner: "admin",
|
||||||
|
application: null,
|
||||||
|
clientId: params.get("client_id"),
|
||||||
|
applicationName: params.get("state"),
|
||||||
|
redirectUri: params.get("redirect_uri"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getApplication();
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateApplication(application) {
|
||||||
|
this.setState({
|
||||||
|
application: application,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getApplication() {
|
||||||
|
if (this.state.applicationName === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationBackend.getApplication(this.state.owner, this.state.applicationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "error") {
|
||||||
|
this.onUpdateApplication(null);
|
||||||
|
this.setState({
|
||||||
|
msg: res.msg,
|
||||||
|
});
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
this.onUpdateApplication(res.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCaptchaProviderItems(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";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(values) {
|
||||||
|
Setting.goToLink(`${this.state.redirectUri}?code=${values.captchaToken}&type=${values.captchaType}&secret=${values.clientSecret}&applicationId=${values.applicationId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCaptchaModal(application) {
|
||||||
|
const captchaProviderItems = this.getCaptchaProviderItems(application);
|
||||||
|
if (captchaProviderItems === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const alwaysProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Always");
|
||||||
|
const dynamicProviderItems = captchaProviderItems.filter(providerItem => providerItem.rule === "Dynamic");
|
||||||
|
const provider = alwaysProviderItems.length > 0
|
||||||
|
? alwaysProviderItems[0].provider
|
||||||
|
: dynamicProviderItems[0].provider;
|
||||||
|
|
||||||
|
return <CaptchaModal
|
||||||
|
owner={provider.owner}
|
||||||
|
name={provider.name}
|
||||||
|
visible={true}
|
||||||
|
onOk={(captchaType, captchaToken, clientSecret) => {
|
||||||
|
const values = {
|
||||||
|
captchaType: captchaType,
|
||||||
|
captchaToken: captchaToken,
|
||||||
|
clientSecret: clientSecret,
|
||||||
|
applicationId: `${provider.owner}/${provider.name}`,
|
||||||
|
};
|
||||||
|
this.callback(values);
|
||||||
|
}}
|
||||||
|
onCancel={() => this.callback({captchaType: "none", captchaToken: "", clientSecret: ""})}
|
||||||
|
isCurrentProvider={true}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
this.renderCaptchaModal(this.state.application)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CaptchaPage;
|
97
web/src/CasbinEditor.js
Normal file
97
web/src/CasbinEditor.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2024 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 React, {useCallback, useEffect, useRef, useState} from "react";
|
||||||
|
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||||
|
import "codemirror/lib/codemirror.css";
|
||||||
|
import "codemirror/mode/properties/properties";
|
||||||
|
import * as Setting from "./Setting";
|
||||||
|
import IframeEditor from "./IframeEditor";
|
||||||
|
import {Tabs} from "antd";
|
||||||
|
|
||||||
|
const {TabPane} = Tabs;
|
||||||
|
|
||||||
|
const CasbinEditor = ({model, onModelTextChange}) => {
|
||||||
|
const [activeKey, setActiveKey] = useState("advanced");
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [localModelText, setLocalModelText] = useState(model.modelText);
|
||||||
|
|
||||||
|
const handleModelTextChange = useCallback((newModelText) => {
|
||||||
|
if (!Setting.builtInObject(model)) {
|
||||||
|
setLocalModelText(newModelText);
|
||||||
|
onModelTextChange(newModelText);
|
||||||
|
}
|
||||||
|
}, [model, onModelTextChange]);
|
||||||
|
|
||||||
|
const syncModelText = useCallback(() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (activeKey === "advanced" && iframeRef.current) {
|
||||||
|
const handleSyncMessage = (event) => {
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
window.removeEventListener("message", handleSyncMessage);
|
||||||
|
handleModelTextChange(event.data.modelText);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("message", handleSyncMessage);
|
||||||
|
iframeRef.current.getModelText();
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [activeKey, handleModelTextChange]);
|
||||||
|
|
||||||
|
const handleTabChange = (key) => {
|
||||||
|
syncModelText().then(() => {
|
||||||
|
setActiveKey(key);
|
||||||
|
if (key === "advanced" && iframeRef.current) {
|
||||||
|
iframeRef.current.updateModelText(localModelText);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalModelText(model.modelText);
|
||||||
|
}, [model.modelText]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
||||||
|
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
||||||
|
<TabPane tab="Basic Editor" key="basic" />
|
||||||
|
<TabPane tab="Advanced Editor" key="advanced" />
|
||||||
|
</Tabs>
|
||||||
|
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
||||||
|
{activeKey === "advanced" ? (
|
||||||
|
<IframeEditor
|
||||||
|
ref={iframeRef}
|
||||||
|
initialModelText={localModelText}
|
||||||
|
onModelTextChange={handleModelTextChange}
|
||||||
|
style={{width: "100%", height: "100%"}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CodeMirror
|
||||||
|
value={localModelText}
|
||||||
|
className="full-height-editor no-horizontal-scroll-editor"
|
||||||
|
options={{mode: "properties", theme: "default"}}
|
||||||
|
onBeforeChange={(editor, data, value) => {
|
||||||
|
handleModelTextChange(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CasbinEditor;
|
@ -288,14 +288,14 @@ class CertEditPage extends React.Component {
|
|||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
this.setState({
|
this.setState({
|
||||||
certName: this.state.cert.name,
|
certName: this.state.cert.name,
|
||||||
|
}, () => {
|
||||||
|
if (exitAfterSave) {
|
||||||
|
this.props.history.push("/certs");
|
||||||
|
} else {
|
||||||
|
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
||||||
|
this.getCert();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exitAfterSave) {
|
|
||||||
this.props.history.push("/certs");
|
|
||||||
} else {
|
|
||||||
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
|
||||||
this.getCert();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
this.updateCertField("name", this.state.certName);
|
this.updateCertField("name", this.state.certName);
|
||||||
|
@ -73,9 +73,11 @@ class CertListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -55,9 +55,11 @@ class EnforcerListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -32,6 +32,7 @@ import {authConfig} from "./auth/Auth";
|
|||||||
import ProductBuyPage from "./ProductBuyPage";
|
import ProductBuyPage from "./ProductBuyPage";
|
||||||
import PaymentResultPage from "./PaymentResultPage";
|
import PaymentResultPage from "./PaymentResultPage";
|
||||||
import QrCodePage from "./QrCodePage";
|
import QrCodePage from "./QrCodePage";
|
||||||
|
import CaptchaPage from "./CaptchaPage";
|
||||||
import CustomHead from "./basic/CustomHead";
|
import CustomHead from "./basic/CustomHead";
|
||||||
|
|
||||||
class EntryPage extends React.Component {
|
class EntryPage extends React.Component {
|
||||||
@ -93,10 +94,12 @@ class EntryPage extends React.Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isDarkMode = this.props.themeAlgorithm.includes("dark");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<CustomHead headerHtml={this.state.application?.headerHtml} />
|
<CustomHead headerHtml={this.state.application?.headerHtml} />
|
||||||
<div className="loginBackground"
|
<div className={`${isDarkMode ? "loginBackgroundDark" : "loginBackground"}`}
|
||||||
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
style={{backgroundImage: Setting.inIframe() || Setting.isMobile() ? null : `url(${this.state.application?.formBackgroundUrl})`}}>
|
||||||
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
|
<Spin size="large" spinning={this.state.application === undefined && this.state.pricing === undefined} tip={i18next.t("login:Loading")}
|
||||||
style={{margin: "0 auto"}} />
|
style={{margin: "0 auto"}} />
|
||||||
@ -120,8 +123,10 @@ class EntryPage extends React.Component {
|
|||||||
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName" render={(props) => <ProductBuyPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
<Route exact path="/buy-plan/:owner/:pricingName/result" render={(props) => <PaymentResultPage {...this.props} pricing={this.state.pricing} onUpdatePricing={onUpdatePricing} {...props} />} />
|
||||||
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
<Route exact path="/qrcode/:owner/:paymentName" render={(props) => <QrCodePage {...this.props} onUpdateApplication={onUpdateApplication} {...props} />} />
|
||||||
|
<Route exact path="/captcha" render={(props) => <CaptchaPage {...props} />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -84,9 +84,11 @@ class GroupListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
66
web/src/IframeEditor.js
Normal file
66
web/src/IframeEditor.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2024 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 React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} from "react";
|
||||||
|
|
||||||
|
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
const [iframeReady, setIframeReady] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleMessage = (event) => {
|
||||||
|
if (event.origin !== "https://editor.casbin.org") {return;}
|
||||||
|
|
||||||
|
if (event.data.type === "modelUpdate") {
|
||||||
|
onModelTextChange(event.data.modelText);
|
||||||
|
} else if (event.data.type === "iframeReady") {
|
||||||
|
setIframeReady(true);
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "initializeModel",
|
||||||
|
modelText: initialModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", handleMessage);
|
||||||
|
return () => window.removeEventListener("message", handleMessage);
|
||||||
|
}, [onModelTextChange, initialModelText]);
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getModelText: () => {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
|
||||||
|
},
|
||||||
|
updateModelText: (newModelText) => {
|
||||||
|
if (iframeReady) {
|
||||||
|
iframeRef.current?.contentWindow.postMessage({
|
||||||
|
type: "updateModelText",
|
||||||
|
modelText: newModelText,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={iframeRef}
|
||||||
|
src="https://editor.casbin.org/model-editor"
|
||||||
|
frameBorder="0"
|
||||||
|
width="100%"
|
||||||
|
height="500px"
|
||||||
|
title="Casbin Model Editor"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default IframeEditor;
|
@ -20,6 +20,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -33,6 +34,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
invitation: null,
|
invitation: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
applications: [],
|
applications: [],
|
||||||
|
groups: [],
|
||||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -41,6 +43,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
this.getInvitation();
|
this.getInvitation();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
this.getApplicationsByOrganization(this.state.organizationName);
|
this.getApplicationsByOrganization(this.state.organizationName);
|
||||||
|
this.getGroupsByOrganization(this.state.organizationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInvitation() {
|
getInvitation() {
|
||||||
@ -75,6 +78,17 @@ class InvitationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroupsByOrganization(organizationName) {
|
||||||
|
GroupBackend.getGroups(organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
groups: res.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
parseInvitationField(key, value) {
|
parseInvitationField(key, value) {
|
||||||
if ([""].includes(key)) {
|
if ([""].includes(key)) {
|
||||||
value = Setting.myParseInt(value);
|
value = Setting.myParseInt(value);
|
||||||
@ -120,7 +134,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value); this.getApplicationsByOrganization(value);})}>
|
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value); this.getApplicationsByOrganization(value);this.getGroupsByOrganization(value);})}>
|
||||||
{
|
{
|
||||||
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
|
||||||
}
|
}
|
||||||
@ -204,6 +218,21 @@ class InvitationEditPage extends React.Component {
|
|||||||
]} />
|
]} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Signup group"), i18next.t("provider:Signup group - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.invitation.signupGroup} onChange={(value => {this.updateInvitationField("signupGroup", value);})}>
|
||||||
|
<Option key={""} value={""}>
|
||||||
|
{i18next.t("general:Default")}
|
||||||
|
</Option>
|
||||||
|
{
|
||||||
|
this.state.groups.map((group, index) => <Option key={index} value={`${group.owner}/${group.name}`}>{group.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
|
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
|
||||||
|
@ -68,9 +68,11 @@ class InvitationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -13,12 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Row, Select, Space, Switch} from "antd";
|
||||||
import {EyeInvisibleOutlined, EyeTwoTone} from "@ant-design/icons";
|
import {EyeInvisibleOutlined, EyeTwoTone, HolderOutlined, UsergroupAddOutlined} from "@ant-design/icons";
|
||||||
import * as LddpBackend from "./backend/LdapBackend";
|
import * as LddpBackend from "./backend/LdapBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import * as GroupBackend from "./backend/GroupBackend";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -30,12 +31,14 @@ class LdapEditPage extends React.Component {
|
|||||||
organizationName: props.match.params.organizationName,
|
organizationName: props.match.params.organizationName,
|
||||||
ldap: null,
|
ldap: null,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
|
groups: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getLdap();
|
this.getLdap();
|
||||||
this.getOrganizations();
|
this.getOrganizations();
|
||||||
|
this.getGroups();
|
||||||
}
|
}
|
||||||
|
|
||||||
getLdap() {
|
getLdap() {
|
||||||
@ -60,6 +63,17 @@ class LdapEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getGroups() {
|
||||||
|
GroupBackend.getGroups(this.state.organizationName)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
groups: res.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateLdapField(key, value) {
|
updateLdapField(key, value) {
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
prevState.ldap[key] = value;
|
prevState.ldap[key] = value;
|
||||||
@ -214,6 +228,31 @@ class LdapEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
|
{Setting.getLabel(i18next.t("ldap:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.ldap.defaultGroup ?? []} onChange={(value => {
|
||||||
|
this.updateLdapField("defaultGroup", value);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Option key={""} value={""}>
|
||||||
|
<Space>
|
||||||
|
{i18next.t("general:Default")}
|
||||||
|
</Space>
|
||||||
|
</Option>
|
||||||
|
{
|
||||||
|
this.state.groups?.map((group) => <Option key={group.name} value={`${group.owner}/${group.name}`}>
|
||||||
|
<Space>
|
||||||
|
{group.type === "Physical" ? <UsergroupAddOutlined /> : <HolderOutlined />}
|
||||||
|
{group.displayName}
|
||||||
|
</Space>
|
||||||
|
</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}}>
|
<Row style={{marginTop: "20px"}}>
|
||||||
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
<Col style={{lineHeight: "32px", textAlign: "right", paddingRight: "25px"}} span={3}>
|
||||||
{Setting.getLabel(i18next.t("ldap:Auto Sync"), i18next.t("ldap:Auto Sync - Tooltip"))} :
|
{Setting.getLabel(i18next.t("ldap:Auto Sync"), i18next.t("ldap:Auto Sync - Tooltip"))} :
|
||||||
|
@ -18,11 +18,7 @@ import * as ModelBackend from "./backend/ModelBackend";
|
|||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import ModelEditor from "./CasbinEditor";
|
||||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
|
||||||
import "codemirror/lib/codemirror.css";
|
|
||||||
|
|
||||||
require("codemirror/mode/properties/properties");
|
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -147,16 +143,10 @@ class ModelEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
{Setting.getLabel(i18next.t("model:Model text"), i18next.t("model:Model text - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22}>
|
<Col span={22}>
|
||||||
<div style={{width: "100%"}} >
|
<div style={{position: "relative", height: "500px"}} >
|
||||||
<CodeMirror
|
<ModelEditor
|
||||||
value={this.state.model.modelText}
|
model={this.state.model}
|
||||||
options={{mode: "properties", theme: "default"}}
|
onModelTextChange={(value) => this.updateModelField("modelText", value)}
|
||||||
onBeforeChange={(editor, data, value) => {
|
|
||||||
if (Setting.builtInObject(this.state.model)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.updateModelField("modelText", value);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -72,9 +72,11 @@ class ModelListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -19,6 +19,7 @@ import * as ApplicationBackend from "./backend/ApplicationBackend";
|
|||||||
import * as LdapBackend from "./backend/LdapBackend";
|
import * as LdapBackend from "./backend/LdapBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import * as Conf from "./Conf";
|
import * as Conf from "./Conf";
|
||||||
|
import * as Obfuscator from "./auth/Obfuscator";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import {LinkOutlined} from "@ant-design/icons";
|
import {LinkOutlined} from "@ant-design/icons";
|
||||||
import LdapTable from "./table/LdapTable";
|
import LdapTable from "./table/LdapTable";
|
||||||
@ -112,6 +113,22 @@ class OrganizationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePasswordObfuscator(key, value) {
|
||||||
|
const organization = this.state.organization;
|
||||||
|
if (organization.passwordObfuscatorType === "") {
|
||||||
|
organization.passwordObfuscatorType = "Plain";
|
||||||
|
}
|
||||||
|
if (key === "type") {
|
||||||
|
organization.passwordObfuscatorType = value;
|
||||||
|
organization.passwordObfuscatorKey = Obfuscator.getRandomKeyForObfuscator(value);
|
||||||
|
} else if (key === "key") {
|
||||||
|
organization.passwordObfuscatorKey = value;
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
organization: organization,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderOrganization() {
|
renderOrganization() {
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
@ -294,6 +311,34 @@ class OrganizationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Password obfuscator"), i18next.t("general:Password obfuscator - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} style={{width: "100%"}}
|
||||||
|
value={this.state.organization.passwordObfuscatorType}
|
||||||
|
onChange={(value => {this.updatePasswordObfuscator("type", value);})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: "Plain", name: "Plain"},
|
||||||
|
{id: "AES", name: "AES"},
|
||||||
|
{id: "DES", name: "DES"},
|
||||||
|
].map((obfuscatorType, index) => <Option key={index} value={obfuscatorType.id}>{obfuscatorType.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{
|
||||||
|
(this.state.organization.passwordObfuscatorType === "Plain" || this.state.organization.passwordObfuscatorType === "") ? null : (<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Password obf key"), i18next.t("general:Password obf key - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.organization.passwordObfuscatorKey} onChange={(e) => {this.updatePasswordObfuscator("key", e.target.value);}} />
|
||||||
|
</Col>
|
||||||
|
</Row>)
|
||||||
|
}
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Supported country codes"), i18next.t("general:Supported country codes - Tooltip"))} :
|
||||||
@ -305,6 +350,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
filterOption={(input, option) => (option?.text ?? "").toLowerCase().includes(input.toLowerCase())}
|
||||||
>
|
>
|
||||||
|
{Setting.getCountryCodeOption({name: i18next.t("organization:All"), code: "All", phone: 0})}
|
||||||
{
|
{
|
||||||
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
|
Setting.getCountryCodeData().map((country) => Setting.getCountryCodeOption(country))
|
||||||
}
|
}
|
||||||
@ -360,7 +406,7 @@ class OrganizationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.defaultApplication} onChange={(value => {this.updateOrganizationField("defaultApplication", value);})}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.organization.defaultApplication} onChange={(value => {this.updateOrganizationField("defaultApplication", value);})}
|
||||||
options={this.state.applications?.map((item) => Setting.getOption(item.name, item.name))
|
options={this.state.applications?.map((item) => Setting.getOption(Setting.getApplicationDisplayName(item.name), item.name))
|
||||||
} />
|
} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@ -528,6 +574,12 @@ class OrganizationEditPage extends React.Component {
|
|||||||
const organization = Setting.deepCopy(this.state.organization);
|
const organization = Setting.deepCopy(this.state.organization);
|
||||||
organization.accountItems = organization.accountItems?.filter(accountItem => accountItem.name !== "Please select an account item");
|
organization.accountItems = organization.accountItems?.filter(accountItem => accountItem.name !== "Please select an account item");
|
||||||
|
|
||||||
|
const passwordObfuscatorErrorMessage = Obfuscator.checkPasswordObfuscator(organization.passwordObfuscatorType, organization.passwordObfuscatorKey);
|
||||||
|
if (passwordObfuscatorErrorMessage.length > 0) {
|
||||||
|
Setting.showMessage("error", passwordObfuscatorErrorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
|
OrganizationBackend.updateOrganization(this.state.organization.owner, this.state.organizationName, organization)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
|
@ -35,6 +35,8 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
passwordType: "plain",
|
passwordType: "plain",
|
||||||
PasswordSalt: "",
|
PasswordSalt: "",
|
||||||
passwordOptions: [],
|
passwordOptions: [],
|
||||||
|
passwordObfuscatorType: "Plain",
|
||||||
|
passwordObfuscatorKey: "",
|
||||||
countryCodes: ["US"],
|
countryCodes: ["US"],
|
||||||
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
defaultAvatar: `${Setting.StaticBaseUrl}/img/casbin.svg`,
|
||||||
defaultApplication: "",
|
defaultApplication: "",
|
||||||
@ -115,11 +117,11 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
|
||||||
pagination: {
|
pagination: {
|
||||||
...this.state.pagination,
|
...this.state.pagination,
|
||||||
total: this.state.pagination.total - 1},
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
window.dispatchEvent(new Event("storageOrganizationsChanged"));
|
window.dispatchEvent(new Event("storageOrganizationsChanged"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -70,9 +70,11 @@ class PaymentListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -69,9 +69,11 @@ class PermissionListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -63,9 +63,11 @@ class PlanListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -59,9 +59,11 @@ class PricingListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -65,9 +65,11 @@ class ProductListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -843,7 +843,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
|
this.state.provider.type !== "ADFS" && this.state.provider.type !== "AzureAD" && this.state.provider.type !== "AzureADB2C" && (this.state.provider.type !== "Casdoor" && this.state.category !== "Storage") && this.state.provider.type !== "Okta" ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@ -870,7 +870,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||||
@ -885,7 +885,9 @@ class ProviderEditPage extends React.Component {
|
|||||||
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
|
Setting.getLabel(i18next.t("general:Provider"), i18next.t("provider:Provider - Tooltip"))
|
||||||
|
: Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.bucket} onChange={e => {
|
<Input value={this.state.provider.bucket} onChange={e => {
|
||||||
@ -906,7 +908,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Qiniu Cloud Kodo", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@ -918,10 +920,24 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? (
|
{["Casdoor"].includes(this.state.provider.type) ? (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.provider.content} onChange={e => {
|
||||||
|
this.updateProviderField("content", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null}
|
||||||
|
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor"].includes(this.state.provider.type) ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
|
Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip")) :
|
||||||
|
Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.provider.regionId} onChange={e => {
|
<Input value={this.state.provider.regionId} onChange={e => {
|
||||||
@ -1298,7 +1314,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
(this.state.provider.type === "Alipay" || this.state.provider.type === "WeChat Pay") ? (
|
(this.state.provider.type === "Alipay" || this.state.provider.type === "WeChat Pay" || this.state.provider.type === "Casdoor") ? (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Cert"), i18next.t("general:Cert - Tooltip"))} :
|
||||||
|
@ -76,9 +76,11 @@ class ProviderListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -40,9 +40,11 @@ class ResourceListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -61,9 +61,11 @@ class RoleListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -27,9 +27,11 @@ class SessionListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -229,6 +229,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_synology.png`,
|
logo: `${StaticBaseUrl}/img/social_synology.png`,
|
||||||
url: "https://www.synology.com/en-global/dsm/feature/file_sharing",
|
url: "https://www.synology.com/en-global/dsm/feature/file_sharing",
|
||||||
},
|
},
|
||||||
|
"Casdoor": {
|
||||||
|
logo: `${StaticBaseUrl}/img/casdoor.png`,
|
||||||
|
url: "https://casdoor.org/docs/provider/storage/overview",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
SAML: {
|
SAML: {
|
||||||
"Aliyun IDaaS": {
|
"Aliyun IDaaS": {
|
||||||
@ -283,6 +287,14 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
url: "https://www.google.com/recaptcha",
|
url: "https://www.google.com/recaptcha",
|
||||||
},
|
},
|
||||||
|
"reCAPTCHA v2": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
|
"reCAPTCHA v3": {
|
||||||
|
logo: `${StaticBaseUrl}/img/social_recaptcha.png`,
|
||||||
|
url: "https://www.google.com/recaptcha",
|
||||||
|
},
|
||||||
"hCaptcha": {
|
"hCaptcha": {
|
||||||
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
logo: `${StaticBaseUrl}/img/social_hcaptcha.png`,
|
||||||
url: "https://www.hcaptcha.com",
|
url: "https://www.hcaptcha.com",
|
||||||
@ -406,6 +418,9 @@ export function getCountryCode(country) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {
|
export function getCountryCodeData(countryCodes = phoneNumber.getCountries()) {
|
||||||
|
if (countryCodes?.includes("All")) {
|
||||||
|
countryCodes = phoneNumber.getCountries();
|
||||||
|
}
|
||||||
return countryCodes?.map((countryCode) => {
|
return countryCodes?.map((countryCode) => {
|
||||||
if (phoneNumber.isSupportedCountry(countryCode)) {
|
if (phoneNumber.isSupportedCountry(countryCode)) {
|
||||||
const name = initCountries().getName(countryCode, getLanguage());
|
const name = initCountries().getName(countryCode, getLanguage());
|
||||||
@ -424,10 +439,10 @@ export function getCountryCodeOption(country) {
|
|||||||
<Option key={country.code} value={country.code} label={`+${country.phone}`} text={`${country.name}, ${country.code}, ${country.phone}`} >
|
<Option key={country.code} value={country.code} label={`+${country.phone}`} text={`${country.name}, ${country.code}, ${country.phone}`} >
|
||||||
<div style={{display: "flex", justifyContent: "space-between", marginRight: "10px"}}>
|
<div style={{display: "flex", justifyContent: "space-between", marginRight: "10px"}}>
|
||||||
<div>
|
<div>
|
||||||
{getCountryImage(country)}
|
{country.code === "All" ? null : getCountryImage(country)}
|
||||||
{`${country.name}`}
|
{`${country.name}`}
|
||||||
</div>
|
</div>
|
||||||
{`+${country.phone}`}
|
{country.code === "All" ? null : `+${country.phone}`}
|
||||||
</div>
|
</div>
|
||||||
</Option>
|
</Option>
|
||||||
);
|
);
|
||||||
@ -1062,6 +1077,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Qiniu Cloud Kodo", name: "Qiniu Cloud Kodo"},
|
{id: "Qiniu Cloud Kodo", name: "Qiniu Cloud Kodo"},
|
||||||
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
|
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
|
||||||
{id: "Synology", name: "Synology"},
|
{id: "Synology", name: "Synology"},
|
||||||
|
{id: "Casdoor", name: "Casdoor"},
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else if (category === "SAML") {
|
} else if (category === "SAML") {
|
||||||
@ -1083,7 +1099,8 @@ export function getProviderTypeOptions(category) {
|
|||||||
} else if (category === "Captcha") {
|
} else if (category === "Captcha") {
|
||||||
return ([
|
return ([
|
||||||
{id: "Default", name: "Default"},
|
{id: "Default", name: "Default"},
|
||||||
{id: "reCAPTCHA", name: "reCAPTCHA"},
|
{id: "reCAPTCHA v2", name: "reCAPTCHA v2"},
|
||||||
|
{id: "reCAPTCHA v3", name: "reCAPTCHA v3"},
|
||||||
{id: "hCaptcha", name: "hCaptcha"},
|
{id: "hCaptcha", name: "hCaptcha"},
|
||||||
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
|
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
|
||||||
{id: "GEETEST", name: "GEETEST"},
|
{id: "GEETEST", name: "GEETEST"},
|
||||||
@ -1154,7 +1171,7 @@ export function renderLogo(application) {
|
|||||||
|
|
||||||
function isSigninMethodEnabled(application, signinMethod) {
|
function isSigninMethodEnabled(application, signinMethod) {
|
||||||
if (application && application.signinMethods) {
|
if (application && application.signinMethods) {
|
||||||
return application.signinMethods.filter(item => item.name === signinMethod).length > 0;
|
return application.signinMethods.filter(item => item.name === signinMethod && item.rule !== "Hide-Password").length > 0;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1371,6 +1388,13 @@ export function getApplicationName(application) {
|
|||||||
return `${application?.owner}/${application?.name}`;
|
return `${application?.owner}/${application?.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getApplicationDisplayName(application) {
|
||||||
|
if (application.isShared) {
|
||||||
|
return `${application.name}(Shared)`;
|
||||||
|
}
|
||||||
|
return application.name;
|
||||||
|
}
|
||||||
|
|
||||||
export function getRandomName() {
|
export function getRandomName() {
|
||||||
return Math.random().toString(36).slice(-6);
|
return Math.random().toString(36).slice(-6);
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,11 @@ class SubscriptionListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -434,10 +434,9 @@ class SyncerEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Input value={this.state.syncer.table}
|
<Input value={this.state.syncer.table} onChange={e => {
|
||||||
disabled={this.state.syncer.type === "Keycloak"} onChange={e => {
|
this.updateSyncerField("table", e.target.value);
|
||||||
this.updateSyncerField("table", e.target.value);
|
}} />
|
||||||
}} />
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
@ -69,9 +69,11 @@ class SyncerListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -61,9 +61,11 @@ class TokenListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -125,7 +125,7 @@ class TransactionEditPage extends React.Component {
|
|||||||
application: application,
|
application: application,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getCerts(application.organization);
|
this.getCerts(application);
|
||||||
|
|
||||||
this.getSamlMetadata(application.enableSamlPostBinding);
|
this.getSamlMetadata(application.enableSamlPostBinding);
|
||||||
});
|
});
|
||||||
|
@ -54,9 +54,11 @@ class TransactionListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -1050,6 +1050,8 @@ class UserEditPage extends React.Component {
|
|||||||
<MfaAccountTable
|
<MfaAccountTable
|
||||||
title={i18next.t("user:MFA accounts")}
|
title={i18next.t("user:MFA accounts")}
|
||||||
table={this.state.user.mfaAccounts}
|
table={this.state.user.mfaAccounts}
|
||||||
|
accessToken={this.props.account?.accessToken}
|
||||||
|
icon={this.state.user.avatar}
|
||||||
onUpdateTable={(table) => {this.updateUserField("mfaAccounts", table);}}
|
onUpdateTable={(table) => {this.updateUserField("mfaAccounts", table);}}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -110,9 +110,11 @@ class UserListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -61,9 +61,11 @@ class WebhookListPage extends BaseListPage {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
|
||||||
this.setState({
|
this.fetch({
|
||||||
data: Setting.deleteRow(this.state.data, i),
|
pagination: {
|
||||||
pagination: {total: this.state.pagination.total - 1},
|
...this.state.pagination,
|
||||||
|
current: this.state.pagination.current > 1 && this.state.data.length === 1 ? this.state.pagination.current - 1 : this.state.pagination.current,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
|
||||||
|
@ -34,25 +34,42 @@ class CasLogout extends React.Component {
|
|||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
const params = new URLSearchParams(this.props.location.search);
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
|
const logoutInterval = 100;
|
||||||
|
|
||||||
|
const logoutTimeOut = (redirectUri) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
AuthBackend.getAccount().then((accountRes) => {
|
||||||
|
if (accountRes.status === "ok") {
|
||||||
|
AuthBackend.logout().then((logoutRes) => {
|
||||||
|
if (logoutRes.status === "ok") {
|
||||||
|
logoutTimeOut(logoutRes.data2);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", `${i18next.t("login:Failed to log out")}: ${logoutRes.msg}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("success", i18next.t("application:Logged out successfully"));
|
||||||
|
this.props.onUpdateAccount(null);
|
||||||
|
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
||||||
|
Setting.goToLink(redirectUri);
|
||||||
|
} else if (params.has("service")) {
|
||||||
|
Setting.goToLink(params.get("service"));
|
||||||
|
} else {
|
||||||
|
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, logoutInterval);
|
||||||
|
};
|
||||||
|
|
||||||
AuthBackend.logout()
|
AuthBackend.logout()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", "Logged out successfully");
|
logoutTimeOut(res.data2);
|
||||||
this.props.onUpdateAccount(null);
|
|
||||||
const redirectUri = res.data2;
|
|
||||||
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
|
|
||||||
Setting.goToLink(redirectUri);
|
|
||||||
} else if (params.has("service")) {
|
|
||||||
Setting.goToLink(params.get("service"));
|
|
||||||
} else {
|
|
||||||
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("login:Failed to log out")}: ${res.msg}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -19,6 +19,7 @@ import {withRouter} from "react-router-dom";
|
|||||||
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
|
import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
|
||||||
import OrganizationSelect from "../common/select/OrganizationSelect";
|
import OrganizationSelect from "../common/select/OrganizationSelect";
|
||||||
import * as Conf from "../Conf";
|
import * as Conf from "../Conf";
|
||||||
|
import * as Obfuscator from "./Obfuscator";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import * as OrganizationBackend from "../backend/OrganizationBackend";
|
import * as OrganizationBackend from "../backend/OrganizationBackend";
|
||||||
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
import * as ApplicationBackend from "../backend/ApplicationBackend";
|
||||||
@ -379,6 +380,14 @@ class LoginPage extends React.Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
|
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
|
||||||
|
const organization = this.getApplicationObj()?.organizationObj;
|
||||||
|
const [passwordCipher, errorMessage] = Obfuscator.encryptByPasswordObfuscator(organization?.passwordObfuscatorType, organization?.passwordObfuscatorKey, values["password"]);
|
||||||
|
if (errorMessage.length > 0) {
|
||||||
|
Setting.showMessage("error", errorMessage);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
values["password"] = passwordCipher;
|
||||||
|
}
|
||||||
if (this.state.enableCaptchaModal === CaptchaRule.Always) {
|
if (this.state.enableCaptchaModal === CaptchaRule.Always) {
|
||||||
this.setState({
|
this.setState({
|
||||||
openCaptchaModal: true,
|
openCaptchaModal: true,
|
||||||
@ -938,7 +947,7 @@ class LoginPage extends React.Component {
|
|||||||
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
|
signinItem.label ? Setting.renderSignupLink(application, signinItem.label) :
|
||||||
(
|
(
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{i18next.t("login:No account?")}
|
{i18next.t("login:No account?")}
|
||||||
{
|
{
|
||||||
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
Setting.renderSignupLink(application, i18next.t("login:sign up now"))
|
||||||
}
|
}
|
||||||
@ -1125,6 +1134,9 @@ class LoginPage extends React.Component {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
application?.signinMethods?.forEach((signinMethod) => {
|
application?.signinMethods?.forEach((signinMethod) => {
|
||||||
|
if (signinMethod.rule === "Hide-Password") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule));
|
const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule));
|
||||||
if (item) {
|
if (item) {
|
||||||
let label = signinMethod.name === signinMethod.displayName ? item.label : signinMethod.displayName;
|
let label = signinMethod.name === signinMethod.displayName ? item.label : signinMethod.displayName;
|
||||||
|
95
web/src/auth/Obfuscator.js
Normal file
95
web/src/auth/Obfuscator.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2024 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 CryptoJS from "crypto-js";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
export function getRandomKeyForObfuscator(obfuscatorType) {
|
||||||
|
if (obfuscatorType === "DES") {
|
||||||
|
return getRandomHexKey(16);
|
||||||
|
} else if (obfuscatorType === "AES") {
|
||||||
|
return getRandomHexKey(32);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const passwordObfuscatorKeyRegexes = {
|
||||||
|
"DES": /^[1-9a-f]{16}$/,
|
||||||
|
"AES": /^[1-9a-f]{32}$/,
|
||||||
|
};
|
||||||
|
|
||||||
|
function encrypt(cipher, key, iv, password) {
|
||||||
|
const encrypted = cipher.encrypt(
|
||||||
|
CryptoJS.enc.Hex.parse(Buffer.from(password, "utf-8").toString("hex")),
|
||||||
|
CryptoJS.enc.Hex.parse(key),
|
||||||
|
{
|
||||||
|
iv: iv,
|
||||||
|
mode: CryptoJS.mode.CBC,
|
||||||
|
pad: CryptoJS.pad.Pkcs7,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return iv.concat(encrypted.ciphertext).toString(CryptoJS.enc.Hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey) {
|
||||||
|
if (passwordObfuscatorType === undefined) {
|
||||||
|
return i18next.t("organization:failed to get password obfuscator");
|
||||||
|
} else if (passwordObfuscatorType === "Plain" || passwordObfuscatorType === "") {
|
||||||
|
return "";
|
||||||
|
} else if (passwordObfuscatorType === "AES" || passwordObfuscatorType === "DES") {
|
||||||
|
if (passwordObfuscatorKeyRegexes[passwordObfuscatorType].test(passwordObfuscatorKey)) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
return `${i18next.t("organization:The password obfuscator key doesn't match the regex")}: ${passwordObfuscatorKeyRegexes[passwordObfuscatorType].source}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return `${i18next.t("organization:unsupported password obfuscator type")}: ${passwordObfuscatorType}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encryptByPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey, password) {
|
||||||
|
const passwordObfuscatorErrorMessage = checkPasswordObfuscator(passwordObfuscatorType, passwordObfuscatorKey);
|
||||||
|
if (passwordObfuscatorErrorMessage.length > 0) {
|
||||||
|
return ["", passwordObfuscatorErrorMessage];
|
||||||
|
} else {
|
||||||
|
if (passwordObfuscatorType === "Plain" || passwordObfuscatorType === "") {
|
||||||
|
return [password, ""];
|
||||||
|
} else if (passwordObfuscatorType === "AES") {
|
||||||
|
return [encryptByAes(passwordObfuscatorKey, password), ""];
|
||||||
|
} else if (passwordObfuscatorType === "DES") {
|
||||||
|
return [encryptByDes(passwordObfuscatorKey, password), ""];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function encryptByDes(key, password) {
|
||||||
|
const iv = CryptoJS.lib.WordArray.random(8);
|
||||||
|
return encrypt(CryptoJS.DES, key, iv, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encryptByAes(key, password) {
|
||||||
|
const iv = CryptoJS.lib.WordArray.random(16);
|
||||||
|
return encrypt(CryptoJS.AES, key, iv, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomHexKey(length) {
|
||||||
|
const characters = "123456789abcdef";
|
||||||
|
let key = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||||
|
key += characters[randomIndex];
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Form, Input, Radio, Result, Row, message} from "antd";
|
import {Button, Form, Input, Radio, Result, Row, Select, message} from "antd";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import * as ProviderButton from "./ProviderButton";
|
import * as ProviderButton from "./ProviderButton";
|
||||||
@ -50,6 +50,38 @@ const formItemLayout = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderFormItem = (signupItem) => {
|
||||||
|
const commonProps = {
|
||||||
|
name: signupItem.name.toLowerCase(),
|
||||||
|
label: signupItem.label || signupItem.name,
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: signupItem.required,
|
||||||
|
message: i18next.t(`signup:Please input your ${signupItem.label || signupItem.name}!`),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!signupItem.type || signupItem.type === "Input") {
|
||||||
|
return (
|
||||||
|
<Form.Item {...commonProps}>
|
||||||
|
<Input placeholder={signupItem.placeholder} />
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
} else if (signupItem.type === "Single Choice" || signupItem.type === "Multiple Choices") {
|
||||||
|
return (
|
||||||
|
<Form.Item {...commonProps}>
|
||||||
|
<Select
|
||||||
|
mode={signupItem.type === "Multiple Choices" ? "multiple" : "single"}
|
||||||
|
placeholder={signupItem.placeholder}
|
||||||
|
showSearch={false}
|
||||||
|
options={signupItem.options.map(option => ({label: option, value: option}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const tailFormItemLayout = {
|
export const tailFormItemLayout = {
|
||||||
wrapperCol: {
|
wrapperCol: {
|
||||||
xs: {
|
xs: {
|
||||||
@ -198,6 +230,22 @@ class SignupPage extends React.Component {
|
|||||||
onFinish(values) {
|
onFinish(values) {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
|
|
||||||
|
if (Array.isArray(values.gender)) {
|
||||||
|
values.gender = values.gender.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(values.bio)) {
|
||||||
|
values.bio = values.bio.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(values.tag)) {
|
||||||
|
values.tag = values.tag.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(values.education)) {
|
||||||
|
values.education = values.education.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
values.plan = params.get("plan");
|
values.plan = params.get("plan");
|
||||||
values.pricing = params.get("pricing");
|
values.pricing = params.get("pricing");
|
||||||
@ -238,6 +286,7 @@ class SignupPage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderFormItem(application, signupItem) {
|
renderFormItem(application, signupItem) {
|
||||||
|
const validItems = ["Gender", "Bio", "Tag", "Education"];
|
||||||
if (!signupItem.visible) {
|
if (!signupItem.visible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -366,7 +415,9 @@ class SignupPage extends React.Component {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<RegionSelect className="signup-region-select" onChange={(value) => {this.setState({region: value});}} />
|
<RegionSelect className="signup-region-select" onChange={(value) => {
|
||||||
|
this.setState({region: value});
|
||||||
|
}} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
} else if (signupItem.name === "Email" || signupItem.name === "Phone" || signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
|
} else if (signupItem.name === "Email" || signupItem.name === "Phone" || signupItem.name === "Email or Phone" || signupItem.name === "Phone or Email") {
|
||||||
@ -669,8 +720,9 @@ class SignupPage extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
||||||
);
|
);
|
||||||
|
} else if (validItems.includes(signupItem.name)) {
|
||||||
|
return renderFormItem(signupItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,8 @@ export const CaptchaWidget = (props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
switch (captchaType) {
|
switch (captchaType) {
|
||||||
case "reCAPTCHA": {
|
case "reCAPTCHA" :
|
||||||
|
case "reCAPTCHA v2": {
|
||||||
const reTimer = setInterval(() => {
|
const reTimer = setInterval(() => {
|
||||||
if (!window.grecaptcha) {
|
if (!window.grecaptcha) {
|
||||||
loadScript("https://recaptcha.net/recaptcha/api.js");
|
loadScript("https://recaptcha.net/recaptcha/api.js");
|
||||||
@ -42,6 +43,32 @@ export const CaptchaWidget = (props) => {
|
|||||||
}, 300);
|
}, 300);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "reCAPTCHA v3": {
|
||||||
|
const reTimer = setInterval(() => {
|
||||||
|
if (!window.grecaptcha) {
|
||||||
|
loadScript(`https://recaptcha.net/recaptcha/api.js?render=${siteKey}`);
|
||||||
|
}
|
||||||
|
if (window.grecaptcha && window.grecaptcha.render) {
|
||||||
|
const clientId = window.grecaptcha.render("captcha", {
|
||||||
|
"sitekey": siteKey,
|
||||||
|
"badge": "inline",
|
||||||
|
"size": "invisible",
|
||||||
|
"callback": onChange,
|
||||||
|
"error-callback": function() {
|
||||||
|
const logoWidth = `${document.getElementById("captcha").offsetWidth + 40}px`;
|
||||||
|
document.getElementsByClassName("grecaptcha-logo")[0].firstChild.style.width = logoWidth;
|
||||||
|
document.getElementsByClassName("grecaptcha-badge")[0].style.width = logoWidth;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
window.grecaptcha.ready(function() {
|
||||||
|
window.grecaptcha.execute(clientId, {action: "submit"});
|
||||||
|
});
|
||||||
|
clearInterval(reTimer);
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "hCaptcha": {
|
case "hCaptcha": {
|
||||||
const hTimer = setInterval(() => {
|
const hTimer = setInterval(() => {
|
||||||
if (!window.hcaptcha) {
|
if (!window.hcaptcha) {
|
||||||
|
@ -115,7 +115,7 @@ export const CaptchaModal = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
<Row>
|
<Row justify={"center"}>
|
||||||
<CaptchaWidget
|
<CaptchaWidget
|
||||||
captchaType={captchaType}
|
captchaType={captchaType}
|
||||||
subType={subType}
|
subType={subType}
|
||||||
|
@ -51,3 +51,19 @@ code {
|
|||||||
.custom-link:hover {
|
.custom-link:hover {
|
||||||
color: rgb(64 64 64) !important;
|
color: rgb(64 64 64) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.full-height-editor {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-height-editor [class*="CodeMirror"] {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-horizontal-scroll-editor [class*="CodeMirror-hscrollbar"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-horizontal-scroll-editor [class*="CodeMirror-scroll"] {
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
@ -24,6 +24,12 @@ import * as serviceWorker from "./serviceWorker";
|
|||||||
import {BrowserRouter} from "react-router-dom";
|
import {BrowserRouter} from "react-router-dom";
|
||||||
import "./backend/FetchFilter";
|
import "./backend/FetchFilter";
|
||||||
|
|
||||||
|
if (!String.prototype.replaceAll) {
|
||||||
|
String.prototype.replaceAll = function(search, replace) {
|
||||||
|
return this.split(search).join(replace);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const container = document.getElementById("root");
|
const container = document.getElementById("root");
|
||||||
|
|
||||||
const app = createRoot(container);
|
const app = createRoot(container);
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"Left": "Left",
|
"Left": "Left",
|
||||||
"Logged in successfully": "Logged in successfully",
|
"Logged in successfully": "Logged in successfully",
|
||||||
"Logged out successfully": "Logged out successfully",
|
"Logged out successfully": "Logged out successfully",
|
||||||
|
"Multiple Choices": "Multiple Choices",
|
||||||
"New Application": "New Application",
|
"New Application": "New Application",
|
||||||
"No verification": "No verification",
|
"No verification": "No verification",
|
||||||
"Normal": "Normal",
|
"Normal": "Normal",
|
||||||
@ -112,6 +113,7 @@
|
|||||||
"Signin session": "Signin session",
|
"Signin session": "Signin session",
|
||||||
"Signup items": "Signup items",
|
"Signup items": "Signup items",
|
||||||
"Signup items - Tooltip": "Items for users to fill in when registering new accounts",
|
"Signup items - Tooltip": "Items for users to fill in when registering new accounts",
|
||||||
|
"Single Choice": "Single Choice",
|
||||||
"Small icon": "Small icon",
|
"Small icon": "Small icon",
|
||||||
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
|
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
|
||||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
|
||||||
@ -121,6 +123,10 @@
|
|||||||
"Token fields - Tooltip": "Token fields - Tooltip",
|
"Token fields - Tooltip": "Token fields - Tooltip",
|
||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "The format of access token",
|
"Token format - Tooltip": "The format of access token",
|
||||||
|
"Token signing method": "Token signing method",
|
||||||
|
"Token signing method - Tooltip": "Signing method of JWT token, needs to be the same algorithm as the certificate",
|
||||||
|
"Use Email as NameID": "Use Email as NameID",
|
||||||
|
"Use Email as NameID - Tooltip": "Use Email as NameID - Tooltip",
|
||||||
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
|
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
@ -177,6 +183,7 @@
|
|||||||
"Access key - Tooltip": "Access key - Tooltip",
|
"Access key - Tooltip": "Access key - Tooltip",
|
||||||
"Access secret": "Access secret",
|
"Access secret": "Access secret",
|
||||||
"Access secret - Tooltip": "Access secret - Tooltip",
|
"Access secret - Tooltip": "Access secret - Tooltip",
|
||||||
|
"Access token is empty": "Access token is empty",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Table name of the policy store",
|
"Adapter - Tooltip": "Table name of the policy store",
|
||||||
@ -234,6 +241,8 @@
|
|||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"Enable dark logo": "Enable dark logo",
|
"Enable dark logo": "Enable dark logo",
|
||||||
"Enable dark logo - Tooltip": "Enable dark logo",
|
"Enable dark logo - Tooltip": "Enable dark logo",
|
||||||
|
"Enable tour": "Enable tour",
|
||||||
|
"Enable tour - Tooltip": "Display tour for users",
|
||||||
"Enabled": "Enabled",
|
"Enabled": "Enabled",
|
||||||
"Enabled successfully": "Enabled successfully",
|
"Enabled successfully": "Enabled successfully",
|
||||||
"Enforcers": "Enforcers",
|
"Enforcers": "Enforcers",
|
||||||
@ -265,6 +274,8 @@
|
|||||||
"Invitations": "Invitations",
|
"Invitations": "Invitations",
|
||||||
"Is enabled": "Is enabled",
|
"Is enabled": "Is enabled",
|
||||||
"Is enabled - Tooltip": "Set whether it can use",
|
"Is enabled - Tooltip": "Set whether it can use",
|
||||||
|
"Is shared": "Is shared",
|
||||||
|
"Is shared - Tooltip": "Share this application with other organizations",
|
||||||
"LDAPs": "LDAPs",
|
"LDAPs": "LDAPs",
|
||||||
"LDAPs - Tooltip": "LDAP servers",
|
"LDAPs - Tooltip": "LDAP servers",
|
||||||
"Languages": "Languages",
|
"Languages": "Languages",
|
||||||
@ -328,6 +339,8 @@
|
|||||||
"Provider - Tooltip": "Payment providers to be configured, including PayPal, Alipay, WeChat Pay, etc.",
|
"Provider - Tooltip": "Payment providers to be configured, including PayPal, Alipay, WeChat Pay, etc.",
|
||||||
"Providers": "Providers",
|
"Providers": "Providers",
|
||||||
"Providers - Tooltip": "Providers to be configured, including 3rd-party login, object storage, verification code, etc.",
|
"Providers - Tooltip": "Providers to be configured, including 3rd-party login, object storage, verification code, etc.",
|
||||||
|
"QR Code": "QR Code",
|
||||||
|
"QR code is too large": "QR code is too large",
|
||||||
"Real name": "Real name",
|
"Real name": "Real name",
|
||||||
"Records": "Records",
|
"Records": "Records",
|
||||||
"Request URI": "Request URI",
|
"Request URI": "Request URI",
|
||||||
@ -441,6 +454,8 @@
|
|||||||
"Base DN": "Base DN",
|
"Base DN": "Base DN",
|
||||||
"Base DN - Tooltip": "Base DN during LDAP search",
|
"Base DN - Tooltip": "Base DN during LDAP search",
|
||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
|
"Default group": "Default group",
|
||||||
|
"Default group - Tooltip": "Group to which users belong after synchronization",
|
||||||
"Edit LDAP": "Edit LDAP",
|
"Edit LDAP": "Edit LDAP",
|
||||||
"Enable SSL": "Enable SSL",
|
"Enable SSL": "Enable SSL",
|
||||||
"Enable SSL - Tooltip": "Whether to enable SSL",
|
"Enable SSL - Tooltip": "Whether to enable SSL",
|
||||||
@ -470,6 +485,7 @@
|
|||||||
"Face ID": "Face ID",
|
"Face ID": "Face ID",
|
||||||
"Face Recognition": "Face Recognition",
|
"Face Recognition": "Face Recognition",
|
||||||
"Face recognition failed": "Face recognition failed",
|
"Face recognition failed": "Face recognition failed",
|
||||||
|
"Failed to log out": "Failed to log out",
|
||||||
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
|
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
|
||||||
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
|
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
|
||||||
"Forgot password?": "Forgot password?",
|
"Forgot password?": "Forgot password?",
|
||||||
@ -551,6 +567,11 @@
|
|||||||
"Your phone is": "Your phone is",
|
"Your phone is": "Your phone is",
|
||||||
"preferred": "preferred"
|
"preferred": "preferred"
|
||||||
},
|
},
|
||||||
|
"mfaAccount": {
|
||||||
|
"Account Name": "Account Name",
|
||||||
|
"Issuer": "Issuer",
|
||||||
|
"Secret Key": "Secret Key"
|
||||||
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
@ -818,6 +839,7 @@
|
|||||||
"Project Id": "Project Id",
|
"Project Id": "Project Id",
|
||||||
"Project Id - Tooltip": "Project Id - Tooltip",
|
"Project Id - Tooltip": "Project Id - Tooltip",
|
||||||
"Prompted": "Prompted",
|
"Prompted": "Prompted",
|
||||||
|
"Provider - Tooltip": "Provider - Tooltip",
|
||||||
"Provider URL": "Provider URL",
|
"Provider URL": "Provider URL",
|
||||||
"Provider URL - Tooltip": "URL for configuring the service provider, this field is only used for reference and is not used in Casdoor",
|
"Provider URL - Tooltip": "URL for configuring the service provider, this field is only used for reference and is not used in Casdoor",
|
||||||
"Public key": "Public key",
|
"Public key": "Public key",
|
||||||
@ -937,6 +959,7 @@
|
|||||||
"Have account?": "Have account?",
|
"Have account?": "Have account?",
|
||||||
"Label": "Label",
|
"Label": "Label",
|
||||||
"Label HTML": "Label HTML",
|
"Label HTML": "Label HTML",
|
||||||
|
"Options": "Options",
|
||||||
"Placeholder": "Placeholder",
|
"Placeholder": "Placeholder",
|
||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
@ -1136,6 +1159,7 @@
|
|||||||
"Link": "Link",
|
"Link": "Link",
|
||||||
"Location": "Location",
|
"Location": "Location",
|
||||||
"Location - Tooltip": "City of residence",
|
"Location - Tooltip": "City of residence",
|
||||||
|
"MFA accounts": "MFA accounts",
|
||||||
"Managed accounts": "Managed accounts",
|
"Managed accounts": "Managed accounts",
|
||||||
"Modify password...": "Modify password...",
|
"Modify password...": "Modify password...",
|
||||||
"Multi-factor authentication": "Multi-factor authentication",
|
"Multi-factor authentication": "Multi-factor authentication",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"Left": "Vlevo",
|
"Left": "Vlevo",
|
||||||
"Logged in successfully": "Úspěšně přihlášen",
|
"Logged in successfully": "Úspěšně přihlášen",
|
||||||
"Logged out successfully": "Úspěšně odhlášen",
|
"Logged out successfully": "Úspěšně odhlášen",
|
||||||
|
"Multiple Choices": "Multiple Choices",
|
||||||
"New Application": "Nová aplikace",
|
"New Application": "Nová aplikace",
|
||||||
"No verification": "Bez ověření",
|
"No verification": "Bez ověření",
|
||||||
"Normal": "Normální",
|
"Normal": "Normální",
|
||||||
@ -112,6 +113,7 @@
|
|||||||
"Signin session": "Přihlašovací relace",
|
"Signin session": "Přihlašovací relace",
|
||||||
"Signup items": "Položky registrace",
|
"Signup items": "Položky registrace",
|
||||||
"Signup items - Tooltip": "Položky, které uživatelé vyplňují při registraci nových účtů",
|
"Signup items - Tooltip": "Položky, které uživatelé vyplňují při registraci nových účtů",
|
||||||
|
"Single Choice": "Single Choice",
|
||||||
"Small icon": "Malá ikona",
|
"Small icon": "Malá ikona",
|
||||||
"Tags - Tooltip": "Pouze uživatelé s tagem uvedeným v tazích aplikace se mohou přihlásit",
|
"Tags - Tooltip": "Pouze uživatelé s tagem uvedeným v tazích aplikace se mohou přihlásit",
|
||||||
"The application does not allow to sign up new account": "Aplikace neumožňuje registraci nového účtu",
|
"The application does not allow to sign up new account": "Aplikace neumožňuje registraci nového účtu",
|
||||||
@ -121,6 +123,10 @@
|
|||||||
"Token fields - Tooltip": "Uživatelská pole zahrnutá v tokenu",
|
"Token fields - Tooltip": "Uživatelská pole zahrnutá v tokenu",
|
||||||
"Token format": "Formát tokenu",
|
"Token format": "Formát tokenu",
|
||||||
"Token format - Tooltip": "Formát přístupového tokenu",
|
"Token format - Tooltip": "Formát přístupového tokenu",
|
||||||
|
"Token signing method": "Token signing method",
|
||||||
|
"Token signing method - Tooltip": "Signing method of JWT token, needs to be the same algorithm as the certificate",
|
||||||
|
"Use Email as NameID": "Use Email as NameID",
|
||||||
|
"Use Email as NameID - Tooltip": "Use Email as NameID - Tooltip",
|
||||||
"You are unexpected to see this prompt page": "Nečekali jste, že uvidíte tuto výzvu"
|
"You are unexpected to see this prompt page": "Nečekali jste, že uvidíte tuto výzvu"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
@ -177,6 +183,7 @@
|
|||||||
"Access key - Tooltip": "Přístupový klíč - Tooltip",
|
"Access key - Tooltip": "Přístupový klíč - Tooltip",
|
||||||
"Access secret": "Přístupové tajemství",
|
"Access secret": "Přístupové tajemství",
|
||||||
"Access secret - Tooltip": "Přístupové tajemství - Tooltip",
|
"Access secret - Tooltip": "Přístupové tajemství - Tooltip",
|
||||||
|
"Access token is empty": "Access token is empty",
|
||||||
"Action": "Akce",
|
"Action": "Akce",
|
||||||
"Adapter": "Adaptér",
|
"Adapter": "Adaptér",
|
||||||
"Adapter - Tooltip": "Název tabulky úložiště politiky",
|
"Adapter - Tooltip": "Název tabulky úložiště politiky",
|
||||||
@ -234,6 +241,8 @@
|
|||||||
"Enable": "Povolit",
|
"Enable": "Povolit",
|
||||||
"Enable dark logo": "Povolit tmavé logo",
|
"Enable dark logo": "Povolit tmavé logo",
|
||||||
"Enable dark logo - Tooltip": "Povolit tmavé logo",
|
"Enable dark logo - Tooltip": "Povolit tmavé logo",
|
||||||
|
"Enable tour": "Enable tour",
|
||||||
|
"Enable tour - Tooltip": "Display tour for users",
|
||||||
"Enabled": "Povoleno",
|
"Enabled": "Povoleno",
|
||||||
"Enabled successfully": "Úspěšně povoleno",
|
"Enabled successfully": "Úspěšně povoleno",
|
||||||
"Enforcers": "Enforcers",
|
"Enforcers": "Enforcers",
|
||||||
@ -265,6 +274,8 @@
|
|||||||
"Invitations": "Pozvánky",
|
"Invitations": "Pozvánky",
|
||||||
"Is enabled": "Je povoleno",
|
"Is enabled": "Je povoleno",
|
||||||
"Is enabled - Tooltip": "Nastavit, zda může být použito",
|
"Is enabled - Tooltip": "Nastavit, zda může být použito",
|
||||||
|
"Is shared": "Is shared",
|
||||||
|
"Is shared - Tooltip": "Share this application with other organizations",
|
||||||
"LDAPs": "LDAPy",
|
"LDAPs": "LDAPy",
|
||||||
"LDAPs - Tooltip": "LDAP servery",
|
"LDAPs - Tooltip": "LDAP servery",
|
||||||
"Languages": "Jazyky",
|
"Languages": "Jazyky",
|
||||||
@ -328,6 +339,8 @@
|
|||||||
"Provider - Tooltip": "Poskytovatelé plateb, které mají být nakonfigurovány, včetně PayPal, Alipay, WeChat Pay, atd.",
|
"Provider - Tooltip": "Poskytovatelé plateb, které mají být nakonfigurovány, včetně PayPal, Alipay, WeChat Pay, atd.",
|
||||||
"Providers": "Poskytovatelé",
|
"Providers": "Poskytovatelé",
|
||||||
"Providers - Tooltip": "Poskytovatelé, kteří mají být nakonfigurováni, včetně přihlášení třetích stran, objektového úložiště, ověřovacího kódu, atd.",
|
"Providers - Tooltip": "Poskytovatelé, kteří mají být nakonfigurováni, včetně přihlášení třetích stran, objektového úložiště, ověřovacího kódu, atd.",
|
||||||
|
"QR Code": "QR Code",
|
||||||
|
"QR code is too large": "QR code is too large",
|
||||||
"Real name": "Skutečné jméno",
|
"Real name": "Skutečné jméno",
|
||||||
"Records": "Záznamy",
|
"Records": "Záznamy",
|
||||||
"Request URI": "Požadavek URI",
|
"Request URI": "Požadavek URI",
|
||||||
@ -441,6 +454,8 @@
|
|||||||
"Base DN": "Základní DN",
|
"Base DN": "Základní DN",
|
||||||
"Base DN - Tooltip": "Základní DN při vyhledávání v LDAP",
|
"Base DN - Tooltip": "Základní DN při vyhledávání v LDAP",
|
||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
|
"Default group": "Default group",
|
||||||
|
"Default group - Tooltip": "Group to which users belong after synchronization",
|
||||||
"Edit LDAP": "Upravit LDAP",
|
"Edit LDAP": "Upravit LDAP",
|
||||||
"Enable SSL": "Povolit SSL",
|
"Enable SSL": "Povolit SSL",
|
||||||
"Enable SSL - Tooltip": "Zda povolit SSL",
|
"Enable SSL - Tooltip": "Zda povolit SSL",
|
||||||
@ -470,6 +485,7 @@
|
|||||||
"Face ID": "Face ID",
|
"Face ID": "Face ID",
|
||||||
"Face Recognition": "Rozpoznávání obličeje",
|
"Face Recognition": "Rozpoznávání obličeje",
|
||||||
"Face recognition failed": "Rozpoznávání obličeje selhalo",
|
"Face recognition failed": "Rozpoznávání obličeje selhalo",
|
||||||
|
"Failed to log out": "Failed to log out",
|
||||||
"Failed to obtain MetaMask authorization": "Nepodařilo se získat autorizaci MetaMask",
|
"Failed to obtain MetaMask authorization": "Nepodařilo se získat autorizaci MetaMask",
|
||||||
"Failed to obtain Web3-Onboard authorization": "Nepodařilo se získat autorizaci Web3-Onboard",
|
"Failed to obtain Web3-Onboard authorization": "Nepodařilo se získat autorizaci Web3-Onboard",
|
||||||
"Forgot password?": "Zapomněli jste heslo?",
|
"Forgot password?": "Zapomněli jste heslo?",
|
||||||
@ -521,7 +537,7 @@
|
|||||||
"Failed to initiate MFA": "Nepodařilo se zahájit MFA",
|
"Failed to initiate MFA": "Nepodařilo se zahájit MFA",
|
||||||
"Have problems?": "Máte problémy?",
|
"Have problems?": "Máte problémy?",
|
||||||
"Multi-factor authentication": "Vícefaktorové ověřování",
|
"Multi-factor authentication": "Vícefaktorové ověřování",
|
||||||
"Multi-factor authentication - Tooltip": "Dvoufaktorové ověřování - Tooltip",
|
"Multi-factor authentication - Tooltip ": "Dvoufaktorové ověřování - Tooltip",
|
||||||
"Multi-factor authentication description": "Popis dvoufaktorového ověřování",
|
"Multi-factor authentication description": "Popis dvoufaktorového ověřování",
|
||||||
"Multi-factor methods": "Metody dvoufaktorového ověřování",
|
"Multi-factor methods": "Metody dvoufaktorového ověřování",
|
||||||
"Multi-factor recover": "Obnovení dvoufaktorového ověřování",
|
"Multi-factor recover": "Obnovení dvoufaktorového ověřování",
|
||||||
@ -551,6 +567,11 @@
|
|||||||
"Your phone is": "Váš telefon je",
|
"Your phone is": "Váš telefon je",
|
||||||
"preferred": "preferované"
|
"preferred": "preferované"
|
||||||
},
|
},
|
||||||
|
"mfaAccount": {
|
||||||
|
"Account Name": "Account Name",
|
||||||
|
"Issuer": "Issuer",
|
||||||
|
"Secret Key": "Secret Key"
|
||||||
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"Edit Model": "Upravit model",
|
"Edit Model": "Upravit model",
|
||||||
"Model text": "Text modelu",
|
"Model text": "Text modelu",
|
||||||
@ -818,6 +839,7 @@
|
|||||||
"Project Id": "ID projektu",
|
"Project Id": "ID projektu",
|
||||||
"Project Id - Tooltip": "Nápověda k ID projektu",
|
"Project Id - Tooltip": "Nápověda k ID projektu",
|
||||||
"Prompted": "Vyzván",
|
"Prompted": "Vyzván",
|
||||||
|
"Provider - Tooltip": "Provider - Tooltip",
|
||||||
"Provider URL": "URL poskytovatele",
|
"Provider URL": "URL poskytovatele",
|
||||||
"Provider URL - Tooltip": "URL pro konfiguraci poskytovatele služby, toto pole je pouze pro referenci a není použito v Casdoor",
|
"Provider URL - Tooltip": "URL pro konfiguraci poskytovatele služby, toto pole je pouze pro referenci a není použito v Casdoor",
|
||||||
"Public key": "Veřejný klíč",
|
"Public key": "Veřejný klíč",
|
||||||
@ -937,6 +959,7 @@
|
|||||||
"Have account?": "Máte účet?",
|
"Have account?": "Máte účet?",
|
||||||
"Label": "Štítek",
|
"Label": "Štítek",
|
||||||
"Label HTML": "HTML štítek",
|
"Label HTML": "HTML štítek",
|
||||||
|
"Options": "Options",
|
||||||
"Placeholder": "Zástupný text",
|
"Placeholder": "Zástupný text",
|
||||||
"Please accept the agreement!": "Prosím přijměte smlouvu!",
|
"Please accept the agreement!": "Prosím přijměte smlouvu!",
|
||||||
"Please click the below button to sign in": "Prosím klikněte na tlačítko níže pro přihlášení",
|
"Please click the below button to sign in": "Prosím klikněte na tlačítko níže pro přihlášení",
|
||||||
@ -1136,6 +1159,7 @@
|
|||||||
"Link": "Odkaz",
|
"Link": "Odkaz",
|
||||||
"Location": "Místo",
|
"Location": "Místo",
|
||||||
"Location - Tooltip": "Město bydliště",
|
"Location - Tooltip": "Město bydliště",
|
||||||
|
"MFA accounts": "MFA accounts",
|
||||||
"Managed accounts": "Spravované účty",
|
"Managed accounts": "Spravované účty",
|
||||||
"Modify password...": "Změnit heslo...",
|
"Modify password...": "Změnit heslo...",
|
||||||
"Multi-factor authentication": "Vícefaktorové ověřování",
|
"Multi-factor authentication": "Vícefaktorové ověřování",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"Left": "Links",
|
"Left": "Links",
|
||||||
"Logged in successfully": "Erfolgreich eingeloggt",
|
"Logged in successfully": "Erfolgreich eingeloggt",
|
||||||
"Logged out successfully": "Erfolgreich ausgeloggt",
|
"Logged out successfully": "Erfolgreich ausgeloggt",
|
||||||
|
"Multiple Choices": "Multiple Choices",
|
||||||
"New Application": "Neue Anwendung",
|
"New Application": "Neue Anwendung",
|
||||||
"No verification": "No verification",
|
"No verification": "No verification",
|
||||||
"Normal": "Normal",
|
"Normal": "Normal",
|
||||||
@ -112,6 +113,7 @@
|
|||||||
"Signin session": "Anmeldesession",
|
"Signin session": "Anmeldesession",
|
||||||
"Signup items": "Registrierungs Items",
|
"Signup items": "Registrierungs Items",
|
||||||
"Signup items - Tooltip": "Items, die Benutzer ausfüllen müssen, wenn sie neue Konten registrieren",
|
"Signup items - Tooltip": "Items, die Benutzer ausfüllen müssen, wenn sie neue Konten registrieren",
|
||||||
|
"Single Choice": "Single Choice",
|
||||||
"Small icon": "Small icon",
|
"Small icon": "Small icon",
|
||||||
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
|
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
|
||||||
"The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto zu registrieren",
|
"The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto zu registrieren",
|
||||||
@ -121,6 +123,10 @@
|
|||||||
"Token fields - Tooltip": "Token fields - Tooltip",
|
"Token fields - Tooltip": "Token fields - Tooltip",
|
||||||
"Token format": "Token-Format",
|
"Token format": "Token-Format",
|
||||||
"Token format - Tooltip": "Das Format des Access-Tokens",
|
"Token format - Tooltip": "Das Format des Access-Tokens",
|
||||||
|
"Token signing method": "Token signing method",
|
||||||
|
"Token signing method - Tooltip": "Signing method of JWT token, needs to be the same algorithm as the certificate",
|
||||||
|
"Use Email as NameID": "Use Email as NameID",
|
||||||
|
"Use Email as NameID - Tooltip": "Use Email as NameID - Tooltip",
|
||||||
"You are unexpected to see this prompt page": "Sie sind unerwartet auf diese Aufforderungsseite gelangt"
|
"You are unexpected to see this prompt page": "Sie sind unerwartet auf diese Aufforderungsseite gelangt"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
@ -177,6 +183,7 @@
|
|||||||
"Access key - Tooltip": "Access key - Tooltip",
|
"Access key - Tooltip": "Access key - Tooltip",
|
||||||
"Access secret": "Access secret",
|
"Access secret": "Access secret",
|
||||||
"Access secret - Tooltip": "Access secret - Tooltip",
|
"Access secret - Tooltip": "Access secret - Tooltip",
|
||||||
|
"Access token is empty": "Access token is empty",
|
||||||
"Action": "Aktion",
|
"Action": "Aktion",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Tabellenname des Policy Stores",
|
"Adapter - Tooltip": "Tabellenname des Policy Stores",
|
||||||
@ -234,6 +241,8 @@
|
|||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"Enable dark logo": "Enable dark logo",
|
"Enable dark logo": "Enable dark logo",
|
||||||
"Enable dark logo - Tooltip": "Enable dark logo",
|
"Enable dark logo - Tooltip": "Enable dark logo",
|
||||||
|
"Enable tour": "Enable tour",
|
||||||
|
"Enable tour - Tooltip": "Display tour for users",
|
||||||
"Enabled": "Enabled",
|
"Enabled": "Enabled",
|
||||||
"Enabled successfully": "Enabled successfully",
|
"Enabled successfully": "Enabled successfully",
|
||||||
"Enforcers": "Enforcers",
|
"Enforcers": "Enforcers",
|
||||||
@ -265,6 +274,8 @@
|
|||||||
"Invitations": "Invitations",
|
"Invitations": "Invitations",
|
||||||
"Is enabled": "Ist aktiviert",
|
"Is enabled": "Ist aktiviert",
|
||||||
"Is enabled - Tooltip": "Festlegen, ob es verwendet werden kann",
|
"Is enabled - Tooltip": "Festlegen, ob es verwendet werden kann",
|
||||||
|
"Is shared": "Is shared",
|
||||||
|
"Is shared - Tooltip": "Share this application with other organizations",
|
||||||
"LDAPs": "LDAPs",
|
"LDAPs": "LDAPs",
|
||||||
"LDAPs - Tooltip": "LDAP-Server",
|
"LDAPs - Tooltip": "LDAP-Server",
|
||||||
"Languages": "Sprachen",
|
"Languages": "Sprachen",
|
||||||
@ -328,6 +339,8 @@
|
|||||||
"Provider - Tooltip": "Zahlungsprovider, die konfiguriert werden müssen, inkl. PayPal, Alipay, WeChat Pay usw.",
|
"Provider - Tooltip": "Zahlungsprovider, die konfiguriert werden müssen, inkl. PayPal, Alipay, WeChat Pay usw.",
|
||||||
"Providers": "Provider",
|
"Providers": "Provider",
|
||||||
"Providers - Tooltip": "Provider, die konfiguriert werden müssen, einschließlich Drittanbieter-Logins, Objektspeicherung, Verifizierungscode usw.",
|
"Providers - Tooltip": "Provider, die konfiguriert werden müssen, einschließlich Drittanbieter-Logins, Objektspeicherung, Verifizierungscode usw.",
|
||||||
|
"QR Code": "QR Code",
|
||||||
|
"QR code is too large": "QR code is too large",
|
||||||
"Real name": "Echter Name",
|
"Real name": "Echter Name",
|
||||||
"Records": "Datensätze",
|
"Records": "Datensätze",
|
||||||
"Request URI": "Request URI",
|
"Request URI": "Request URI",
|
||||||
@ -441,6 +454,8 @@
|
|||||||
"Base DN": "Basis-DN",
|
"Base DN": "Basis-DN",
|
||||||
"Base DN - Tooltip": "Basis-DN während der LDAP-Suche",
|
"Base DN - Tooltip": "Basis-DN während der LDAP-Suche",
|
||||||
"CN": "KN",
|
"CN": "KN",
|
||||||
|
"Default group": "Default group",
|
||||||
|
"Default group - Tooltip": "Group to which users belong after synchronization",
|
||||||
"Edit LDAP": "LDAP bearbeiten",
|
"Edit LDAP": "LDAP bearbeiten",
|
||||||
"Enable SSL": "Aktivieren Sie SSL",
|
"Enable SSL": "Aktivieren Sie SSL",
|
||||||
"Enable SSL - Tooltip": "Ob SSL aktiviert werden soll",
|
"Enable SSL - Tooltip": "Ob SSL aktiviert werden soll",
|
||||||
@ -470,6 +485,7 @@
|
|||||||
"Face ID": "Face ID",
|
"Face ID": "Face ID",
|
||||||
"Face Recognition": "Face Recognition",
|
"Face Recognition": "Face Recognition",
|
||||||
"Face recognition failed": "Face recognition failed",
|
"Face recognition failed": "Face recognition failed",
|
||||||
|
"Failed to log out": "Failed to log out",
|
||||||
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
|
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
|
||||||
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
|
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
|
||||||
"Forgot password?": "Passwort vergessen?",
|
"Forgot password?": "Passwort vergessen?",
|
||||||
@ -551,6 +567,11 @@
|
|||||||
"Your phone is": "Your phone is",
|
"Your phone is": "Your phone is",
|
||||||
"preferred": "preferred"
|
"preferred": "preferred"
|
||||||
},
|
},
|
||||||
|
"mfaAccount": {
|
||||||
|
"Account Name": "Account Name",
|
||||||
|
"Issuer": "Issuer",
|
||||||
|
"Secret Key": "Secret Key"
|
||||||
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"Edit Model": "Modell bearbeiten",
|
"Edit Model": "Modell bearbeiten",
|
||||||
"Model text": "Modelltext",
|
"Model text": "Modelltext",
|
||||||
@ -818,6 +839,7 @@
|
|||||||
"Project Id": "Project Id",
|
"Project Id": "Project Id",
|
||||||
"Project Id - Tooltip": "Project Id - Tooltip",
|
"Project Id - Tooltip": "Project Id - Tooltip",
|
||||||
"Prompted": "ausgelöst",
|
"Prompted": "ausgelöst",
|
||||||
|
"Provider - Tooltip": "Provider - Tooltip",
|
||||||
"Provider URL": "Anbieter-URL",
|
"Provider URL": "Anbieter-URL",
|
||||||
"Provider URL - Tooltip": "URL zur Konfiguration des Dienstanbieters, dieses Feld dient nur als Referenz und wird in Casdoor nicht verwendet",
|
"Provider URL - Tooltip": "URL zur Konfiguration des Dienstanbieters, dieses Feld dient nur als Referenz und wird in Casdoor nicht verwendet",
|
||||||
"Public key": "Public key",
|
"Public key": "Public key",
|
||||||
@ -937,6 +959,7 @@
|
|||||||
"Have account?": "Haben Sie ein Konto?",
|
"Have account?": "Haben Sie ein Konto?",
|
||||||
"Label": "Label",
|
"Label": "Label",
|
||||||
"Label HTML": "Label HTML",
|
"Label HTML": "Label HTML",
|
||||||
|
"Options": "Options",
|
||||||
"Placeholder": "Placeholder",
|
"Placeholder": "Placeholder",
|
||||||
"Please accept the agreement!": "Bitte akzeptieren Sie die Vereinbarung!",
|
"Please accept the agreement!": "Bitte akzeptieren Sie die Vereinbarung!",
|
||||||
"Please click the below button to sign in": "Bitte klicken Sie auf den untenstehenden Button, um sich anzumelden",
|
"Please click the below button to sign in": "Bitte klicken Sie auf den untenstehenden Button, um sich anzumelden",
|
||||||
@ -1136,6 +1159,7 @@
|
|||||||
"Link": "Link",
|
"Link": "Link",
|
||||||
"Location": "Ort",
|
"Location": "Ort",
|
||||||
"Location - Tooltip": "Stadt des Wohnsitzes",
|
"Location - Tooltip": "Stadt des Wohnsitzes",
|
||||||
|
"MFA accounts": "MFA accounts",
|
||||||
"Managed accounts": "Verwaltete Konten",
|
"Managed accounts": "Verwaltete Konten",
|
||||||
"Modify password...": "Passwort ändern...",
|
"Modify password...": "Passwort ändern...",
|
||||||
"Multi-factor authentication": "Multi-factor authentication",
|
"Multi-factor authentication": "Multi-factor authentication",
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
"Left": "Left",
|
"Left": "Left",
|
||||||
"Logged in successfully": "Logged in successfully",
|
"Logged in successfully": "Logged in successfully",
|
||||||
"Logged out successfully": "Logged out successfully",
|
"Logged out successfully": "Logged out successfully",
|
||||||
|
"Multiple Choices": "Multiple Choices",
|
||||||
"New Application": "New Application",
|
"New Application": "New Application",
|
||||||
"No verification": "No verification",
|
"No verification": "No verification",
|
||||||
"Normal": "Normal",
|
"Normal": "Normal",
|
||||||
@ -112,6 +113,7 @@
|
|||||||
"Signin session": "Signin session",
|
"Signin session": "Signin session",
|
||||||
"Signup items": "Signup items",
|
"Signup items": "Signup items",
|
||||||
"Signup items - Tooltip": "Items for users to fill in when registering new accounts",
|
"Signup items - Tooltip": "Items for users to fill in when registering new accounts",
|
||||||
|
"Single Choice": "Single Choice",
|
||||||
"Small icon": "Small icon",
|
"Small icon": "Small icon",
|
||||||
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
|
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
|
||||||
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
|
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
|
||||||
@ -121,6 +123,10 @@
|
|||||||
"Token fields - Tooltip": "The user fields included in the token",
|
"Token fields - Tooltip": "The user fields included in the token",
|
||||||
"Token format": "Token format",
|
"Token format": "Token format",
|
||||||
"Token format - Tooltip": "The format of access token",
|
"Token format - Tooltip": "The format of access token",
|
||||||
|
"Token signing method": "Token signing method",
|
||||||
|
"Token signing method - Tooltip": "Signing method of JWT token, needs to be the same algorithm as the certificate",
|
||||||
|
"Use Email as NameID": "Use Email as NameID",
|
||||||
|
"Use Email as NameID - Tooltip": "Use Email as NameID - Tooltip",
|
||||||
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
|
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
|
||||||
},
|
},
|
||||||
"cert": {
|
"cert": {
|
||||||
@ -177,6 +183,7 @@
|
|||||||
"Access key - Tooltip": "Access key - Tooltip",
|
"Access key - Tooltip": "Access key - Tooltip",
|
||||||
"Access secret": "Access secret",
|
"Access secret": "Access secret",
|
||||||
"Access secret - Tooltip": "Access secret - Tooltip",
|
"Access secret - Tooltip": "Access secret - Tooltip",
|
||||||
|
"Access token is empty": "Access token is empty",
|
||||||
"Action": "Action",
|
"Action": "Action",
|
||||||
"Adapter": "Adapter",
|
"Adapter": "Adapter",
|
||||||
"Adapter - Tooltip": "Table name of the policy store",
|
"Adapter - Tooltip": "Table name of the policy store",
|
||||||
@ -234,6 +241,8 @@
|
|||||||
"Enable": "Enable",
|
"Enable": "Enable",
|
||||||
"Enable dark logo": "Enable dark logo",
|
"Enable dark logo": "Enable dark logo",
|
||||||
"Enable dark logo - Tooltip": "Enable dark logo",
|
"Enable dark logo - Tooltip": "Enable dark logo",
|
||||||
|
"Enable tour": "Enable tour",
|
||||||
|
"Enable tour - Tooltip": "Display tour for users",
|
||||||
"Enabled": "Enabled",
|
"Enabled": "Enabled",
|
||||||
"Enabled successfully": "Enabled successfully",
|
"Enabled successfully": "Enabled successfully",
|
||||||
"Enforcers": "Enforcers",
|
"Enforcers": "Enforcers",
|
||||||
@ -265,6 +274,8 @@
|
|||||||
"Invitations": "Invitations",
|
"Invitations": "Invitations",
|
||||||
"Is enabled": "Is enabled",
|
"Is enabled": "Is enabled",
|
||||||
"Is enabled - Tooltip": "Set whether it can use",
|
"Is enabled - Tooltip": "Set whether it can use",
|
||||||
|
"Is shared": "Is shared",
|
||||||
|
"Is shared - Tooltip": "Share this application with other organizations",
|
||||||
"LDAPs": "LDAPs",
|
"LDAPs": "LDAPs",
|
||||||
"LDAPs - Tooltip": "LDAP servers",
|
"LDAPs - Tooltip": "LDAP servers",
|
||||||
"Languages": "Languages",
|
"Languages": "Languages",
|
||||||
@ -328,6 +339,8 @@
|
|||||||
"Provider - Tooltip": "Payment providers to be configured, including PayPal, Alipay, WeChat Pay, etc.",
|
"Provider - Tooltip": "Payment providers to be configured, including PayPal, Alipay, WeChat Pay, etc.",
|
||||||
"Providers": "Providers",
|
"Providers": "Providers",
|
||||||
"Providers - Tooltip": "Providers to be configured, including 3rd-party login, object storage, verification code, etc.",
|
"Providers - Tooltip": "Providers to be configured, including 3rd-party login, object storage, verification code, etc.",
|
||||||
|
"QR Code": "QR Code",
|
||||||
|
"QR code is too large": "QR code is too large",
|
||||||
"Real name": "Real name",
|
"Real name": "Real name",
|
||||||
"Records": "Records",
|
"Records": "Records",
|
||||||
"Request URI": "Request URI",
|
"Request URI": "Request URI",
|
||||||
@ -441,6 +454,8 @@
|
|||||||
"Base DN": "Base DN",
|
"Base DN": "Base DN",
|
||||||
"Base DN - Tooltip": "Base DN during LDAP search",
|
"Base DN - Tooltip": "Base DN during LDAP search",
|
||||||
"CN": "CN",
|
"CN": "CN",
|
||||||
|
"Default group": "Default group",
|
||||||
|
"Default group - Tooltip": "Group to which users belong after synchronization",
|
||||||
"Edit LDAP": "Edit LDAP",
|
"Edit LDAP": "Edit LDAP",
|
||||||
"Enable SSL": "Enable SSL",
|
"Enable SSL": "Enable SSL",
|
||||||
"Enable SSL - Tooltip": "Whether to enable SSL",
|
"Enable SSL - Tooltip": "Whether to enable SSL",
|
||||||
@ -470,6 +485,7 @@
|
|||||||
"Face ID": "Face ID",
|
"Face ID": "Face ID",
|
||||||
"Face Recognition": "Face Recognition",
|
"Face Recognition": "Face Recognition",
|
||||||
"Face recognition failed": "Face recognition failed",
|
"Face recognition failed": "Face recognition failed",
|
||||||
|
"Failed to log out": "Failed to log out",
|
||||||
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
|
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
|
||||||
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
|
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
|
||||||
"Forgot password?": "Forgot password?",
|
"Forgot password?": "Forgot password?",
|
||||||
@ -551,6 +567,11 @@
|
|||||||
"Your phone is": "Your phone is",
|
"Your phone is": "Your phone is",
|
||||||
"preferred": "preferred"
|
"preferred": "preferred"
|
||||||
},
|
},
|
||||||
|
"mfaAccount": {
|
||||||
|
"Account Name": "Account Name",
|
||||||
|
"Issuer": "Issuer",
|
||||||
|
"Secret Key": "Secret Key"
|
||||||
|
},
|
||||||
"model": {
|
"model": {
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
@ -818,6 +839,7 @@
|
|||||||
"Project Id": "Project Id",
|
"Project Id": "Project Id",
|
||||||
"Project Id - Tooltip": "Project Id - Tooltip",
|
"Project Id - Tooltip": "Project Id - Tooltip",
|
||||||
"Prompted": "Prompted",
|
"Prompted": "Prompted",
|
||||||
|
"Provider - Tooltip": "Provider - Tooltip",
|
||||||
"Provider URL": "Provider URL",
|
"Provider URL": "Provider URL",
|
||||||
"Provider URL - Tooltip": "URL for configuring the service provider, this field is only used for reference and is not used in Casdoor",
|
"Provider URL - Tooltip": "URL for configuring the service provider, this field is only used for reference and is not used in Casdoor",
|
||||||
"Public key": "Public key",
|
"Public key": "Public key",
|
||||||
@ -937,6 +959,7 @@
|
|||||||
"Have account?": "Have account?",
|
"Have account?": "Have account?",
|
||||||
"Label": "Label",
|
"Label": "Label",
|
||||||
"Label HTML": "Label HTML",
|
"Label HTML": "Label HTML",
|
||||||
|
"Options": "Options",
|
||||||
"Placeholder": "Placeholder",
|
"Placeholder": "Placeholder",
|
||||||
"Please accept the agreement!": "Please accept the agreement!",
|
"Please accept the agreement!": "Please accept the agreement!",
|
||||||
"Please click the below button to sign in": "Please click the below button to sign in",
|
"Please click the below button to sign in": "Please click the below button to sign in",
|
||||||
@ -1136,6 +1159,7 @@
|
|||||||
"Link": "Link",
|
"Link": "Link",
|
||||||
"Location": "Location",
|
"Location": "Location",
|
||||||
"Location - Tooltip": "City of residence",
|
"Location - Tooltip": "City of residence",
|
||||||
|
"MFA accounts": "MFA accounts",
|
||||||
"Managed accounts": "Managed accounts",
|
"Managed accounts": "Managed accounts",
|
||||||
"Modify password...": "Modify password...",
|
"Modify password...": "Modify password...",
|
||||||
"Multi-factor authentication": "Multi-factor authentication",
|
"Multi-factor authentication": "Multi-factor authentication",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user