Compare commits

...

16 Commits

Author SHA1 Message Date
Yang Luo
85cb68eb66 feat: unbind LDAP clients if not used any more 2023-12-02 17:51:25 +08:00
Yang Luo
b25b5f0249 Support original accessToken in token APIs 2023-12-02 16:56:18 +08:00
Yang Luo
947dcf6e75 Fix "All" roles bug in permission edit page 2023-12-02 15:26:52 +08:00
Yang Luo
113c27db73 Improve logout's id_token_hint logic 2023-12-02 02:13:34 +08:00
Nex Zhu
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
Yang Luo
a5f9f61381 feat: add token hash to improve performance 2023-11-30 18:05:30 +08:00
Daniil Mikhaylov
2ce8c93ead feat: Improve LDAP filter support (#2519) 2023-11-26 23:11:49 +08:00
Yang Luo
da41ac7275 Improve error handling in getFaviconFileBuffer() 2023-11-25 18:31:33 +08:00
hsluoyz
fd0c70a827 feat: Revert "feat: fix login page path after logout" (#2516)
This reverts commit 23d4488b64.
2023-11-24 15:52:59 +08:00
Yang Luo
c4a6f07672 Allow app user in demo mode 2023-11-24 01:04:23 +08:00
Nex Zhu
a67f541171 feat: in LDAP, search '*' should return all properties (#2511) 2023-11-22 23:52:40 +08:00
Yang Luo
192968bac8 Improve permission.State 2023-11-22 00:03:33 +08:00
aiden
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
songjf
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
xzgan
1a91e7b0f9 feat: support LDAP in Linux (#2508) 2023-11-21 14:01:27 +08:00
Yang Luo
811999b6cc feat: fix error handling in CheckPassword() related functions 2023-11-20 21:49:19 +08:00
28 changed files with 718 additions and 221 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

@@ -34,6 +34,7 @@ import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid"
"golang.org/x/oauth2"
)
var (
@@ -154,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
@@ -331,8 +333,6 @@ func (c *ApiController) Login() {
}
var user *object.User
var msg string
if authForm.Password == "" {
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error(), nil)
@@ -354,20 +354,21 @@ func (c *ApiController) Login() {
}
// check result through Email or Phone
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
return
}
// disable the verification code
err := object.DisableVerificationCode(checkDest)
err = object.DisableVerificationCode(checkDest)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
} else {
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error(), nil)
return
@@ -386,7 +387,8 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error())
return
} else if enableCaptcha {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
var isHuman bool
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
if err != nil {
c.ResponseError(err.Error())
return
@@ -399,13 +401,15 @@ func (c *ApiController) Login() {
}
password := authForm.Password
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
}
if msg != "" {
resp = &Response{Status: "error", Msg: msg}
if err != nil {
c.ResponseError(err.Error())
return
} else {
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error())
return
@@ -416,7 +420,8 @@ func (c *ApiController) Login() {
return
}
organization, err := object.GetOrganizationByUser(user)
var organization *object.Organization
organization, err = object.GetOrganizationByUser(user)
if err != nil {
c.ResponseError(err.Error())
}
@@ -461,12 +466,15 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
var organization *object.Organization
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
if err != nil {
c.ResponseError(c.T(err.Error()))
}
provider, err := object.GetProvider(util.GetId("admin", authForm.Provider))
var provider *object.Provider
provider, err = object.GetProvider(util.GetId("admin", authForm.Provider))
if err != nil {
c.ResponseError(err.Error())
return
@@ -488,7 +496,8 @@ func (c *ApiController) Login() {
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
// OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
idProvider, err := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
var idProvider idp.IdProvider
idProvider, err = idp.GetIdProvider(idpInfo, authForm.RedirectUri)
if err != nil {
c.ResponseError(err.Error())
return
@@ -506,7 +515,8 @@ func (c *ApiController) Login() {
}
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
token, err := idProvider.GetToken(authForm.Code)
var token *oauth2.Token
token, err = idProvider.GetToken(authForm.Code)
if err != nil {
c.ResponseError(err.Error())
return
@@ -548,7 +558,7 @@ func (c *ApiController) Login() {
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
}
// sync info from 3rd-party if possible
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
if err != nil {
c.ResponseError(err.Error())
return
@@ -593,14 +603,16 @@ func (c *ApiController) Login() {
}
// Handle username conflicts
tmpUser, err := object.GetUser(util.GetId(application.Organization, userInfo.Username))
var tmpUser *object.User
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
if err != nil {
c.ResponseError(err.Error())
return
}
if tmpUser != nil {
uid, err := uuid.NewRandom()
var uid uuid.UUID
uid, err = uuid.NewRandom()
if err != nil {
c.ResponseError(err.Error())
return
@@ -611,14 +623,16 @@ func (c *ApiController) Login() {
}
properties := map[string]string{}
count, err := object.GetUserCount(application.Organization, "", "", "")
var count int64
count, err = object.GetUserCount(application.Organization, "", "", "")
if err != nil {
c.ResponseError(err.Error())
return
}
properties["no"] = strconv.Itoa(int(count + 2))
initScore, err := organization.GetInitScore()
var initScore int
initScore, err = organization.GetInitScore()
if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return
@@ -650,7 +664,8 @@ func (c *ApiController) Login() {
Properties: properties,
}
affected, err := object.AddUser(user)
var affected bool
affected, err = object.AddUser(user)
if err != nil {
c.ResponseError(err.Error())
return
@@ -672,7 +687,7 @@ func (c *ApiController) Login() {
}
// sync info from 3rd-party if possible
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
if err != nil {
c.ResponseError(err.Error())
return
@@ -708,7 +723,8 @@ func (c *ApiController) Login() {
return
}
oldUser, err := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
var oldUser *object.User
oldUser, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
return
@@ -719,7 +735,8 @@ func (c *ApiController) Login() {
return
}
user, err := object.GetUser(userId)
var user *object.User
user, err = object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
@@ -732,7 +749,8 @@ func (c *ApiController) Login() {
return
}
isLinked, err := object.LinkUserAccount(user, provider.Type, userInfo.Id)
var isLinked bool
isLinked, err = object.LinkUserAccount(user, provider.Type, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
return
@@ -745,7 +763,8 @@ func (c *ApiController) Login() {
}
}
} else if c.getMfaUserSession() != "" {
user, err := object.GetUser(c.getMfaUserSession())
var user *object.User
user, err = object.GetUser(c.getMfaUserSession())
if err != nil {
c.ResponseError(err.Error())
return
@@ -778,7 +797,8 @@ func (c *ApiController) Login() {
return
}
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error())
return
@@ -799,7 +819,8 @@ func (c *ApiController) Login() {
} else {
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error())
return

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 {

View File

@@ -476,16 +476,16 @@ func (c *ApiController) SetPassword() {
isAdmin := c.IsAdmin()
if isAdmin {
if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
}
} else if code == "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
}
@@ -518,11 +518,11 @@ func (c *ApiController) CheckUserPassword() {
return
}
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
if msg == "" {
c.ResponseOk()
_, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
} else {
c.ResponseError(msg)
c.ResponseOk()
}
}

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"
@@ -49,20 +50,20 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
if r.AuthenticationChoice() == "simple" {
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
if err != "" {
log.Printf("Bind failed ,ErrMsg=%s", err)
if err != nil {
log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
res.SetDiagnosticMessage(fmt.Sprintf("getNameAndOrgFromDN() error: %s", err.Error()))
w.Write(res)
return
}
bindPassword := string(r.AuthenticationSimple())
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
if err != "" {
if err != nil {
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err.Error())
w.Write(res)
return
}
@@ -78,7 +79,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
m.Client.OrgName = bindOrg
} else {
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
res.SetDiagnosticMessage("Authentication method not supported, please use Simple Authentication")
}
w.Write(res)
}
@@ -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,9 +24,73 @@ import (
"github.com/lor00x/goldap/message"
ldap "github.com/forestmgy/ldapserver"
"github.com/xorm-io/builder"
)
func getNameAndOrgFromDN(DN string) (string, string, string) {
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))
for _, field := range DNFields {
@@ -37,12 +101,12 @@ func getNameAndOrgFromDN(DN string) (string, string, string) {
}
if params["cn"] == "" {
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
return "", "", fmt.Errorf("please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com")
}
if params["ou"] == "" {
return params["cn"], object.CasdoorOrganization, ""
return params["cn"], object.CasdoorOrganization, nil
}
return params["cn"], params["ou"], ""
return params["cn"], params["ou"], nil
}
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
@@ -50,7 +114,11 @@ func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
return "", "", ldap.LDAPResultInvalidDNSyntax
}
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
name, org, err := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
if err != nil {
panic(err)
}
return name, org, ldap.LDAPResultSuccess
}
@@ -83,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()
@@ -94,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)
}
@@ -144,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)
}
@@ -178,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

@@ -142,7 +142,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
return ""
}
func checkSigninErrorTimes(user *User, lang string) string {
func checkSigninErrorTimes(user *User, lang string) error {
if user.SigninWrongTimes >= SigninWrongTimesLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
@@ -150,37 +150,39 @@ func checkSigninErrorTimes(user *User, lang string) string {
// deny the login if the error times is greater than the limit and the last login time is less than the duration
if minutes > 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
}
// reset the error times
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
return err
}
return ""
return nil
}
func CheckPassword(user *User, password string, lang string, options ...bool) string {
func CheckPassword(user *User, password string, lang string, options ...bool) error {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
// check the login error times
if !enableCaptcha {
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
err := checkSigninErrorTimes(user, lang)
if err != nil {
return err
}
}
organization, err := GetOrganizationByUser(user)
if err != nil {
panic(err)
return err
}
if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist")
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
}
passwordType := user.PasswordType
@@ -191,19 +193,17 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
if credManager != nil {
if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
return ""
return resetUserSigninErrorTimes(user)
}
}
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
return ""
return resetUserSigninErrorTimes(user)
}
return recordSigninErrorInfo(user, lang, enableCaptcha)
} else {
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
}
}
@@ -217,10 +217,10 @@ func CheckPasswordComplexity(user *User, password string) string {
return CheckPasswordComplexityByOrg(organization, password)
}
func checkLdapUserPassword(user *User, password string, lang string) string {
func checkLdapUserPassword(user *User, password string, lang string) error {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
return err.Error()
return err
}
ldapLoginSuccess := false
@@ -237,65 +237,73 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
return err.Error()
conn.Close()
return err
}
if len(searchResult.Entries) == 0 {
conn.Close()
continue
}
if len(searchResult.Entries) > 1 {
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
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 {
if !hit {
return "user not exist"
return fmt.Errorf("user not exist")
}
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
return fmt.Errorf(i18n.Translate(lang, "check:LDAP user name or password incorrect"))
}
return ""
return nil
}
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
user, err := GetUserByFields(organization, username)
if err != nil {
panic(err)
return nil, err
}
if user == nil || user.IsDeleted {
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
return nil, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
}
if user.IsForbidden {
return nil, i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator")
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator"))
}
if user.Ldap != "" {
// ONLY for ldap users
if msg := checkLdapUserPassword(user, password, lang); msg != "" {
if msg == "user not exist" {
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
// only for LDAP users
err = checkLdapUserPassword(user, password, lang)
if err != nil {
if err.Error() == "user not exist" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
}
return nil, msg
return nil, err
}
} else {
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
return nil, msg
err = CheckPassword(user, password, lang, enableCaptcha)
if err != nil {
return nil, err
}
}
return user, ""
return user, nil
}
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
@@ -308,7 +316,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
if userId != "" {
targetUser, err := GetUser(userId)
if err != nil {
panic(err)
return false, err
}
if targetUser == nil {
@@ -366,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

@@ -36,20 +36,23 @@ func isValidRealName(s string) bool {
return reRealName.MatchString(s)
}
func resetUserSigninErrorTimes(user *User) {
func resetUserSigninErrorTimes(user *User) error {
// if the password is correct and wrong times is not zero, reset the error times
if user.SigninWrongTimes == 0 {
return
return nil
}
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
return err
}
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
// increase failed login count
if user.SigninWrongTimes < SigninWrongTimesLimit {
user.SigninWrongTimes++
@@ -61,13 +64,18 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
}
// update user
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
if err != nil {
return err
}
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances == 0 && enableCaptcha {
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect"))
} else if leftChances >= 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
}
// don't show the chance error message if the user has no chance left
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
}

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
}
@@ -621,25 +679,25 @@ func GetPasswordToken(application *Application, username string, password string
if err != nil {
return nil, nil, err
}
if user == nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user does not exist",
}, nil
}
var msg string
if user.Ldap != "" {
msg = checkLdapUserPassword(user, password, "en")
err = checkLdapUserPassword(user, password, "en")
} else {
msg = CheckPassword(user, password, "en")
err = CheckPassword(user, password, "en")
}
if msg != "" {
if err != nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "invalid username or password",
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
}, nil
}
if user.IsForbidden {
return nil, &TokenError{
Error: InvalidGrant,
@@ -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

@@ -192,32 +192,32 @@ func CheckVerificationCode(dest string, code string, lang string) *VerifyResult
return &VerifyResult{VerificationSuccess, ""}
}
func DisableVerificationCode(dest string) (err error) {
func DisableVerificationCode(dest string) error {
record, err := getVerificationRecord(dest)
if record == nil || err != nil {
return
return nil
}
record.IsUsed = true
_, err = ormer.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record)
return
return err
}
func CheckSigninCode(user *User, dest, code, lang string) string {
func CheckSigninCode(user *User, dest, code, lang string) error {
// check the login error times
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
err := checkSigninErrorTimes(user, lang)
if err != nil {
return err
}
result := CheckVerificationCode(dest, code, lang)
switch result.Code {
case VerificationSuccess:
resetUserSigninErrorTimes(user)
return ""
return resetUserSigninErrorTimes(user)
case wrongCodeError:
return recordSigninErrorInfo(user, lang)
default:
return result.Msg
return fmt.Errorf(result.Msg)
}
}

View File

@@ -55,15 +55,18 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
password := rfc2865.UserPassword_GetString(r.Packet)
organization := rfc2865.Class_GetString(r.Packet)
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
if organization == "" {
w.Write(r.Response(radius.CodeAccessReject))
return
}
_, msg := object.CheckUserPassword(organization, username, password, "en")
if msg != "" {
_, err := object.CheckUserPassword(organization, username, password, "en")
if err != nil {
w.Write(r.Response(radius.CodeAccessReject))
return
}
w.Write(r.Response(radius.CodeAccessAccept))
}

View File

@@ -83,13 +83,12 @@ func AutoSigninFilter(ctx *context.Context) {
password := ctx.Input.Query("password")
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
owner, name := util.GetOwnerAndNameFromId(userId)
_, msg := object.CheckUserPassword(owner, name, password, "en")
if msg != "" {
responseError(ctx, msg)
_, err = object.CheckUserPassword(owner, name, password, "en")
if err != nil {
responseError(ctx, err.Error())
return
}
setSessionUser(ctx, userId)
return
}
}

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 {