mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-25 17:00:29 +08:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
802b6812a9 | ||
![]() |
a5a627f92e | ||
![]() |
9701818a6e | ||
![]() |
06986fbd41 | ||
![]() |
3d12ac8dc2 | ||
![]() |
f01839123f | ||
![]() |
e1b3b0ac6a | ||
![]() |
4b0a2fdbfc | ||
![]() |
db551eb24a | ||
![]() |
18b49bb731 | ||
![]() |
17653888a3 | ||
![]() |
ee16616df4 | ||
![]() |
ea450005e0 | ||
![]() |
4c5ad14f6b | ||
![]() |
49dda2aea5 | ||
![]() |
a74a004540 | ||
![]() |
2b89f6b37b | ||
![]() |
c699e35e6b | ||
![]() |
e28d90d0aa | ||
![]() |
4fc7600865 | ||
![]() |
19f62a461b | ||
![]() |
7ddc2778c0 | ||
![]() |
b96fa2a995 | ||
![]() |
fcfb73af6e | ||
![]() |
43bebc03b9 | ||
![]() |
c5f25cbc7d | ||
![]() |
3feb6ce84d | ||
![]() |
08d6b45fc5 | ||
![]() |
56d0de64dc | ||
![]() |
1813e8e8c7 | ||
![]() |
e27c764a55 | ||
![]() |
e5a2057382 | ||
![]() |
8457ff7433 | ||
![]() |
888a6f2feb | ||
![]() |
b57b64fc36 | ||
![]() |
0d239ba1cf | ||
![]() |
8927e08217 | ||
![]() |
0636069584 | ||
![]() |
4d0f73c84e | ||
![]() |
74a2478e10 | ||
![]() |
acc6f3e887 | ||
![]() |
185ab9750a | ||
![]() |
48adc050d6 | ||
![]() |
b0e318c9db | ||
![]() |
f9a6efc00f | ||
![]() |
bd4a6775dd | ||
![]() |
e3a43d0062 | ||
![]() |
0cf281cac0 |
@@ -28,6 +28,7 @@ ldapServerPort = 389
|
|||||||
ldapsCertId = ""
|
ldapsCertId = ""
|
||||||
ldapsServerPort = 636
|
ldapsServerPort = 636
|
||||||
radiusServerPort = 1812
|
radiusServerPort = 1812
|
||||||
|
radiusDefaultOrganization = "built-in"
|
||||||
radiusSecret = "secret"
|
radiusSecret = "secret"
|
||||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -330,6 +331,8 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verificationType := ""
|
||||||
|
|
||||||
if authForm.Username != "" {
|
if authForm.Username != "" {
|
||||||
if authForm.Type == ResponseTypeLogin {
|
if authForm.Type == ResponseTypeLogin {
|
||||||
if c.GetSessionUsername() != "" {
|
if c.GetSessionUsername() != "" {
|
||||||
@@ -424,6 +427,12 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if verificationCodeType == object.VerifyTypePhone {
|
||||||
|
verificationType = "sms"
|
||||||
|
} else {
|
||||||
|
verificationType = "email"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var application *object.Application
|
var application *object.Application
|
||||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||||
@@ -523,8 +532,19 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
if user.IsMfaEnabled() {
|
if user.IsMfaEnabled() {
|
||||||
c.setMfaUserSession(user.GetId())
|
c.setMfaUserSession(user.GetId())
|
||||||
c.ResponseOk(object.NextMfa, user.GetPreferredMfaProps(true))
|
mfaList := object.GetAllMfaProps(user, true)
|
||||||
return
|
mfaAllowList := []*object.MfaProps{}
|
||||||
|
for _, prop := range mfaList {
|
||||||
|
if prop.MfaType == verificationType || !prop.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mfaAllowList = append(mfaAllowList, prop)
|
||||||
|
}
|
||||||
|
if len(mfaAllowList) >= 1 {
|
||||||
|
c.SetSession("verificationCodeType", verificationType)
|
||||||
|
c.ResponseOk(object.NextMfa, mfaAllowList)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||||
@@ -617,6 +637,17 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
|
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.EmailRegex != "" {
|
||||||
|
reg, err := regexp.Compile(provider.EmailRegex)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reg.MatchString(userInfo.Email) {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("check:Email is invalid")))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if authForm.Method == "signup" {
|
if authForm.Method == "signup" {
|
||||||
@@ -854,8 +885,12 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if authForm.Passcode != "" {
|
if authForm.Passcode != "" {
|
||||||
|
if authForm.MfaType == c.GetSession("verificationCodeType") {
|
||||||
|
c.ResponseError("Invalid multi-factor authentication type")
|
||||||
|
return
|
||||||
|
}
|
||||||
user.CountryCode = user.GetCountryCode(user.CountryCode)
|
user.CountryCode = user.GetCountryCode(user.CountryCode)
|
||||||
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetPreferredMfaProps(false))
|
mfaUtil := object.GetMfaUtil(authForm.MfaType, user.GetMfaProps(authForm.MfaType, false))
|
||||||
if mfaUtil == nil {
|
if mfaUtil == nil {
|
||||||
c.ResponseError("Invalid multi-factor authentication type")
|
c.ResponseError("Invalid multi-factor authentication type")
|
||||||
return
|
return
|
||||||
@@ -866,6 +901,7 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.SetSession("verificationCodeType", "")
|
||||||
} else if authForm.RecoveryCode != "" {
|
} else if authForm.RecoveryCode != "" {
|
||||||
err = object.MfaRecover(user, authForm.RecoveryCode)
|
err = object.MfaRecover(user, authForm.RecoveryCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -70,15 +70,33 @@ func (c *ApiController) GetGroups() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
err = object.ExtendGroupsWithUsers(groups)
|
groupsHaveChildrenMap, err := object.GetGroupsHaveChildrenMap(groups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
_, ok := groupsHaveChildrenMap[group.Name]
|
||||||
|
if ok {
|
||||||
|
group.HaveChildren = true
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(groups, paginator.Nums())
|
parent, ok := groupsHaveChildrenMap[group.ParentId]
|
||||||
|
if ok {
|
||||||
|
group.ParentName = parent.DisplayName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = object.ExtendGroupsWithUsers(groups)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(groups, paginator.Nums())
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,6 +21,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *RootController) HandleScim() {
|
func (c *RootController) HandleScim() {
|
||||||
|
_, ok := c.RequireAdmin()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
path := c.Ctx.Request.URL.Path
|
path := c.Ctx.Request.URL.Path
|
||||||
c.Ctx.Request.URL.Path = strings.TrimPrefix(path, "/scim")
|
c.Ctx.Request.URL.Path = strings.TrimPrefix(path, "/scim")
|
||||||
scim.Server.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
scim.Server.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||||
|
@@ -93,7 +93,7 @@ func (c *ApiController) SendEmail() {
|
|||||||
|
|
||||||
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
|
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
|
||||||
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
|
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
|
||||||
err = object.DailSmtpServer(provider)
|
err = object.TestSmtpServer(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@@ -353,13 +353,7 @@ func (c *ApiController) AddUser() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := object.GetUserCount("", "", "", "")
|
if err := checkQuotaForUser(); err != nil {
|
||||||
if err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkQuotaForUser(int(count)); err != nil {
|
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -580,7 +574,11 @@ func (c *ApiController) SetPassword() {
|
|||||||
if user.Ldap == "" {
|
if user.Ldap == "" {
|
||||||
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
|
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
|
||||||
} else {
|
} else {
|
||||||
err = object.ResetLdapPassword(targetUser, newPassword, c.GetAcceptLanguage())
|
if isAdmin {
|
||||||
|
err = object.ResetLdapPassword(targetUser, "", newPassword, c.GetAcceptLanguage())
|
||||||
|
} else {
|
||||||
|
err = object.ResetLdapPassword(targetUser, oldPassword, newPassword, c.GetAcceptLanguage())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -294,12 +294,18 @@ func checkQuotaForProvider(count int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkQuotaForUser(count int) error {
|
func checkQuotaForUser() error {
|
||||||
quota := conf.GetConfigQuota().User
|
quota := conf.GetConfigQuota().User
|
||||||
if quota == -1 {
|
if quota == -1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if count >= quota {
|
|
||||||
|
count, err := object.GetUserCount("", "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if int(count) >= quota {
|
||||||
return fmt.Errorf("user quota is exceeded")
|
return fmt.Errorf("user quota is exceeded")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@@ -16,7 +16,9 @@ package email
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/gomail/v2"
|
"github.com/casdoor/gomail/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +35,13 @@ func NewSmtpEmailProvider(userName string, password string, host string, port in
|
|||||||
|
|
||||||
dialer.SSL = !disableSsl
|
dialer.SSL = !disableSsl
|
||||||
|
|
||||||
|
if strings.HasSuffix(host, ".amazonaws.com") {
|
||||||
|
socks5Proxy := conf.GetConfigString("socks5Proxy")
|
||||||
|
if socks5Proxy != "" {
|
||||||
|
dialer.SetSocks5Proxy(socks5Proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &SmtpEmailProvider{Dialer: dialer}
|
return &SmtpEmailProvider{Dialer: dialer}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
go.mod
10
go.mod
@@ -10,9 +10,9 @@ require (
|
|||||||
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.25.0
|
github.com/casdoor/go-sms-sender v0.25.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.1.0
|
||||||
github.com/casdoor/ldapserver v1.2.0
|
github.com/casdoor/ldapserver v1.2.0
|
||||||
github.com/casdoor/notify v0.45.0
|
github.com/casdoor/notify v1.0.0
|
||||||
github.com/casdoor/oss v1.8.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
|
||||||
@@ -60,10 +60,10 @@ require (
|
|||||||
github.com/xorm-io/core v0.7.4
|
github.com/xorm-io/core v0.7.4
|
||||||
github.com/xorm-io/xorm v1.1.6
|
github.com/xorm-io/xorm v1.1.6
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.21.0
|
golang.org/x/crypto v0.32.0
|
||||||
golang.org/x/net v0.21.0
|
golang.org/x/net v0.34.0
|
||||||
golang.org/x/oauth2 v0.17.0
|
golang.org/x/oauth2 v0.17.0
|
||||||
golang.org/x/text v0.14.0
|
golang.org/x/text v0.21.0
|
||||||
google.golang.org/api v0.150.0
|
google.golang.org/api v0.150.0
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
|
41
go.sum
41
go.sum
@@ -1089,12 +1089,12 @@ github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXy
|
|||||||
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.25.0 h1:eF4cOCSbjVg7+0uLlJQnna/FQ0BWW+Fp/x4cXhzQu1Y=
|
github.com/casdoor/go-sms-sender v0.25.0 h1:eF4cOCSbjVg7+0uLlJQnna/FQ0BWW+Fp/x4cXhzQu1Y=
|
||||||
github.com/casdoor/go-sms-sender v0.25.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0=
|
github.com/casdoor/go-sms-sender v0.25.0/go.mod h1:bOm4H8/YfJmEHjBatEVQFOnAf0OOn1B0Wi5B7zDhws0=
|
||||||
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
|
github.com/casdoor/gomail/v2 v2.1.0 h1:ua97E3CARnF1Ik8ga/Drz9uGZfaElXJumFexiErWUxM=
|
||||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
github.com/casdoor/gomail/v2 v2.1.0/go.mod h1:GFzOD9RhY0nODiiPaQiOa6DfoKtmO9aTesu5qrp26OI=
|
||||||
github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow=
|
github.com/casdoor/ldapserver v1.2.0 h1:HdSYe+ULU6z9K+2BqgTrJKQRR4//ERAXB64ttOun6Ow=
|
||||||
github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s=
|
github.com/casdoor/ldapserver v1.2.0/go.mod h1:VwYU2vqQ2pA8sa00PRekH71R2XmgfzMKhmp1XrrDu2s=
|
||||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
github.com/casdoor/notify v1.0.0 h1:oldsaaQFPrlufm/OA314z8DwFVE1Tc9Gt1z4ptRHhXw=
|
||||||
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
github.com/casdoor/notify v1.0.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||||
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
github.com/casdoor/oss v1.8.0 h1:uuyKhDIp7ydOtV4lpqhAY23Ban2Ln8La8+QT36CwylM=
|
||||||
github.com/casdoor/oss v1.8.0/go.mod h1:uaqO7KBI2lnZcnB8rF7O6C2bN7llIbfC5Ql8ex1yR1U=
|
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=
|
||||||
@@ -2163,8 +2163,10 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
|
|||||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -2230,8 +2232,10 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
|||||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20171115151908-9dfe39835686/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -2319,8 +2323,10 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@@ -2375,8 +2381,11 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -2503,8 +2512,11 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -2524,8 +2536,10 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
|||||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||||
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
|
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||||
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@@ -2546,8 +2560,10 @@ golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@@ -2634,8 +2650,9 @@ golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
|||||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||||
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@@ -188,10 +188,23 @@ type GitHubUserInfo struct {
|
|||||||
} `json:"plan"`
|
} `json:"plan"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GitHubUserEmailInfo struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Primary bool `json:"primary"`
|
||||||
|
Verified bool `json:"verified"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GitHubErrorInfo struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
DocumentationUrl string `json:"documentation_url"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Add("Authorization", "token "+token.AccessToken)
|
req.Header.Add("Authorization", "token "+token.AccessToken)
|
||||||
resp, err := idp.Client.Do(req)
|
resp, err := idp.Client.Do(req)
|
||||||
@@ -212,6 +225,42 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if githubUserInfo.Email == "" {
|
||||||
|
reqEmail, err := http.NewRequest("GET", "https://api.github.com/user/emails", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reqEmail.Header.Add("Authorization", "token "+token.AccessToken)
|
||||||
|
respEmail, err := idp.Client.Do(reqEmail)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer respEmail.Body.Close()
|
||||||
|
emailBody, err := io.ReadAll(respEmail.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if respEmail.StatusCode != 200 {
|
||||||
|
var errMessage GitHubErrorInfo
|
||||||
|
err = json.Unmarshal(emailBody, &errMessage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("GithubIdProvider:GetUserInfo() error, status code = %d, error message = %v\n", respEmail.StatusCode, errMessage)
|
||||||
|
} else {
|
||||||
|
var userEmails []GitHubUserEmailInfo
|
||||||
|
err = json.Unmarshal(emailBody, &userEmails)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: strconv.Itoa(githubUserInfo.Id),
|
Id: strconv.Itoa(githubUserInfo.Id),
|
||||||
Username: githubUserInfo.Login,
|
Username: githubUserInfo.Login,
|
||||||
@@ -248,3 +297,27 @@ func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte,
|
|||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (idp *GithubIdProvider) getEmailFromEmailsResult(emailInfo []GitHubUserEmailInfo) string {
|
||||||
|
primaryEmail := ""
|
||||||
|
verifiedEmail := ""
|
||||||
|
|
||||||
|
for _, addr := range emailInfo {
|
||||||
|
if !addr.Verified || strings.Contains(addr.Email, "users.noreply.github.com") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.Primary {
|
||||||
|
primaryEmail = addr.Email
|
||||||
|
break
|
||||||
|
} else if verifiedEmail == "" {
|
||||||
|
verifiedEmail = addr.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if primaryEmail != "" {
|
||||||
|
return primaryEmail
|
||||||
|
}
|
||||||
|
|
||||||
|
return verifiedEmail
|
||||||
|
}
|
||||||
|
161
idp/kwai.go
Normal file
161
idp/kwai.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// 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 idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KwaiIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKwaiIdProvider(clientId string, clientSecret string, redirectUrl string) *KwaiIdProvider {
|
||||||
|
idp := &KwaiIdProvider{}
|
||||||
|
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *KwaiIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *KwaiIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||||
|
endpoint := oauth2.Endpoint{
|
||||||
|
TokenURL: "https://open.kuaishou.com/oauth2/access_token",
|
||||||
|
AuthURL: "https://open.kuaishou.com/oauth2/authorize", // qr code: /oauth2/connect
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &oauth2.Config{
|
||||||
|
Scopes: []string{"user_info"},
|
||||||
|
Endpoint: endpoint,
|
||||||
|
ClientID: clientId,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
RedirectURL: redirectUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type KwaiTokenResp struct {
|
||||||
|
Result int `json:"result"`
|
||||||
|
ErrorMsg string `json:"error_msg"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
RefreshTokenExpiresIn int `json:"refresh_token_expires_in"`
|
||||||
|
OpenId string `json:"open_id"`
|
||||||
|
Scopes []string `json:"scopes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetToken use code to get access_token
|
||||||
|
func (idp *KwaiIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
params := map[string]string{
|
||||||
|
"app_id": idp.Config.ClientID,
|
||||||
|
"app_secret": idp.Config.ClientSecret,
|
||||||
|
"code": code,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
}
|
||||||
|
tokenUrl := fmt.Sprintf("%s?app_id=%s&app_secret=%s&code=%s&grant_type=authorization_code",
|
||||||
|
idp.Config.Endpoint.TokenURL, params["app_id"], params["app_secret"], params["code"])
|
||||||
|
resp, err := idp.Client.Get(tokenUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var tokenResp KwaiTokenResp
|
||||||
|
err = json.Unmarshal(body, &tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tokenResp.Result != 1 {
|
||||||
|
return nil, fmt.Errorf("get token error: %s", tokenResp.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenResp.AccessToken,
|
||||||
|
RefreshToken: tokenResp.RefreshToken,
|
||||||
|
Expiry: time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
raw["open_id"] = tokenResp.OpenId
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// More details: https://open.kuaishou.com/openapi/user_info
|
||||||
|
type KwaiUserInfo struct {
|
||||||
|
Result int `json:"result"`
|
||||||
|
ErrorMsg string `json:"error_msg"`
|
||||||
|
UserInfo struct {
|
||||||
|
Head string `json:"head"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sex string `json:"sex"`
|
||||||
|
City string `json:"city"`
|
||||||
|
} `json:"user_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo use token to get user profile
|
||||||
|
func (idp *KwaiIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
userInfoUrl := fmt.Sprintf("https://open.kuaishou.com/openapi/user_info?app_id=%s&access_token=%s",
|
||||||
|
idp.Config.ClientID, token.AccessToken)
|
||||||
|
|
||||||
|
resp, err := idp.Client.Get(userInfoUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var kwaiUserInfo KwaiUserInfo
|
||||||
|
err = json.Unmarshal(body, &kwaiUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kwaiUserInfo.Result != 1 {
|
||||||
|
return nil, fmt.Errorf("get user info error: %s", kwaiUserInfo.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
Id: token.Extra("open_id").(string),
|
||||||
|
Username: kwaiUserInfo.UserInfo.Name,
|
||||||
|
DisplayName: kwaiUserInfo.UserInfo.Name,
|
||||||
|
AvatarUrl: kwaiUserInfo.UserInfo.Head,
|
||||||
|
Extra: map[string]string{
|
||||||
|
"gender": kwaiUserInfo.UserInfo.Sex,
|
||||||
|
"city": kwaiUserInfo.UserInfo.City,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
@@ -113,6 +113,8 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error
|
|||||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Douyin":
|
case "Douyin":
|
||||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
|
case "Kwai":
|
||||||
|
return NewKwaiIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Bilibili":
|
case "Bilibili":
|
||||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "MetaMask":
|
case "MetaMask":
|
||||||
|
5
main.go
5
main.go
@@ -83,6 +83,11 @@ func main() {
|
|||||||
// logs.SetLevel(logs.LevelInformational)
|
// logs.SetLevel(logs.LevelInformational)
|
||||||
logs.SetLogFuncCall(false)
|
logs.SetLogFuncCall(false)
|
||||||
|
|
||||||
|
err = util.StopOldInstance(port)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
go ldap.StartLdapServer()
|
go ldap.StartLdapServer()
|
||||||
go radius.StartRadiusServer()
|
go radius.StartRadiusServer()
|
||||||
go object.ClearThroughputPerSecond()
|
go object.ClearThroughputPerSecond()
|
||||||
|
29
notification/cucloud.go
Normal file
29
notification/cucloud.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2025 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 notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/notify"
|
||||||
|
"github.com/casdoor/notify/service/cucloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCucloudProvider(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType string) (notify.Notifier, error) {
|
||||||
|
cucloud := cucloud.New(accessKey, secretKey, topicName, messageTitle, cloudRegionCode, accountId, notifyType)
|
||||||
|
|
||||||
|
notifier := notify.New()
|
||||||
|
notifier.UseServices(cucloud)
|
||||||
|
|
||||||
|
return notifier, nil
|
||||||
|
}
|
@@ -16,7 +16,7 @@ package notification
|
|||||||
|
|
||||||
import "github.com/casdoor/notify"
|
import "github.com/casdoor/notify"
|
||||||
|
|
||||||
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
|
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string, regionId string) (notify.Notifier, error) {
|
||||||
if typ == "Telegram" {
|
if typ == "Telegram" {
|
||||||
return NewTelegramProvider(clientSecret, receiver)
|
return NewTelegramProvider(clientSecret, receiver)
|
||||||
} else if typ == "Custom HTTP" {
|
} else if typ == "Custom HTTP" {
|
||||||
@@ -53,6 +53,8 @@ func GetNotificationProvider(typ string, clientId string, clientSecret string, c
|
|||||||
return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
|
return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
|
||||||
} else if typ == "Viber" {
|
} else if typ == "Viber" {
|
||||||
return NewViberProvider(clientId, clientSecret, appId, receiver)
|
return NewViberProvider(clientId, clientSecret, appId, receiver)
|
||||||
|
} else if typ == "CUCloud" {
|
||||||
|
return NewCucloudProvider(clientId, clientSecret, appId, title, regionId, clientId2, metaData)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@@ -481,7 +481,10 @@ func GetApplicationByClientId(clientId string) (*Application, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetApplication(id string) (*Application, error) {
|
func GetApplication(id string) (*Application, error) {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return getApplication(owner, name)
|
return getApplication(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,23 +16,18 @@
|
|||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import "github.com/casdoor/casdoor/email"
|
||||||
"crypto/tls"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/email"
|
// TestSmtpServer Test the SMTP server
|
||||||
"github.com/casdoor/gomail/v2"
|
func TestSmtpServer(provider *Provider) error {
|
||||||
)
|
smtpEmailProvider := email.NewSmtpEmailProvider(provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.Type, provider.DisableSsl)
|
||||||
|
sender, err := smtpEmailProvider.Dialer.Dial()
|
||||||
func getDialer(provider *Provider) *gomail.Dialer {
|
if err != nil {
|
||||||
dialer := &gomail.Dialer{}
|
return err
|
||||||
dialer = gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
|
||||||
if provider.Type == "SUBMAIL" {
|
|
||||||
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
|
||||||
}
|
}
|
||||||
|
defer sender.Close()
|
||||||
|
|
||||||
dialer.SSL = !provider.DisableSsl
|
return nil
|
||||||
|
|
||||||
return dialer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||||
@@ -50,16 +45,3 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
|
|||||||
|
|
||||||
return emailProvider.Send(fromAddress, fromName, dest, title, content)
|
return emailProvider.Send(fromAddress, fromName, dest, title, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DailSmtpServer Dail Smtp server
|
|
||||||
func DailSmtpServer(provider *Provider) error {
|
|
||||||
dialer := getDialer(provider)
|
|
||||||
|
|
||||||
sender, err := dialer.Dial()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer sender.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -19,288 +19,90 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dashboard struct {
|
type DashboardDateItem struct {
|
||||||
OrganizationCounts []int `json:"organizationCounts"`
|
CreatedTime string `json:"createTime"`
|
||||||
UserCounts []int `json:"userCounts"`
|
|
||||||
ProviderCounts []int `json:"providerCounts"`
|
|
||||||
ApplicationCounts []int `json:"applicationCounts"`
|
|
||||||
SubscriptionCounts []int `json:"subscriptionCounts"`
|
|
||||||
RoleCounts []int `json:"roleCounts"`
|
|
||||||
GroupCounts []int `json:"groupCounts"`
|
|
||||||
ResourceCounts []int `json:"resourceCounts"`
|
|
||||||
CertCounts []int `json:"certCounts"`
|
|
||||||
PermissionCounts []int `json:"permissionCounts"`
|
|
||||||
TransactionCounts []int `json:"transactionCounts"`
|
|
||||||
ModelCounts []int `json:"modelCounts"`
|
|
||||||
AdapterCounts []int `json:"adapterCounts"`
|
|
||||||
EnforcerCounts []int `json:"enforcerCounts"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetDashboard(owner string) (*Dashboard, error) {
|
type DashboardMapItem struct {
|
||||||
|
dashboardDateItems []DashboardDateItem
|
||||||
|
itemCount int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDashboard(owner string) (*map[string][]int64, error) {
|
||||||
if owner == "All" {
|
if owner == "All" {
|
||||||
owner = ""
|
owner = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
dashboard := &Dashboard{
|
dashboard := make(map[string][]int64)
|
||||||
OrganizationCounts: make([]int, 31),
|
dashboardMap := sync.Map{}
|
||||||
UserCounts: make([]int, 31),
|
tableNames := []string{"organization", "user", "provider", "application", "subscription", "role", "group", "resource", "cert", "permission", "transaction", "model", "adapter", "enforcer"}
|
||||||
ProviderCounts: make([]int, 31),
|
|
||||||
ApplicationCounts: make([]int, 31),
|
time30day := time.Now().AddDate(0, 0, -30)
|
||||||
SubscriptionCounts: make([]int, 31),
|
var wg sync.WaitGroup
|
||||||
RoleCounts: make([]int, 31),
|
var err error
|
||||||
GroupCounts: make([]int, 31),
|
wg.Add(len(tableNames))
|
||||||
ResourceCounts: make([]int, 31),
|
ch := make(chan error, len(tableNames))
|
||||||
CertCounts: make([]int, 31),
|
for _, tableName := range tableNames {
|
||||||
PermissionCounts: make([]int, 31),
|
dashboard[tableName+"Counts"] = make([]int64, 31)
|
||||||
TransactionCounts: make([]int, 31),
|
tableName := tableName
|
||||||
ModelCounts: make([]int, 31),
|
go func(ch chan error) {
|
||||||
AdapterCounts: make([]int, 31),
|
defer wg.Done()
|
||||||
EnforcerCounts: make([]int, 31),
|
dashboardDateItems := []DashboardDateItem{}
|
||||||
|
var countResult int64
|
||||||
|
|
||||||
|
dbQueryBefore := ormer.Engine.Cols("created_time")
|
||||||
|
dbQueryAfter := ormer.Engine.Cols("created_time")
|
||||||
|
|
||||||
|
if owner != "" {
|
||||||
|
dbQueryAfter = dbQueryAfter.And("owner = ?", owner)
|
||||||
|
dbQueryBefore = dbQueryBefore.And("owner = ?", owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
if countResult, err = dbQueryBefore.And("created_time < ?", time30day).Table(tableName).Count(); err != nil {
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = dbQueryAfter.And("created_time >= ?", time30day).Table(tableName).Find(&dashboardDateItems); err != nil {
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dashboardMap.Store(tableName, DashboardMapItem{
|
||||||
|
dashboardDateItems: dashboardDateItems,
|
||||||
|
itemCount: countResult,
|
||||||
|
})
|
||||||
|
}(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
organizations := []Organization{}
|
|
||||||
users := []User{}
|
|
||||||
providers := []Provider{}
|
|
||||||
applications := []Application{}
|
|
||||||
subscriptions := []Subscription{}
|
|
||||||
roles := []Role{}
|
|
||||||
groups := []Group{}
|
|
||||||
resources := []Resource{}
|
|
||||||
certs := []Cert{}
|
|
||||||
permissions := []Permission{}
|
|
||||||
transactions := []Transaction{}
|
|
||||||
models := []Model{}
|
|
||||||
adapters := []Adapter{}
|
|
||||||
enforcers := []Enforcer{}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
wg.Add(14)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&roles, &Role{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
if err := ormer.Engine.Find(&groups, &Group{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&resources, &Resource{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&certs, &Cert{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&permissions, &Permission{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&transactions, &Transaction{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&models, &Model{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&adapters, &Adapter{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if err := ormer.Engine.Find(&enforcers, &Enforcer{Owner: owner}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
close(ch)
|
||||||
|
|
||||||
|
for err = range ch {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
for i := 30; i >= 0; i-- {
|
for i := 30; i >= 0; i-- {
|
||||||
cutTime := nowTime.AddDate(0, 0, -i)
|
cutTime := nowTime.AddDate(0, 0, -i)
|
||||||
dashboard.OrganizationCounts[30-i] = countCreatedBefore(organizations, cutTime)
|
for _, tableName := range tableNames {
|
||||||
dashboard.UserCounts[30-i] = countCreatedBefore(users, cutTime)
|
item, exist := dashboardMap.Load(tableName)
|
||||||
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
|
if !exist {
|
||||||
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
|
continue
|
||||||
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
|
}
|
||||||
dashboard.RoleCounts[30-i] = countCreatedBefore(roles, cutTime)
|
dashboard[tableName+"Counts"][30-i] = countCreatedBefore(item.(DashboardMapItem), cutTime)
|
||||||
dashboard.GroupCounts[30-i] = countCreatedBefore(groups, cutTime)
|
}
|
||||||
dashboard.ResourceCounts[30-i] = countCreatedBefore(resources, cutTime)
|
|
||||||
dashboard.CertCounts[30-i] = countCreatedBefore(certs, cutTime)
|
|
||||||
dashboard.PermissionCounts[30-i] = countCreatedBefore(permissions, cutTime)
|
|
||||||
dashboard.TransactionCounts[30-i] = countCreatedBefore(transactions, cutTime)
|
|
||||||
dashboard.ModelCounts[30-i] = countCreatedBefore(models, cutTime)
|
|
||||||
dashboard.AdapterCounts[30-i] = countCreatedBefore(adapters, cutTime)
|
|
||||||
dashboard.EnforcerCounts[30-i] = countCreatedBefore(enforcers, cutTime)
|
|
||||||
}
|
}
|
||||||
return dashboard, nil
|
return &dashboard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func countCreatedBefore(objects interface{}, before time.Time) int {
|
func countCreatedBefore(dashboardMapItem DashboardMapItem, before time.Time) int64 {
|
||||||
count := 0
|
count := dashboardMapItem.itemCount
|
||||||
switch obj := objects.(type) {
|
for _, e := range dashboardMapItem.dashboardDateItems {
|
||||||
case []Organization:
|
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime)
|
||||||
for _, o := range obj {
|
if createdTime.Before(before) {
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", o.CreatedTime)
|
count++
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []User:
|
|
||||||
for _, u := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", u.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Provider:
|
|
||||||
for _, p := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Application:
|
|
||||||
for _, a := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Subscription:
|
|
||||||
for _, s := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", s.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Role:
|
|
||||||
for _, r := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Group:
|
|
||||||
for _, g := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", g.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Resource:
|
|
||||||
for _, r := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", r.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Cert:
|
|
||||||
for _, c := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", c.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Permission:
|
|
||||||
for _, p := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Transaction:
|
|
||||||
for _, t := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", t.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Model:
|
|
||||||
for _, m := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", m.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Adapter:
|
|
||||||
for _, a := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case []Enforcer:
|
|
||||||
for _, e := range obj {
|
|
||||||
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", e.CreatedTime)
|
|
||||||
if createdTime.Before(before) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count
|
return count
|
||||||
|
@@ -17,7 +17,6 @@ package object
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@@ -36,12 +35,14 @@ type Group struct {
|
|||||||
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
|
ContactEmail string `xorm:"varchar(100)" json:"contactEmail"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
ParentId string `xorm:"varchar(100)" json:"parentId"`
|
ParentId string `xorm:"varchar(100)" json:"parentId"`
|
||||||
|
ParentName string `xorm:"-" json:"parentName"`
|
||||||
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
|
IsTopGroup bool `xorm:"bool" json:"isTopGroup"`
|
||||||
Users []string `xorm:"-" json:"users"`
|
Users []string `xorm:"-" json:"users"`
|
||||||
|
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
Children []*Group `json:"children,omitempty"`
|
HaveChildren bool `xorm:"-" json:"haveChildren"`
|
||||||
|
Children []*Group `json:"children,omitempty"`
|
||||||
|
|
||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
}
|
}
|
||||||
@@ -79,6 +80,26 @@ func GetPaginationGroups(owner string, offset, limit int, field, value, sortFiel
|
|||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetGroupsHaveChildrenMap(groups []*Group) (map[string]*Group, error) {
|
||||||
|
groupsHaveChildren := []*Group{}
|
||||||
|
resultMap := make(map[string]*Group)
|
||||||
|
|
||||||
|
groupIds := []string{}
|
||||||
|
for _, group := range groups {
|
||||||
|
groupIds = append(groupIds, group.Name)
|
||||||
|
groupIds = append(groupIds, group.ParentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := ormer.Engine.Cols("owner", "name", "parent_id", "display_name").Distinct("parent_id").In("parent_id", groupIds).Find(&groupsHaveChildren)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, group := range groups {
|
||||||
|
resultMap[group.Name] = group
|
||||||
|
}
|
||||||
|
return resultMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getGroup(owner string, name string) (*Group, error) {
|
func getGroup(owner string, name string) (*Group, error) {
|
||||||
if owner == "" || name == "" {
|
if owner == "" || name == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@@ -298,17 +319,11 @@ func ExtendGroupWithUsers(group *Group) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
users, err := GetUsers(group.Owner)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
groupId := group.GetId()
|
groupId := group.GetId()
|
||||||
userIds := []string{}
|
userIds := []string{}
|
||||||
for _, user := range users {
|
userIds, err := userEnforcer.GetAllUsersByGroup(groupId)
|
||||||
if util.InSlice(user.Groups, groupId) {
|
if err != nil {
|
||||||
userIds = append(userIds, user.GetId())
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.Users = userIds
|
group.Users = userIds
|
||||||
@@ -316,29 +331,14 @@ func ExtendGroupWithUsers(group *Group) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExtendGroupsWithUsers(groups []*Group) error {
|
func ExtendGroupsWithUsers(groups []*Group) error {
|
||||||
var wg sync.WaitGroup
|
|
||||||
errChan := make(chan error, len(groups))
|
|
||||||
|
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
wg.Add(1)
|
users, err := userEnforcer.GetAllUsersByGroup(group.GetId())
|
||||||
go func(group *Group) {
|
|
||||||
defer wg.Done()
|
|
||||||
err := ExtendGroupWithUsers(group)
|
|
||||||
if err != nil {
|
|
||||||
errChan <- err
|
|
||||||
}
|
|
||||||
}(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
close(errChan)
|
|
||||||
|
|
||||||
for err := range errChan {
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
group.Users = users
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,6 +33,7 @@ type Ldap struct {
|
|||||||
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"`
|
DefaultGroup string `xorm:"varchar(100)" json:"defaultGroup"`
|
||||||
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
|
|
||||||
AutoSync int `json:"autoSync"`
|
AutoSync int `json:"autoSync"`
|
||||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||||
@@ -149,7 +150,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", "default_group").Update(ldap)
|
"port", "enable_ssl", "username", "password", "base_dn", "filter", "filter_fields", "auto_sync", "default_group", "password_type").Update(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
@@ -15,6 +15,8 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -373,7 +375,7 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
|
|||||||
return existUuids, nil
|
return existUuids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ResetLdapPassword(user *User, newPassword string, lang string) error {
|
func ResetLdapPassword(user *User, oldPassword string, newPassword string, lang string) error {
|
||||||
ldaps, err := GetLdaps(user.Owner)
|
ldaps, err := GetLdaps(user.Owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -416,8 +418,32 @@ func ResetLdapPassword(user *User, newPassword string, lang string) error {
|
|||||||
}
|
}
|
||||||
modifyPasswordRequest.Replace("unicodePwd", []string{pwdEncoded})
|
modifyPasswordRequest.Replace("unicodePwd", []string{pwdEncoded})
|
||||||
modifyPasswordRequest.Replace("userAccountControl", []string{"512"})
|
modifyPasswordRequest.Replace("userAccountControl", []string{"512"})
|
||||||
|
} else if oldPassword != "" {
|
||||||
|
modifyPasswordRequestWithOldPassword := goldap.NewPasswordModifyRequest(userDn, oldPassword, newPassword)
|
||||||
|
_, err = conn.Conn.PasswordModify(modifyPasswordRequestWithOldPassword)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return nil
|
||||||
} else {
|
} else {
|
||||||
pwdEncoded = newPassword
|
switch ldapServer.PasswordType {
|
||||||
|
case "SSHA":
|
||||||
|
pwdEncoded, err = generateSSHA(newPassword)
|
||||||
|
break
|
||||||
|
case "MD5":
|
||||||
|
md5Byte := md5.Sum([]byte(newPassword))
|
||||||
|
md5Password := base64.StdEncoding.EncodeToString(md5Byte[:])
|
||||||
|
pwdEncoded = "{MD5}" + md5Password
|
||||||
|
break
|
||||||
|
case "Plain":
|
||||||
|
pwdEncoded = newPassword
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
pwdEncoded = newPassword
|
||||||
|
break
|
||||||
|
}
|
||||||
modifyPasswordRequest.Replace("userPassword", []string{pwdEncoded})
|
modifyPasswordRequest.Replace("userPassword", []string{pwdEncoded})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
object/ldap_password_type.go
Normal file
36
object/ldap_password_type.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 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 object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateSSHA(password string) (string, error) {
|
||||||
|
salt := make([]byte, 4)
|
||||||
|
_, err := rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
combined := append([]byte(password), salt...)
|
||||||
|
hash := sha1.Sum(combined)
|
||||||
|
hashWithSalt := append(hash[:], salt...)
|
||||||
|
encoded := base64.StdEncoding.EncodeToString(hashWithSalt)
|
||||||
|
|
||||||
|
return "{SSHA}" + encoded, nil
|
||||||
|
}
|
@@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
|
func getNotificationClient(provider *Provider) (notify.Notifier, error) {
|
||||||
var client notify.Notifier
|
var client notify.Notifier
|
||||||
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata)
|
client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata, provider.RegionId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -69,8 +69,8 @@ type Organization struct {
|
|||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(200)" json:"masterPassword"`
|
||||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
DefaultPassword string `xorm:"varchar(200)" json:"defaultPassword"`
|
||||||
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||||
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
IpWhitelist string `xorm:"varchar(200)" json:"ipWhitelist"`
|
||||||
InitScore int `json:"initScore"`
|
InitScore int `json:"initScore"`
|
||||||
@@ -151,7 +151,10 @@ func getOrganization(owner string, name string) (*Organization, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganization(id string) (*Organization, error) {
|
func GetOrganization(id string) (*Organization, error) {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name, err := util.GetOwnerAndNameFromIdWithError(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return getOrganization(owner, name)
|
return getOrganization(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
@@ -70,6 +71,7 @@ type Provider struct {
|
|||||||
IdP string `xorm:"mediumtext" json:"idP"`
|
IdP string `xorm:"mediumtext" json:"idP"`
|
||||||
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
|
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
|
||||||
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
|
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
|
||||||
|
EmailRegex string `xorm:"varchar(200)" json:"emailRegex"`
|
||||||
|
|
||||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||||
}
|
}
|
||||||
@@ -200,6 +202,13 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.EmailRegex != "" {
|
||||||
|
_, err := regexp.Compile(provider.EmailRegex)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if name != provider.Name {
|
if name != provider.Name {
|
||||||
err := providerChangeTrigger(name, provider.Name)
|
err := providerChangeTrigger(name, provider.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -234,6 +243,13 @@ func AddProvider(provider *Provider) (bool, error) {
|
|||||||
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.EmailRegex != "" {
|
||||||
|
_, err := regexp.Compile(provider.EmailRegex)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.Insert(provider)
|
affected, err := ormer.Engine.Insert(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -421,7 +437,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
|||||||
providerInfo.ClientId = provider.ClientId2
|
providerInfo.ClientId = provider.ClientId2
|
||||||
providerInfo.ClientSecret = provider.ClientSecret2
|
providerInfo.ClientSecret = provider.ClientSecret2
|
||||||
}
|
}
|
||||||
} else if provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "ADFS" || provider.Type == "Okta" {
|
} else if provider.Type == "ADFS" || provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "Casdoor" || provider.Type == "Okta" {
|
||||||
providerInfo.HostUrl = provider.Domain
|
providerInfo.HostUrl = provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
logPostOnly = conf.GetConfigBool("logPostOnly")
|
logPostOnly = conf.GetConfigBool("logPostOnly")
|
||||||
passwordRegex = regexp.MustCompile("\"password\":\".+\"")
|
passwordRegex = regexp.MustCompile("\"password\":\"([^\"]*?)\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
type Record struct {
|
type Record struct {
|
||||||
|
@@ -338,6 +338,10 @@ func roleChangeTrigger(oldName string, newName string) error {
|
|||||||
|
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
for j, u := range role.Roles {
|
for j, u := range role.Roles {
|
||||||
|
if u == "*" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
owner, name := util.GetOwnerAndNameFromId(u)
|
owner, name := util.GetOwnerAndNameFromId(u)
|
||||||
if name == oldName {
|
if name == oldName {
|
||||||
role.Roles[j] = util.GetId(owner, newName)
|
role.Roles[j] = util.GetId(owner, newName)
|
||||||
@@ -358,6 +362,10 @@ func roleChangeTrigger(oldName string, newName string) error {
|
|||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
for j, u := range permission.Roles {
|
for j, u := range permission.Roles {
|
||||||
// u = organization/username
|
// u = organization/username
|
||||||
|
if u == "*" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
owner, name := util.GetOwnerAndNameFromId(u)
|
owner, name := util.GetOwnerAndNameFromId(u)
|
||||||
if name == oldName {
|
if name == oldName {
|
||||||
permission.Roles[j] = util.GetId(owner, newName)
|
permission.Roles[j] = util.GetId(owner, newName)
|
||||||
|
@@ -338,6 +338,9 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
} else if authnRequest.AssertionConsumerServiceURL == "" {
|
} else if authnRequest.AssertionConsumerServiceURL == "" {
|
||||||
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
|
return "", "", "", fmt.Errorf("err: SAML request don't has attribute 'AssertionConsumerServiceURL' in <samlp:AuthnRequest>")
|
||||||
}
|
}
|
||||||
|
if authnRequest.ProtocolBinding == "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" {
|
||||||
|
method = "POST"
|
||||||
|
}
|
||||||
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -184,6 +185,15 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
|
|||||||
return proxyTicket
|
return proxyTicket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func escapeXMLText(input string) (string, error) {
|
||||||
|
var sb strings.Builder
|
||||||
|
err := xml.EscapeText(&sb, []byte(input))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return sb.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func GenerateCasToken(userId string, service string) (string, error) {
|
func GenerateCasToken(userId string, service string) (string, error) {
|
||||||
user, err := GetUser(userId)
|
user, err := GetUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -225,6 +235,11 @@ func GenerateCasToken(userId string, service string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if value != "" {
|
if value != "" {
|
||||||
|
if escapedValue, err := escapeXMLText(value); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
value = escapedValue
|
||||||
|
}
|
||||||
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
||||||
Name: k,
|
Name: k,
|
||||||
Value: value,
|
Value: value,
|
||||||
|
@@ -309,22 +309,29 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var oldTokenScope string
|
||||||
if application.TokenFormat == "JWT-Standard" {
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
_, err = ParseStandardJwtToken(refreshToken, cert)
|
oldToken, err := ParseStandardJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
oldTokenScope = oldToken.Scope
|
||||||
} else {
|
} else {
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
oldToken, err := ParseJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
oldTokenScope = oldToken.Scope
|
||||||
|
}
|
||||||
|
|
||||||
|
if scope == "" {
|
||||||
|
scope = oldTokenScope
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new token
|
// generate a new token
|
||||||
|
@@ -129,6 +129,7 @@ type User struct {
|
|||||||
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
|
||||||
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
Okta string `xorm:"okta varchar(100)" json:"okta"`
|
||||||
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
|
||||||
|
Kwai string `xorm:"kwai varchar(100)" json:"kwai"`
|
||||||
Line string `xorm:"line varchar(100)" json:"line"`
|
Line string `xorm:"line varchar(100)" json:"line"`
|
||||||
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
|
Amazon string `xorm:"amazon varchar(100)" json:"amazon"`
|
||||||
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
|
Auth0 string `xorm:"auth0 varchar(100)" json:"auth0"`
|
||||||
@@ -237,6 +238,7 @@ type MfaAccount struct {
|
|||||||
AccountName string `xorm:"varchar(100)" json:"accountName"`
|
AccountName string `xorm:"varchar(100)" json:"accountName"`
|
||||||
Issuer string `xorm:"varchar(100)" json:"issuer"`
|
Issuer string `xorm:"varchar(100)" json:"issuer"`
|
||||||
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
|
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
|
||||||
|
Origin string `xorm:"varchar(100)" json:"origin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FaceId struct {
|
type FaceId struct {
|
||||||
@@ -679,6 +681,10 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
user.Password = oldUser.Password
|
user.Password = oldUser.Password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.Id != oldUser.Id && user.Id == "" {
|
||||||
|
user.Id = oldUser.Id
|
||||||
|
}
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
|
||||||
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -693,7 +699,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
||||||
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
||||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "kwai", "line", "amazon",
|
||||||
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||||
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
|
||||||
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
|
||||||
@@ -840,11 +846,14 @@ func AddUser(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := GetUserCount(user.Owner, "", "", "")
|
rankingItem := GetAccountItemByName("Ranking", organization)
|
||||||
if err != nil {
|
if rankingItem != nil {
|
||||||
return false, err
|
count, err := GetUserCount(user.Owner, "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
user.Ranking = int(count + 1)
|
||||||
}
|
}
|
||||||
user.Ranking = int(count + 1)
|
|
||||||
|
|
||||||
if user.Groups != nil && len(user.Groups) > 0 {
|
if user.Groups != nil && len(user.Groups) > 0 {
|
||||||
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
_, err = userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||||
@@ -956,6 +965,14 @@ func DeleteUser(user *User) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok, err := userEnforcer.DeleteGroupsForUser(user.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
organization, err := GetOrganizationByUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@@ -57,7 +57,7 @@ type VerificationRecord struct {
|
|||||||
Receiver string `xorm:"varchar(100) index notnull" json:"receiver"`
|
Receiver string `xorm:"varchar(100) index notnull" json:"receiver"`
|
||||||
Code string `xorm:"varchar(10) notnull" json:"code"`
|
Code string `xorm:"varchar(10) notnull" json:"code"`
|
||||||
Time int64 `xorm:"notnull" json:"time"`
|
Time int64 `xorm:"notnull" json:"time"`
|
||||||
IsUsed bool
|
IsUsed bool `xorm:"notnull" json:"isUsed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsAllowSend(user *User, remoteAddr, recordType string) error {
|
func IsAllowSend(user *User, remoteAddr, recordType string) error {
|
||||||
|
@@ -68,8 +68,10 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
|||||||
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
||||||
|
|
||||||
if organization == "" {
|
if organization == "" {
|
||||||
w.Write(r.Response(radius.CodeAccessReject))
|
organization = conf.GetConfigString("radiusDefaultOrganization")
|
||||||
return
|
if organization == "" {
|
||||||
|
organization = "built-in"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
|
@@ -133,6 +133,11 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
path += urlPath
|
path += urlPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
organizationThemeCookie, err := appendThemeCookie(ctx, urlPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(path, "/../") || !util.FileExist(path) {
|
if strings.Contains(path, "/../") || !util.FileExist(path) {
|
||||||
path = webBuildFolder + "/index.html"
|
path = webBuildFolder + "/index.html"
|
||||||
}
|
}
|
||||||
@@ -149,13 +154,13 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if oldStaticBaseUrl == newStaticBaseUrl {
|
if oldStaticBaseUrl == newStaticBaseUrl {
|
||||||
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path)
|
makeGzipResponse(ctx.ResponseWriter, ctx.Request, path, organizationThemeCookie)
|
||||||
} else {
|
} else {
|
||||||
serveFileWithReplace(ctx.ResponseWriter, ctx.Request, path)
|
serveFileWithReplace(ctx.ResponseWriter, ctx.Request, path, organizationThemeCookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serveFileWithReplace(w http.ResponseWriter, r *http.Request, name string) {
|
func serveFileWithReplace(w http.ResponseWriter, r *http.Request, name string, organizationThemeCookie *OrganizationThemeCookie) {
|
||||||
f, err := os.Open(filepath.Clean(name))
|
f, err := os.Open(filepath.Clean(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -168,7 +173,13 @@ func serveFileWithReplace(w http.ResponseWriter, r *http.Request, name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldContent := util.ReadStringFromPath(name)
|
oldContent := util.ReadStringFromPath(name)
|
||||||
newContent := strings.ReplaceAll(oldContent, oldStaticBaseUrl, newStaticBaseUrl)
|
newContent := oldContent
|
||||||
|
if organizationThemeCookie != nil {
|
||||||
|
newContent = strings.ReplaceAll(newContent, "https://cdn.casbin.org/img/favicon.png", organizationThemeCookie.Favicon)
|
||||||
|
newContent = strings.ReplaceAll(newContent, "<title>Casdoor</title>", fmt.Sprintf("<title>%s</title>", organizationThemeCookie.DisplayName))
|
||||||
|
}
|
||||||
|
|
||||||
|
newContent = strings.ReplaceAll(newContent, oldStaticBaseUrl, newStaticBaseUrl)
|
||||||
|
|
||||||
http.ServeContent(w, r, d.Name(), d.ModTime(), strings.NewReader(newContent))
|
http.ServeContent(w, r, d.Name(), d.ModTime(), strings.NewReader(newContent))
|
||||||
}
|
}
|
||||||
@@ -182,14 +193,14 @@ func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
|||||||
return w.Writer.Write(b)
|
return w.Writer.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeGzipResponse(w http.ResponseWriter, r *http.Request, path string) {
|
func makeGzipResponse(w http.ResponseWriter, r *http.Request, path string, organizationThemeCookie *OrganizationThemeCookie) {
|
||||||
if !enableGzip || !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
if !enableGzip || !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||||
serveFileWithReplace(w, r, path)
|
serveFileWithReplace(w, r, path, organizationThemeCookie)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Encoding", "gzip")
|
w.Header().Set("Content-Encoding", "gzip")
|
||||||
gz := gzip.NewWriter(w)
|
gz := gzip.NewWriter(w)
|
||||||
defer gz.Close()
|
defer gz.Close()
|
||||||
gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
gzw := gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||||
serveFileWithReplace(gzw, r, path)
|
serveFileWithReplace(gzw, r, path, organizationThemeCookie)
|
||||||
}
|
}
|
||||||
|
119
routers/theme_filter.go
Normal file
119
routers/theme_filter.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2025 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 (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/beego/beego/context"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrganizationThemeCookie struct {
|
||||||
|
ThemeData *object.ThemeData
|
||||||
|
LogoUrl string
|
||||||
|
FooterHtml string
|
||||||
|
Favicon string
|
||||||
|
DisplayName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendThemeCookie(ctx *context.Context, urlPath string) (*OrganizationThemeCookie, error) {
|
||||||
|
organizationThemeCookie, err := getOrganizationThemeCookieFromUrlPath(ctx, urlPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if organizationThemeCookie != nil {
|
||||||
|
return organizationThemeCookie, setThemeDataCookie(ctx, organizationThemeCookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOrganizationThemeCookieFromUrlPath(ctx *context.Context, urlPath string) (*OrganizationThemeCookie, error) {
|
||||||
|
var application *object.Application
|
||||||
|
var organization *object.Organization
|
||||||
|
var err error
|
||||||
|
if urlPath == "/login" {
|
||||||
|
application, err = object.GetDefaultApplication(fmt.Sprintf("admin/built-in"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(urlPath, "/login/oauth/authorize") {
|
||||||
|
clientId := ctx.Input.Query("client_id")
|
||||||
|
if clientId == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
application, err = object.GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(urlPath, "/login/saml") {
|
||||||
|
owner, _ := strings.CutPrefix(urlPath, "/login/saml/authorize/")
|
||||||
|
application, err = object.GetApplication(owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(urlPath, "/login/") {
|
||||||
|
owner, _ := strings.CutPrefix(urlPath, "/login/")
|
||||||
|
if owner == "undefined" || strings.Count(owner, "/") > 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
application, err = object.GetDefaultApplication(fmt.Sprintf("admin/%s", owner))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(urlPath, "/cas/") && strings.HasSuffix(urlPath, "/login") {
|
||||||
|
owner, _ := strings.CutPrefix(urlPath, "/cas/")
|
||||||
|
owner, _ = strings.CutSuffix(owner, "/login")
|
||||||
|
application, err = object.GetApplication(owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if application == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
organization = application.OrganizationObj
|
||||||
|
if organization == nil {
|
||||||
|
organization, err = object.GetOrganization(fmt.Sprintf("admin/%s", application.Organization))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
organizationThemeCookie := &OrganizationThemeCookie{
|
||||||
|
application.ThemeData,
|
||||||
|
application.Logo,
|
||||||
|
application.FooterHtml,
|
||||||
|
organization.Favicon,
|
||||||
|
organization.DisplayName,
|
||||||
|
}
|
||||||
|
|
||||||
|
return organizationThemeCookie, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setThemeDataCookie(ctx *context.Context, organizationThemeCookie *OrganizationThemeCookie) error {
|
||||||
|
themeDataString, err := json.Marshal(organizationThemeCookie.ThemeData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.SetCookie("organizationTheme", string(themeDataString))
|
||||||
|
ctx.SetCookie("organizationLogo", organizationThemeCookie.LogoUrl)
|
||||||
|
ctx.SetCookie("organizationFootHtml", organizationThemeCookie.FooterHtml)
|
||||||
|
return nil
|
||||||
|
}
|
@@ -23,7 +23,10 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
|||||||
case "AWS S3":
|
case "AWS S3":
|
||||||
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
|
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
|
||||||
case "MinIO":
|
case "MinIO":
|
||||||
return NewMinIOS3StorageProvider(clientId, clientSecret, "_", bucket, endpoint), nil
|
if region == "" {
|
||||||
|
region = "_"
|
||||||
|
}
|
||||||
|
return NewMinIOS3StorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
|
||||||
case "Aliyun OSS":
|
case "Aliyun OSS":
|
||||||
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
|
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint), nil
|
||||||
case "Tencent Cloud COS":
|
case "Tencent Cloud COS":
|
||||||
|
@@ -7558,6 +7558,9 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int64"
|
"format": "int64"
|
||||||
},
|
},
|
||||||
|
"kwai": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@@ -4981,6 +4981,8 @@ definitions:
|
|||||||
karma:
|
karma:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
kwai:
|
||||||
|
type: string
|
||||||
language:
|
language:
|
||||||
type: string
|
type: string
|
||||||
lark:
|
lark:
|
||||||
|
97
util/process.go
Normal file
97
util/process.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2025 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPidByPort(port int) (int, error) {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
cmd = exec.Command("cmd", "/c", "netstat -ano | findstr :"+strconv.Itoa(port))
|
||||||
|
case "darwin", "linux":
|
||||||
|
cmd = exec.Command("lsof", "-t", "-i", ":"+strconv.Itoa(port))
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
if exitErr.ExitCode() == 1 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) > 0 {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if fields[1] == "0.0.0.0:"+strconv.Itoa(port) {
|
||||||
|
pid, err := strconv.Atoi(fields[len(fields)-1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pid, err := strconv.Atoi(fields[0])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return pid, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func StopOldInstance(port int) error {
|
||||||
|
pid, err := getPidByPort(port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pid == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
process, err := os.FindProcess(pid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = process.Kill()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
fmt.Printf("The old instance with pid: %d has been stopped\n", pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
const CracoLessPlugin = require("craco-less");
|
const CracoLessPlugin = require("craco-less");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devServer: {
|
devServer: {
|
||||||
@@ -55,47 +56,42 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
webpack: {
|
webpack: {
|
||||||
configure: {
|
configure: (webpackConfig, { env, paths }) => {
|
||||||
|
paths.appBuild = path.resolve(__dirname, "build-temp");
|
||||||
|
webpackConfig.output.path = path.resolve(__dirname, "build-temp");
|
||||||
|
|
||||||
// ignore webpack warnings by source-map-loader
|
// ignore webpack warnings by source-map-loader
|
||||||
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
|
// https://github.com/facebook/create-react-app/pull/11752#issuecomment-1345231546
|
||||||
ignoreWarnings: [
|
webpackConfig.ignoreWarnings = [
|
||||||
function ignoreSourcemapsloaderWarnings(warning) {
|
function ignoreSourcemapsloaderWarnings(warning) {
|
||||||
return (
|
return (
|
||||||
warning.module &&
|
warning.module &&
|
||||||
warning.module.resource.includes('node_modules') &&
|
warning.module.resource.includes("node_modules") &&
|
||||||
warning.details &&
|
warning.details &&
|
||||||
warning.details.includes('source-map-loader')
|
warning.details.includes("source-map-loader")
|
||||||
)
|
);
|
||||||
},
|
},
|
||||||
],
|
];
|
||||||
|
|
||||||
// use polyfill Buffer with Webpack 5
|
// use polyfill Buffer with Webpack 5
|
||||||
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
|
// https://viglucci.io/articles/how-to-polyfill-buffer-with-webpack-5
|
||||||
// https://craco.js.org/docs/configuration/webpack/
|
// https://craco.js.org/docs/configuration/webpack/
|
||||||
resolve: {
|
webpackConfig.resolve.fallback = {
|
||||||
fallback: {
|
buffer: require.resolve("buffer/"),
|
||||||
// "process": require.resolve('process/browser'),
|
process: false,
|
||||||
// "util": require.resolve("util/"),
|
util: false,
|
||||||
// "url": require.resolve("url/"),
|
url: false,
|
||||||
// "zlib": require.resolve("browserify-zlib"),
|
zlib: false,
|
||||||
// "stream": require.resolve("stream-browserify"),
|
stream: false,
|
||||||
// "http": require.resolve("stream-http"),
|
http: false,
|
||||||
// "https": require.resolve("https-browserify"),
|
https: false,
|
||||||
// "assert": require.resolve("assert/"),
|
assert: false,
|
||||||
"buffer": require.resolve('buffer/'),
|
crypto: false,
|
||||||
"process": false,
|
os: false,
|
||||||
"util": false,
|
fs: false,
|
||||||
"url": false,
|
};
|
||||||
"zlib": false,
|
|
||||||
"stream": false,
|
return webpackConfig;
|
||||||
"http": false,
|
|
||||||
"https": false,
|
|
||||||
"assert": false,
|
|
||||||
"buffer": false,
|
|
||||||
"crypto": false,
|
|
||||||
"os": false,
|
|
||||||
"fs": false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
21
web/mv.js
Normal file
21
web/mv.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const sourceDir = path.join(__dirname, "build-temp");
|
||||||
|
const targetDir = path.join(__dirname, "build");
|
||||||
|
|
||||||
|
if (!fs.existsSync(sourceDir)) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`Source directory "${sourceDir}" does not exist.`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(targetDir)) {
|
||||||
|
fs.rmSync(targetDir, {recursive: true, force: true});
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Target directory "${targetDir}" has been deleted successfully.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.renameSync(sourceDir, targetDir);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`Renamed "${sourceDir}" to "${targetDir}" successfully.`);
|
@@ -57,6 +57,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env PORT=7001 craco start",
|
"start": "cross-env PORT=7001 craco start",
|
||||||
"build": "craco build",
|
"build": "craco build",
|
||||||
|
"postbuild": "node mv.js",
|
||||||
"test": "craco test",
|
"test": "craco test",
|
||||||
"eject": "craco eject",
|
"eject": "craco eject",
|
||||||
"crowdin:sync": "crowdin upload && crowdin download",
|
"crowdin:sync": "crowdin upload && crowdin download",
|
||||||
|
@@ -36,6 +36,7 @@ const {Footer, Content} = Layout;
|
|||||||
|
|
||||||
import {setTwoToneColor} from "@ant-design/icons";
|
import {setTwoToneColor} from "@ant-design/icons";
|
||||||
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
import * as ApplicationBackend from "./backend/ApplicationBackend";
|
||||||
|
import * as Cookie from "cookie";
|
||||||
|
|
||||||
setTwoToneColor("rgb(87,52,211)");
|
setTwoToneColor("rgb(87,52,211)");
|
||||||
|
|
||||||
@@ -269,7 +270,9 @@ class App extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFooter() {
|
renderFooter(logo, footerHtml) {
|
||||||
|
logo = logo ?? this.state.logo;
|
||||||
|
footerHtml = footerHtml ?? this.state.application?.footerHtml;
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
|
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
|
||||||
@@ -280,14 +283,14 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
{
|
{
|
||||||
this.state.application?.footerHtml && this.state.application.footerHtml !== "" ?
|
footerHtml && footerHtml !== "" ?
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div dangerouslySetInnerHTML={{__html: this.state.application.footerHtml}} />
|
<div dangerouslySetInnerHTML={{__html: footerHtml}} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
: (
|
: (
|
||||||
Conf.CustomFooter !== null ? Conf.CustomFooter : (
|
Conf.CustomFooter !== null ? Conf.CustomFooter : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
|
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={logo} /></a>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -360,11 +363,27 @@ class App extends Component {
|
|||||||
|
|
||||||
renderPage() {
|
renderPage() {
|
||||||
if (this.isDoorPages()) {
|
if (this.isDoorPages()) {
|
||||||
|
let themeData = this.state.themeData;
|
||||||
|
let logo = this.state.logo;
|
||||||
|
let footerHtml = null;
|
||||||
|
if (this.state.organization === undefined) {
|
||||||
|
const curCookie = Cookie.parse(document.cookie);
|
||||||
|
if (curCookie["organizationTheme"] && curCookie["organizationTheme"] !== "null") {
|
||||||
|
themeData = JSON.parse(curCookie["organizationTheme"]);
|
||||||
|
}
|
||||||
|
if (curCookie["organizationLogo"] && curCookie["organizationLogo"] !== "") {
|
||||||
|
logo = curCookie["organizationLogo"];
|
||||||
|
}
|
||||||
|
if (curCookie["organizationFootHtml"] && curCookie["organizationFootHtml"] !== "") {
|
||||||
|
footerHtml = curCookie["organizationFootHtml"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider theme={{
|
<ConfigProvider theme={{
|
||||||
token: {
|
token: {
|
||||||
colorPrimary: this.state.themeData.colorPrimary,
|
colorPrimary: themeData.colorPrimary,
|
||||||
borderRadius: this.state.themeData.borderRadius,
|
borderRadius: themeData.borderRadius,
|
||||||
},
|
},
|
||||||
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||||
}}>
|
}}>
|
||||||
@@ -401,7 +420,7 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
</Content>
|
</Content>
|
||||||
{
|
{
|
||||||
this.renderFooter()
|
this.renderFooter(logo, footerHtml)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.renderAiAssistant()
|
this.renderAiAssistant()
|
||||||
|
@@ -19,6 +19,7 @@ import "codemirror/mode/properties/properties";
|
|||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import IframeEditor from "./IframeEditor";
|
import IframeEditor from "./IframeEditor";
|
||||||
import {Tabs} from "antd";
|
import {Tabs} from "antd";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
const {TabPane} = Tabs;
|
const {TabPane} = Tabs;
|
||||||
|
|
||||||
@@ -68,8 +69,8 @@ const CasbinEditor = ({model, onModelTextChange}) => {
|
|||||||
return (
|
return (
|
||||||
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
<div style={{height: "100%", width: "100%", display: "flex", flexDirection: "column"}}>
|
||||||
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
<Tabs activeKey={activeKey} onChange={handleTabChange} style={{flex: "0 0 auto", marginTop: "-10px"}}>
|
||||||
<TabPane tab="Basic Editor" key="basic" />
|
<TabPane tab={i18next.t("model:Basic Editor")} key="basic" />
|
||||||
<TabPane tab="Advanced Editor" key="advanced" />
|
<TabPane tab={i18next.t("model:Advanced Editor")} key="advanced" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
<div style={{flex: "1 1 auto", overflow: "hidden"}}>
|
||||||
{activeKey === "advanced" ? (
|
{activeKey === "advanced" ? (
|
||||||
|
@@ -111,7 +111,7 @@ class EntryPage extends React.Component {
|
|||||||
<div className={`${isDarkMode ? "loginBackgroundDark" : "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={{width: "100%", margin: "0 auto", position: "absolute"}} />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} applicationName={authConfig.appName} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage {...this.props} application={this.state.application} onUpdateApplication={onUpdateApplication} {...props} />)} />
|
||||||
|
@@ -33,18 +33,6 @@ class GroupListPage extends BaseListPage {
|
|||||||
}
|
}
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
super.UNSAFE_componentWillMount();
|
super.UNSAFE_componentWillMount();
|
||||||
this.getGroups(this.state.owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
getGroups(organizationName) {
|
|
||||||
GroupBackend.getGroups(organizationName)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === "ok") {
|
|
||||||
this.setState({
|
|
||||||
groups: res.data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newGroup() {
|
newGroup() {
|
||||||
@@ -188,12 +176,8 @@ class GroupListPage extends BaseListPage {
|
|||||||
{record.parentId}
|
{record.parentId}
|
||||||
</Link>;
|
</Link>;
|
||||||
}
|
}
|
||||||
const parentGroup = this.state.groups.find((group) => group.name === text);
|
return <Link to={`/groups/${record.owner}/${record.parentId}`}>
|
||||||
if (parentGroup === undefined) {
|
{record?.parentName}
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return <Link to={`/groups/${parentGroup.owner}/${parentGroup.name}`}>
|
|
||||||
{parentGroup?.displayName}
|
|
||||||
</Link>;
|
</Link>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -215,12 +199,11 @@ class GroupListPage extends BaseListPage {
|
|||||||
width: "180px",
|
width: "180px",
|
||||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||||
render: (text, record, index) => {
|
render: (text, record, index) => {
|
||||||
const haveChildren = this.state.groups.find((group) => group.parentId === record.id) !== undefined;
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/groups/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/groups/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
|
||||||
<PopconfirmModal
|
<PopconfirmModal
|
||||||
disabled={haveChildren}
|
disabled={record.haveChildren}
|
||||||
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
|
||||||
onConfirm={() => this.deleteGroup(index)}
|
onConfirm={() => this.deleteGroup(index)}
|
||||||
>
|
>
|
||||||
|
@@ -17,6 +17,7 @@ import React, {forwardRef, useEffect, useImperativeHandle, useRef, useState} fro
|
|||||||
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) => {
|
||||||
const iframeRef = useRef(null);
|
const iframeRef = useRef(null);
|
||||||
const [iframeReady, setIframeReady] = useState(false);
|
const [iframeReady, setIframeReady] = useState(false);
|
||||||
|
const currentLang = localStorage.getItem("language") || "en";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMessage = (event) => {
|
const handleMessage = (event) => {
|
||||||
@@ -26,24 +27,31 @@ const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) =>
|
|||||||
onModelTextChange(event.data.modelText);
|
onModelTextChange(event.data.modelText);
|
||||||
} else if (event.data.type === "iframeReady") {
|
} else if (event.data.type === "iframeReady") {
|
||||||
setIframeReady(true);
|
setIframeReady(true);
|
||||||
iframeRef.current?.contentWindow.postMessage({
|
if (initialModelText && iframeRef.current?.contentWindow) {
|
||||||
type: "initializeModel",
|
iframeRef.current.contentWindow.postMessage({
|
||||||
modelText: initialModelText,
|
type: "initializeModel",
|
||||||
}, "*");
|
modelText: initialModelText,
|
||||||
|
lang: currentLang,
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("message", handleMessage);
|
window.addEventListener("message", handleMessage);
|
||||||
return () => window.removeEventListener("message", handleMessage);
|
return () => window.removeEventListener("message", handleMessage);
|
||||||
}, [onModelTextChange, initialModelText]);
|
}, [onModelTextChange, initialModelText, currentLang]);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
getModelText: () => {
|
getModelText: () => {
|
||||||
iframeRef.current?.contentWindow.postMessage({type: "getModelText"}, "*");
|
if (iframeRef.current?.contentWindow) {
|
||||||
|
iframeRef.current.contentWindow.postMessage({
|
||||||
|
type: "getModelText",
|
||||||
|
}, "*");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateModelText: (newModelText) => {
|
updateModelText: (newModelText) => {
|
||||||
if (iframeReady) {
|
if (iframeReady && iframeRef.current?.contentWindow) {
|
||||||
iframeRef.current?.contentWindow.postMessage({
|
iframeRef.current.contentWindow.postMessage({
|
||||||
type: "updateModelText",
|
type: "updateModelText",
|
||||||
modelText: newModelText,
|
modelText: newModelText,
|
||||||
}, "*");
|
}, "*");
|
||||||
@@ -54,7 +62,7 @@ const IframeEditor = forwardRef(({initialModelText, onModelTextChange}, ref) =>
|
|||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
src="https://editor.casbin.org/model-editor"
|
src={`https://editor.casbin.org/model-editor?lang=${currentLang}`}
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
width="100%"
|
width="100%"
|
||||||
height="500px"
|
height="500px"
|
||||||
|
@@ -106,6 +106,22 @@ class InvitationEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
copySignupLink() {
|
||||||
|
let defaultApplication;
|
||||||
|
if (this.state.invitation.owner === "built-in") {
|
||||||
|
defaultApplication = "app-built-in";
|
||||||
|
} else {
|
||||||
|
const selectedOrganization = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner);
|
||||||
|
defaultApplication = selectedOrganization.defaultApplication;
|
||||||
|
if (!defaultApplication) {
|
||||||
|
Setting.showMessage("error", i18next.t("invitation:You need to specify a default application for ") + selectedOrganization.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
|
||||||
|
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
||||||
|
}
|
||||||
|
|
||||||
renderInvitation() {
|
renderInvitation() {
|
||||||
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
|
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
|
||||||
return (
|
return (
|
||||||
@@ -114,16 +130,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}
|
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}
|
||||||
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} onClick={() => {
|
<Button style={{marginLeft: "20px"}} onClick={_ => this.copySignupLink()}>
|
||||||
let defaultApplication;
|
|
||||||
if (this.state.invitation.owner === "built-in") {
|
|
||||||
defaultApplication = "app-built-in";
|
|
||||||
} else {
|
|
||||||
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
|
|
||||||
}
|
|
||||||
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
|
|
||||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
|
||||||
}}>
|
|
||||||
{i18next.t("application:Copy signup page URL")}
|
{i18next.t("application:Copy signup page URL")}
|
||||||
</Button>
|
</Button>
|
||||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
@@ -330,16 +337,7 @@ class InvitationEditPage extends React.Component {
|
|||||||
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
<div style={{marginTop: "20px", marginLeft: "40px"}}>
|
||||||
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
|
||||||
<Button style={{marginLeft: "20px"}} size="large" onClick={() => {
|
<Button style={{marginLeft: "20px"}} size="large" onClick={_ => this.copySignupLink()}>
|
||||||
let defaultApplication;
|
|
||||||
if (this.state.invitation.owner === "built-in") {
|
|
||||||
defaultApplication = "app-built-in";
|
|
||||||
} else {
|
|
||||||
defaultApplication = Setting.getArrayItem(this.state.organizations, "name", this.state.invitation.owner).defaultApplication;
|
|
||||||
}
|
|
||||||
copy(`${window.location.origin}/signup/${defaultApplication}?invitationCode=${this.state.invitation?.defaultCode}`);
|
|
||||||
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
|
|
||||||
}}>
|
|
||||||
{i18next.t("application:Copy signup page URL")}
|
{i18next.t("application:Copy signup page URL")}
|
||||||
</Button>
|
</Button>
|
||||||
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
|
||||||
|
@@ -228,6 +228,21 @@ 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("general:Password type"), i18next.t("general:Password type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={21}>
|
||||||
|
<Select virtual={false} style={{width: "100%"}} value={this.state.ldap.passwordType ?? []} onChange={(value => {
|
||||||
|
this.updateLdapField("passwordType", value);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Option key={"Plain"} value={"Plain"}>{i18next.t("general:Plain")}</Option>
|
||||||
|
<Option key={"SSHA"} value={"SSHA"} >SSHA</Option>
|
||||||
|
<Option key={"MD5"} value={"MD5"} >MD5</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:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
|
{Setting.getLabel(i18next.t("ldap:Default group"), i18next.t("ldap:Default group - Tooltip"))} :
|
||||||
|
@@ -297,6 +297,8 @@ class ProviderEditPage extends React.Component {
|
|||||||
return Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Scene"), i18next.t("provider:Scene - Tooltip"));
|
||||||
} else if (provider.type === "WeChat Pay") {
|
} else if (provider.type === "WeChat Pay") {
|
||||||
return Setting.getLabel(i18next.t("provider:App ID"), i18next.t("provider:App ID - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:App ID"), i18next.t("provider:App ID - Tooltip"));
|
||||||
|
} else if (provider.type === "CUCloud") {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Account ID"), i18next.t("provider:Account ID - Tooltip"));
|
||||||
} else {
|
} else {
|
||||||
return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"));
|
||||||
}
|
}
|
||||||
@@ -393,6 +395,9 @@ class ProviderEditPage extends React.Component {
|
|||||||
} else if (provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
|
} else if (provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
|
||||||
text = i18next.t("provider:App Key");
|
text = i18next.t("provider:App Key");
|
||||||
tooltip = i18next.t("provider:App Key - Tooltip");
|
tooltip = i18next.t("provider:App Key - Tooltip");
|
||||||
|
} else if (provider.type === "CUCloud") {
|
||||||
|
text = i18next.t("provider:Topic name");
|
||||||
|
tooltip = i18next.t("provider:Topic name - Tooltip");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,6 +638,20 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
this.state.provider.category === "OAuth" ? (
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Email regex"), i18next.t("provider:Email regex - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22}>
|
||||||
|
<TextArea rows={4} value={this.state.provider.emailRegex} onChange={e => {
|
||||||
|
this.updateProviderField("emailRegex", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.type === "Custom" ? (
|
this.state.provider.type === "Custom" ? (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -757,7 +776,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (
|
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" && this.state.provider.type !== "CUCloud" ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<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}>
|
||||||
@@ -770,7 +789,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{
|
{
|
||||||
(this.state.provider.type === "WeChat Pay") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
|
(this.state.provider.type === "WeChat Pay" || this.state.provider.type === "CUCloud") || (this.state.provider.category === "Email" && (this.state.provider.type === "Azure ACS" || this.state.provider.type === "SendGrid")) ? null : (
|
||||||
<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}>
|
||||||
{this.getClientSecret2Label(this.state.provider)} :
|
{this.getClientSecret2Label(this.state.provider)} :
|
||||||
@@ -856,9 +875,9 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? (
|
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email", "CUCloud"].includes(this.state.provider.type) ? (
|
||||||
<div>
|
<div>
|
||||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
{["Local File System", "CUCloud"].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"), i18next.t("provider:Region endpoint for Internet"))} :
|
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
|
||||||
@@ -870,7 +889,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", "Casdoor"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology", "Casdoor", "CUCloud"].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"))} :
|
||||||
@@ -882,7 +901,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Local File System", "CUCloud"].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}>
|
||||||
{["Casdoor"].includes(this.state.provider.type) ?
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
@@ -896,7 +915,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "CUCloud"].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:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||||
@@ -908,7 +927,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
{["Custom HTTP SMS", "Synology", "Casdoor"].includes(this.state.provider.type) ? null : (
|
{["Custom HTTP SMS", "Synology", "Casdoor", "CUCloud"].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"))} :
|
||||||
@@ -932,7 +951,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
) : null}
|
) : null}
|
||||||
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS"].includes(this.state.provider.type) ? (
|
{["AWS S3", "Tencent Cloud COS", "Qiniu Cloud Kodo", "Casdoor", "CUCloud OSS", "MinIO", "CUCloud"].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}>
|
||||||
{["Casdoor"].includes(this.state.provider.type) ?
|
{["Casdoor"].includes(this.state.provider.type) ?
|
||||||
@@ -971,7 +990,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
) : null}
|
) : null}
|
||||||
{["Custom HTTP"].includes(this.state.provider.type) ? (
|
{["Custom HTTP", "CUCloud"].includes(this.state.provider.type) ? (
|
||||||
<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("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
|
||||||
@@ -983,7 +1002,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
) : null}
|
) : null}
|
||||||
{["Google Chat"].includes(this.state.provider.type) ? (
|
{["Google Chat", "CUCloud"].includes(this.state.provider.type) ? (
|
||||||
<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("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Metadata"), i18next.t("provider:Metadata - Tooltip"))} :
|
||||||
|
@@ -405,6 +405,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_viber.png`,
|
logo: `${StaticBaseUrl}/img/social_viber.png`,
|
||||||
url: "https://www.viber.com/",
|
url: "https://www.viber.com/",
|
||||||
},
|
},
|
||||||
|
"CUCloud": {
|
||||||
|
logo: `${StaticBaseUrl}/img/cucloud.png`,
|
||||||
|
url: "https://www.cucloud.cn/",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -985,6 +989,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Bilibili", name: "Bilibili"},
|
{id: "Bilibili", name: "Bilibili"},
|
||||||
{id: "Okta", name: "Okta"},
|
{id: "Okta", name: "Okta"},
|
||||||
{id: "Douyin", name: "Douyin"},
|
{id: "Douyin", name: "Douyin"},
|
||||||
|
{id: "Kwai", name: "Kwai"},
|
||||||
{id: "Line", name: "Line"},
|
{id: "Line", name: "Line"},
|
||||||
{id: "Amazon", name: "Amazon"},
|
{id: "Amazon", name: "Amazon"},
|
||||||
{id: "Auth0", name: "Auth0"},
|
{id: "Auth0", name: "Auth0"},
|
||||||
@@ -1136,6 +1141,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Reddit", name: "Reddit"},
|
{id: "Reddit", name: "Reddit"},
|
||||||
{id: "Rocket Chat", name: "Rocket Chat"},
|
{id: "Rocket Chat", name: "Rocket Chat"},
|
||||||
{id: "Viber", name: "Viber"},
|
{id: "Viber", name: "Viber"},
|
||||||
|
{id: "CUCloud", name: "CUCloud"},
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@@ -204,7 +204,7 @@ class AuthCallback extends React.Component {
|
|||||||
}
|
}
|
||||||
const SAMLResponse = res.data;
|
const SAMLResponse = res.data;
|
||||||
const redirectUri = res.data2.redirectUrl;
|
const redirectUri = res.data2.redirectUrl;
|
||||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@@ -264,6 +264,9 @@ class ForgetPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onValuesChange={(changedValues, allValues) => {
|
onValuesChange={(changedValues, allValues) => {
|
||||||
|
if (!changedValues.dest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const verifyType = changedValues.dest?.indexOf("@") === -1 ? "phone" : "email";
|
const verifyType = changedValues.dest?.indexOf("@") === -1 ? "phone" : "email";
|
||||||
this.setState({
|
this.setState({
|
||||||
dest: changedValues.dest,
|
dest: changedValues.dest,
|
||||||
|
31
web/src/auth/KwaiLoginButton.js
Normal file
31
web/src/auth/KwaiLoginButton.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// 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 {createButton} from "react-social-login-buttons";
|
||||||
|
import {StaticBaseUrl} from "../Setting";
|
||||||
|
|
||||||
|
function Icon({width = 24, height = 24}) {
|
||||||
|
return <img src={`${StaticBaseUrl}/buttons/kwai.svg`} alt="Sign in with Kwai" style={{width: width, height: height}} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
text: "Sign in with Kwai",
|
||||||
|
icon: Icon,
|
||||||
|
style: {background: "#ffffff", color: "#000000"},
|
||||||
|
activeStyle: {background: "#ededee"},
|
||||||
|
};
|
||||||
|
|
||||||
|
const KwaiLoginButton = createButton(config);
|
||||||
|
|
||||||
|
export default KwaiLoginButton;
|
@@ -37,6 +37,7 @@ import RedirectForm from "../common/RedirectForm";
|
|||||||
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
|
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
|
||||||
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
|
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
|
||||||
import * as ProviderButton from "./ProviderButton";
|
import * as ProviderButton from "./ProviderButton";
|
||||||
|
import {EmailMfaType, SmsMfaType, TotpMfaType} from "./MfaSetupPage";
|
||||||
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
|
const FaceRecognitionModal = lazy(() => import("../common/modal/FaceRecognitionModal"));
|
||||||
|
|
||||||
class LoginPage extends React.Component {
|
class LoginPage extends React.Component {
|
||||||
@@ -440,19 +441,12 @@ class LoginPage extends React.Component {
|
|||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
if (res.data === NextMfa) {
|
if (res.data === NextMfa) {
|
||||||
this.setState({
|
this.setState({
|
||||||
getVerifyTotp: () => {
|
mfaProps: res.data2,
|
||||||
return (
|
selectedMfaProp: this.getPreferredMfaProp(res.data2),
|
||||||
<MfaAuthVerifyForm
|
}, () => {
|
||||||
mfaProps={res.data2}
|
this.setState({
|
||||||
formValues={values}
|
getVerifyTotp: () => this.renderMfaAuthVerifyForm(values, casParams, loginHandler),
|
||||||
authParams={casParams}
|
});
|
||||||
application={this.getApplicationObj()}
|
|
||||||
onFail={(errorMessage) => {
|
|
||||||
Setting.showMessage("error", errorMessage);
|
|
||||||
}}
|
|
||||||
onSuccess={(res) => loginHandler(res)}
|
|
||||||
/>);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
loginHandler(res);
|
loginHandler(res);
|
||||||
@@ -505,7 +499,7 @@ class LoginPage extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
const SAMLResponse = res.data;
|
const SAMLResponse = res.data;
|
||||||
const redirectUri = res.data2.redirectUrl;
|
const redirectUri = res.data2.redirectUrl;
|
||||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
Setting.goToLink(`${redirectUri}${redirectUri.includes("?") ? "&" : "?"}SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -513,19 +507,12 @@ class LoginPage extends React.Component {
|
|||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
if (res.data === NextMfa) {
|
if (res.data === NextMfa) {
|
||||||
this.setState({
|
this.setState({
|
||||||
getVerifyTotp: () => {
|
mfaProps: res.data2,
|
||||||
return (
|
selectedMfaProp: this.getPreferredMfaProp(res.data2),
|
||||||
<MfaAuthVerifyForm
|
}, () => {
|
||||||
mfaProps={res.data2}
|
this.setState({
|
||||||
formValues={values}
|
getVerifyTotp: () => this.renderMfaAuthVerifyForm(values, oAuthParams, loginHandler),
|
||||||
authParams={oAuthParams}
|
});
|
||||||
application={this.getApplicationObj()}
|
|
||||||
onFail={(errorMessage) => {
|
|
||||||
Setting.showMessage("error", errorMessage);
|
|
||||||
}}
|
|
||||||
onSuccess={(res) => loginHandler(res)}
|
|
||||||
/>);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} else if (res.data === "SelectPlan") {
|
} else if (res.data === "SelectPlan") {
|
||||||
// paid-user does not have active or pending subscription, go to application default pricing page to select-plan
|
// paid-user does not have active or pending subscription, go to application default pricing page to select-plan
|
||||||
@@ -545,6 +532,49 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderMfaAuthVerifyForm(values, authParams, onSuccess) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MfaAuthVerifyForm
|
||||||
|
mfaProps={this.state.selectedMfaProp}
|
||||||
|
formValues={values}
|
||||||
|
authParams={authParams}
|
||||||
|
application={this.getApplicationObj()}
|
||||||
|
onFail={(errorMessage) => {
|
||||||
|
Setting.showMessage("error", errorMessage);
|
||||||
|
}}
|
||||||
|
onSuccess={(res) => onSuccess(res)}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
{
|
||||||
|
this.state.mfaProps.map((mfa) => {
|
||||||
|
if (this.state.selectedMfaProp.mfaType === mfa.mfaType) {return null;}
|
||||||
|
let mfaI18n = "";
|
||||||
|
switch (mfa.mfaType) {
|
||||||
|
case SmsMfaType: mfaI18n = i18next.t("mfa:Use SMS"); break;
|
||||||
|
case TotpMfaType: mfaI18n = i18next.t("mfa:Use Authenticator App"); break ;
|
||||||
|
case EmailMfaType: mfaI18n = i18next.t("mfa:Use Email") ;break;
|
||||||
|
}
|
||||||
|
return <div key={mfa.mfaType}><Button type={"link"} onClick={() => {
|
||||||
|
this.setState({
|
||||||
|
selectedMfaProp: mfa,
|
||||||
|
});
|
||||||
|
}}>{mfaI18n}</Button></div>;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPreferredMfaProp(mfaProps) {
|
||||||
|
for (const i in mfaProps) {
|
||||||
|
if (mfaProps[i].isPreffered) {
|
||||||
|
return mfaProps[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mfaProps[0];
|
||||||
|
}
|
||||||
|
|
||||||
isProviderVisible(providerItem) {
|
isProviderVisible(providerItem) {
|
||||||
if (this.state.mode === "signup") {
|
if (this.state.mode === "signup") {
|
||||||
return Setting.isProviderVisibleForSignUp(providerItem);
|
return Setting.isProviderVisibleForSignUp(providerItem);
|
||||||
|
@@ -119,6 +119,10 @@ const authInfo = {
|
|||||||
scope: "user_info",
|
scope: "user_info",
|
||||||
endpoint: "https://open.douyin.com/platform/oauth/connect",
|
endpoint: "https://open.douyin.com/platform/oauth/connect",
|
||||||
},
|
},
|
||||||
|
Kwai: {
|
||||||
|
scope: "user_info",
|
||||||
|
endpoint: "https://open.kuaishou.com/oauth2/connect",
|
||||||
|
},
|
||||||
Custom: {
|
Custom: {
|
||||||
endpoint: "https://example.com/",
|
endpoint: "https://example.com/",
|
||||||
},
|
},
|
||||||
@@ -470,6 +474,8 @@ export function getAuthUrl(application, provider, method, code) {
|
|||||||
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${provider.domain}/v1/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
|
} else if (provider.type === "Douyin" || provider.type === "TikTok") {
|
||||||
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
return `${endpoint}?client_key=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
|
} else if (provider.type === "Kwai") {
|
||||||
|
return `${endpoint}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
|
||||||
} else if (provider.type === "Custom") {
|
} else if (provider.type === "Custom") {
|
||||||
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
|
return `${provider.customAuthUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${provider.scopes}&response_type=code&state=${state}`;
|
||||||
} else if (provider.type === "Bilibili") {
|
} else if (provider.type === "Bilibili") {
|
||||||
|
@@ -40,6 +40,7 @@ import SteamLoginButton from "./SteamLoginButton";
|
|||||||
import BilibiliLoginButton from "./BilibiliLoginButton";
|
import BilibiliLoginButton from "./BilibiliLoginButton";
|
||||||
import OktaLoginButton from "./OktaLoginButton";
|
import OktaLoginButton from "./OktaLoginButton";
|
||||||
import DouyinLoginButton from "./DouyinLoginButton";
|
import DouyinLoginButton from "./DouyinLoginButton";
|
||||||
|
import KwaiLoginButton from "./KwaiLoginButton";
|
||||||
import LoginButton from "./LoginButton";
|
import LoginButton from "./LoginButton";
|
||||||
import * as AuthBackend from "./AuthBackend";
|
import * as AuthBackend from "./AuthBackend";
|
||||||
import {WechatOfficialAccountModal} from "./Util";
|
import {WechatOfficialAccountModal} from "./Util";
|
||||||
@@ -96,6 +97,8 @@ function getSigninButton(provider) {
|
|||||||
return <OktaLoginButton text={text} align={"center"} />;
|
return <OktaLoginButton text={text} align={"center"} />;
|
||||||
} else if (provider.type === "Douyin") {
|
} else if (provider.type === "Douyin") {
|
||||||
return <DouyinLoginButton text={text} align={"center"} />;
|
return <DouyinLoginButton text={text} align={"center"} />;
|
||||||
|
} else if (provider.type === "Kwai") {
|
||||||
|
return <KwaiLoginButton text={text} align={"center"} />;
|
||||||
} else {
|
} else {
|
||||||
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
|
return <LoginButton key={provider.type} type={provider.type} logoUrl={getProviderLogoURL(provider)} />;
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,8 @@ export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application
|
|||||||
|
|
||||||
const verify = ({passcode}) => {
|
const verify = ({passcode}) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const values = {...formValues, passcode, mfaType};
|
const values = {...formValues, passcode};
|
||||||
|
values["mfaType"] = mfaProps.mfaType;
|
||||||
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
|
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
|
||||||
loginFunction(values, authParams).then((res) => {
|
loginFunction(values, authParams).then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
@@ -71,7 +72,7 @@ export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application
|
|||||||
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
<div style={{marginBottom: 24, textAlign: "center", fontSize: "24px"}}>
|
||||||
{i18next.t("mfa:Multi-factor authentication")}
|
{i18next.t("mfa:Multi-factor authentication")}
|
||||||
</div>
|
</div>
|
||||||
{mfaType === SmsMfaType || mfaType === EmailMfaType ? (
|
{mfaProps.mfaType === SmsMfaType || mfaProps.mfaType === EmailMfaType ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div style={{marginBottom: 24}}>
|
<div style={{marginBottom: 24}}>
|
||||||
{i18next.t("mfa:You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue")}
|
{i18next.t("mfa:You have enabled Multi-Factor Authentication, Please click 'Send Code' to continue")}
|
||||||
|
@@ -9,11 +9,11 @@ export function MfaEnableForm({user, mfaType, secret, recoveryCodes, dest, count
|
|||||||
const data = {
|
const data = {
|
||||||
mfaType,
|
mfaType,
|
||||||
secret,
|
secret,
|
||||||
recoveryCodes,
|
|
||||||
dest,
|
dest,
|
||||||
countryCode,
|
countryCode,
|
||||||
...user,
|
...user,
|
||||||
};
|
};
|
||||||
|
data["recoveryCodes"] = recoveryCodes[0];
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
MfaBackend.MfaSetupEnable(data).then(res => {
|
MfaBackend.MfaSetupEnable(data).then(res => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
|
@@ -105,7 +105,7 @@ export const PasswordModal = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasOldPassword = user.password !== "";
|
const hasOldPassword = (user.password !== "" || user.ldap !== "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Pokročilý editor",
|
||||||
|
"Basic Editor": "Základní editor",
|
||||||
"Edit Model": "Upravit model",
|
"Edit Model": "Upravit model",
|
||||||
"Model text": "Text modelu",
|
"Model text": "Text modelu",
|
||||||
"Model text - Tooltip": "Casbin model řízení přístupu, včetně vestavěných modelů jako ACL, RBAC, ABAC, RESTful, atd. Můžete také vytvářet vlastní modely. Pro více informací navštivte webové stránky Casbin",
|
"Model text - Tooltip": "Casbin model řízení přístupu, včetně vestavěných modelů jako ACL, RBAC, ABAC, RESTful, atd. Můžete také vytvářet vlastní modely. Pro více informací navštivte webové stránky Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Erweiterter Editor",
|
||||||
|
"Basic Editor": "Basis-Editor",
|
||||||
"Edit Model": "Modell bearbeiten",
|
"Edit Model": "Modell bearbeiten",
|
||||||
"Model text": "Modelltext",
|
"Model text": "Modelltext",
|
||||||
"Model text - Tooltip": "Casbin Zugriffskontrollmodell inklusive integrierter Modelle wie ACL, RBAC, ABAC, RESTful, usw. Sie können auch benutzerdefinierte Modelle erstellen. Weitere Informationen finden Sie auf der Casbin-Website",
|
"Model text - Tooltip": "Casbin Zugriffskontrollmodell inklusive integrierter Modelle wie ACL, RBAC, ABAC, RESTful, usw. Sie können auch benutzerdefinierte Modelle erstellen. Weitere Informationen finden Sie auf der Casbin-Website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Editor avanzado",
|
||||||
|
"Basic Editor": "Editor básico",
|
||||||
"Edit Model": "Editar modelo",
|
"Edit Model": "Editar modelo",
|
||||||
"Model text": "Texto modelo",
|
"Model text": "Texto modelo",
|
||||||
"Model text - Tooltip": "Modelo de control de acceso Casbin, incluyendo modelos integrados como ACL, RBAC, ABAC, RESTful, etc. También puede crear modelos personalizados. Para obtener más información, visite el sitio web de Casbin",
|
"Model text - Tooltip": "Modelo de control de acceso Casbin, incluyendo modelos integrados como ACL, RBAC, ABAC, RESTful, etc. También puede crear modelos personalizados. Para obtener más información, visite el sitio web de Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "کلید مخفی"
|
"Secret Key": "کلید مخفی"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "ویرایشگر پیشرفته",
|
||||||
|
"Basic Editor": "ویرایشگر ابتدایی",
|
||||||
"Edit Model": "ویرایش مدل",
|
"Edit Model": "ویرایش مدل",
|
||||||
"Model text": "متن مدل",
|
"Model text": "متن مدل",
|
||||||
"Model text - Tooltip": "مدل کنترل دسترسی Casbin، شامل مدلهای داخلی مانند ACL، RBAC، ABAC، RESTful و غیره. همچنین میتوانید مدلهای سفارشی ایجاد کنید. برای اطلاعات بیشتر، لطفاً به وبسایت Casbin مراجعه کنید",
|
"Model text - Tooltip": "مدل کنترل دسترسی Casbin، شامل مدلهای داخلی مانند ACL، RBAC، ABAC، RESTful و غیره. همچنین میتوانید مدلهای سفارشی ایجاد کنید. برای اطلاعات بیشتر، لطفاً به وبسایت Casbin مراجعه کنید",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Éditeur avancé",
|
||||||
|
"Basic Editor": "Éditeur de base",
|
||||||
"Edit Model": "Modifier le modèle",
|
"Edit Model": "Modifier le modèle",
|
||||||
"Model text": "Définition du modèle",
|
"Model text": "Définition du modèle",
|
||||||
"Model text - Tooltip": "Modèle de contrôle d'accès Casbin, comprenant des modèles intégrés tels que ACL, RBAC, ABAC, RESTful, etc. Vous pouvez également créer des modèles personnalisés. Pour plus d'informations, veuillez visiter le site web de Casbin",
|
"Model text - Tooltip": "Modèle de contrôle d'accès Casbin, comprenant des modèles intégrés tels que ACL, RBAC, ABAC, RESTful, etc. Vous pouvez également créer des modèles personnalisés. Pour plus d'informations, veuillez visiter le site web de Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Editor lanjutan",
|
||||||
|
"Basic Editor": "Editor dasar",
|
||||||
"Edit Model": "Mengedit Model",
|
"Edit Model": "Mengedit Model",
|
||||||
"Model text": "Teks Model",
|
"Model text": "Teks Model",
|
||||||
"Model text - Tooltip": "Model kontrol akses Casbin, termasuk model bawaan seperti ACL, RBAC, ABAC, RESTful, dll. Anda juga dapat membuat model kustom. Untuk informasi lebih lanjut, silakan kunjungi situs web Casbin",
|
"Model text - Tooltip": "Model kontrol akses Casbin, termasuk model bawaan seperti ACL, RBAC, ABAC, RESTful, dll. Anda juga dapat membuat model kustom. Untuk informasi lebih lanjut, silakan kunjungi situs web Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "編集モデル",
|
"Edit Model": "編集モデル",
|
||||||
"Model text": "モデルテキスト",
|
"Model text": "モデルテキスト",
|
||||||
"Model text - Tooltip": "Casbinのアクセス制御モデルには、ACL、RBAC、ABAC、RESTfulなどの組み込みモデルが含まれています。カスタムモデルも作成できます。詳細については、Casbinのウェブサイトをご覧ください",
|
"Model text - Tooltip": "Casbinのアクセス制御モデルには、ACL、RBAC、ABAC、RESTfulなどの組み込みモデルが含まれています。カスタムモデルも作成できます。詳細については、Casbinのウェブサイトをご覧ください",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "고급 편집기",
|
||||||
|
"Basic Editor": "기본 편집기",
|
||||||
"Edit Model": "편집 형태 모델",
|
"Edit Model": "편집 형태 모델",
|
||||||
"Model text": "모델 텍스트",
|
"Model text": "모델 텍스트",
|
||||||
"Model text - Tooltip": "Casbin 액세스 제어 모델은 ACL, RBAC, ABAC, RESTful 등의 내장된 모델을 포함하며 사용자 정의 모델도 만들 수 있습니다. 자세한 정보는 Casbin 웹 사이트를 방문하십시오",
|
"Model text - Tooltip": "Casbin 액세스 제어 모델은 ACL, RBAC, ABAC, RESTful 등의 내장된 모델을 포함하며 사용자 정의 모델도 만들 수 있습니다. 자세한 정보는 Casbin 웹 사이트를 방문하십시오",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Editor Avançado",
|
||||||
|
"Basic Editor": "Editor Básico",
|
||||||
"Edit Model": "Editar Modelo",
|
"Edit Model": "Editar Modelo",
|
||||||
"Model text": "Texto do Modelo",
|
"Model text": "Texto do Modelo",
|
||||||
"Model text - Tooltip": "Modelo de controle de acesso Casbin, incluindo modelos incorporados como ACL, RBAC, ABAC, RESTful, etc. Você também pode criar modelos personalizados. Para obter mais informações, visite o site do Casbin",
|
"Model text - Tooltip": "Modelo de controle de acesso Casbin, incluindo modelos incorporados como ACL, RBAC, ABAC, RESTful, etc. Você também pode criar modelos personalizados. Para obter mais informações, visite o site do Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Расширенный редактор",
|
||||||
|
"Basic Editor": "Базовый редактор",
|
||||||
"Edit Model": "Редактировать модель",
|
"Edit Model": "Редактировать модель",
|
||||||
"Model text": "Модельный текст",
|
"Model text": "Модельный текст",
|
||||||
"Model text - Tooltip": "Модель контроля доступа Casbin, включая встроенные модели, такие как ACL, RBAC, ABAC, RESTful и т. д. Вы также можете создавать свои собственные модели. Для получения дополнительной информации, пожалуйста, посетите веб-сайт Casbin",
|
"Model text - Tooltip": "Модель контроля доступа Casbin, включая встроенные модели, такие как ACL, RBAC, ABAC, RESTful и т. д. Вы также можете создавать свои собственные модели. Для получения дополнительной информации, пожалуйста, посетите веб-сайт Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Rozšírený editor",
|
||||||
|
"Basic Editor": "Základný editor",
|
||||||
"Edit Model": "Upraviť model",
|
"Edit Model": "Upraviť model",
|
||||||
"Model text": "Text modelu",
|
"Model text": "Text modelu",
|
||||||
"Model text - Tooltip": "Model prístupu Casbin, vrátane vstavaných modelov ako ACL, RBAC, ABAC, RESTful, atď. Môžete tiež vytvoriť vlastné modely. Pre viac informácií navštívte web Casbin",
|
"Model text - Tooltip": "Model prístupu Casbin, vrátane vstavaných modelov ako ACL, RBAC, ABAC, RESTful, atď. Môžete tiež vytvoriť vlastné modely. Pre viac informácií navštívte web Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Edit Model",
|
"Edit Model": "Edit Model",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Advanced Editor",
|
||||||
|
"Basic Editor": "Basic Editor",
|
||||||
"Edit Model": "Modeli Düzenle",
|
"Edit Model": "Modeli Düzenle",
|
||||||
"Model text": "Model text",
|
"Model text": "Model text",
|
||||||
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
"Model text - Tooltip": "Casbin access control model, including built-in models like ACL, RBAC, ABAC, RESTful, etc. You can also create custom models. For more information, please visit the Casbin website",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Розширений редактор",
|
||||||
|
"Basic Editor": "Базовий редактор",
|
||||||
"Edit Model": "Редагувати модель",
|
"Edit Model": "Редагувати модель",
|
||||||
"Model text": "Текст моделі",
|
"Model text": "Текст моделі",
|
||||||
"Model text - Tooltip": "Модель контролю доступу Casbin, включаючи такі вбудовані моделі, як ACL, RBAC, ABAC, RESTful тощо. Ви також можете створювати власні моделі. ",
|
"Model text - Tooltip": "Модель контролю доступу Casbin, включаючи такі вбудовані моделі, як ACL, RBAC, ABAC, RESTful тощо. Ви також можете створювати власні моделі. ",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "Secret Key"
|
"Secret Key": "Secret Key"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "Editor nâng cao",
|
||||||
|
"Basic Editor": "Editor cơ bản",
|
||||||
"Edit Model": "Sửa mô hình",
|
"Edit Model": "Sửa mô hình",
|
||||||
"Model text": "Văn bản mẫu",
|
"Model text": "Văn bản mẫu",
|
||||||
"Model text - Tooltip": "Mô hình kiểm soát truy cập Casbin, bao gồm các mô hình tích hợp như ACL, RBAC, ABAC, RESTful, v.v. Bạn cũng có thể tạo các mô hình tùy chỉnh. Để biết thêm thông tin, vui lòng truy cập trang web Casbin",
|
"Model text - Tooltip": "Mô hình kiểm soát truy cập Casbin, bao gồm các mô hình tích hợp như ACL, RBAC, ABAC, RESTful, v.v. Bạn cũng có thể tạo các mô hình tùy chỉnh. Để biết thêm thông tin, vui lòng truy cập trang web Casbin",
|
||||||
|
@@ -592,6 +592,8 @@
|
|||||||
"Secret Key": "密钥"
|
"Secret Key": "密钥"
|
||||||
},
|
},
|
||||||
"model": {
|
"model": {
|
||||||
|
"Advanced Editor": "高级编辑器",
|
||||||
|
"Basic Editor": "基础编辑器",
|
||||||
"Edit Model": "编辑模型",
|
"Edit Model": "编辑模型",
|
||||||
"Model text": "模型文本",
|
"Model text": "模型文本",
|
||||||
"Model text - Tooltip": "Casbin访问控制模型,支持ACL、RBAC、ABAC、RESTful等内置模型,也可以自定义模型,具体请查看Casbin官网",
|
"Model text - Tooltip": "Casbin访问控制模型,支持ACL、RBAC、ABAC、RESTful等内置模型,也可以自定义模型,具体请查看Casbin官网",
|
||||||
|
@@ -105,6 +105,18 @@ class MfaAccountTable extends React.Component {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("mfaAccount:Origin"),
|
||||||
|
dataIndex: "origin",
|
||||||
|
key: "origin",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Input value={text} onChange={e => {
|
||||||
|
this.updateField(table, index, "origin", e.target.value);
|
||||||
|
}} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: i18next.t("mfaAccount:Secret Key"),
|
title: i18next.t("mfaAccount:Secret Key"),
|
||||||
dataIndex: "secretKey",
|
dataIndex: "secretKey",
|
||||||
|
Reference in New Issue
Block a user