Compare commits

..

28 Commits

Author SHA1 Message Date
Yang Luo
37b8b09cc0 feat: update go-sms-sender to v0.16.0 to fix first number missing bug in AmazonSNSClient.SendMessage 2023-12-06 20:05:48 +08:00
Yang Luo
482eb61168 feat: improve StaticFilter() 2023-12-05 18:33:06 +08:00
Lars Lehtonen
8819a8697b feat: fix dropped error in stripe.go (#2525) 2023-12-05 16:02:33 +08:00
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
Jiankun Yang
7786018051 feat: use short state for OAuth provider (#2504)
* fix: use fixed length of state

* fix: use short state
2023-11-19 07:30:29 +08:00
xzgan
6c72f86d03 fix: support LDAP in linux (#2500)
Co-authored-by: Xiang Zhen Gan <m1353825@163.com>
2023-11-16 23:58:09 +08:00
Yang Luo
5b151f4ec4 feat: improve cert edit page UI 2023-11-13 15:57:46 +08:00
Yang Luo
e9b7d1266f Fix API typo: /get-global-certs 2023-11-13 14:22:40 +08:00
Yang Luo
2d4998228c Add organization.MasterVerificationCode 2023-11-13 13:53:41 +08:00
Yang Luo
d3ed6c348b Improve GetOAuthToken() API's parameter handling 2023-11-13 02:30:32 +08:00
songjf
a22e05dcc1 feat: fix the UI and navigation errors on the prompt page (#2486) 2023-11-12 15:54:38 +08:00
haiwu
0ac2b69f5a feat: support WeChat Pay via JSAPI (#2488)
* feat: support wechat jsapi payment

* feat: add log

* feat: update sign

* feat: process wechat pay result

* feat: process wechat pay result

* feat: save wechat openid for different app

* feat: save wechat openid for different app

* feat: add SetUserOAuthProperties for signup

* feat: fix openid for wechat

* feat: get user extra property in buyproduct

* feat: remove log

* feat: remove log

* feat: gofumpt code

* feat: change lr->crlf

* feat: change crlf->lf

* feat: improve code
2023-11-11 17:16:57 +08:00
Yang Luo
d090e9c860 Improve downloadImage() 2023-11-10 08:35:21 +08:00
63 changed files with 1294 additions and 410 deletions

View File

@@ -151,7 +151,7 @@ func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath
return true return true
} else if urlPath == "/api/update-user" { } else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information // 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 true
} }
return false return false

View File

@@ -282,17 +282,15 @@ func (c *ApiController) Logout() {
return return
} }
affected, application, token, err := object.ExpireTokenByAccessToken(accessToken) _, application, token, err := object.ExpireTokenByAccessToken(accessToken)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if token == nil {
if !affected {
c.ResponseError(c.T("token:Token not found, invalid accessToken")) c.ResponseError(c.T("token:Token not found, invalid accessToken"))
return return
} }
if application == nil { if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application) c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
return return
@@ -319,7 +317,15 @@ func (c *ApiController) Logout() {
return return
} else { } else {
if application.IsRedirectUriValid(redirectUri) { 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 { } else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri)) c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return return

View File

@@ -34,6 +34,7 @@ import (
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/oauth2"
) )
var ( 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: ""} resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
} else { } else {
scope := c.Input().Get("scope") 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) resp = tokenToResponse(token)
} }
} else if form.Type == ResponseTypeSaml { // saml flow } else if form.Type == ResponseTypeSaml { // saml flow
@@ -331,8 +333,6 @@ func (c *ApiController) Login() {
} }
var user *object.User var user *object.User
var msg string
if authForm.Password == "" { if authForm.Password == "" {
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil { if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
@@ -354,20 +354,21 @@ func (c *ApiController) Login() {
} }
// check result through Email or Phone // check result through Email or Phone
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage()) err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
if len(checkResult) != 0 { if err != nil {
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult)) c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
return return
} }
// disable the verification code // disable the verification code
err := object.DisableVerificationCode(checkDest) err = object.DisableVerificationCode(checkDest)
if err != nil { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
} }
} else { } 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 { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
@@ -386,7 +387,8 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} else if enableCaptcha { } 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -399,13 +401,15 @@ func (c *ApiController) Login() {
} }
password := authForm.Password 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 != "" { if err != nil {
resp = &Response{Status: "error", Msg: msg} c.ResponseError(err.Error())
return
} else { } 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -416,7 +420,8 @@ func (c *ApiController) Login() {
return return
} }
organization, err := object.GetOrganizationByUser(user) var organization *object.Organization
organization, err = object.GetOrganizationByUser(user)
if err != nil { if err != nil {
c.ResponseError(err.Error()) 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)) c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return 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 { if err != nil {
c.ResponseError(c.T(err.Error())) 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -488,7 +496,8 @@ func (c *ApiController) Login() {
} else if provider.Category == "OAuth" || provider.Category == "Web3" { } else if provider.Category == "OAuth" || provider.Category == "Web3" {
// OAuth // OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider) 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -506,7 +515,8 @@ func (c *ApiController) Login() {
} }
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338 // 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -547,7 +557,12 @@ func (c *ApiController) Login() {
if user.IsForbidden { if user.IsForbidden {
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator")) 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)
if err != nil {
c.ResponseError(err.Error())
return
}
resp = c.HandleLoggedIn(application, user, &authForm) resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
@@ -588,14 +603,16 @@ func (c *ApiController) Login() {
} }
// Handle username conflicts // 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if tmpUser != nil { if tmpUser != nil {
uid, err := uuid.NewRandom() var uid uuid.UUID
uid, err = uuid.NewRandom()
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -606,14 +623,16 @@ func (c *ApiController) Login() {
} }
properties := map[string]string{} properties := map[string]string{}
count, err := object.GetUserCount(application.Organization, "", "", "") var count int64
count, err = object.GetUserCount(application.Organization, "", "", "")
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
properties["no"] = strconv.Itoa(int(count + 2)) properties["no"] = strconv.Itoa(int(count + 2))
initScore, err := organization.GetInitScore() var initScore int
initScore, err = organization.GetInitScore()
if err != nil { if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error()) c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return return
@@ -645,7 +664,8 @@ func (c *ApiController) Login() {
Properties: properties, Properties: properties,
} }
affected, err := object.AddUser(user) var affected bool
affected, err = object.AddUser(user)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -667,7 +687,7 @@ func (c *ApiController) Login() {
} }
// sync info from 3rd-party if possible // 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -703,7 +723,8 @@ func (c *ApiController) Login() {
return 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -714,7 +735,8 @@ func (c *ApiController) Login() {
return return
} }
user, err := object.GetUser(userId) var user *object.User
user, err = object.GetUser(userId)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -727,7 +749,8 @@ func (c *ApiController) Login() {
return 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -740,7 +763,8 @@ func (c *ApiController) Login() {
} }
} }
} else if c.getMfaUserSession() != "" { } else if c.getMfaUserSession() != "" {
user, err := object.GetUser(c.getMfaUserSession()) var user *object.User
user, err = object.GetUser(c.getMfaUserSession())
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -773,7 +797,8 @@ func (c *ApiController) Login() {
return 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -794,7 +819,8 @@ func (c *ApiController) Login() {
} else { } else {
if c.GetSessionUsername() != "" { if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in // 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

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

View File

@@ -65,13 +65,13 @@ func (c *ApiController) GetCerts() {
} }
} }
// GetGlobleCerts // GetGlobalCerts
// @Title GetGlobleCerts // @Title GetGlobalCerts
// @Tag Cert API // @Tag Cert API
// @Description get globle certs // @Description get globle certs
// @Success 200 {array} object.Cert The Response object // @Success 200 {array} object.Cert The Response object
// @router /get-globle-certs [get] // @router /get-global-certs [get]
func (c *ApiController) GetGlobleCerts() { func (c *ApiController) GetGlobalCerts() {
limit := c.Input().Get("pageSize") limit := c.Input().Get("pageSize")
page := c.Input().Get("p") page := c.Input().Get("p")
field := c.Input().Get("field") field := c.Input().Get("field")
@@ -80,7 +80,7 @@ func (c *ApiController) GetGlobleCerts() {
sortOrder := c.Input().Get("sortOrder") sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" { if limit == "" || page == "" {
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts()) maskedCerts, err := object.GetMaskedCerts(object.GetGlobalCerts())
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

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

View File

@@ -163,6 +163,8 @@ func (c *ApiController) BuyProduct() {
id := c.Input().Get("id") id := c.Input().Get("id")
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
providerName := c.Input().Get("providerName") providerName := c.Input().Get("providerName")
paymentEnv := c.Input().Get("paymentEnv")
// buy `pricingName/planName` for `paidUserName` // buy `pricingName/planName` for `paidUserName`
pricingName := c.Input().Get("pricingName") pricingName := c.Input().Get("pricingName")
planName := c.Input().Get("planName") planName := c.Input().Get("planName")
@@ -187,11 +189,11 @@ func (c *ApiController) BuyProduct() {
return return
} }
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host) payment, attachInfo, err := object.BuyProduct(id, user, providerName, pricingName, planName, host, paymentEnv)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
c.ResponseOk(payment) c.ResponseOk(payment, attachInfo)
} }

View File

@@ -158,10 +158,9 @@ func (c *ApiController) DeleteToken() {
// @Success 401 {object} object.TokenError The Response object // @Success 401 {object} object.TokenError The Response object
// @router api/login/oauth/access_token [post] // @router api/login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() { func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
refreshToken := c.Input().Get("refresh_token")
clientId := c.Input().Get("client_id") clientId := c.Input().Get("client_id")
clientSecret := c.Input().Get("client_secret") clientSecret := c.Input().Get("client_secret")
grantType := c.Input().Get("grant_type")
code := c.Input().Get("code") code := c.Input().Get("code")
verifier := c.Input().Get("code_verifier") verifier := c.Input().Get("code_verifier")
scope := c.Input().Get("scope") scope := c.Input().Get("scope")
@@ -169,35 +168,61 @@ func (c *ApiController) GetOAuthToken() {
password := c.Input().Get("password") password := c.Input().Get("password")
tag := c.Input().Get("tag") tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar") avatar := c.Input().Get("avatar")
refreshToken := c.Input().Get("refresh_token")
if clientId == "" && clientSecret == "" { if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth() clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
} }
if clientId == "" {
// If clientID is empty, try to read data from RequestBody if len(c.Ctx.Input.RequestBody) != 0 {
// If clientId is empty, try to read data from RequestBody
var tokenRequest TokenRequest var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil { err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
clientId = tokenRequest.ClientId if err == nil {
clientSecret = tokenRequest.ClientSecret if clientId == "" {
grantType = tokenRequest.GrantType clientId = tokenRequest.ClientId
refreshToken = tokenRequest.RefreshToken }
code = tokenRequest.Code if clientSecret == "" {
verifier = tokenRequest.Verifier clientSecret = tokenRequest.ClientSecret
scope = tokenRequest.Scope }
username = tokenRequest.Username if grantType == "" {
password = tokenRequest.Password grantType = tokenRequest.GrantType
tag = tokenRequest.Tag }
avatar = tokenRequest.Avatar if code == "" {
code = tokenRequest.Code
}
if verifier == "" {
verifier = tokenRequest.Verifier
}
if scope == "" {
scope = tokenRequest.Scope
}
if username == "" {
username = tokenRequest.Username
}
if password == "" {
password = tokenRequest.Password
}
if tag == "" {
tag = tokenRequest.Tag
}
if avatar == "" {
avatar = tokenRequest.Avatar
}
if refreshToken == "" {
refreshToken = tokenRequest.RefreshToken
}
} }
} }
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage()) token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
c.Data["json"] = oAuthtoken c.Data["json"] = token
c.SetTokenErrorHttpStatus() c.SetTokenErrorHttpStatus()
c.ServeJSON() c.ServeJSON()
} }

View File

@@ -15,10 +15,10 @@
package controllers package controllers
type TokenRequest struct { type TokenRequest struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
ClientId string `json:"client_id"` ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"` ClientSecret string `json:"client_secret"`
GrantType string `json:"grant_type"`
Code string `json:"code"`
Verifier string `json:"code_verifier"` Verifier string `json:"code_verifier"`
Scope string `json:"scope"` Scope string `json:"scope"`
Username string `json:"username"` Username string `json:"username"`

View File

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

10
go.mod
View File

@@ -9,9 +9,8 @@ require (
github.com/aws/aws-sdk-go v1.45.5 github.com/aws/aws-sdk-go v1.45.5
github.com/beego/beego v1.12.12 github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.77.2 github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.15.0 github.com/casdoor/go-sms-sender v0.16.0
github.com/casdoor/gomail/v2 v2.0.1 github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.45.0 github.com/casdoor/notify v0.45.0
github.com/casdoor/oss v1.3.0 github.com/casdoor/oss v1.3.0
@@ -23,8 +22,9 @@ require (
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3 github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
github.com/fogleman/gg v1.3.0 github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0 github.com/forestmgy/ldapserver v1.1.0
github.com/go-asn1-ber/asn1-ber v1.5.5
github.com/go-git/go-git/v5 v5.6.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-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
@@ -32,6 +32,7 @@ require (
github.com/go-webauthn/webauthn v0.6.0 github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.3.1 github.com/google/uuid v1.3.1
github.com/json-iterator/go v1.1.12
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v1.2.21 github.com/lestrrat-go/jwx v1.2.21
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
@@ -61,10 +62,9 @@ require (
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6 github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.12.0 golang.org/x/crypto v0.13.0
golang.org/x/net v0.14.0 golang.org/x/net v0.14.0
golang.org/x/oauth2 v0.11.0 golang.org/x/oauth2 v0.11.0
golang.org/x/text v0.13.0 // indirect
google.golang.org/api v0.138.0 google.golang.org/api v0.138.0
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0

31
go.sum
View File

@@ -778,8 +778,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 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-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@@ -823,6 +823,8 @@ 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-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-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
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 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc= 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= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
@@ -912,9 +914,8 @@ github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQ
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM=
github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
@@ -922,8 +923,8 @@ github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk= github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs= github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc= github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
github.com/casdoor/go-sms-sender v0.15.0 h1:9SWj/jd5c7jIteTRUrqbkpWbtIXMDv+t1CEfDhO06m0= github.com/casdoor/go-sms-sender v0.16.0 h1:5OFwf5cxJQUcWdJXKOHzXaf+RydQuswdKhCgvfMY1PU=
github.com/casdoor/go-sms-sender v0.15.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs= github.com/casdoor/go-sms-sender v0.16.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs=
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w= github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q= github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk= github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
@@ -1081,8 +1082,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= 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/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.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
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/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 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/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
@@ -1110,8 +1111,8 @@ github.com/go-lark/lark v1.9.0 h1:FX21osIw6ssBH4hc4yO83AJrkRZONPji2jp5y8xQJZo=
github.com/go-lark/lark v1.9.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM= github.com/go-lark/lark v1.9.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= 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.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg= 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.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.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
@@ -1916,7 +1917,6 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -1947,8 +1947,9 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 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.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-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -2271,8 +2272,9 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.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.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-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -2287,8 +2289,9 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= 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.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.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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

View File

@@ -31,6 +31,7 @@ type UserInfo struct {
Phone string Phone string
CountryCode string CountryCode string
AvatarUrl string AvatarUrl string
Extra map[string]string
} }
type ProviderInfo struct { type ProviderInfo struct {

View File

@@ -186,15 +186,24 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
id = wechatUserInfo.Openid id = wechatUserInfo.Openid
} }
extra := make(map[string]string)
extra["wechat_unionid"] = wechatUserInfo.Openid
// For WeChat, different appId corresponds to different openId
extra[BuildWechatOpenIdKey(idp.Config.ClientID)] = wechatUserInfo.Openid
userInfo := UserInfo{ userInfo := UserInfo{
Id: id, Id: id,
Username: wechatUserInfo.Nickname, Username: wechatUserInfo.Nickname,
DisplayName: wechatUserInfo.Nickname, DisplayName: wechatUserInfo.Nickname,
AvatarUrl: wechatUserInfo.Headimgurl, AvatarUrl: wechatUserInfo.Headimgurl,
Extra: extra,
} }
return &userInfo, nil return &userInfo, nil
} }
func BuildWechatOpenIdKey(appId string) string {
return fmt.Sprintf("wechat_openid_%s", appId)
}
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) { func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret) accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
request, err := http.NewRequest("GET", accessTokenUrl, nil) request, err := http.NewRequest("GET", accessTokenUrl, nil)

View File

@@ -16,6 +16,7 @@ package ldap
import ( import (
"fmt" "fmt"
"hash/fnv"
"log" "log"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@@ -49,20 +50,20 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
if r.AuthenticationChoice() == "simple" { if r.AuthenticationChoice() == "simple" {
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name())) bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
if err != "" { if err != nil {
log.Printf("Bind failed ,ErrMsg=%s", err) log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax) res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
res.SetDiagnosticMessage("bind failed ErrMsg: " + err) res.SetDiagnosticMessage(fmt.Sprintf("getNameAndOrgFromDN() error: %s", err.Error()))
w.Write(res) w.Write(res)
return return
} }
bindPassword := string(r.AuthenticationSimple()) bindPassword := string(r.AuthenticationSimple())
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en") 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) log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
res.SetResultCode(ldap.LDAPResultInvalidCredentials) res.SetResultCode(ldap.LDAPResultInvalidCredentials)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err) res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err.Error())
w.Write(res) w.Write(res)
return return
} }
@@ -78,7 +79,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
m.Client.OrgName = bindOrg m.Client.OrgName = bindOrg
} else { } else {
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported) 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) w.Write(res)
} }
@@ -113,10 +114,22 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
} }
for _, user := range users { 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 := ldap.NewSearchResultEntry(dn)
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
for _, attr := range r.Attributes() { 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)) e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
if string(attr) == "cn" { if string(attr) == "cn" {
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user)) e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
@@ -127,3 +140,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
} }
w.Write(res) 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" "github.com/lor00x/goldap/message"
ldap "github.com/forestmgy/ldapserver" 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, ",") DNFields := strings.Split(DN, ",")
params := make(map[string]string, len(DNFields)) params := make(map[string]string, len(DNFields))
for _, field := range DNFields { for _, field := range DNFields {
@@ -37,12 +101,12 @@ func getNameAndOrgFromDN(DN string) (string, string, string) {
} }
if params["cn"] == "" { 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"] == "" { 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) { func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
@@ -50,7 +114,11 @@ func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
return "", "", ldap.LDAPResultInvalidDNSyntax 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 return name, org, ldap.LDAPResultSuccess
} }
@@ -83,6 +151,92 @@ func stringInSlice(value string, list []string) bool {
return false 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) { func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
var err error var err error
r := m.GetSearchRequest() 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 name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
if m.Client.IsGlobalAdmin && org == "*" { if m.Client.IsGlobalAdmin && org == "*" {
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
filteredUsers, err = object.GetGlobalUsers()
if err != nil { if err != nil {
panic(err) panic(err)
} }
return filteredUsers, ldap.LDAPResultSuccess return filteredUsers, ldap.LDAPResultSuccess
} }
if m.Client.IsGlobalAdmin || org == m.Client.OrgName { if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
filteredUsers, err = object.GetUsers(org) filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -144,7 +297,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
return nil, ldap.LDAPResultNoSuchObject return nil, ldap.LDAPResultNoSuchObject
} }
users, err := object.GetUsersByTag(org, name) users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -178,24 +331,17 @@ func getUserPasswordWithType(user *object.User) string {
} }
func getAttribute(attributeName string, user *object.User) message.AttributeValue { func getAttribute(attributeName string, user *object.User) message.AttributeValue {
switch attributeName { v, ok := ldapAttributesMapping[attributeName]
case "cn": if !ok {
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:
return "" 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

@@ -319,6 +319,9 @@ func GetMaskedApplication(application *Application, userId string) *Application
if application.OrganizationObj.DefaultPassword != "" { if application.OrganizationObj.DefaultPassword != "" {
application.OrganizationObj.DefaultPassword = "***" application.OrganizationObj.DefaultPassword = "***"
} }
if application.OrganizationObj.MasterVerificationCode != "" {
application.OrganizationObj.MasterVerificationCode = "***"
}
if application.OrganizationObj.PasswordType != "" { if application.OrganizationObj.PasswordType != "" {
application.OrganizationObj.PasswordType = "***" application.OrganizationObj.PasswordType = "***"
} }

View File

@@ -87,7 +87,7 @@ func GetGlobalCertsCount(field, value string) (int64, error) {
return session.Count(&Cert{}) return session.Count(&Cert{})
} }
func GetGlobleCerts() ([]*Cert, error) { func GetGlobalCerts() ([]*Cert, error) {
certs := []*Cert{} certs := []*Cert{}
err := ormer.Engine.Desc("created_time").Find(&certs) err := ormer.Engine.Desc("created_time").Find(&certs)
if err != nil { if err != nil {
@@ -163,6 +163,12 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
return false, err return false, err
} }
} }
err := cert.populateContent()
if err != nil {
return false, err
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert) affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
if err != nil { if err != nil {
return false, err return false, err
@@ -172,10 +178,9 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
} }
func AddCert(cert *Cert) (bool, error) { func AddCert(cert *Cert) (bool, error) {
if cert.Certificate == "" || cert.PrivateKey == "" { err := cert.populateContent()
certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner) if err != nil {
cert.Certificate = certificate return false, err
cert.PrivateKey = privateKey
} }
affected, err := ormer.Engine.Insert(cert) affected, err := ormer.Engine.Insert(cert)
@@ -199,6 +204,20 @@ func (p *Cert) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name) return fmt.Sprintf("%s/%s", p.Owner, p.Name)
} }
func (p *Cert) populateContent() error {
if p.Certificate == "" || p.PrivateKey == "" {
certificate, privateKey, err := generateRsaKeys(p.BitSize, p.ExpireInYears, p.Name, p.Owner)
if err != nil {
return err
}
p.Certificate = certificate
p.PrivateKey = privateKey
}
return nil
}
func getCertByApplication(application *Application) (*Cert, error) { func getCertByApplication(application *Application) (*Cert, error) {
if application.Cert != "" { if application.Cert != "" {
return getCertByName(application.Cert) return getCertByName(application.Cert)

View File

@@ -142,7 +142,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
return "" return ""
} }
func checkSigninErrorTimes(user *User, lang string) string { func checkSigninErrorTimes(user *User, lang string) error {
if user.SigninWrongTimes >= SigninWrongTimesLimit { if user.SigninWrongTimes >= SigninWrongTimesLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime) lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime) 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 // 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 { 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 // reset the error times
user.SigninWrongTimes = 0 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 enableCaptcha := false
if len(options) > 0 { if len(options) > 0 {
enableCaptcha = options[0] enableCaptcha = options[0]
} }
// check the login error times // check the login error times
if !enableCaptcha { if !enableCaptcha {
if msg := checkSigninErrorTimes(user, lang); msg != "" { err := checkSigninErrorTimes(user, lang)
return msg if err != nil {
return err
} }
} }
organization, err := GetOrganizationByUser(user) organization, err := GetOrganizationByUser(user)
if err != nil { if err != nil {
panic(err) return err
} }
if organization == nil { 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 passwordType := user.PasswordType
@@ -191,19 +193,17 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
if credManager != nil { if credManager != nil {
if organization.MasterPassword != "" { if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) { if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
resetUserSigninErrorTimes(user) return resetUserSigninErrorTimes(user)
return ""
} }
} }
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) { if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
resetUserSigninErrorTimes(user) return resetUserSigninErrorTimes(user)
return ""
} }
return recordSigninErrorInfo(user, lang, enableCaptcha) return recordSigninErrorInfo(user, lang, enableCaptcha)
} else { } 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) 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) ldaps, err := GetLdaps(user.Owner)
if err != nil { if err != nil {
return err.Error() return err
} }
ldapLoginSuccess := false ldapLoginSuccess := false
@@ -237,65 +237,73 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
searchResult, err := conn.Conn.Search(searchReq) searchResult, err := conn.Conn.Search(searchReq)
if err != nil { if err != nil {
return err.Error() conn.Close()
return err
} }
if len(searchResult.Entries) == 0 { if len(searchResult.Entries) == 0 {
conn.Close()
continue continue
} }
if len(searchResult.Entries) > 1 { 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 hit = true
dn := searchResult.Entries[0].DN 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 ldapLoginSuccess = true
conn.Close()
break break
} }
conn.Close()
} }
if !ldapLoginSuccess { if !ldapLoginSuccess {
if !hit { 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 enableCaptcha := false
if len(options) > 0 { if len(options) > 0 {
enableCaptcha = options[0] enableCaptcha = options[0]
} }
user, err := GetUserByFields(organization, username) user, err := GetUserByFields(organization, username)
if err != nil { if err != nil {
panic(err) return nil, err
} }
if user == nil || user.IsDeleted { 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 { 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 != "" { if user.Ldap != "" {
// ONLY for ldap users // only for LDAP users
if msg := checkLdapUserPassword(user, password, lang); msg != "" { err = checkLdapUserPassword(user, password, lang)
if msg == "user not exist" { if err != nil {
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username) 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 { } else {
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" { err = CheckPassword(user, password, lang, enableCaptcha)
return nil, msg if err != nil {
return nil, err
} }
} }
return user, "" return user, nil
} }
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) { 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 != "" { if userId != "" {
targetUser, err := GetUser(userId) targetUser, err := GetUser(userId)
if err != nil { if err != nil {
panic(err) return false, err
} }
if targetUser == nil { if targetUser == nil {
@@ -366,7 +374,7 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
allowCount := 0 allowCount := 0
denyCount := 0 denyCount := 0
for _, permission := range permissions { 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 continue
} }

View File

@@ -36,20 +36,23 @@ func isValidRealName(s string) bool {
return reRealName.MatchString(s) 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 the password is correct and wrong times is not zero, reset the error times
if user.SigninWrongTimes == 0 { if user.SigninWrongTimes == 0 {
return return nil
} }
user.SigninWrongTimes = 0 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 enableCaptcha := false
if len(options) > 0 { if len(options) > 0 {
enableCaptcha = options[0] enableCaptcha = options[0]
} }
// increase failed login count // increase failed login count
if user.SigninWrongTimes < SigninWrongTimesLimit { if user.SigninWrongTimes < SigninWrongTimesLimit {
user.SigninWrongTimes++ user.SigninWrongTimes++
@@ -61,13 +64,18 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
} }
// update user // 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 leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances == 0 && enableCaptcha { 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 { } 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 // 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", Name: "permission-built-in",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Permission", DisplayName: "Built-in Permission",
Description: "Built-in Permission",
Users: []string{"built-in/*"}, Users: []string{"built-in/*"},
Groups: []string{},
Roles: []string{}, Roles: []string{},
Domains: []string{}, Domains: []string{},
Model: "model-built-in", Model: "model-built-in",
Adapter: "",
ResourceType: "Application", ResourceType: "Application",
Resources: []string{"app-built-in"}, Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"}, Actions: []string{"Read", "Write", "Admin"},
Effect: "Allow", Effect: "Allow",
IsEnabled: true, IsEnabled: true,
Submitter: "admin",
Approver: "admin",
ApproveTime: util.GetCurrentTime(),
State: "Approved",
} }
_, err = AddPermission(permission) _, err = AddPermission(permission)
if err != nil { if err != nil {

View File

@@ -100,6 +100,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
users, err := conn.GetLdapUsers(ldap) users, err := conn.GetLdapUsers(ldap)
if err != nil { if err != nil {
conn.Close()
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err)) logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue continue
} }
@@ -111,6 +112,8 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
} else { } else {
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed))) 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 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) { func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
SearchFilter := "(objectClass=*)" SearchFilter := "(objectClass=*)"
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"} SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}

View File

@@ -51,23 +51,24 @@ type Organization struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"` WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"` Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"` PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"` PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"` CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"` DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"` DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"` Languages []string `xorm:"varchar(255)" json:"languages"`
ThemeData *ThemeData `xorm:"json" json:"themeData"` ThemeData *ThemeData `xorm:"json" json:"themeData"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"` MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"` DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
InitScore int `json:"initScore"` MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
EnableSoftDeletion bool `json:"enableSoftDeletion"` InitScore int `json:"initScore"`
IsProfilePublic bool `json:"isProfilePublic"` EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"` MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"` AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
@@ -159,6 +160,9 @@ func GetMaskedOrganization(organization *Organization, errs ...error) (*Organiza
if organization.DefaultPassword != "" { if organization.DefaultPassword != "" {
organization.DefaultPassword = "***" organization.DefaultPassword = "***"
} }
if organization.MasterVerificationCode != "" {
organization.MasterVerificationCode = "***"
}
return organization, nil return organization, nil
} }
@@ -213,6 +217,9 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
if organization.DefaultPassword == "***" { if organization.DefaultPassword == "***" {
session.Omit("default_password") session.Omit("default_password")
} }
if organization.MasterVerificationCode == "***" {
session.Omit("master_verification_code")
}
affected, err := session.Update(organization) affected, err := session.Update(organization)
if err != nil { if err != nil {

View File

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

View File

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

View File

@@ -17,6 +17,8 @@ package object
import ( import (
"fmt" "fmt"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/pp" "github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@@ -158,30 +160,28 @@ func (product *Product) getProvider(providerName string) (*Provider, error) {
return provider, nil return provider, nil
} }
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) { func BuyProduct(id string, user *User, providerName, pricingName, planName, host, paymentEnv string) (payment *Payment, attachInfo map[string]interface{}, err error) {
product, err := GetProduct(id) product, err := GetProduct(id)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if product == nil { if product == nil {
return nil, fmt.Errorf("the product: %s does not exist", id) return nil, nil, fmt.Errorf("the product: %s does not exist", id)
} }
provider, err := product.getProvider(providerName) provider, err := product.getProvider(providerName)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
pProvider, err := GetPaymentProvider(provider) pProvider, err := GetPaymentProvider(provider)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
owner := product.Owner owner := product.Owner
productName := product.Name
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName) payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId()) paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
productDisplayName := product.DisplayName
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName) returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName)
@@ -191,26 +191,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
if pricingName != "" && planName != "" { if pricingName != "" && planName != "" {
plan, err := GetPlan(util.GetId(owner, planName)) plan, err := GetPlan(util.GetId(owner, planName))
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if plan == nil { if plan == nil {
return nil, fmt.Errorf("the plan: %s does not exist", planName) return nil, nil, fmt.Errorf("the plan: %s does not exist", planName)
} }
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period) sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
_, err = AddSubscription(sub) _, err = AddSubscription(sub)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name) returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
} }
} }
// Create an OrderId and get the payUrl // Create an order
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl) payReq := &pp.PayReq{
ProviderName: providerName,
ProductName: product.Name,
PayerName: payerName,
PayerId: user.Id,
PaymentName: paymentName,
ProductDisplayName: product.DisplayName,
Price: product.Price,
Currency: product.Currency,
ReturnUrl: returnUrl,
NotifyUrl: notifyUrl,
PaymentEnv: paymentEnv,
}
// custom process for WeChat & WeChat Pay
if provider.Type == "WeChat Pay" {
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
if err != nil {
return nil, nil, err
}
}
payResp, err := pProvider.Pay(payReq)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
// Create a Payment linked with Product and Order // Create a Payment linked with Product and Order
payment := &Payment{ payment = &Payment{
Owner: product.Owner, Owner: product.Owner,
Name: paymentName, Name: paymentName,
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
@@ -219,8 +239,8 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
Provider: provider.Name, Provider: provider.Name,
Type: provider.Type, Type: provider.Type,
ProductName: productName, ProductName: product.Name,
ProductDisplayName: productDisplayName, ProductDisplayName: product.DisplayName,
Detail: product.Detail, Detail: product.Detail,
Tag: product.Tag, Tag: product.Tag,
Currency: product.Currency, Currency: product.Currency,
@@ -228,10 +248,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
ReturnUrl: product.ReturnUrl, ReturnUrl: product.ReturnUrl,
User: user.Name, User: user.Name,
PayUrl: payUrl, PayUrl: payResp.PayUrl,
SuccessUrl: returnUrl, SuccessUrl: returnUrl,
State: pp.PaymentStateCreated, State: pp.PaymentStateCreated,
OutOrderId: orderId, OutOrderId: payResp.OrderId,
} }
if provider.Type == "Dummy" { if provider.Type == "Dummy" {
@@ -240,13 +260,13 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
affected, err := AddPayment(payment) affected, err := AddPayment(payment)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if !affected { if !affected {
return nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment)) return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
} }
return payment, err return payment, payResp.AttachInfo, nil
} }
func ExtendProductWithProviders(product *Product) error { func ExtendProductWithProviders(product *Product) error {

View File

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

View File

@@ -24,14 +24,14 @@ import (
"time" "time"
) )
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string) { func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string, error) {
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa // https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key // https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
// Generate RSA key. // Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize) key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil { if err != nil {
panic(err) return "", "", err
} }
// Encode private key to PKCS#1 ASN.1 PEM. // Encode private key to PKCS#1 ASN.1 PEM.
@@ -54,9 +54,10 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
}, },
BasicConstraintsValid: true, BasicConstraintsValid: true,
} }
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key) cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil { if err != nil {
panic(err) return "", "", err
} }
// Generate a pem block with the certificate // Generate a pem block with the certificate
@@ -65,5 +66,5 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
Bytes: cert, Bytes: cert,
}) })
return string(certPem), string(privateKeyPem) return string(certPem), string(privateKeyPem), nil
} }

View File

@@ -23,7 +23,10 @@ import (
func TestGenerateRsaKeys(t *testing.T) { func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key" fileId := "token_jwt_key"
certificate, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization") certificate, privateKey, err := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}
// Write certificate (aka certificate) to file. // Write certificate (aka certificate) to file.
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId)) util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))

View File

@@ -22,6 +22,7 @@ import (
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/go-webauthn/webauthn/webauthn" "github.com/go-webauthn/webauthn/webauthn"
"github.com/xorm-io/builder"
"github.com/xorm-io/core" "github.com/xorm-io/core"
) )
@@ -231,6 +232,20 @@ func GetGlobalUsers() ([]*User, error) {
return users, nil 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) { func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
users := []*User{} users := []*User{}
session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder) session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder)
@@ -266,9 +281,27 @@ func GetUsers(owner string) ([]*User, error) {
return users, nil return users, nil
} }
func GetUsersByTag(owner string, tag string) ([]*User, error) { func GetUsersWithFilter(owner string, cond builder.Cond) ([]*User, error) {
users := []*User{} 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 { if err != nil {
return nil, err return nil, err
} }

View File

@@ -35,11 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error()) fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") || strings.Contains(err.Error(), "unrecognized name") { return nil, "", nil
return nil, "", nil
} else {
return nil, "", err
}
} }
defer resp.Body.Close() defer resp.Body.Close()
@@ -58,6 +54,8 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
if strings.Contains(contentType, "text/html") { if strings.Contains(contentType, "text/html") {
fileExtension = ".html" fileExtension = ".html"
} else if contentType == "image/vnd.microsoft.icon" {
fileExtension = ".ico"
} else { } else {
fileExtensions, err := mime.ExtensionsByType(contentType) fileExtensions, err := mime.ExtensionsByType(contentType)
if err != nil { if err != nil {

View File

@@ -186,10 +186,47 @@ func parseSize(sizes string) []int {
return nil return nil
} }
var publicEmailDomains = map[string]int{
"gmail.com": 1,
"163.com": 1,
"qq.com": 1,
"yahoo.com": 1,
"hotmail.com": 1,
"outlook.com": 1,
"icloud.com": 1,
"mail.com": 1,
"aol.com": 1,
"live.com": 1,
"yandex.com": 1,
"yahoo.co.jp": 1,
"yahoo.co.in": 1,
"yahoo.co.uk": 1,
"me.com": 1,
"msn.com": 1,
"comcast.net": 1,
"sbcglobal.net": 1,
"verizon.net": 1,
"earthlink.net": 1,
"cox.net": 1,
"rediffmail.com": 1,
"in.com": 1,
"hotmail.co.uk": 1,
"hotmail.fr": 1,
"zoho.com": 1,
"gmx.com": 1,
"gmx.de": 1,
"gmx.net": 1,
}
func isPublicEmailDomain(domain string) bool {
_, exists := publicEmailDomains[domain]
return exists
}
func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) { func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
tokens := strings.Split(email, "@") tokens := strings.Split(email, "@")
domain := tokens[1] domain := tokens[1]
if domain == "gmail.com" || domain == "163.com" || domain == "qq.com" { if isPublicEmailDomain(domain) {
return nil, "", nil return nil, "", nil
} }
@@ -203,11 +240,11 @@ func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, str
if buffer != nil { if buffer != nil {
faviconUrl, err = GetFaviconUrl(buffer.String()) faviconUrl, err = GetFaviconUrl(buffer.String())
if err != nil { if err != nil {
return nil, "", err fmt.Printf("getFaviconFileBuffer() error, faviconUrl is empty, error = %s\n", err.Error())
} } else {
if !strings.HasPrefix(faviconUrl, "http") {
if !strings.HasPrefix(faviconUrl, "http") { faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl) }
} }
} }

View File

@@ -20,6 +20,8 @@ import (
"reflect" "reflect"
"strings" "strings"
jsoniter "github.com/json-iterator/go"
"github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/xorm-io/core" "github.com/xorm-io/core"
@@ -142,6 +144,25 @@ func setUserProperty(user *User, field string, value string) {
} }
} }
func getUserProperty(user *User, field string) string {
if user.Properties == nil {
return ""
}
return user.Properties[field]
}
func getUserExtraProperty(user *User, providerType, key string) (string, error) {
extraJson := getUserProperty(user, fmt.Sprintf("oauth_%s_extra", providerType))
if extraJson == "" {
return "", nil
}
extra := make(map[string]string)
if err := jsoniter.Unmarshal([]byte(extraJson), &extra); err != nil {
return "", err
}
return extra[key], nil
}
func SetUserOAuthProperties(organization *Organization, user *User, providerType string, userInfo *idp.UserInfo) (bool, error) { func SetUserOAuthProperties(organization *Organization, user *User, providerType string, userInfo *idp.UserInfo) (bool, error) {
if userInfo.Id != "" { if userInfo.Id != "" {
propertyName := fmt.Sprintf("oauth_%s_id", providerType) propertyName := fmt.Sprintf("oauth_%s_id", providerType)
@@ -185,6 +206,27 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
} }
} }
if userInfo.Extra != nil {
// Save extra info as json string
propertyName := fmt.Sprintf("oauth_%s_extra", providerType)
oldExtraJson := getUserProperty(user, propertyName)
extra := make(map[string]string)
if oldExtraJson != "" {
if err := jsoniter.Unmarshal([]byte(oldExtraJson), &extra); err != nil {
return false, err
}
}
for k, v := range userInfo.Extra {
extra[k] = v
}
newExtraJson, err := jsoniter.Marshal(extra)
if err != nil {
return false, err
}
setUserProperty(user, propertyName, string(newExtraJson))
}
return UpdateUserForAllFields(user.GetId(), user) return UpdateUserForAllFields(user.GetId(), user)
} }

View File

@@ -82,7 +82,12 @@ func IsAllowSend(user *User, remoteAddr, recordType string) error {
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error { func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
sender := organization.DisplayName sender := organization.DisplayName
title := provider.Title title := provider.Title
code := getRandomCode(6) code := getRandomCode(6)
if organization.MasterVerificationCode != "" {
code = organization.MasterVerificationCode
}
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes." // "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(provider.Content, code) content := fmt.Sprintf(provider.Content, code)
@@ -107,6 +112,10 @@ func SendVerificationCodeToPhone(organization *Organization, user *User, provide
} }
code := getRandomCode(6) code := getRandomCode(6)
if organization.MasterVerificationCode != "" {
code = organization.MasterVerificationCode
}
if err := SendSms(provider, code, dest); err != nil { if err := SendSms(provider, code, dest); err != nil {
return err return err
} }
@@ -156,7 +165,7 @@ func getVerificationRecord(dest string) (*VerificationRecord, error) {
return &record, nil return &record, nil
} }
func CheckVerificationCode(dest, code, lang string) *VerifyResult { func CheckVerificationCode(dest string, code string, lang string) *VerifyResult {
record, err := getVerificationRecord(dest) record, err := getVerificationRecord(dest)
if err != nil { if err != nil {
panic(err) panic(err)
@@ -183,32 +192,32 @@ func CheckVerificationCode(dest, code, lang string) *VerifyResult {
return &VerifyResult{VerificationSuccess, ""} return &VerifyResult{VerificationSuccess, ""}
} }
func DisableVerificationCode(dest string) (err error) { func DisableVerificationCode(dest string) error {
record, err := getVerificationRecord(dest) record, err := getVerificationRecord(dest)
if record == nil || err != nil { if record == nil || err != nil {
return return nil
} }
record.IsUsed = true record.IsUsed = true
_, err = ormer.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record) _, 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 // check the login error times
if msg := checkSigninErrorTimes(user, lang); msg != "" { err := checkSigninErrorTimes(user, lang)
return msg if err != nil {
return err
} }
result := CheckVerificationCode(dest, code, lang) result := CheckVerificationCode(dest, code, lang)
switch result.Code { switch result.Code {
case VerificationSuccess: case VerificationSuccess:
resetUserSigninErrorTimes(user) return resetUserSigninErrorTimes(user)
return ""
case wrongCodeError: case wrongCodeError:
return recordSigninErrorInfo(user, lang) return recordSigninErrorInfo(user, lang)
default: default:
return result.Msg return fmt.Errorf(result.Msg)
} }
} }

View File

@@ -49,20 +49,24 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
return pp, nil return pp, nil
} }
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *AlipayPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
// pp.Client.DebugSwitch = gopay.DebugOn // pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
pp.Client.SetReturnUrl(returnUrl) pp.Client.SetReturnUrl(r.ReturnUrl)
pp.Client.SetNotifyUrl(notifyUrl) pp.Client.SetNotifyUrl(r.NotifyUrl)
bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName})) bm.Set("subject", joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName}))
bm.Set("out_trade_no", paymentName) bm.Set("out_trade_no", r.PaymentName)
bm.Set("total_amount", priceFloat64ToString(price)) bm.Set("total_amount", priceFloat64ToString(r.Price))
payUrl, err := pp.Client.TradePagePay(context.Background(), bm) payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
return payUrl, paymentName, nil payResp := &PayResp{
PayUrl: payUrl,
OrderId: r.PaymentName,
}
return payResp, nil
} }
func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@@ -21,8 +21,10 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
return pp, nil return pp, nil
} }
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *DummyPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
return returnUrl, "", nil return &PayResp{
PayUrl: r.ReturnUrl,
}, nil
} }
func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@@ -153,22 +153,22 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
return respBytes, nil return respBytes, nil
} }
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *GcPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
payReqInfo := GcPayReqInfo{ payReqInfo := GcPayReqInfo{
OrderDate: util.GenerateSimpleTimeId(), OrderDate: util.GenerateSimpleTimeId(),
OrderNo: paymentName, OrderNo: r.PaymentName,
Amount: getPriceString(price), Amount: getPriceString(r.Price),
Xmpch: pp.Xmpch, Xmpch: pp.Xmpch,
Body: productDisplayName, Body: r.ProductDisplayName,
ReturnUrl: returnUrl, ReturnUrl: r.ReturnUrl,
NotifyUrl: notifyUrl, NotifyUrl: r.NotifyUrl,
Remark1: payerName, Remark1: r.PayerName,
Remark2: productName, Remark2: r.ProductName,
} }
b, err := json.Marshal(payReqInfo) b, err := json.Marshal(payReqInfo)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
body := GcRequestBody{ body := GcRequestBody{
@@ -184,36 +184,38 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
bodyBytes, err := json.Marshal(body) bodyBytes, err := json.Marshal(body)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
respBytes, err := pp.doPost(bodyBytes) respBytes, err := pp.doPost(bodyBytes)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
var respBody GcResponseBody var respBody GcResponseBody
err = json.Unmarshal(respBytes, &respBody) err = json.Unmarshal(respBytes, &respBody)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
if respBody.ReturnCode != "SUCCESS" { if respBody.ReturnCode != "SUCCESS" {
return "", "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg) return nil, fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
} }
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data) payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
var payRespInfo GcPayRespInfo var payRespInfo GcPayRespInfo
err = json.Unmarshal(payRespInfoBytes, &payRespInfo) err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
payResp := &PayResp{
return payRespInfo.PayUrl, "", nil PayUrl: payRespInfo.PayUrl,
}
return payResp, nil
} }
func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@@ -49,16 +49,16 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
return pp, nil return pp, nil
} }
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *PaypalPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
// https://github.com/go-pay/gopay/blob/main/doc/paypal.md // https://github.com/go-pay/gopay/blob/main/doc/paypal.md
units := make([]*paypal.PurchaseUnit, 0, 1) units := make([]*paypal.PurchaseUnit, 0, 1)
unit := &paypal.PurchaseUnit{ unit := &paypal.PurchaseUnit{
ReferenceId: util.GetRandomString(16), ReferenceId: util.GetRandomString(16),
Amount: &paypal.Amount{ Amount: &paypal.Amount{
CurrencyCode: currency, // e.g."USD" CurrencyCode: r.Currency, // e.g."USD"
Value: priceFloat64ToString(price), // e.g."100.00" Value: priceFloat64ToString(r.Price), // e.g."100.00"
}, },
Description: joinAttachString([]string{productDisplayName, productName, providerName}), Description: joinAttachString([]string{r.ProductDisplayName, r.ProductName, r.ProviderName}),
} }
units = append(units, unit) units = append(units, unit)
@@ -68,23 +68,27 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
bm.SetBodyMap("application_context", func(b gopay.BodyMap) { bm.SetBodyMap("application_context", func(b gopay.BodyMap) {
b.Set("brand_name", "Casdoor") b.Set("brand_name", "Casdoor")
b.Set("locale", "en-PT") b.Set("locale", "en-PT")
b.Set("return_url", returnUrl) b.Set("return_url", r.ReturnUrl)
b.Set("cancel_url", returnUrl) b.Set("cancel_url", r.ReturnUrl)
}) })
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm) ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
if ppRsp.Code != paypal.Success { if ppRsp.Code != paypal.Success {
return "", "", errors.New(ppRsp.Error) return nil, errors.New(ppRsp.Error)
} }
// {"id":"9BR68863NE220374S","status":"CREATED", // {"id":"9BR68863NE220374S","status":"CREATED",
// "links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"self","method":"GET"}, // "links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"self","method":"GET"},
// {"href":"https://www.sandbox.paypal.com/checkoutnow?token=9BR68863NE220374S","rel":"approve","method":"GET"}, // {"href":"https://www.sandbox.paypal.com/checkoutnow?token=9BR68863NE220374S","rel":"approve","method":"GET"},
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"update","method":"PATCH"}, // {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"update","method":"PATCH"},
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S/capture","rel":"capture","method":"POST"}]} // {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S/capture","rel":"capture","method":"POST"}]}
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil payResp := &PayResp{
PayUrl: ppRsp.Response.Links[1].Href,
OrderId: ppRsp.Response.Id,
}
return payResp, nil
} }
func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@@ -24,6 +24,32 @@ const (
PaymentStateError PaymentState = "Error" PaymentStateError PaymentState = "Error"
) )
const (
PaymentEnvWechatBrowser = "WechatBrowser"
)
type PayReq struct {
ProviderName string
ProductName string
PayerName string
PayerId string
PaymentName string
ProductDisplayName string
Price float64
Currency string
ReturnUrl string
NotifyUrl string
PaymentEnv string
}
type PayResp struct {
PayUrl string
OrderId string
AttachInfo map[string]interface{}
}
type NotifyResult struct { type NotifyResult struct {
PaymentName string PaymentName string
PaymentStatus PaymentState PaymentStatus PaymentState
@@ -39,7 +65,7 @@ type NotifyResult struct {
} }
type PaymentProvider interface { type PaymentProvider interface {
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) Pay(req *PayReq) (*PayResp, error)
Notify(body []byte, orderId string) (*NotifyResult, error) Notify(body []byte, orderId string) (*NotifyResult, error)
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
GetResponseError(err error) string GetResponseError(err error) string

View File

@@ -46,30 +46,30 @@ func NewStripePaymentProvider(PublishableKey, SecretKey string) (*StripePaymentP
return pp, nil return pp, nil
} }
func (pp *StripePaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (payUrl string, orderId string, err error) { func (pp *StripePaymentProvider) Pay(r *PayReq) (*PayResp, error) {
// Create a temp product // Create a temp product
description := joinAttachString([]string{productName, productDisplayName, providerName}) description := joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName})
productParams := &stripe.ProductParams{ productParams := &stripe.ProductParams{
Name: stripe.String(productDisplayName), Name: stripe.String(r.ProductDisplayName),
Description: stripe.String(description), Description: stripe.String(description),
DefaultPriceData: &stripe.ProductDefaultPriceDataParams{ DefaultPriceData: &stripe.ProductDefaultPriceDataParams{
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)), UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
Currency: stripe.String(currency), Currency: stripe.String(r.Currency),
}, },
} }
sProduct, err := stripeProduct.New(productParams) sProduct, err := stripeProduct.New(productParams)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
// Create a price for an existing product // Create a price for an existing product
priceParams := &stripe.PriceParams{ priceParams := &stripe.PriceParams{
Currency: stripe.String(currency), Currency: stripe.String(r.Currency),
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)), UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
Product: stripe.String(sProduct.ID), Product: stripe.String(sProduct.ID),
} }
sPrice, err := stripePrice.New(priceParams) sPrice, err := stripePrice.New(priceParams)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
// Create a Checkout Session // Create a Checkout Session
checkoutParams := &stripe.CheckoutSessionParams{ checkoutParams := &stripe.CheckoutSessionParams{
@@ -80,17 +80,21 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
}, },
}, },
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)), Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
SuccessURL: stripe.String(returnUrl), SuccessURL: stripe.String(r.ReturnUrl),
CancelURL: stripe.String(returnUrl), CancelURL: stripe.String(r.ReturnUrl),
ClientReferenceID: stripe.String(paymentName), ClientReferenceID: stripe.String(r.PaymentName),
ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()), ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()),
} }
checkoutParams.AddMetadata("product_description", description) checkoutParams.AddMetadata("product_description", description)
sCheckout, err := stripeCheckout.New(checkoutParams) sCheckout, err := stripeCheckout.New(checkoutParams)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
return sCheckout.URL, sCheckout.ID, nil payResp := &PayResp{
PayUrl: sCheckout.URL,
OrderId: sCheckout.ID,
}
return payResp, nil
} }
func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
@@ -128,6 +132,9 @@ func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyRes
} }
// Once payment is successful, the Checkout Session will contain a reference to the successful `PaymentIntent` // Once payment is successful, the Checkout Session will contain a reference to the successful `PaymentIntent`
sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil) sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil)
if err != nil {
return nil, err
}
var ( var (
productName string productName string
productDisplayName string productDisplayName string

View File

@@ -63,27 +63,66 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, seria
return pp, nil return pp, nil
} }
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *WechatPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName})) desc := joinAttachString([]string{r.ProductDisplayName, r.ProductName, r.ProviderName})
bm.Set("attach", desc)
bm.Set("appid", pp.AppId) bm.Set("appid", pp.AppId)
bm.Set("description", productDisplayName) bm.Set("description", r.ProductDisplayName)
bm.Set("notify_url", notifyUrl) bm.Set("notify_url", r.NotifyUrl)
bm.Set("out_trade_no", paymentName) bm.Set("out_trade_no", r.PaymentName)
bm.SetBodyMap("amount", func(bm gopay.BodyMap) { bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", priceFloat64ToInt64(price)) bm.Set("total", priceFloat64ToInt64(r.Price))
bm.Set("currency", currency) bm.Set("currency", r.Currency)
}) })
// In Wechat browser, we use JSAPI
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm) if r.PaymentEnv == PaymentEnvWechatBrowser {
if err != nil { if r.PayerId == "" {
return "", "", err return nil, errors.New("failed to get the payer's openid, please retry login")
}
bm.SetBodyMap("payer", func(bm gopay.BodyMap) {
bm.Set("openid", r.PayerId) // If the account is signup via Wechat, the PayerId is the Wechat OpenId e.g.oxW9O1ZDvgreSHuBSQDiQ2F055PI
})
jsapiRsp, err := pp.Client.V3TransactionJsapi(context.Background(), bm)
if err != nil {
return nil, err
}
if jsapiRsp.Code != wechat.Success {
return nil, errors.New(jsapiRsp.Error)
}
// use RSA256 to sign the pay request
params, err := pp.Client.PaySignOfJSAPI(pp.AppId, jsapiRsp.Response.PrepayId)
if err != nil {
return nil, err
}
payResp := &PayResp{
PayUrl: "",
OrderId: r.PaymentName, // Wechat can use paymentName as the OutTradeNo to query order status
AttachInfo: map[string]interface{}{
"appId": params.AppId,
"timeStamp": params.TimeStamp,
"nonceStr": params.NonceStr,
"package": params.Package,
"signType": "RSA",
"paySign": params.PaySign,
},
}
return payResp, nil
} else {
// In other case, we use NativeAPI
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
if err != nil {
return nil, err
}
if nativeRsp.Code != wechat.Success {
return nil, errors.New(nativeRsp.Error)
}
payResp := &PayResp{
PayUrl: nativeRsp.Response.CodeUrl,
OrderId: r.PaymentName, // Wechat can use paymentName as the OutTradeNo to query order status
}
return payResp, nil
} }
if nativeRsp.Code != wechat.Success {
return "", "", errors.New(nativeRsp.Error)
}
return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status
} }
func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) { func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@@ -55,15 +55,18 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
password := rfc2865.UserPassword_GetString(r.Packet) password := rfc2865.UserPassword_GetString(r.Packet)
organization := rfc2865.Class_GetString(r.Packet) organization := rfc2865.Class_GetString(r.Packet)
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password) log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
if organization == "" { if organization == "" {
w.Write(r.Response(radius.CodeAccessReject)) w.Write(r.Response(radius.CodeAccessReject))
return 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)) w.Write(r.Response(radius.CodeAccessReject))
return return
} }
w.Write(r.Response(radius.CodeAccessAccept)) w.Write(r.Response(radius.CodeAccessAccept))
} }

View File

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

View File

@@ -204,7 +204,7 @@ func initAPI() {
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer") beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts") beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
beego.Router("/api/get-globle-certs", &controllers.ApiController{}, "GET:GetGlobleCerts") beego.Router("/api/get-global-certs", &controllers.ApiController{}, "GET:GetGlobalCerts")
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert") beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert") beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert") beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")

View File

@@ -129,7 +129,7 @@ func StaticFilter(ctx *context.Context) {
path += urlPath path += urlPath
} }
if !util.FileExist(path) { if strings.Contains(path, "/../") || !util.FileExist(path) {
path = webBuildFolder + "/index.html" path = webBuildFolder + "/index.html"
} }
if !util.FileExist(path) { if !util.FileExist(path) {

View File

@@ -2023,13 +2023,13 @@
} }
} }
}, },
"/api/get-globle-certs": { "/api/get-global-certs": {
"get": { "get": {
"tags": [ "tags": [
"Cert API" "Cert API"
], ],
"description": "get globle certs", "description": "get globle certs",
"operationId": "ApiController.GetGlobleCerts", "operationId": "ApiController.GetGlobalCerts",
"responses": { "responses": {
"200": { "200": {
"description": "The Response object", "description": "The Response object",

View File

@@ -1311,12 +1311,12 @@ paths:
type: array type: array
items: items:
$ref: '#/definitions/object.User' $ref: '#/definitions/object.User'
/api/get-globle-certs: /api/get-global-certs:
get: get:
tags: tags:
- Cert API - Cert API
description: get globle certs description: get globle certs
operationId: ApiController.GetGlobleCerts operationId: ApiController.GetGlobalCerts
responses: responses:
"200": "200":
description: The Response object description: The Response object

View File

@@ -171,10 +171,27 @@ class CertEditPage extends React.Component {
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
this.updateCertField("cryptoAlgorithm", value); this.updateCertField("cryptoAlgorithm", value);
if (value === "RS256") {
this.updateCertField("bitSize", 2048);
} else if (value === "HS256" || value === "ES256") {
this.updateCertField("bitSize", 256);
} else if (value === "ES384") {
this.updateCertField("bitSize", 384);
} else if (value === "ES521") {
this.updateCertField("bitSize", 521);
} else {
this.updateCertField("bitSize", 0);
}
this.updateCertField("certificate", "");
this.updateCertField("privateKey", "");
})}> })}>
{ {
[ [
{id: "RS256", name: "RS256"}, {id: "RS256", name: "RS256 (RSA + SHA256)"},
{id: "HS256", name: "HS256 (HMAC + SHA256)"},
{id: "ES256", name: "ES256 (ECDSA using P-256 + SHA256)"},
{id: "ES384", name: "ES384 (ECDSA using P-384 + SHA256)"},
{id: "ES521", name: "ES521 (ECDSA using P-521 + SHA256)"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
@@ -185,9 +202,15 @@ class CertEditPage extends React.Component {
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.cert.bitSize} onChange={value => { <Select virtual={false} style={{width: "100%"}} value={this.state.cert.bitSize} onChange={(value => {
this.updateCertField("bitSize", value); this.updateCertField("bitSize", value);
}} /> this.updateCertField("certificate", "");
this.updateCertField("privateKey", "");
})}>
{
Setting.getCryptoAlgorithmOptions(this.state.cert.cryptoAlgorithm).map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
@@ -205,14 +228,14 @@ class CertEditPage extends React.Component {
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
</Col> </Col>
<Col span={editorWidth} > <Col span={editorWidth} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => { <Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.certificate === ""} onClick={() => {
copy(this.state.cert.certificate); copy(this.state.cert.certificate);
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully")); Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
}} }}
> >
{i18next.t("cert:Copy certificate")} {i18next.t("cert:Copy certificate")}
</Button> </Button>
<Button type="primary" onClick={() => { <Button type="primary" disabled={this.state.cert.certificate === ""} onClick={() => {
const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"}); const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.pem"); FileSaver.saveAs(blob, "token_jwt_key.pem");
}} }}
@@ -228,14 +251,14 @@ class CertEditPage extends React.Component {
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} : {Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
</Col> </Col>
<Col span={editorWidth} > <Col span={editorWidth} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => { <Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.privateKey === ""} onClick={() => {
copy(this.state.cert.privateKey); copy(this.state.cert.privateKey);
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully")); Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
}} }}
> >
{i18next.t("cert:Copy private key")} {i18next.t("cert:Copy private key")}
</Button> </Button>
<Button type="primary" onClick={() => { <Button type="primary" disabled={this.state.cert.privateKey === ""} onClick={() => {
const blob = new Blob([this.state.cert.privateKey], {type: "text/plain;charset=utf-8"}); const blob = new Blob([this.state.cert.privateKey], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.key"); FileSaver.saveAs(blob, "token_jwt_key.key");
}} }}
@@ -265,6 +288,7 @@ class CertEditPage extends React.Component {
this.props.history.push("/certs"); this.props.history.push("/certs");
} else { } else {
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`); this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
this.getCert();
} }
} else { } else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);

View File

@@ -239,7 +239,7 @@ class CertListPage extends BaseListPage {
value = params.type; value = params.type;
} }
this.setState({loading: true}); this.setState({loading: true});
(Setting.isDefaultOrganizationSelected(this.props.account) ? CertBackend.getGlobleCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) (Setting.isDefaultOrganizationSelected(this.props.account) ? CertBackend.getGlobalCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: CertBackend.getCerts(Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)) : CertBackend.getCerts(Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => { .then((res) => {
this.setState({ this.setState({

View File

@@ -323,6 +323,16 @@ class OrganizationEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Master verification code"), i18next.t("general:Master verification code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.masterVerificationCode} onChange={e => {
this.updateOrganizationField("masterVerificationCode", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} : {Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :

View File

@@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
payment: payment, payment: payment,
}); });
if (payment.state === "Created") { if (payment.state === "Created") {
if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) { if (["PayPal", "Stripe", "Alipay", "WeChat Pay"].includes(payment.type)) {
this.setState({ this.setState({
timeout: setTimeout(async() => { timeout: setTimeout(async() => {
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName); await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);

View File

@@ -303,7 +303,7 @@ class PermissionEditPage extends React.Component {
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} : {Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
</Col> </Col>
<Col span={22} > <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);})} onChange={(value => {this.updatePermissionField("roles", value);})}
options={[ options={[
Setting.getOption(i18next.t("organization:All"), "*"), Setting.getOption(i18next.t("organization:All"), "*"),
@@ -323,7 +323,7 @@ class PermissionEditPage extends React.Component {
})} })}
options={[ options={[
Setting.getOption(i18next.t("organization:All"), "*"), 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> </Col>

View File

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

View File

@@ -31,6 +31,7 @@ class ProductBuyPage extends React.Component {
pricingName: props?.pricingName ?? props?.match?.params?.pricingName ?? null, pricingName: props?.pricingName ?? props?.match?.params?.pricingName ?? null,
planName: params.get("plan"), planName: params.get("plan"),
userName: params.get("user"), userName: params.get("user"),
paymentEnv: "",
product: null, product: null,
pricing: props?.pricing ?? null, pricing: props?.pricing ?? null,
plan: null, plan: null,
@@ -38,8 +39,21 @@ class ProductBuyPage extends React.Component {
}; };
} }
getPaymentEnv() {
let env = "";
const ua = navigator.userAgent.toLocaleLowerCase();
// Only support Wechat Pay in Wechat Browser for mobile devices
if (ua.indexOf("micromessenger") !== -1 && ua.indexOf("mobile") !== -1) {
env = "WechatBrowser";
}
this.setState({
paymentEnv: env,
});
}
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
this.getProduct(); this.getProduct();
this.getPaymentEnv();
} }
setStateAsync(state) { setStateAsync(state) {
@@ -127,23 +141,74 @@ class ProductBuyPage extends React.Component {
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`; return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
} }
// Call Weechat Pay via jsapi
onBridgeReady(attachInfo) {
const {WeixinJSBridge} = window;
// Setting.showMessage("success", "attachInfo is " + JSON.stringify(attachInfo));
this.setState({
isPlacingOrder: false,
});
WeixinJSBridge.invoke(
"getBrandWCPayRequest", {
"appId": attachInfo.appId,
"timeStamp": attachInfo.timeStamp,
"nonceStr": attachInfo.nonceStr,
"package": attachInfo.package,
"signType": attachInfo.signType,
"paySign": attachInfo.paySign,
},
function(res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
Setting.goToLink(attachInfo.payment.successUrl);
return ;
} else {
if (res.err_msg === "get_brand_wcpay_request:cancel") {
Setting.showMessage("error", i18next.t("product:Payment cancelled"));
} else {
Setting.showMessage("error", i18next.t("product:Payment failed"));
}
}
}
);
}
// In Wechat browser, call this function to pay via jsapi
callWechatPay(attachInfo) {
const {WeixinJSBridge} = window;
if (typeof WeixinJSBridge === "undefined") {
if (document.addEventListener) {
document.addEventListener("WeixinJSBridgeReady", () => this.onBridgeReady(attachInfo), false);
} else if (document.attachEvent) {
document.attachEvent("WeixinJSBridgeReady", () => this.onBridgeReady(attachInfo));
document.attachEvent("onWeixinJSBridgeReady", () => this.onBridgeReady(attachInfo));
}
} else {
this.onBridgeReady(attachInfo);
}
}
buyProduct(product, provider) { buyProduct(product, provider) {
this.setState({ this.setState({
isPlacingOrder: true, isPlacingOrder: true,
}); });
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "") ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "", this.state.paymentEnv)
.then((res) => { .then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
const payment = res.data; const payment = res.data;
const attachInfo = res.data2;
let payUrl = payment.payUrl; let payUrl = payment.payUrl;
if (provider.type === "WeChat Pay") { if (provider.type === "WeChat Pay") {
if (this.state.paymentEnv === "WechatBrowser") {
attachInfo.payment = payment;
this.callWechatPay(attachInfo);
return ;
}
payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`; payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`;
} }
Setting.goToLink(payUrl); Setting.goToLink(payUrl);
} else { } else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.setState({ this.setState({
isPlacingOrder: false, isPlacingOrder: false,
}); });
@@ -218,7 +283,7 @@ class ProductBuyPage extends React.Component {
return ( return (
<div className="login-content"> <div className="login-content">
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} > <Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
<Descriptions title={<span style={{fontSize: 28}}>{i18next.t("product:Buy Product")}</span>} bordered> <Descriptions title={<span style={Setting.isMobile() ? {fontSize: 20} : {fontSize: 28}}>{i18next.t("product:Buy Product")}</span>} bordered>
<Descriptions.Item label={i18next.t("general:Name")} span={3}> <Descriptions.Item label={i18next.t("general:Name")} span={3}>
<span style={{fontSize: 25}}> <span style={{fontSize: 25}}>
{Setting.getLanguageText(product?.displayName)} {Setting.getLanguageText(product?.displayName)}

View File

@@ -1069,6 +1069,38 @@ export function getProviderTypeOptions(category) {
} }
} }
export function getCryptoAlgorithmOptions(cryptoAlgorithm) {
if (cryptoAlgorithm === "RS256") {
return (
[
{id: 1024, name: "1024"},
{id: 2048, name: "2048"},
{id: 4096, name: "4096"},
]
);
} else if (cryptoAlgorithm === "HS256" || cryptoAlgorithm === "ES256") {
return (
[
{id: 256, name: "256"},
]
);
} else if (cryptoAlgorithm === "ES384") {
return (
[
{id: 384, name: "384"},
]
);
} else if (cryptoAlgorithm === "ES521") {
return (
[
{id: 521, name: "521"},
]
);
} else {
return [];
}
}
export function renderLogo(application) { export function renderLogo(application) {
if (application === null) { if (application === null) {
return null; return null;

View File

@@ -337,7 +337,7 @@ class LoginPage extends React.Component {
const casParams = Util.getCasParameters(); const casParams = Util.getCasParameters();
values["type"] = this.state.type; values["type"] = this.state.type;
AuthBackend.loginCas(values, casParams).then((res) => { AuthBackend.loginCas(values, casParams).then((res) => {
if (res.status === "ok") { const loginHandler = (res) => {
let msg = "Logged in successfully. "; let msg = "Logged in successfully. ";
if (casParams.service === "") { 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. // 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); newUrl.searchParams.append("ticket", st);
window.location.href = newUrl.toString(); 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 { } else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
} }
@@ -361,7 +383,7 @@ class LoginPage extends React.Component {
this.populateOauthValues(values); this.populateOauthValues(values);
AuthBackend.login(values, oAuthParams) AuthBackend.login(values, oAuthParams)
.then((res) => { .then((res) => {
const callback = (res) => { const loginHandler = (res) => {
const responseType = values["type"]; const responseType = values["type"];
if (responseType === "login") { if (responseType === "login") {
@@ -396,12 +418,12 @@ class LoginPage extends React.Component {
<MfaAuthVerifyForm <MfaAuthVerifyForm
mfaProps={res.data2} mfaProps={res.data2}
formValues={values} formValues={values}
oAuthParams={oAuthParams} authParams={oAuthParams}
application={this.getApplicationObj()} application={this.getApplicationObj()}
onFail={() => { onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed")); 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; const sub = res.data2;
Setting.goToLink(`/buy-plan/${sub.owner}/${sub.pricing}/result?subscription=${sub.name}`); Setting.goToLink(`/buy-plan/${sub.owner}/${sub.pricing}/result?subscription=${sub.name}`);
} else { } else {
callback(res); loginHandler(res);
} }
} else { } else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`); Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);

View File

@@ -262,7 +262,7 @@ class PromptPage extends React.Component {
initSteps(user, application) { initSteps(user, application) {
const steps = []; const steps = [];
if (!Setting.isPromptAnswered(user, application) && this.state.promptType === "provider") { if (Setting.hasPromptPage(application)) {
steps.push({ steps.push({
content: this.renderPromptProvider(application), content: this.renderPromptProvider(application),
name: "provider", name: "provider",

View File

@@ -382,7 +382,7 @@ export function getAuthUrl(application, provider, method) {
let redirectUri = `${window.location.origin}/callback`; let redirectUri = `${window.location.origin}/callback`;
const scope = authInfo[provider.type].scope; const scope = authInfo[provider.type].scope;
const isShortState = provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger"); const isShortState = (provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger")) || (provider.type === "Twitter");
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState); const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier")) const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))

View File

@@ -24,7 +24,7 @@ import MfaVerifyTotpForm from "./MfaVerifyTotpForm";
export const NextMfa = "NextMfa"; export const NextMfa = "NextMfa";
export const RequiredMfa = "RequiredMfa"; 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.password = "";
formValues.username = ""; formValues.username = "";
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -34,7 +34,8 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
const verify = ({passcode}) => { const verify = ({passcode}) => {
setLoading(true); setLoading(true);
const values = {...formValues, passcode, mfaType}; 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") { if (res.status === "ok") {
onSuccess(res); onSuccess(res);
} else { } else {
@@ -49,7 +50,9 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
const recover = () => { const recover = () => {
setLoading(true); 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") { if (res.status === "ok") {
onSuccess(res); onSuccess(res);
} else { } else {

View File

@@ -24,8 +24,8 @@ export function getCerts(owner, page = "", pageSize = "", field = "", value = ""
}).then(res => res.json()); }).then(res => res.json());
} }
export function getGlobleCerts(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") { export function getGlobalCerts(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-globle-certs?&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, { return fetch(`${Setting.ServerUrl}/api/get-global-certs?&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET", method: "GET",
credentials: "include", credentials: "include",
headers: { headers: {

View File

@@ -70,8 +70,8 @@ export function deleteProduct(product) {
}).then(res => res.json()); }).then(res => res.json());
} }
export function buyProduct(owner, name, providerName, pricingName = "", planName = "", userName = "") { export function buyProduct(owner, name, providerName, pricingName = "", planName = "", userName = "", paymentEnv = "") {
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerName=${providerName}&pricingName=${pricingName}&planName=${planName}&userName=${userName}`, { return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerName=${providerName}&pricingName=${pricingName}&planName=${planName}&userName=${userName}&paymentEnv=${paymentEnv}`, {
method: "POST", method: "POST",
credentials: "include", credentials: "include",
headers: { headers: {