Compare commits

...

15 Commits

Author SHA1 Message Date
85cb68eb66 feat: unbind LDAP clients if not used any more 2023-12-02 17:51:25 +08:00
b25b5f0249 Support original accessToken in token APIs 2023-12-02 16:56:18 +08:00
947dcf6e75 Fix "All" roles bug in permission edit page 2023-12-02 15:26:52 +08:00
113c27db73 Improve logout's id_token_hint logic 2023-12-02 02:13:34 +08:00
badfe34755 feat: add "nonce" into the OAuth and OIDC tokens, for some apps require "nonce" to integrate (#2522) 2023-12-01 18:29:39 +08:00
a5f9f61381 feat: add token hash to improve performance 2023-11-30 18:05:30 +08:00
2ce8c93ead feat: Improve LDAP filter support (#2519) 2023-11-26 23:11:49 +08:00
da41ac7275 Improve error handling in getFaviconFileBuffer() 2023-11-25 18:31:33 +08:00
fd0c70a827 feat: Revert "feat: fix login page path after logout" (#2516)
This reverts commit 23d4488b64.
2023-11-24 15:52:59 +08:00
c4a6f07672 Allow app user in demo mode 2023-11-24 01:04:23 +08:00
a67f541171 feat: in LDAP, search '*' should return all properties (#2511) 2023-11-22 23:52:40 +08:00
192968bac8 Improve permission.State 2023-11-22 00:03:33 +08:00
23d4488b64 feat: fix login page path after logout (#2493)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-11-21 23:37:35 +08:00
23f4684e1d feat: make MFA works for CAS login (#2506)
* feat: make MFA works for CAS login

* fix: Reduced code redundancy

* fix: Modified the format of the code.

* fix: fix an error with the 'res' variable

* Update LoginPage.js

* Update LoginPage.js

* Update LoginPage.js

* Update MfaAuthVerifyForm.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-11-21 21:35:19 +08:00
1a91e7b0f9 feat: support LDAP in Linux (#2508) 2023-11-21 14:01:27 +08:00
23 changed files with 568 additions and 107 deletions

View File

@ -151,7 +151,7 @@ func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath
return true
} else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
return true
}
return false

View File

@ -282,17 +282,15 @@ func (c *ApiController) Logout() {
return
}
affected, application, token, err := object.ExpireTokenByAccessToken(accessToken)
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
if err != nil {
c.ResponseError(err.Error())
return
}
if !affected {
if token == nil {
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
return
@ -319,7 +317,15 @@ func (c *ApiController) Logout() {
return
} else {
if application.IsRedirectUriValid(redirectUri) {
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
redirectUrl := redirectUri
if state != "" {
if strings.Contains(redirectUri, "?") {
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
} else {
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
}
}
c.Ctx.Redirect(http.StatusFound, redirectUrl)
} else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return

View File

@ -155,7 +155,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
} else {
scope := c.Input().Get("scope")
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
nonce := c.Input().Get("nonce")
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
resp = tokenToResponse(token)
}
} else if form.Type == ResponseTypeSaml { // saml flow

View File

@ -243,7 +243,13 @@ func (c *ApiController) GetAllObjects() {
return
}
c.ResponseOk(object.GetAllObjects(userId))
objects, err := object.GetAllObjects(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(objects)
}
func (c *ApiController) GetAllActions() {
@ -253,7 +259,13 @@ func (c *ApiController) GetAllActions() {
return
}
c.ResponseOk(object.GetAllActions(userId))
actions, err := object.GetAllActions(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(actions)
}
func (c *ApiController) GetAllRoles() {
@ -263,5 +275,11 @@ func (c *ApiController) GetAllRoles() {
return
}
c.ResponseOk(object.GetAllRoles(userId))
roles, err := object.GetAllRoles(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(roles)
}

View File

@ -59,6 +59,7 @@ func (c *ApiController) GetLdapUsers() {
c.ResponseError(err.Error())
return
}
defer conn.Close()
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
//if err != nil {

4
go.mod
View File

@ -24,7 +24,7 @@ require (
github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0
github.com/go-git/go-git/v5 v5.6.0
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.6.0
@ -62,7 +62,7 @@ require (
github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.12.0
golang.org/x/crypto v0.13.0
golang.org/x/net v0.14.0
golang.org/x/oauth2 v0.11.0
golang.org/x/text v0.13.0 // indirect

13
go.sum
View File

@ -780,6 +780,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -823,6 +825,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
@ -1083,6 +1086,8 @@ github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
@ -1112,6 +1117,8 @@ github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2C
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
@ -1950,6 +1957,8 @@ golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
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-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -2274,6 +2283,8 @@ golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -2290,6 +2301,8 @@ golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@ -16,6 +16,7 @@ package ldap
import (
"fmt"
"hash/fnv"
"log"
"github.com/casdoor/casdoor/conf"
@ -113,11 +114,22 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
}
for _, user := range users {
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
e := ldap.NewSearchResultEntry(dn)
e.AddAttribute(message.AttributeDescription("uid"), message.AttributeValue(user.Id))
e.AddAttribute(message.AttributeDescription("cn"), message.AttributeValue(user.Name))
for _, attr := range r.Attributes() {
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
e.AddAttribute("uidNumber", message.AttributeValue(uidNumberStr))
e.AddAttribute("gidNumber", message.AttributeValue(uidNumberStr))
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
e.AddAttribute("cn", message.AttributeValue(user.Name))
e.AddAttribute("uid", message.AttributeValue(user.Id))
attrs := r.Attributes()
for _, attr := range attrs {
if string(attr) == "*" {
attrs = AdditionalLdapAttributes
break
}
}
for _, attr := range attrs {
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
if string(attr) == "cn" {
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
@ -128,3 +140,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
}
w.Write(res)
}
func hash(s string) uint32 {
h := fnv.New32a()
h.Write([]byte(s))
return h.Sum32()
}

View File

@ -24,8 +24,72 @@ import (
"github.com/lor00x/goldap/message"
ldap "github.com/forestmgy/ldapserver"
"github.com/xorm-io/builder"
)
type AttributeMapper func(user *object.User) message.AttributeValue
type FieldRelation struct {
userField string
notSearchable bool
hideOnStarOp bool
fieldMapper AttributeMapper
}
func (rel FieldRelation) GetField() (string, error) {
if rel.notSearchable {
return "", fmt.Errorf("attribute %s not supported", rel.userField)
}
return rel.userField, nil
}
func (rel FieldRelation) GetAttributeValue(user *object.User) message.AttributeValue {
return rel.fieldMapper(user)
}
var ldapAttributesMapping = map[string]FieldRelation{
"cn": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Name)
}},
"uid": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Name)
}},
"displayname": {userField: "displayName", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.DisplayName)
}},
"email": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Email)
}},
"mail": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Email)
}},
"mobile": {userField: "phone", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Phone)
}},
"title": {userField: "tag", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Tag)
}},
"userPassword": {
userField: "userPassword",
notSearchable: true,
fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(getUserPasswordWithType(user))
},
},
}
var AdditionalLdapAttributes []message.LDAPString
func init() {
for k, v := range ldapAttributesMapping {
if v.hideOnStarOp {
continue
}
AdditionalLdapAttributes = append(AdditionalLdapAttributes, message.LDAPString(k))
}
}
func getNameAndOrgFromDN(DN string) (string, string, error) {
DNFields := strings.Split(DN, ",")
params := make(map[string]string, len(DNFields))
@ -87,6 +151,92 @@ func stringInSlice(value string, list []string) bool {
return false
}
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
switch f := filter.(type) {
case message.FilterAnd:
conditions := make([]builder.Cond, len(f))
for i, v := range f {
cond, err := buildUserFilterCondition(v)
if err != nil {
return nil, err
}
conditions[i] = cond
}
return builder.And(conditions...), nil
case message.FilterOr:
conditions := make([]builder.Cond, len(f))
for i, v := range f {
cond, err := buildUserFilterCondition(v)
if err != nil {
return nil, err
}
conditions[i] = cond
}
return builder.Or(conditions...), nil
case message.FilterNot:
cond, err := buildUserFilterCondition(f.Filter)
if err != nil {
return nil, err
}
return builder.Not{cond}, nil
case message.FilterEqualityMatch:
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
if err != nil {
return nil, err
}
return builder.Eq{field: string(f.AssertionValue())}, nil
case message.FilterPresent:
field, err := getUserFieldFromAttribute(string(f))
if err != nil {
return nil, err
}
return builder.NotNull{field}, nil
case message.FilterGreaterOrEqual:
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
if err != nil {
return nil, err
}
return builder.Gte{field: string(f.AssertionValue())}, nil
case message.FilterLessOrEqual:
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
if err != nil {
return nil, err
}
return builder.Lte{field: string(f.AssertionValue())}, nil
case message.FilterSubstrings:
field, err := getUserFieldFromAttribute(string(f.Type_()))
if err != nil {
return nil, err
}
var expr string
for _, substring := range f.Substrings() {
switch s := substring.(type) {
case message.SubstringInitial:
expr += string(s) + "%"
continue
case message.SubstringAny:
expr += string(s) + "%"
continue
case message.SubstringFinal:
expr += string(s)
continue
}
}
return builder.Expr(field+" LIKE ?", expr), nil
default:
return nil, fmt.Errorf("LDAP filter operation %#v not supported", f)
}
}
func buildSafeCondition(filter interface{}) builder.Cond {
condition, err := buildUserFilterCondition(filter)
if err != nil {
log.Printf("err = %v", err.Error())
return nil
}
return condition
}
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
var err error
r := m.GetSearchRequest()
@ -98,15 +248,14 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
if m.Client.IsGlobalAdmin && org == "*" {
filteredUsers, err = object.GetGlobalUsers()
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
if err != nil {
panic(err)
}
return filteredUsers, ldap.LDAPResultSuccess
}
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
filteredUsers, err = object.GetUsers(org)
filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
if err != nil {
panic(err)
}
@ -148,7 +297,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
return nil, ldap.LDAPResultNoSuchObject
}
users, err := object.GetUsersByTag(org, name)
users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
if err != nil {
panic(err)
}
@ -182,24 +331,17 @@ func getUserPasswordWithType(user *object.User) string {
}
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
switch attributeName {
case "cn":
return message.AttributeValue(user.Name)
case "uid":
return message.AttributeValue(user.Name)
case "displayname":
return message.AttributeValue(user.DisplayName)
case "email":
return message.AttributeValue(user.Email)
case "mail":
return message.AttributeValue(user.Email)
case "mobile":
return message.AttributeValue(user.Phone)
case "title":
return message.AttributeValue(user.Tag)
case "userPassword":
return message.AttributeValue(getUserPasswordWithType(user))
default:
v, ok := ldapAttributesMapping[attributeName]
if !ok {
return ""
}
return v.GetAttributeValue(user)
}
func getUserFieldFromAttribute(attributeName string) (string, error) {
v, ok := ldapAttributesMapping[attributeName]
if !ok {
return "", fmt.Errorf("attribute %s not supported", attributeName)
}
return v.GetField()
}

87
ldap/util_test.go Normal file
View File

@ -0,0 +1,87 @@
package ldap
import (
"testing"
"github.com/stretchr/testify/assert"
ber "github.com/go-asn1-ber/asn1-ber"
goldap "github.com/go-ldap/ldap/v3"
"github.com/lor00x/goldap/message"
"github.com/xorm-io/builder"
)
func args(exp ...interface{}) []interface{} {
return exp
}
func TestLdapFilterAsQuery(t *testing.T) {
scenarios := []struct {
description string
input string
expectedExpr string
expectedArgs []interface{}
}{
{"Should be SQL for FilterAnd", "(&(mail=2)(email=1))", "email=? AND email=?", args("2", "1")},
{"Should be SQL for FilterOr", "(|(mail=2)(email=1))", "email=? OR email=?", args("2", "1")},
{"Should be SQL for FilterNot", "(!(mail=2))", "NOT email=?", args("2")},
{"Should be SQL for FilterEqualityMatch", "(mail=2)", "email=?", args("2")},
{"Should be SQL for FilterPresent", "(mail=*)", "email IS NOT NULL", nil},
{"Should be SQL for FilterGreaterOrEqual", "(mail>=admin)", "email>=?", args("admin")},
{"Should be SQL for FilterLessOrEqual", "(mail<=admin)", "email<=?", args("admin")},
{"Should be SQL for FilterSubstrings", "(mail=admin*ex*c*m)", "email LIKE ?", args("admin%ex%c%m")},
}
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
searchRequest, err := buildLdapSearchRequest(scenery.input)
if err != nil {
assert.FailNow(t, "Unable to create searchRequest", err)
}
m, err := message.ReadLDAPMessage(message.NewBytes(0, searchRequest.Bytes()))
if err != nil {
assert.FailNow(t, "Unable to create searchRequest", err)
}
req := m.ProtocolOp().(message.SearchRequest)
cond, err := buildUserFilterCondition(req.Filter())
if err != nil {
assert.FailNow(t, "Unable to build condition", err)
}
expr, args, err := builder.ToSQL(cond)
if err != nil {
assert.FailNow(t, "Unable to build sql", err)
}
assert.Equal(t, scenery.expectedExpr, expr)
assert.Equal(t, scenery.expectedArgs, args)
})
}
}
func buildLdapSearchRequest(filter string) (*ber.Packet, error) {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 1, "MessageID"))
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, goldap.ApplicationSearchRequest, nil, "Search Request")
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Base DN"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, 0, "Scope"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, 0, "Deref Aliases"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 0, "Size Limit"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 0, "Time Limit"))
pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, false, "Types Only"))
// compile and encode filter
filterPacket, err := goldap.CompileFilter(filter)
if err != nil {
return nil, err
}
pkt.AppendChild(filterPacket)
// encode attributes
attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "*", "Attribute"))
pkt.AppendChild(attributesPacket)
packet.AppendChild(pkt)
return packet, nil
}

View File

@ -237,22 +237,28 @@ func checkLdapUserPassword(user *User, password string, lang string) error {
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
conn.Close()
return err
}
if len(searchResult.Entries) == 0 {
conn.Close()
continue
}
if len(searchResult.Entries) > 1 {
conn.Close()
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
}
hit = true
dn := searchResult.Entries[0].DN
if err := conn.Conn.Bind(dn, password); err == nil {
if err = conn.Conn.Bind(dn, password); err == nil {
ldapLoginSuccess = true
conn.Close()
break
}
conn.Close()
}
if !ldapLoginSuccess {
@ -368,7 +374,7 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
allowCount := 0
denyCount := 0
for _, permission := range permissions {
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
if !permission.IsEnabled || permission.State != "Approved" || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
continue
}

View File

@ -396,15 +396,22 @@ func initBuiltInPermission() {
Name: "permission-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Permission",
Description: "Built-in Permission",
Users: []string{"built-in/*"},
Groups: []string{},
Roles: []string{},
Domains: []string{},
Model: "model-built-in",
Adapter: "",
ResourceType: "Application",
Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"},
Effect: "Allow",
IsEnabled: true,
Submitter: "admin",
Approver: "admin",
ApproveTime: util.GetCurrentTime(),
State: "Approved",
}
_, err = AddPermission(permission)
if err != nil {

View File

@ -100,6 +100,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
users, err := conn.GetLdapUsers(ldap)
if err != nil {
conn.Close()
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
@ -111,6 +112,8 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
} else {
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
}
conn.Close()
}
}

View File

@ -81,6 +81,17 @@ func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
return &LdapConn{Conn: conn, IsAD: isAD}, nil
}
func (l *LdapConn) Close() {
if l.Conn == nil {
return
}
err := l.Conn.Unbind()
if err != nil {
panic(err)
}
}
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
SearchFilter := "(objectClass=*)"
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}

View File

@ -120,7 +120,11 @@ func checkPermissionValid(permission *Permission) error {
return nil
}
groupingPolicies := getGroupingPolicies(permission)
groupingPolicies, err := getGroupingPolicies(permission)
if err != nil {
return err
}
if len(groupingPolicies) > 0 {
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
if err != nil {

View File

@ -23,6 +23,7 @@ import (
"github.com/casbin/casbin/v2/log"
"github.com/casbin/casbin/v2/model"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
@ -137,6 +138,16 @@ func getPolicies(permission *Permission) [][]string {
}
func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
roleOwner, roleName := util.GetOwnerAndNameFromId(roleId)
if roleName == "*" {
roles, err := GetRoles(roleOwner)
if err != nil {
return []*Role{}, err
}
return roles, nil
}
role, err := GetRole(roleId)
if err != nil {
return []*Role{}, err
@ -162,7 +173,7 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
return roles, nil
}
func getGroupingPolicies(permission *Permission) [][]string {
func getGroupingPolicies(permission *Permission) ([][]string, error) {
var groupingPolicies [][]string
domainExist := len(permission.Domains) > 0
@ -170,12 +181,18 @@ func getGroupingPolicies(permission *Permission) [][]string {
for _, roleId := range permission.Roles {
visited := map[string]struct{}{}
if roleId == "*" {
roleId = util.GetId(permission.Owner, "*")
}
rolesInRole, err := getRolesInRole(roleId, visited)
if err != nil {
panic(err)
return nil, err
}
for _, role := range rolesInRole {
roleId := role.GetId()
roleId = role.GetId()
for _, subUser := range role.Users {
if domainExist {
for _, domain := range permission.Domains {
@ -198,7 +215,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
}
}
return groupingPolicies
return groupingPolicies, nil
}
func addPolicies(permission *Permission) error {
@ -231,7 +248,10 @@ func addGroupingPolicies(permission *Permission) error {
return err
}
groupingPolicies := getGroupingPolicies(permission)
groupingPolicies, err := getGroupingPolicies(permission)
if err != nil {
return err
}
if len(groupingPolicies) > 0 {
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
@ -249,7 +269,10 @@ func removeGroupingPolicies(permission *Permission) error {
return err
}
groupingPolicies := getGroupingPolicies(permission)
groupingPolicies, err := getGroupingPolicies(permission)
if err != nil {
return err
}
if len(groupingPolicies) > 0 {
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
@ -287,7 +310,12 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
return nil, err
}
for _, role := range GetAllRoles(userId) {
allRoles, err := GetAllRoles(userId)
if err != nil {
return nil, err
}
for _, role := range allRoles {
permissionsByRole, err := GetPermissionsByRole(role)
if err != nil {
return nil, err
@ -321,17 +349,17 @@ func GetAllActions(userId string) ([]string, error) {
})
}
func GetAllRoles(userId string) []string {
func GetAllRoles(userId string) ([]string, error) {
roles, err := getRolesByUser(userId)
if err != nil {
panic(err)
return nil, err
}
var res []string
res := []string{}
for _, role := range roles {
res = append(res, role.Name)
}
return res
return res, nil
}
func GetBuiltInModel(modelText string) (model.Model, error) {

View File

@ -17,6 +17,7 @@ package object
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"time"
@ -51,15 +52,17 @@ type Token struct {
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
Code string `xorm:"varchar(100) index" json:"code"`
AccessToken string `xorm:"mediumtext" json:"accessToken"`
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
ExpiresIn int `json:"expiresIn"`
Scope string `xorm:"varchar(100)" json:"scope"`
TokenType string `xorm:"varchar(100)" json:"tokenType"`
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
CodeIsUsed bool `json:"codeIsUsed"`
CodeExpireIn int64 `json:"codeExpireIn"`
Code string `xorm:"varchar(100) index" json:"code"`
AccessToken string `xorm:"mediumtext" json:"accessToken"`
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
AccessTokenHash string `xorm:"varchar(100) index" json:"accessTokenHash"`
RefreshTokenHash string `xorm:"varchar(100) index" json:"refreshTokenHash"`
ExpiresIn int `json:"expiresIn"`
Scope string `xorm:"varchar(100)" json:"scope"`
TokenType string `xorm:"varchar(100)" json:"tokenType"`
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
CodeIsUsed bool `json:"codeIsUsed"`
CodeExpireIn int64 `json:"codeExpireIn"`
}
type TokenWrapper struct {
@ -141,6 +144,48 @@ func getTokenByCode(code string) (*Token, error) {
return nil, nil
}
func GetTokenByAccessToken(accessToken string) (*Token, error) {
token := Token{AccessTokenHash: getTokenHash(accessToken)}
existed, err := ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
if !existed {
token = Token{AccessToken: accessToken}
existed, err = ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
}
if !existed {
return nil, nil
}
return &token, nil
}
func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
token := Token{RefreshTokenHash: getTokenHash(refreshToken)}
existed, err := ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
if !existed {
token = Token{RefreshToken: refreshToken}
existed, err = ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
}
if !existed {
return nil, nil
}
return &token, nil
}
func updateUsedByCode(token *Token) bool {
affected, err := ormer.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
if err != nil {
@ -159,6 +204,24 @@ func (token *Token) GetId() string {
return fmt.Sprintf("%s/%s", token.Owner, token.Name)
}
func getTokenHash(input string) string {
hash := sha256.Sum256([]byte(input))
res := hex.EncodeToString(hash[:])
if len(res) > 64 {
return res[:64]
}
return res
}
func (token *Token) popularHashes() {
if token.AccessTokenHash == "" && token.AccessToken != "" {
token.AccessTokenHash = getTokenHash(token.AccessToken)
}
if token.RefreshTokenHash == "" && token.RefreshToken != "" {
token.RefreshTokenHash = getTokenHash(token.RefreshToken)
}
}
func UpdateToken(id string, token *Token) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if t, err := getToken(owner, name); err != nil {
@ -167,6 +230,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
return false, nil
}
token.popularHashes()
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(token)
if err != nil {
return false, err
@ -176,6 +241,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
}
func AddToken(token *Token) (bool, error) {
token.popularHashes()
affected, err := ormer.Engine.Insert(token)
if err != nil {
return false, err
@ -194,18 +261,16 @@ func DeleteToken(token *Token) (bool, error) {
}
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
token := Token{AccessToken: accessToken}
existed, err := ormer.Engine.Get(&token)
token, err := GetTokenByAccessToken(accessToken)
if err != nil {
return false, nil, nil, err
}
if !existed {
if token == nil {
return false, nil, nil, nil
}
token.ExpiresIn = 0
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(&token)
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
if err != nil {
return false, nil, nil, err
}
@ -215,22 +280,7 @@ func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, e
return false, nil, nil, err
}
return affected != 0, application, &token, nil
}
func GetTokenByAccessToken(accessToken string) (*Token, error) {
// Check if the accessToken is in the database
token := Token{AccessToken: accessToken}
existed, err := ormer.Engine.Get(&token)
if err != nil {
return nil, err
}
if !existed {
return nil, nil
}
return &token, nil
return affected != 0, application, token, nil
}
func GetTokenByTokenAndApplication(token string, application string) (*Token, error) {
@ -432,16 +482,17 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
ErrorDescription: "client_id is invalid",
}, nil
}
if clientSecret != "" && application.ClientSecret != clientSecret {
return &TokenError{
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}, nil
}
// check whether the refresh token is valid, and has not expired.
token := Token{RefreshToken: refreshToken}
existed, err := ormer.Engine.Get(&token)
if err != nil || !existed {
token, err := GetTokenByRefreshToken(refreshToken)
if err != nil || token == nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: "refresh token is invalid, expired or revoked",
@ -452,6 +503,12 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
if err != nil {
return nil, err
}
if cert == nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
}, nil
}
_, err = ParseJwtToken(refreshToken, cert)
if err != nil {
@ -460,6 +517,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil
}
// generate a new token
user, err := getUser(application.Organization, token.User)
if err != nil {
@ -477,6 +535,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
if err != nil {
return nil, err
}
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return &TokenError{
@ -504,7 +563,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
return nil, err
}
_, err = DeleteToken(&token)
_, err = DeleteToken(token)
if err != nil {
return nil, err
}
@ -517,7 +576,6 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
ExpiresIn: newToken.ExpiresIn,
Scope: newToken.Scope,
}
return tokenWrapper, nil
}
@ -729,13 +787,13 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
// GetTokenByUser
// Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
err := ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
if err != nil {
return nil, err
}

View File

@ -22,6 +22,7 @@ import (
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/xorm-io/builder"
"github.com/xorm-io/core"
)
@ -231,6 +232,20 @@ func GetGlobalUsers() ([]*User, error) {
return users, nil
}
func GetGlobalUsersWithFilter(cond builder.Cond) ([]*User, error) {
users := []*User{}
session := ormer.Engine.Desc("created_time")
if cond != nil {
session = session.Where(cond)
}
err := session.Find(&users)
if err != nil {
return nil, err
}
return users, nil
}
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
users := []*User{}
session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder)
@ -266,9 +281,27 @@ func GetUsers(owner string) ([]*User, error) {
return users, nil
}
func GetUsersByTag(owner string, tag string) ([]*User, error) {
func GetUsersWithFilter(owner string, cond builder.Cond) ([]*User, error) {
users := []*User{}
err := ormer.Engine.Desc("created_time").Find(&users, &User{Owner: owner, Tag: tag})
session := ormer.Engine.Desc("created_time")
if cond != nil {
session = session.Where(cond)
}
err := session.Find(&users, &User{Owner: owner})
if err != nil {
return nil, err
}
return users, nil
}
func GetUsersByTagWithFilter(owner string, tag string, cond builder.Cond) ([]*User, error) {
users := []*User{}
session := ormer.Engine.Desc("created_time")
if cond != nil {
session = session.Where(cond)
}
err := session.Find(&users, &User{Owner: owner, Tag: tag})
if err != nil {
return nil, err
}

View File

@ -240,11 +240,11 @@ func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, str
if buffer != nil {
faviconUrl, err = GetFaviconUrl(buffer.String())
if err != nil {
return nil, "", err
}
if !strings.HasPrefix(faviconUrl, "http") {
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
fmt.Printf("getFaviconFileBuffer() error, faviconUrl is empty, error = %s\n", err.Error())
} else {
if !strings.HasPrefix(faviconUrl, "http") {
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
}
}
}

View File

@ -303,7 +303,7 @@ class PermissionEditPage extends React.Component {
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
</Col>
<Col span={22} >
<Select disabled={!this.hasRoleDefinition(this.state.model)} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
<Select disabled={!this.hasRoleDefinition(this.state.model)} placeholder={this.hasRoleDefinition(this.state.model) ? "" : "This field is disabled because the model is empty or it doesn't support RBAC (in another word, doesn't contain [role_definition])"} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
onChange={(value => {this.updatePermissionField("roles", value);})}
options={[
Setting.getOption(i18next.t("organization:All"), "*"),
@ -323,7 +323,7 @@ class PermissionEditPage extends React.Component {
})}
options={[
Setting.getOption(i18next.t("organization:All"), "*"),
...this.state.permission.domains.map((domain) => Setting.getOption(domain, domain)),
...this.state.permission.domains.filter(domain => domain !== "*").map((domain) => Setting.getOption(domain, domain)),
]}
/>
</Col>

View File

@ -44,7 +44,7 @@ class PermissionListPage extends BaseListPage {
submitter: this.props.account.name,
approver: "",
approveTime: "",
state: "Pending",
state: Setting.isLocalAdminUser(this.props.account) ? "Approved" : "Pending",
};
}

View File

@ -337,7 +337,7 @@ class LoginPage extends React.Component {
const casParams = Util.getCasParameters();
values["type"] = this.state.type;
AuthBackend.loginCas(values, casParams).then((res) => {
if (res.status === "ok") {
const loginHandler = (res) => {
let msg = "Logged in successfully. ";
if (casParams.service === "") {
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
@ -351,6 +351,28 @@ class LoginPage extends React.Component {
newUrl.searchParams.append("ticket", st);
window.location.href = newUrl.toString();
}
};
if (res.status === "ok") {
if (res.data === NextMfa) {
this.setState({
getVerifyTotp: () => {
return (
<MfaAuthVerifyForm
mfaProps={res.data2}
formValues={values}
authParams={casParams}
application={this.getApplicationObj()}
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
}}
onSuccess={(res) => loginHandler(res)}
/>);
},
});
} else {
loginHandler(res);
}
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
}
@ -361,7 +383,7 @@ class LoginPage extends React.Component {
this.populateOauthValues(values);
AuthBackend.login(values, oAuthParams)
.then((res) => {
const callback = (res) => {
const loginHandler = (res) => {
const responseType = values["type"];
if (responseType === "login") {
@ -396,12 +418,12 @@ class LoginPage extends React.Component {
<MfaAuthVerifyForm
mfaProps={res.data2}
formValues={values}
oAuthParams={oAuthParams}
authParams={oAuthParams}
application={this.getApplicationObj()}
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
}}
onSuccess={(res) => callback(res)}
onSuccess={(res) => loginHandler(res)}
/>);
},
});
@ -414,7 +436,7 @@ class LoginPage extends React.Component {
const sub = res.data2;
Setting.goToLink(`/buy-plan/${sub.owner}/${sub.pricing}/result?subscription=${sub.name}`);
} else {
callback(res);
loginHandler(res);
}
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);

View File

@ -24,7 +24,7 @@ import MfaVerifyTotpForm from "./MfaVerifyTotpForm";
export const NextMfa = "NextMfa";
export const RequiredMfa = "RequiredMfa";
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application, onSuccess, onFail}) {
formValues.password = "";
formValues.username = "";
const [loading, setLoading] = useState(false);
@ -34,7 +34,8 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
const verify = ({passcode}) => {
setLoading(true);
const values = {...formValues, passcode, mfaType};
AuthBackend.login(values, oAuthParams).then((res) => {
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
loginFunction(values, authParams).then((res) => {
if (res.status === "ok") {
onSuccess(res);
} else {
@ -49,7 +50,9 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
const recover = () => {
setLoading(true);
AuthBackend.login({...formValues, recoveryCode}, oAuthParams).then(res => {
const values = {...formValues, recoveryCode};
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
loginFunction(values, authParams).then((res) => {
if (res.status === "ok") {
onSuccess(res);
} else {