mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-16 19:13:51 +08:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
396b6fb65f | |||
be637fca81 | |||
374928e719 | |||
5c103e8cd3 | |||
85b86e8831 | |||
08864686f3 | |||
dc06eb9948 | |||
b068202e74 | |||
cb16567c7b | |||
4eb725d47a | |||
ce72a172b0 | |||
5521962e0c | |||
37b8b09cc0 | |||
482eb61168 | |||
8819a8697b | |||
85cb68eb66 | |||
b25b5f0249 | |||
947dcf6e75 | |||
113c27db73 | |||
badfe34755 | |||
a5f9f61381 | |||
2ce8c93ead | |||
da41ac7275 | |||
fd0c70a827 | |||
c4a6f07672 | |||
a67f541171 | |||
192968bac8 | |||
23d4488b64 | |||
23f4684e1d | |||
1a91e7b0f9 | |||
811999b6cc | |||
7786018051 | |||
6c72f86d03 | |||
5b151f4ec4 | |||
e9b7d1266f | |||
2d4998228c | |||
d3ed6c348b | |||
a22e05dcc1 | |||
0ac2b69f5a | |||
d090e9c860 |
@ -151,7 +151,7 @@ func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath
|
||||
return true
|
||||
} else if urlPath == "/api/update-user" {
|
||||
// Allow ordinary users to update their own information
|
||||
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
|
||||
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -282,17 +282,15 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
}
|
||||
|
||||
affected, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !affected {
|
||||
if token == nil {
|
||||
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
|
||||
return
|
||||
@ -319,7 +317,15 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
} else {
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
||||
redirectUrl := redirectUri
|
||||
if state != "" {
|
||||
if strings.Contains(redirectUri, "?") {
|
||||
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
} else {
|
||||
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
}
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, redirectUrl)
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
@ -473,7 +479,7 @@ func (c *ApiController) GetCaptcha() {
|
||||
Type: captchaProvider.Type,
|
||||
SubType: captchaProvider.SubType,
|
||||
ClientId: captchaProvider.ClientId,
|
||||
ClientSecret: captchaProvider.ClientSecret,
|
||||
ClientSecret: "***",
|
||||
ClientId2: captchaProvider.ClientId2,
|
||||
ClientSecret2: captchaProvider.ClientSecret2,
|
||||
})
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -154,7 +155,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
||||
} else {
|
||||
scope := c.Input().Get("scope")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
|
||||
nonce := c.Input().Get("nonce")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||
resp = tokenToResponse(token)
|
||||
}
|
||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||
@ -331,8 +333,6 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if authForm.Password == "" {
|
||||
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
@ -354,20 +354,21 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||
err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// disable the verification code
|
||||
err := object.DisableVerificationCode(checkDest)
|
||||
err = object.DisableVerificationCode(checkDest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
@ -386,7 +387,18 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if enableCaptcha {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
captchaProvider, err := object.GetCaptchaProviderByApplication(util.GetId(application.Owner, application.Name), "false", c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
authForm.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
var isHuman bool
|
||||
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -399,13 +411,15 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
password := authForm.Password
|
||||
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
resp = &Response{Status: "error", Msg: msg}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else {
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -416,7 +430,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
@ -461,12 +476,15 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
}
|
||||
|
||||
provider, err := object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
var provider *object.Provider
|
||||
provider, err = object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -488,7 +506,8 @@ func (c *ApiController) Login() {
|
||||
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
||||
// OAuth
|
||||
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
|
||||
idProvider, err := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
||||
var idProvider idp.IdProvider
|
||||
idProvider, err = idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -506,7 +525,8 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
||||
token, err := idProvider.GetToken(authForm.Code)
|
||||
var token *oauth2.Token
|
||||
token, err = idProvider.GetToken(authForm.Code)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -547,7 +567,12 @@ func (c *ApiController) Login() {
|
||||
if user.IsForbidden {
|
||||
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)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
@ -588,14 +613,16 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// Handle username conflicts
|
||||
tmpUser, err := object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||
var tmpUser *object.User
|
||||
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if tmpUser != nil {
|
||||
uid, err := uuid.NewRandom()
|
||||
var uid uuid.UUID
|
||||
uid, err = uuid.NewRandom()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -606,14 +633,16 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
properties := map[string]string{}
|
||||
count, err := object.GetUserCount(application.Organization, "", "", "")
|
||||
var count int64
|
||||
count, err = object.GetUserCount(application.Organization, "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
properties["no"] = strconv.Itoa(int(count + 2))
|
||||
initScore, err := organization.GetInitScore()
|
||||
var initScore int
|
||||
initScore, err = organization.GetInitScore()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
@ -645,7 +674,8 @@ func (c *ApiController) Login() {
|
||||
Properties: properties,
|
||||
}
|
||||
|
||||
affected, err := object.AddUser(user)
|
||||
var affected bool
|
||||
affected, err = object.AddUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -667,7 +697,7 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -703,7 +733,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
oldUser, err := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
var oldUser *object.User
|
||||
oldUser, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -714,7 +745,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
var user *object.User
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -727,7 +759,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
isLinked, err := object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
var isLinked bool
|
||||
isLinked, err = object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -740,7 +773,8 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
} else if c.getMfaUserSession() != "" {
|
||||
user, err := object.GetUser(c.getMfaUserSession())
|
||||
var user *object.User
|
||||
user, err = object.GetUser(c.getMfaUserSession())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -773,7 +807,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -794,7 +829,8 @@ func (c *ApiController) Login() {
|
||||
} else {
|
||||
if c.GetSessionUsername() != "" {
|
||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -243,7 +243,13 @@ func (c *ApiController) GetAllObjects() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllObjects(userId))
|
||||
objects, err := object.GetAllObjects(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(objects)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllActions() {
|
||||
@ -253,7 +259,13 @@ func (c *ApiController) GetAllActions() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllActions(userId))
|
||||
actions, err := object.GetAllActions(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(actions)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllRoles() {
|
||||
@ -263,5 +275,11 @@ func (c *ApiController) GetAllRoles() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllRoles(userId))
|
||||
roles, err := object.GetAllRoles(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(roles)
|
||||
}
|
||||
|
@ -65,13 +65,13 @@ func (c *ApiController) GetCerts() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobleCerts
|
||||
// @Title GetGlobleCerts
|
||||
// GetGlobalCerts
|
||||
// @Title GetGlobalCerts
|
||||
// @Tag Cert API
|
||||
// @Description get globle certs
|
||||
// @Success 200 {array} object.Cert The Response object
|
||||
// @router /get-globle-certs [get]
|
||||
func (c *ApiController) GetGlobleCerts() {
|
||||
// @router /get-global-certs [get]
|
||||
func (c *ApiController) GetGlobalCerts() {
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
@ -80,7 +80,7 @@ func (c *ApiController) GetGlobleCerts() {
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts())
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetGlobalCerts())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -59,6 +59,7 @@ func (c *ApiController) GetLdapUsers() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
//if err != nil {
|
||||
|
@ -163,6 +163,8 @@ func (c *ApiController) BuyProduct() {
|
||||
id := c.Input().Get("id")
|
||||
host := c.Ctx.Request.Host
|
||||
providerName := c.Input().Get("providerName")
|
||||
paymentEnv := c.Input().Get("paymentEnv")
|
||||
|
||||
// buy `pricingName/planName` for `paidUserName`
|
||||
pricingName := c.Input().Get("pricingName")
|
||||
planName := c.Input().Get("planName")
|
||||
@ -187,11 +189,11 @@ func (c *ApiController) BuyProduct() {
|
||||
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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment)
|
||||
c.ResponseOk(payment, attachInfo)
|
||||
}
|
||||
|
@ -158,10 +158,9 @@ func (c *ApiController) DeleteToken() {
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router api/login/oauth/access_token [post]
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
refreshToken := c.Input().Get("refresh_token")
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
grantType := c.Input().Get("grant_type")
|
||||
code := c.Input().Get("code")
|
||||
verifier := c.Input().Get("code_verifier")
|
||||
scope := c.Input().Get("scope")
|
||||
@ -169,35 +168,61 @@ func (c *ApiController) GetOAuthToken() {
|
||||
password := c.Input().Get("password")
|
||||
tag := c.Input().Get("tag")
|
||||
avatar := c.Input().Get("avatar")
|
||||
refreshToken := c.Input().Get("refresh_token")
|
||||
|
||||
if clientId == "" && clientSecret == "" {
|
||||
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
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
||||
clientId = tokenRequest.ClientId
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
grantType = tokenRequest.GrantType
|
||||
refreshToken = tokenRequest.RefreshToken
|
||||
code = tokenRequest.Code
|
||||
verifier = tokenRequest.Verifier
|
||||
scope = tokenRequest.Scope
|
||||
username = tokenRequest.Username
|
||||
password = tokenRequest.Password
|
||||
tag = tokenRequest.Tag
|
||||
avatar = tokenRequest.Avatar
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
||||
if err == nil {
|
||||
if clientId == "" {
|
||||
clientId = tokenRequest.ClientId
|
||||
}
|
||||
if clientSecret == "" {
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
}
|
||||
if grantType == "" {
|
||||
grantType = tokenRequest.GrantType
|
||||
}
|
||||
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
|
||||
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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = oAuthtoken
|
||||
c.Data["json"] = token
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@ -15,10 +15,10 @@
|
||||
package controllers
|
||||
|
||||
type TokenRequest struct {
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
ClientId string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
Verifier string `json:"code_verifier"`
|
||||
Scope string `json:"scope"`
|
||||
Username string `json:"username"`
|
||||
|
@ -476,16 +476,16 @@ func (c *ApiController) SetPassword() {
|
||||
isAdmin := c.IsAdmin()
|
||||
if isAdmin {
|
||||
if oldPassword != "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
} else if code == "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -518,11 +518,11 @@ func (c *ApiController) CheckUserPassword() {
|
||||
return
|
||||
}
|
||||
|
||||
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
|
||||
if msg == "" {
|
||||
c.ResponseOk()
|
||||
_, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
} else {
|
||||
c.ResponseError(msg)
|
||||
c.ResponseOk()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,17 +53,34 @@ func (c *ApiController) SendVerificationCode() {
|
||||
return
|
||||
}
|
||||
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
provider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if provider != nil {
|
||||
if vform.CaptchaType != provider.Type {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
|
||||
if provider.Type != "Default" {
|
||||
vform.ClientSecret = provider.ClientSecret
|
||||
}
|
||||
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(vform.ApplicationId)
|
||||
@ -225,6 +242,16 @@ func (c *ApiController) VerifyCaptcha() {
|
||||
return
|
||||
}
|
||||
|
||||
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
vform.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
|
82
email/custom_http.go
Normal file
82
email/custom_http.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
type HttpEmailProvider struct {
|
||||
endpoint string
|
||||
method string
|
||||
}
|
||||
|
||||
func NewHttpEmailProvider(endpoint string, method string) *HttpEmailProvider {
|
||||
client := &HttpEmailProvider{
|
||||
endpoint: endpoint,
|
||||
method: method,
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||
var req *http.Request
|
||||
var err error
|
||||
if c.method == "POST" {
|
||||
formValues := url.Values{}
|
||||
formValues.Set("fromName", fromName)
|
||||
formValues.Set("toAddress", toAddress)
|
||||
formValues.Set("subject", subject)
|
||||
formValues.Set("content", content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("fromName", fromName)
|
||||
q.Add("toAddress", toAddress)
|
||||
q.Add("subject", subject)
|
||||
q.Add("content", content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
} else {
|
||||
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HttpEmailProvider's Send() error, custom HTTP Email request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -18,9 +18,11 @@ type EmailProvider interface {
|
||||
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
||||
}
|
||||
|
||||
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool) EmailProvider {
|
||||
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string) EmailProvider {
|
||||
if typ == "Azure ACS" {
|
||||
return NewAzureACSEmailProvider(clientSecret, host)
|
||||
} else if typ == "Custom HTTP Email" {
|
||||
return NewHttpEmailProvider(endpoint, method)
|
||||
} else {
|
||||
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||
}
|
||||
|
22
go.mod
22
go.mod
@ -9,12 +9,11 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.45.5
|
||||
github.com/beego/beego v1.12.12
|
||||
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/casdoor/go-sms-sender v0.15.0
|
||||
github.com/casdoor/go-sms-sender v0.17.0
|
||||
github.com/casdoor/gomail/v2 v2.0.1
|
||||
github.com/casdoor/notify v0.45.0
|
||||
github.com/casdoor/oss v1.3.0
|
||||
github.com/casdoor/oss v1.4.1
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
@ -23,15 +22,17 @@ require (
|
||||
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||
github.com/fogleman/gg v1.3.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-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-mysql-org/go-mysql v1.7.0
|
||||
github.com/go-pay/gopay v1.5.72
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/go-webauthn/webauthn v0.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/lestrrat-go/jwx v1.2.21
|
||||
github.com/lib/pq v1.10.9
|
||||
@ -42,7 +43,7 @@ require (
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/prometheus/client_model v0.3.0
|
||||
github.com/prometheus/client_model v0.4.0
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.9.0
|
||||
@ -61,11 +62,10 @@ require (
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/api v0.138.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
google.golang.org/api v0.150.0
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
||||
|
@ -31,6 +31,7 @@ type UserInfo struct {
|
||||
Phone string
|
||||
CountryCode string
|
||||
AvatarUrl string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
type ProviderInfo struct {
|
||||
|
@ -186,15 +186,24 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
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{
|
||||
Id: id,
|
||||
Username: wechatUserInfo.Nickname,
|
||||
DisplayName: wechatUserInfo.Nickname,
|
||||
AvatarUrl: wechatUserInfo.Headimgurl,
|
||||
Extra: extra,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func BuildWechatOpenIdKey(appId string) string {
|
||||
return fmt.Sprintf("wechat_openid_%s", appId)
|
||||
}
|
||||
|
||||
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)
|
||||
request, err := http.NewRequest("GET", accessTokenUrl, nil)
|
||||
|
@ -16,6 +16,7 @@ package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"log"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -49,20 +50,20 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
|
||||
if r.AuthenticationChoice() == "simple" {
|
||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
|
||||
if err != "" {
|
||||
log.Printf("Bind failed ,ErrMsg=%s", err)
|
||||
if err != nil {
|
||||
log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
|
||||
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
||||
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
||||
res.SetDiagnosticMessage(fmt.Sprintf("getNameAndOrgFromDN() error: %s", err.Error()))
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
||||
if err != "" {
|
||||
if err != nil {
|
||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err.Error())
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
@ -78,7 +79,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
m.Client.OrgName = bindOrg
|
||||
} else {
|
||||
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
|
||||
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
||||
res.SetDiagnosticMessage("Authentication method not supported, please use Simple Authentication")
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
@ -113,10 +114,22 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
|
||||
for _, attr := range r.Attributes() {
|
||||
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
|
||||
e.AddAttribute("uidNumber", message.AttributeValue(uidNumberStr))
|
||||
e.AddAttribute("gidNumber", message.AttributeValue(uidNumberStr))
|
||||
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
|
||||
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||
e.AddAttribute("uid", message.AttributeValue(user.Id))
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
attrs = AdditionalLdapAttributes
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||
if string(attr) == "cn" {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||
@ -127,3 +140,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
func hash(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(s))
|
||||
return h.Sum32()
|
||||
}
|
||||
|
200
ldap/util.go
200
ldap/util.go
@ -24,9 +24,73 @@ import (
|
||||
"github.com/lor00x/goldap/message"
|
||||
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
|
||||
"github.com/xorm-io/builder"
|
||||
)
|
||||
|
||||
func getNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
type AttributeMapper func(user *object.User) message.AttributeValue
|
||||
|
||||
type FieldRelation struct {
|
||||
userField string
|
||||
notSearchable bool
|
||||
hideOnStarOp bool
|
||||
fieldMapper AttributeMapper
|
||||
}
|
||||
|
||||
func (rel FieldRelation) GetField() (string, error) {
|
||||
if rel.notSearchable {
|
||||
return "", fmt.Errorf("attribute %s not supported", rel.userField)
|
||||
}
|
||||
return rel.userField, nil
|
||||
}
|
||||
|
||||
func (rel FieldRelation) GetAttributeValue(user *object.User) message.AttributeValue {
|
||||
return rel.fieldMapper(user)
|
||||
}
|
||||
|
||||
var ldapAttributesMapping = map[string]FieldRelation{
|
||||
"cn": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Name)
|
||||
}},
|
||||
"uid": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Name)
|
||||
}},
|
||||
"displayname": {userField: "displayName", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.DisplayName)
|
||||
}},
|
||||
"email": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Email)
|
||||
}},
|
||||
"mail": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Email)
|
||||
}},
|
||||
"mobile": {userField: "phone", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Phone)
|
||||
}},
|
||||
"title": {userField: "tag", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(user.Tag)
|
||||
}},
|
||||
"userPassword": {
|
||||
userField: "userPassword",
|
||||
notSearchable: true,
|
||||
fieldMapper: func(user *object.User) message.AttributeValue {
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var AdditionalLdapAttributes []message.LDAPString
|
||||
|
||||
func init() {
|
||||
for k, v := range ldapAttributesMapping {
|
||||
if v.hideOnStarOp {
|
||||
continue
|
||||
}
|
||||
AdditionalLdapAttributes = append(AdditionalLdapAttributes, message.LDAPString(k))
|
||||
}
|
||||
}
|
||||
|
||||
func getNameAndOrgFromDN(DN string) (string, string, error) {
|
||||
DNFields := strings.Split(DN, ",")
|
||||
params := make(map[string]string, len(DNFields))
|
||||
for _, field := range DNFields {
|
||||
@ -37,12 +101,12 @@ func getNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
}
|
||||
|
||||
if params["cn"] == "" {
|
||||
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
||||
return "", "", fmt.Errorf("please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com")
|
||||
}
|
||||
if params["ou"] == "" {
|
||||
return params["cn"], object.CasdoorOrganization, ""
|
||||
return params["cn"], object.CasdoorOrganization, nil
|
||||
}
|
||||
return params["cn"], params["ou"], ""
|
||||
return params["cn"], params["ou"], nil
|
||||
}
|
||||
|
||||
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
||||
@ -50,7 +114,11 @@ func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
||||
return "", "", ldap.LDAPResultInvalidDNSyntax
|
||||
}
|
||||
|
||||
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
||||
name, org, err := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return name, org, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
@ -83,6 +151,92 @@ func stringInSlice(value string, list []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
||||
switch f := filter.(type) {
|
||||
case message.FilterAnd:
|
||||
conditions := make([]builder.Cond, len(f))
|
||||
for i, v := range f {
|
||||
cond, err := buildUserFilterCondition(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = cond
|
||||
}
|
||||
return builder.And(conditions...), nil
|
||||
case message.FilterOr:
|
||||
conditions := make([]builder.Cond, len(f))
|
||||
for i, v := range f {
|
||||
cond, err := buildUserFilterCondition(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = cond
|
||||
}
|
||||
return builder.Or(conditions...), nil
|
||||
case message.FilterNot:
|
||||
cond, err := buildUserFilterCondition(f.Filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Not{cond}, nil
|
||||
case message.FilterEqualityMatch:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Eq{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterPresent:
|
||||
field, err := getUserFieldFromAttribute(string(f))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.NotNull{field}, nil
|
||||
case message.FilterGreaterOrEqual:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Gte{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterLessOrEqual:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Lte{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterSubstrings:
|
||||
field, err := getUserFieldFromAttribute(string(f.Type_()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var expr string
|
||||
for _, substring := range f.Substrings() {
|
||||
switch s := substring.(type) {
|
||||
case message.SubstringInitial:
|
||||
expr += string(s) + "%"
|
||||
continue
|
||||
case message.SubstringAny:
|
||||
expr += string(s) + "%"
|
||||
continue
|
||||
case message.SubstringFinal:
|
||||
expr += string(s)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return builder.Expr(field+" LIKE ?", expr), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("LDAP filter operation %#v not supported", f)
|
||||
}
|
||||
}
|
||||
|
||||
func buildSafeCondition(filter interface{}) builder.Cond {
|
||||
condition, err := buildUserFilterCondition(filter)
|
||||
if err != nil {
|
||||
log.Printf("err = %v", err.Error())
|
||||
return nil
|
||||
}
|
||||
return condition
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
||||
var err error
|
||||
r := m.GetSearchRequest()
|
||||
@ -94,15 +248,14 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if m.Client.IsGlobalAdmin && org == "*" {
|
||||
|
||||
filteredUsers, err = object.GetGlobalUsers()
|
||||
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
||||
filteredUsers, err = object.GetUsers(org)
|
||||
filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -144,7 +297,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
return nil, ldap.LDAPResultNoSuchObject
|
||||
}
|
||||
|
||||
users, err := object.GetUsersByTag(org, name)
|
||||
users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -178,24 +331,17 @@ func getUserPasswordWithType(user *object.User) string {
|
||||
}
|
||||
|
||||
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
|
||||
switch attributeName {
|
||||
case "cn":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "uid":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "displayname":
|
||||
return message.AttributeValue(user.DisplayName)
|
||||
case "email":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mail":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mobile":
|
||||
return message.AttributeValue(user.Phone)
|
||||
case "title":
|
||||
return message.AttributeValue(user.Tag)
|
||||
case "userPassword":
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
default:
|
||||
v, ok := ldapAttributesMapping[attributeName]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return v.GetAttributeValue(user)
|
||||
}
|
||||
|
||||
func getUserFieldFromAttribute(attributeName string) (string, error) {
|
||||
v, ok := ldapAttributesMapping[attributeName]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("attribute %s not supported", attributeName)
|
||||
}
|
||||
return v.GetField()
|
||||
}
|
||||
|
87
ldap/util_test.go
Normal file
87
ldap/util_test.go
Normal 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
|
||||
}
|
@ -15,10 +15,10 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
appVersion: "1.17.0"
|
||||
|
@ -59,6 +59,9 @@ spec:
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /conf
|
||||
{{ if .Values.extraContainersEnabled }}
|
||||
{{- .Values.extraContainers | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: config-volume
|
||||
projected:
|
||||
|
@ -108,3 +108,10 @@ nodeSelector: {}
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
# -- Optionally add extra sidecar containers.
|
||||
extraContainersEnabled: false
|
||||
extraContainers: ""
|
||||
# extraContainers: |
|
||||
# - name: ...
|
||||
# image: ...
|
@ -15,10 +15,11 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
@ -39,26 +40,29 @@ func NewCustomHttpProvider(endpoint string, method string, paramName string) (*H
|
||||
}
|
||||
|
||||
func (c *HttpNotificationClient) Send(ctx context.Context, subject string, content string) error {
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
|
||||
req, err := http.NewRequest(c.method, c.endpoint, bytes.NewBufferString(content))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.method == "POST" {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.PostForm = map[string][]string{
|
||||
c.paramName: {content},
|
||||
formValues := url.Values{}
|
||||
formValues.Set(c.paramName, content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add(c.paramName, content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -66,7 +70,7 @@ func (c *HttpNotificationClient) Send(ctx context.Context, subject string, conte
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
|
||||
return fmt.Errorf("HttpNotificationClient's SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -319,6 +319,9 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
||||
if application.OrganizationObj.DefaultPassword != "" {
|
||||
application.OrganizationObj.DefaultPassword = "***"
|
||||
}
|
||||
if application.OrganizationObj.MasterVerificationCode != "" {
|
||||
application.OrganizationObj.MasterVerificationCode = "***"
|
||||
}
|
||||
if application.OrganizationObj.PasswordType != "" {
|
||||
application.OrganizationObj.PasswordType = "***"
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func GetGlobalCertsCount(field, value string) (int64, error) {
|
||||
return session.Count(&Cert{})
|
||||
}
|
||||
|
||||
func GetGlobleCerts() ([]*Cert, error) {
|
||||
func GetGlobalCerts() ([]*Cert, error) {
|
||||
certs := []*Cert{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&certs)
|
||||
if err != nil {
|
||||
@ -163,6 +163,12 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
err := cert.populateContent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -172,10 +178,9 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
|
||||
}
|
||||
|
||||
func AddCert(cert *Cert) (bool, error) {
|
||||
if cert.Certificate == "" || cert.PrivateKey == "" {
|
||||
certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
|
||||
cert.Certificate = certificate
|
||||
cert.PrivateKey = privateKey
|
||||
err := cert.populateContent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(cert)
|
||||
@ -199,6 +204,20 @@ func (p *Cert) GetId() string {
|
||||
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) {
|
||||
if application.Cert != "" {
|
||||
return getCertByName(application.Cert)
|
||||
|
@ -142,7 +142,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkSigninErrorTimes(user *User, lang string) string {
|
||||
func checkSigninErrorTimes(user *User, lang string) error {
|
||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
|
||||
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
|
||||
@ -150,37 +150,39 @@ func checkSigninErrorTimes(user *User, lang string) string {
|
||||
|
||||
// deny the login if the error times is greater than the limit and the last login time is less than the duration
|
||||
if minutes > 0 {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
|
||||
}
|
||||
|
||||
// reset the error times
|
||||
user.SigninWrongTimes = 0
|
||||
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
|
||||
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckPassword(user *User, password string, lang string, options ...bool) string {
|
||||
func CheckPassword(user *User, password string, lang string, options ...bool) error {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
// check the login error times
|
||||
if !enableCaptcha {
|
||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
||||
return msg
|
||||
err := checkSigninErrorTimes(user, lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
organization, err := GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if organization == nil {
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
|
||||
}
|
||||
|
||||
passwordType := user.PasswordType
|
||||
@ -191,19 +193,17 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
|
||||
if credManager != nil {
|
||||
if organization.MasterPassword != "" {
|
||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||
resetUserSigninErrorTimes(user)
|
||||
return ""
|
||||
return resetUserSigninErrorTimes(user)
|
||||
}
|
||||
}
|
||||
|
||||
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
||||
resetUserSigninErrorTimes(user)
|
||||
return ""
|
||||
return resetUserSigninErrorTimes(user)
|
||||
}
|
||||
|
||||
return recordSigninErrorInfo(user, lang, enableCaptcha)
|
||||
} else {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,10 +217,10 @@ func CheckPasswordComplexity(user *User, password string) string {
|
||||
return CheckPasswordComplexityByOrg(organization, password)
|
||||
}
|
||||
|
||||
func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||
func checkLdapUserPassword(user *User, password string, lang string) error {
|
||||
ldaps, err := GetLdaps(user.Owner)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
return err
|
||||
}
|
||||
|
||||
ldapLoginSuccess := false
|
||||
@ -237,65 +237,73 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||
|
||||
searchResult, err := conn.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
if len(searchResult.Entries) > 1 {
|
||||
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
|
||||
conn.Close()
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
|
||||
}
|
||||
|
||||
hit = true
|
||||
dn := searchResult.Entries[0].DN
|
||||
if err := conn.Conn.Bind(dn, password); err == nil {
|
||||
if err = conn.Conn.Bind(dn, password); err == nil {
|
||||
ldapLoginSuccess = true
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
if !ldapLoginSuccess {
|
||||
if !hit {
|
||||
return "user not exist"
|
||||
return fmt.Errorf("user not exist")
|
||||
}
|
||||
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:LDAP user name or password incorrect"))
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
|
||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
user, err := GetUserByFields(organization, username)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user == nil || user.IsDeleted {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||
return nil, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||
}
|
||||
|
||||
if user.IsForbidden {
|
||||
return nil, i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator")
|
||||
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator"))
|
||||
}
|
||||
|
||||
if user.Ldap != "" {
|
||||
// ONLY for ldap users
|
||||
if msg := checkLdapUserPassword(user, password, lang); msg != "" {
|
||||
if msg == "user not exist" {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
||||
// only for LDAP users
|
||||
err = checkLdapUserPassword(user, password, lang)
|
||||
if err != nil {
|
||||
if err.Error() == "user not exist" {
|
||||
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
||||
}
|
||||
return nil, msg
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
|
||||
return nil, msg
|
||||
err = CheckPassword(user, password, lang, enableCaptcha)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return user, ""
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
|
||||
@ -308,7 +316,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
||||
if userId != "" {
|
||||
targetUser, err := GetUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if targetUser == nil {
|
||||
@ -366,7 +374,7 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
||||
allowCount := 0
|
||||
denyCount := 0
|
||||
for _, permission := range permissions {
|
||||
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||
if !permission.IsEnabled || permission.State != "Approved" || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -36,20 +36,23 @@ func isValidRealName(s string) bool {
|
||||
return reRealName.MatchString(s)
|
||||
}
|
||||
|
||||
func resetUserSigninErrorTimes(user *User) {
|
||||
func resetUserSigninErrorTimes(user *User) error {
|
||||
// if the password is correct and wrong times is not zero, reset the error times
|
||||
if user.SigninWrongTimes == 0 {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
user.SigninWrongTimes = 0
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
|
||||
// increase failed login count
|
||||
if user.SigninWrongTimes < SigninWrongTimesLimit {
|
||||
user.SigninWrongTimes++
|
||||
@ -61,13 +64,18 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
|
||||
}
|
||||
|
||||
// update user
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
|
||||
if leftChances == 0 && enableCaptcha {
|
||||
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||
} else if leftChances >= 0 {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
||||
}
|
||||
|
||||
// don't show the chance error message if the user has no chance left
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
|
||||
}
|
||||
|
||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl)
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl, provider.Endpoint, provider.Method)
|
||||
|
||||
fromAddress := provider.ClientId2
|
||||
if fromAddress == "" {
|
||||
|
@ -271,7 +271,9 @@ func GetGroupUsers(groupId string) ([]*User, error) {
|
||||
users := []*User{}
|
||||
owner, _ := util.GetOwnerAndNameFromId(groupId)
|
||||
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -303,6 +305,9 @@ func GroupChangeTrigger(oldName, newName string) error {
|
||||
|
||||
groups := []*Group{}
|
||||
err = session.Where("parent_id = ?", oldName).Find(&groups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, group := range groups {
|
||||
group.ParentId = newName
|
||||
_, err := session.ID(core.PK{group.Owner, group.Name}).Cols("parent_id").Update(group)
|
||||
|
@ -396,15 +396,22 @@ func initBuiltInPermission() {
|
||||
Name: "permission-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Permission",
|
||||
Description: "Built-in Permission",
|
||||
Users: []string{"built-in/*"},
|
||||
Groups: []string{},
|
||||
Roles: []string{},
|
||||
Domains: []string{},
|
||||
Model: "model-built-in",
|
||||
Adapter: "",
|
||||
ResourceType: "Application",
|
||||
Resources: []string{"app-built-in"},
|
||||
Actions: []string{"Read", "Write", "Admin"},
|
||||
Effect: "Allow",
|
||||
IsEnabled: true,
|
||||
Submitter: "admin",
|
||||
Approver: "admin",
|
||||
ApproveTime: util.GetCurrentTime(),
|
||||
State: "Approved",
|
||||
}
|
||||
_, err = AddPermission(permission)
|
||||
if err != nil {
|
||||
|
@ -100,6 +100,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
||||
|
||||
users, err := conn.GetLdapUsers(ldap)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
}
|
||||
@ -111,6 +112,8 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
||||
} else {
|
||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,17 @@ func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
||||
return &LdapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
|
||||
func (l *LdapConn) Close() {
|
||||
if l.Conn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := l.Conn.Unbind()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectClass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
|
@ -51,23 +51,24 @@ type Organization struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||
InitScore int `json:"initScore"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||
InitScore int `json:"initScore"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
|
||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||
@ -159,6 +160,9 @@ func GetMaskedOrganization(organization *Organization, errs ...error) (*Organiza
|
||||
if organization.DefaultPassword != "" {
|
||||
organization.DefaultPassword = "***"
|
||||
}
|
||||
if organization.MasterVerificationCode != "" {
|
||||
organization.MasterVerificationCode = "***"
|
||||
}
|
||||
return organization, nil
|
||||
}
|
||||
|
||||
@ -213,6 +217,9 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
|
||||
if organization.DefaultPassword == "***" {
|
||||
session.Omit("default_password")
|
||||
}
|
||||
if organization.MasterVerificationCode == "***" {
|
||||
session.Omit("master_verification_code")
|
||||
}
|
||||
|
||||
affected, err := session.Update(organization)
|
||||
if err != nil {
|
||||
|
@ -120,7 +120,11 @@ func checkPermissionValid(permission *Permission) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/casbin/casbin/v2/log"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
)
|
||||
|
||||
@ -137,6 +138,16 @@ func getPolicies(permission *Permission) [][]string {
|
||||
}
|
||||
|
||||
func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
|
||||
roleOwner, roleName := util.GetOwnerAndNameFromId(roleId)
|
||||
if roleName == "*" {
|
||||
roles, err := GetRoles(roleOwner)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
role, err := GetRole(roleId)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
@ -162,7 +173,7 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func getGroupingPolicies(permission *Permission) [][]string {
|
||||
func getGroupingPolicies(permission *Permission) ([][]string, error) {
|
||||
var groupingPolicies [][]string
|
||||
|
||||
domainExist := len(permission.Domains) > 0
|
||||
@ -170,12 +181,18 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
|
||||
for _, roleId := range permission.Roles {
|
||||
visited := map[string]struct{}{}
|
||||
|
||||
if roleId == "*" {
|
||||
roleId = util.GetId(permission.Owner, "*")
|
||||
}
|
||||
|
||||
rolesInRole, err := getRolesInRole(roleId, visited)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range rolesInRole {
|
||||
roleId := role.GetId()
|
||||
roleId = role.GetId()
|
||||
for _, subUser := range role.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
@ -198,7 +215,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
}
|
||||
}
|
||||
|
||||
return groupingPolicies
|
||||
return groupingPolicies, nil
|
||||
}
|
||||
|
||||
func addPolicies(permission *Permission) error {
|
||||
@ -231,7 +248,10 @@ func addGroupingPolicies(permission *Permission) error {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
@ -249,7 +269,10 @@ func removeGroupingPolicies(permission *Permission) error {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
@ -287,7 +310,12 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range GetAllRoles(userId) {
|
||||
allRoles, err := GetAllRoles(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range allRoles {
|
||||
permissionsByRole, err := GetPermissionsByRole(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -321,17 +349,17 @@ func GetAllActions(userId string) ([]string, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func GetAllRoles(userId string) []string {
|
||||
func GetAllRoles(userId string) ([]string, error) {
|
||||
roles, err := getRolesByUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res []string
|
||||
res := []string{}
|
||||
for _, role := range roles {
|
||||
res = append(res, role.Name)
|
||||
}
|
||||
return res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func GetBuiltInModel(modelText string) (model.Model, error) {
|
||||
|
@ -17,6 +17,8 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -158,30 +160,28 @@ func (product *Product) getProvider(providerName string) (*Provider, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pProvider, err := GetPaymentProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
owner := product.Owner
|
||||
productName := product.Name
|
||||
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||
paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
|
||||
productDisplayName := product.DisplayName
|
||||
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
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 != "" {
|
||||
plan, err := GetPlan(util.GetId(owner, planName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
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)
|
||||
_, err = AddSubscription(sub)
|
||||
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)
|
||||
}
|
||||
}
|
||||
// Create an OrderId and get the payUrl
|
||||
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
// Create an order
|
||||
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 {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Create a Payment linked with Product and Order
|
||||
payment := &Payment{
|
||||
payment = &Payment{
|
||||
Owner: product.Owner,
|
||||
Name: paymentName,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
@ -219,8 +239,8 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
Provider: provider.Name,
|
||||
Type: provider.Type,
|
||||
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProductName: product.Name,
|
||||
ProductDisplayName: product.DisplayName,
|
||||
Detail: product.Detail,
|
||||
Tag: product.Tag,
|
||||
Currency: product.Currency,
|
||||
@ -228,10 +248,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
ReturnUrl: product.ReturnUrl,
|
||||
|
||||
User: user.Name,
|
||||
PayUrl: payUrl,
|
||||
PayUrl: payResp.PayUrl,
|
||||
SuccessUrl: returnUrl,
|
||||
State: pp.PaymentStateCreated,
|
||||
OutOrderId: orderId,
|
||||
OutOrderId: payResp.OrderId,
|
||||
}
|
||||
|
||||
if provider.Type == "Dummy" {
|
||||
@ -240,13 +260,13 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
|
||||
affected, err := AddPayment(payment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -37,7 +37,7 @@ type Provider struct {
|
||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(200)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||
ClientSecret string `xorm:"varchar(3000)" json:"clientSecret"`
|
||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||
ClientSecret2 string `xorm:"varchar(500)" json:"clientSecret2"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
)
|
||||
|
||||
// https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html
|
||||
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a
|
||||
type RadiusAccounting struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
|
@ -26,6 +26,8 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
|
||||
|
||||
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
|
||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
|
||||
} else if provider.Type == "Custom HTTP SMS" {
|
||||
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.Title)
|
||||
} else {
|
||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||
}
|
||||
|
83
object/sms_custom_http.go
Normal file
83
object/sms_custom_http.go
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
type HttpSmsClient struct {
|
||||
endpoint string
|
||||
method string
|
||||
paramName string
|
||||
}
|
||||
|
||||
func newHttpSmsClient(endpoint string, method string, paramName string) (*HttpSmsClient, error) {
|
||||
client := &HttpSmsClient{
|
||||
endpoint: endpoint,
|
||||
method: method,
|
||||
paramName: paramName,
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error {
|
||||
phoneNumber := targetPhoneNumber[0]
|
||||
content := param["code"]
|
||||
|
||||
var req *http.Request
|
||||
var err error
|
||||
if c.method == "POST" {
|
||||
formValues := url.Values{}
|
||||
formValues.Set("phoneNumber", phoneNumber)
|
||||
formValues.Set(c.paramName, content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("phoneNumber", phoneNumber)
|
||||
q.Add(c.paramName, content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
} else {
|
||||
return fmt.Errorf("HttpSmsClient's SendMessage() error, unsupported method: %s", c.method)
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HttpSmsClient's SendMessage() error, custom HTTP SMS request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -112,7 +112,10 @@ func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface,
|
||||
|
||||
if provider.Domain == "" {
|
||||
provider.Domain = storageProvider.GetEndpoint()
|
||||
UpdateProvider(provider.GetId(), provider)
|
||||
_, err := UpdateProvider(provider.GetId(), provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return storageProvider, nil
|
||||
@ -126,7 +129,12 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
|
||||
|
||||
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
||||
|
||||
_, err = storageProvider.Put(objectKey, fileBuffer)
|
||||
objectKeyRefined := objectKey
|
||||
if provider.Type == "Google Cloud Storage" {
|
||||
objectKeyRefined = strings.TrimPrefix(objectKeyRefined, "/")
|
||||
}
|
||||
|
||||
_, err = storageProvider.Put(objectKeyRefined, fileBuffer)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
144
object/token.go
144
object/token.go
@ -17,6 +17,7 @@ package object
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -51,15 +52,17 @@ type Token struct {
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
|
||||
Code string `xorm:"varchar(100) index" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||
CodeIsUsed bool `json:"codeIsUsed"`
|
||||
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||
Code string `xorm:"varchar(100) index" json:"code"`
|
||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||
AccessTokenHash string `xorm:"varchar(100) index" json:"accessTokenHash"`
|
||||
RefreshTokenHash string `xorm:"varchar(100) index" json:"refreshTokenHash"`
|
||||
ExpiresIn int `json:"expiresIn"`
|
||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||
CodeIsUsed bool `json:"codeIsUsed"`
|
||||
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||
}
|
||||
|
||||
type TokenWrapper struct {
|
||||
@ -141,6 +144,48 @@ func getTokenByCode(code string) (*Token, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func GetTokenByAccessToken(accessToken string) (*Token, error) {
|
||||
token := Token{AccessTokenHash: getTokenHash(accessToken)}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
token = Token{AccessToken: accessToken}
|
||||
existed, err = ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, nil
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
|
||||
token := Token{RefreshTokenHash: getTokenHash(refreshToken)}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
token = Token{RefreshToken: refreshToken}
|
||||
existed, err = ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, nil
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func updateUsedByCode(token *Token) bool {
|
||||
affected, err := ormer.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
|
||||
if err != nil {
|
||||
@ -159,6 +204,24 @@ func (token *Token) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", token.Owner, token.Name)
|
||||
}
|
||||
|
||||
func getTokenHash(input string) string {
|
||||
hash := sha256.Sum256([]byte(input))
|
||||
res := hex.EncodeToString(hash[:])
|
||||
if len(res) > 64 {
|
||||
return res[:64]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (token *Token) popularHashes() {
|
||||
if token.AccessTokenHash == "" && token.AccessToken != "" {
|
||||
token.AccessTokenHash = getTokenHash(token.AccessToken)
|
||||
}
|
||||
if token.RefreshTokenHash == "" && token.RefreshToken != "" {
|
||||
token.RefreshTokenHash = getTokenHash(token.RefreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateToken(id string, token *Token) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if t, err := getToken(owner, name); err != nil {
|
||||
@ -167,6 +230,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
token.popularHashes()
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -176,6 +241,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
|
||||
}
|
||||
|
||||
func AddToken(token *Token) (bool, error) {
|
||||
token.popularHashes()
|
||||
|
||||
affected, err := ormer.Engine.Insert(token)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -194,18 +261,16 @@ func DeleteToken(token *Token) (bool, error) {
|
||||
}
|
||||
|
||||
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
||||
token := Token{AccessToken: accessToken}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
token, err := GetTokenByAccessToken(accessToken)
|
||||
if err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
if token == nil {
|
||||
return false, nil, nil, nil
|
||||
}
|
||||
|
||||
token.ExpiresIn = 0
|
||||
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(&token)
|
||||
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
|
||||
if err != nil {
|
||||
return false, nil, nil, err
|
||||
}
|
||||
@ -215,22 +280,7 @@ func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, e
|
||||
return false, nil, nil, err
|
||||
}
|
||||
|
||||
return affected != 0, application, &token, nil
|
||||
}
|
||||
|
||||
func GetTokenByAccessToken(accessToken string) (*Token, error) {
|
||||
// Check if the accessToken is in the database
|
||||
token := Token{AccessToken: accessToken}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
return affected != 0, application, token, nil
|
||||
}
|
||||
|
||||
func GetTokenByTokenAndApplication(token string, application string) (*Token, error) {
|
||||
@ -432,16 +482,17 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
ErrorDescription: "client_id is invalid",
|
||||
}, nil
|
||||
}
|
||||
|
||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||
return &TokenError{
|
||||
Error: InvalidClient,
|
||||
ErrorDescription: "client_secret is invalid",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// check whether the refresh token is valid, and has not expired.
|
||||
token := Token{RefreshToken: refreshToken}
|
||||
existed, err := ormer.Engine.Get(&token)
|
||||
if err != nil || !existed {
|
||||
token, err := GetTokenByRefreshToken(refreshToken)
|
||||
if err != nil || token == nil {
|
||||
return &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: "refresh token is invalid, expired or revoked",
|
||||
@ -452,6 +503,12 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cert == nil {
|
||||
return &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
|
||||
}, nil
|
||||
}
|
||||
|
||||
_, err = ParseJwtToken(refreshToken, cert)
|
||||
if err != nil {
|
||||
@ -460,6 +517,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generate a new token
|
||||
user, err := getUser(application.Organization, token.User)
|
||||
if err != nil {
|
||||
@ -477,6 +535,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
||||
if err != nil {
|
||||
return &TokenError{
|
||||
@ -504,7 +563,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = DeleteToken(&token)
|
||||
_, err = DeleteToken(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -517,7 +576,6 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
||||
ExpiresIn: newToken.ExpiresIn,
|
||||
Scope: newToken.Scope,
|
||||
}
|
||||
|
||||
return tokenWrapper, nil
|
||||
}
|
||||
|
||||
@ -621,25 +679,25 @@ func GetPasswordToken(application *Application, username string, password string
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: "the user does not exist",
|
||||
}, nil
|
||||
}
|
||||
var msg string
|
||||
|
||||
if user.Ldap != "" {
|
||||
msg = checkLdapUserPassword(user, password, "en")
|
||||
err = checkLdapUserPassword(user, password, "en")
|
||||
} else {
|
||||
msg = CheckPassword(user, password, "en")
|
||||
err = CheckPassword(user, password, "en")
|
||||
}
|
||||
if msg != "" {
|
||||
if err != nil {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidGrant,
|
||||
ErrorDescription: "invalid username or password",
|
||||
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if user.IsForbidden {
|
||||
return nil, &TokenError{
|
||||
Error: InvalidGrant,
|
||||
@ -729,13 +787,13 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
||||
|
||||
// GetTokenByUser
|
||||
// Implicit flow
|
||||
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
|
||||
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
|
||||
err := ExtendUserWithRolesAndPermissions(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -24,14 +24,14 @@ import (
|
||||
"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/43822945/golang-can-i-create-x509keypair-using-rsa-key
|
||||
|
||||
// Generate RSA key.
|
||||
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Encode private key to PKCS#1 ASN.1 PEM.
|
||||
@ -54,9 +54,10 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
|
||||
},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// Generate a pem block with the certificate
|
||||
@ -65,5 +66,5 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
|
||||
Bytes: cert,
|
||||
})
|
||||
|
||||
return string(certPem), string(privateKeyPem)
|
||||
return string(certPem), string(privateKeyPem), nil
|
||||
}
|
||||
|
@ -23,7 +23,10 @@ import (
|
||||
|
||||
func TestGenerateRsaKeys(t *testing.T) {
|
||||
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.
|
||||
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/xorm-io/builder"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
@ -231,6 +232,20 @@ func GetGlobalUsers() ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetGlobalUsersWithFilter(cond builder.Cond) ([]*User, error) {
|
||||
users := []*User{}
|
||||
session := ormer.Engine.Desc("created_time")
|
||||
if cond != nil {
|
||||
session = session.Where(cond)
|
||||
}
|
||||
err := session.Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
|
||||
users := []*User{}
|
||||
session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder)
|
||||
@ -266,9 +281,27 @@ func GetUsers(owner string) ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetUsersByTag(owner string, tag string) ([]*User, error) {
|
||||
func GetUsersWithFilter(owner string, cond builder.Cond) ([]*User, error) {
|
||||
users := []*User{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&users, &User{Owner: owner, Tag: tag})
|
||||
session := ormer.Engine.Desc("created_time")
|
||||
if cond != nil {
|
||||
session = session.Where(cond)
|
||||
}
|
||||
err := session.Find(&users, &User{Owner: owner})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetUsersByTagWithFilter(owner string, tag string, cond builder.Cond) ([]*User, error) {
|
||||
users := []*User{}
|
||||
session := ormer.Engine.Desc("created_time")
|
||||
if cond != nil {
|
||||
session = session.Where(cond)
|
||||
}
|
||||
err := session.Find(&users, &User{Owner: owner, Tag: tag})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -988,7 +1021,10 @@ func GenerateIdForNewUser(application *Application) (string, error) {
|
||||
|
||||
lastUserId := -1
|
||||
if lastUser != nil {
|
||||
lastUserId = util.ParseInt(lastUser.Id)
|
||||
lastUserId, err = util.ParseIntWithError(lastUser.Id)
|
||||
if err != nil {
|
||||
return util.GenerateId(), nil
|
||||
}
|
||||
}
|
||||
|
||||
res := strconv.Itoa(lastUserId + 1)
|
||||
|
@ -35,11 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
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
|
||||
} else {
|
||||
return nil, "", err
|
||||
}
|
||||
return nil, "", nil
|
||||
}
|
||||
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") {
|
||||
fileExtension = ".html"
|
||||
} else if contentType == "image/vnd.microsoft.icon" {
|
||||
fileExtension = ".ico"
|
||||
} else {
|
||||
fileExtensions, err := mime.ExtensionsByType(contentType)
|
||||
if err != nil {
|
||||
|
@ -186,10 +186,47 @@ func parseSize(sizes string) []int {
|
||||
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) {
|
||||
tokens := strings.Split(email, "@")
|
||||
domain := tokens[1]
|
||||
if domain == "gmail.com" || domain == "163.com" || domain == "qq.com" {
|
||||
if isPublicEmailDomain(domain) {
|
||||
return nil, "", nil
|
||||
}
|
||||
|
||||
@ -203,11 +240,11 @@ func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, str
|
||||
if buffer != nil {
|
||||
faviconUrl, err = GetFaviconUrl(buffer.String())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(faviconUrl, "http") {
|
||||
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
|
||||
fmt.Printf("getFaviconFileBuffer() error, faviconUrl is empty, error = %s\n", err.Error())
|
||||
} else {
|
||||
if !strings.HasPrefix(faviconUrl, "http") {
|
||||
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"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) {
|
||||
if userInfo.Id != "" {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
sender := organization.DisplayName
|
||||
title := provider.Title
|
||||
|
||||
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."
|
||||
content := fmt.Sprintf(provider.Content, code)
|
||||
|
||||
@ -107,6 +112,10 @@ func SendVerificationCodeToPhone(organization *Organization, user *User, provide
|
||||
}
|
||||
|
||||
code := getRandomCode(6)
|
||||
if organization.MasterVerificationCode != "" {
|
||||
code = organization.MasterVerificationCode
|
||||
}
|
||||
|
||||
if err := SendSms(provider, code, dest); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -156,7 +165,7 @@ func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
func CheckVerificationCode(dest, code, lang string) *VerifyResult {
|
||||
func CheckVerificationCode(dest string, code string, lang string) *VerifyResult {
|
||||
record, err := getVerificationRecord(dest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -183,32 +192,32 @@ func CheckVerificationCode(dest, code, lang string) *VerifyResult {
|
||||
return &VerifyResult{VerificationSuccess, ""}
|
||||
}
|
||||
|
||||
func DisableVerificationCode(dest string) (err error) {
|
||||
func DisableVerificationCode(dest string) error {
|
||||
record, err := getVerificationRecord(dest)
|
||||
if record == nil || err != nil {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
record.IsUsed = true
|
||||
_, err = ormer.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record)
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func CheckSigninCode(user *User, dest, code, lang string) string {
|
||||
func CheckSigninCode(user *User, dest, code, lang string) error {
|
||||
// check the login error times
|
||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
||||
return msg
|
||||
err := checkSigninErrorTimes(user, lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := CheckVerificationCode(dest, code, lang)
|
||||
switch result.Code {
|
||||
case VerificationSuccess:
|
||||
resetUserSigninErrorTimes(user)
|
||||
return ""
|
||||
return resetUserSigninErrorTimes(user)
|
||||
case wrongCodeError:
|
||||
return recordSigninErrorInfo(user, lang)
|
||||
default:
|
||||
return result.Msg
|
||||
return fmt.Errorf(result.Msg)
|
||||
}
|
||||
}
|
||||
|
||||
|
20
pp/alipay.go
20
pp/alipay.go
@ -49,20 +49,24 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
|
||||
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
|
||||
bm := gopay.BodyMap{}
|
||||
pp.Client.SetReturnUrl(returnUrl)
|
||||
pp.Client.SetNotifyUrl(notifyUrl)
|
||||
bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName}))
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.Set("total_amount", priceFloat64ToString(price))
|
||||
pp.Client.SetReturnUrl(r.ReturnUrl)
|
||||
pp.Client.SetNotifyUrl(r.NotifyUrl)
|
||||
bm.Set("subject", joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName}))
|
||||
bm.Set("out_trade_no", r.PaymentName)
|
||||
bm.Set("total_amount", priceFloat64ToString(r.Price))
|
||||
|
||||
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||
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) {
|
||||
|
@ -21,8 +21,10 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
||||
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) {
|
||||
return returnUrl, "", nil
|
||||
func (pp *DummyPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||
return &PayResp{
|
||||
PayUrl: r.ReturnUrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
|
36
pp/gc.go
36
pp/gc.go
@ -153,22 +153,22 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
||||
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{
|
||||
OrderDate: util.GenerateSimpleTimeId(),
|
||||
OrderNo: paymentName,
|
||||
Amount: getPriceString(price),
|
||||
OrderNo: r.PaymentName,
|
||||
Amount: getPriceString(r.Price),
|
||||
Xmpch: pp.Xmpch,
|
||||
Body: productDisplayName,
|
||||
ReturnUrl: returnUrl,
|
||||
NotifyUrl: notifyUrl,
|
||||
Remark1: payerName,
|
||||
Remark2: productName,
|
||||
Body: r.ProductDisplayName,
|
||||
ReturnUrl: r.ReturnUrl,
|
||||
NotifyUrl: r.NotifyUrl,
|
||||
Remark1: r.PayerName,
|
||||
Remark2: r.ProductName,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(payReqInfo)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body := GcRequestBody{
|
||||
@ -184,36 +184,38 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respBytes, err := pp.doPost(bodyBytes)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var respBody GcResponseBody
|
||||
err = json.Unmarshal(respBytes, &respBody)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var payRespInfo GcPayRespInfo
|
||||
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return payRespInfo.PayUrl, "", nil
|
||||
payResp := &PayResp{
|
||||
PayUrl: payRespInfo.PayUrl,
|
||||
}
|
||||
return payResp, nil
|
||||
}
|
||||
|
||||
func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||
|
22
pp/paypal.go
22
pp/paypal.go
@ -49,16 +49,16 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
|
||||
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
|
||||
units := make([]*paypal.PurchaseUnit, 0, 1)
|
||||
unit := &paypal.PurchaseUnit{
|
||||
ReferenceId: util.GetRandomString(16),
|
||||
Amount: &paypal.Amount{
|
||||
CurrencyCode: currency, // e.g."USD"
|
||||
Value: priceFloat64ToString(price), // e.g."100.00"
|
||||
CurrencyCode: r.Currency, // e.g."USD"
|
||||
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)
|
||||
|
||||
@ -68,23 +68,27 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
|
||||
bm.SetBodyMap("application_context", func(b gopay.BodyMap) {
|
||||
b.Set("brand_name", "Casdoor")
|
||||
b.Set("locale", "en-PT")
|
||||
b.Set("return_url", returnUrl)
|
||||
b.Set("cancel_url", returnUrl)
|
||||
b.Set("return_url", r.ReturnUrl)
|
||||
b.Set("cancel_url", r.ReturnUrl)
|
||||
})
|
||||
|
||||
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
if ppRsp.Code != paypal.Success {
|
||||
return "", "", errors.New(ppRsp.Error)
|
||||
return nil, errors.New(ppRsp.Error)
|
||||
}
|
||||
// {"id":"9BR68863NE220374S","status":"CREATED",
|
||||
// "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://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"}]}
|
||||
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) {
|
||||
|
@ -24,6 +24,32 @@ const (
|
||||
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 {
|
||||
PaymentName string
|
||||
PaymentStatus PaymentState
|
||||
@ -39,7 +65,7 @@ type NotifyResult struct {
|
||||
}
|
||||
|
||||
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)
|
||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||
GetResponseError(err error) string
|
||||
|
35
pp/stripe.go
35
pp/stripe.go
@ -46,30 +46,30 @@ func NewStripePaymentProvider(PublishableKey, SecretKey string) (*StripePaymentP
|
||||
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
|
||||
description := joinAttachString([]string{productName, productDisplayName, providerName})
|
||||
description := joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName})
|
||||
productParams := &stripe.ProductParams{
|
||||
Name: stripe.String(productDisplayName),
|
||||
Name: stripe.String(r.ProductDisplayName),
|
||||
Description: stripe.String(description),
|
||||
DefaultPriceData: &stripe.ProductDefaultPriceDataParams{
|
||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
|
||||
Currency: stripe.String(currency),
|
||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
|
||||
Currency: stripe.String(r.Currency),
|
||||
},
|
||||
}
|
||||
sProduct, err := stripeProduct.New(productParams)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
// Create a price for an existing product
|
||||
priceParams := &stripe.PriceParams{
|
||||
Currency: stripe.String(currency),
|
||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
|
||||
Currency: stripe.String(r.Currency),
|
||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
|
||||
Product: stripe.String(sProduct.ID),
|
||||
}
|
||||
sPrice, err := stripePrice.New(priceParams)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return nil, err
|
||||
}
|
||||
// Create a Checkout Session
|
||||
checkoutParams := &stripe.CheckoutSessionParams{
|
||||
@ -80,17 +80,21 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
|
||||
},
|
||||
},
|
||||
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
||||
SuccessURL: stripe.String(returnUrl),
|
||||
CancelURL: stripe.String(returnUrl),
|
||||
ClientReferenceID: stripe.String(paymentName),
|
||||
SuccessURL: stripe.String(r.ReturnUrl),
|
||||
CancelURL: stripe.String(r.ReturnUrl),
|
||||
ClientReferenceID: stripe.String(r.PaymentName),
|
||||
ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()),
|
||||
}
|
||||
checkoutParams.AddMetadata("product_description", description)
|
||||
sCheckout, err := stripeCheckout.New(checkoutParams)
|
||||
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) {
|
||||
@ -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`
|
||||
sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
productName string
|
||||
productDisplayName string
|
||||
|
@ -63,27 +63,66 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, seria
|
||||
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.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("description", productDisplayName)
|
||||
bm.Set("notify_url", notifyUrl)
|
||||
bm.Set("out_trade_no", paymentName)
|
||||
bm.Set("description", r.ProductDisplayName)
|
||||
bm.Set("notify_url", r.NotifyUrl)
|
||||
bm.Set("out_trade_no", r.PaymentName)
|
||||
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||
bm.Set("total", priceFloat64ToInt64(price))
|
||||
bm.Set("currency", currency)
|
||||
bm.Set("total", priceFloat64ToInt64(r.Price))
|
||||
bm.Set("currency", r.Currency)
|
||||
})
|
||||
|
||||
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
// In Wechat browser, we use JSAPI
|
||||
if r.PaymentEnv == PaymentEnvWechatBrowser {
|
||||
if r.PayerId == "" {
|
||||
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) {
|
||||
|
@ -17,15 +17,16 @@ package radius
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"layeh.com/radius"
|
||||
"layeh.com/radius/rfc2865"
|
||||
"layeh.com/radius/rfc2866"
|
||||
)
|
||||
|
||||
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3
|
||||
func StartRadiusServer() {
|
||||
secret := conf.GetConfigString("radiusSecret")
|
||||
server := radius.PacketServer{
|
||||
@ -55,15 +56,18 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
||||
password := rfc2865.UserPassword_GetString(r.Packet)
|
||||
organization := rfc2865.Class_GetString(r.Packet)
|
||||
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
||||
|
||||
if organization == "" {
|
||||
w.Write(r.Response(radius.CodeAccessReject))
|
||||
return
|
||||
}
|
||||
_, msg := object.CheckUserPassword(organization, username, password, "en")
|
||||
if msg != "" {
|
||||
|
||||
_, err := object.CheckUserPassword(organization, username, password, "en")
|
||||
if err != nil {
|
||||
w.Write(r.Response(radius.CodeAccessReject))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(r.Response(radius.CodeAccessAccept))
|
||||
}
|
||||
|
||||
@ -71,6 +75,11 @@ func handleAccountingRequest(w radius.ResponseWriter, r *radius.Request) {
|
||||
statusType := rfc2866.AcctStatusType_Get(r.Packet)
|
||||
username := rfc2865.UserName_GetString(r.Packet)
|
||||
organization := rfc2865.Class_GetString(r.Packet)
|
||||
|
||||
if strings.Contains(username, "/") {
|
||||
organization, username = util.GetOwnerAndNameFromId(username)
|
||||
}
|
||||
|
||||
log.Printf("handleAccountingRequest() username=%v, org=%v, statusType=%v", username, organization, statusType)
|
||||
w.Write(r.Response(radius.CodeAccountingResponse))
|
||||
var err error
|
||||
|
@ -83,13 +83,12 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
password := ctx.Input.Query("password")
|
||||
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
|
||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||
_, msg := object.CheckUserPassword(owner, name, password, "en")
|
||||
if msg != "" {
|
||||
responseError(ctx, msg)
|
||||
_, err = object.CheckUserPassword(owner, name, password, "en")
|
||||
if err != nil {
|
||||
responseError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
setSessionUser(ctx, userId)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ func initAPI() {
|
||||
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
||||
|
||||
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/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
|
||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||
|
@ -129,7 +129,7 @@ func StaticFilter(ctx *context.Context) {
|
||||
path += urlPath
|
||||
}
|
||||
|
||||
if !util.FileExist(path) {
|
||||
if strings.Contains(path, "/../") || !util.FileExist(path) {
|
||||
path = webBuildFolder + "/index.html"
|
||||
}
|
||||
if !util.FileExist(path) {
|
||||
|
@ -19,13 +19,15 @@ import (
|
||||
"github.com/casdoor/oss/googlecloud"
|
||||
)
|
||||
|
||||
func NewGoogleCloudStorageProvider(clientId string, clientSecret string, bucket string, endpoint string) oss.StorageInterface {
|
||||
sp, _ := googlecloud.New(&googlecloud.Config{
|
||||
AccessID: clientId,
|
||||
AccessKey: clientSecret,
|
||||
Bucket: bucket,
|
||||
Endpoint: endpoint,
|
||||
func NewGoogleCloudStorageProvider(clientSecret string, bucket string, endpoint string) oss.StorageInterface {
|
||||
sp, err := googlecloud.New(&googlecloud.Config{
|
||||
ServiceAccountJson: clientSecret,
|
||||
Bucket: bucket,
|
||||
Endpoint: endpoint,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sp
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
||||
case "Qiniu Cloud Kodo":
|
||||
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||
case "Google Cloud Storage":
|
||||
return NewGoogleCloudStorageProvider(clientId, clientSecret, bucket, endpoint)
|
||||
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -2023,13 +2023,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/get-globle-certs": {
|
||||
"/api/get-global-certs": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Cert API"
|
||||
],
|
||||
"description": "get globle certs",
|
||||
"operationId": "ApiController.GetGlobleCerts",
|
||||
"operationId": "ApiController.GetGlobalCerts",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The Response object",
|
||||
|
@ -1311,12 +1311,12 @@ paths:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/object.User'
|
||||
/api/get-globle-certs:
|
||||
/api/get-global-certs:
|
||||
get:
|
||||
tags:
|
||||
- Cert API
|
||||
description: get globle certs
|
||||
operationId: ApiController.GetGlobleCerts
|
||||
operationId: ApiController.GetGlobalCerts
|
||||
responses:
|
||||
"200":
|
||||
description: The Response object
|
||||
|
@ -45,6 +45,19 @@ func ParseInt(s string) int {
|
||||
return i
|
||||
}
|
||||
|
||||
func ParseIntWithError(s string) (int, error) {
|
||||
if s == "" {
|
||||
return 0, fmt.Errorf("ParseIntWithError() error, empty string")
|
||||
}
|
||||
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func ParseFloat(s string) float64 {
|
||||
f, err := strconv.ParseFloat(s, 64)
|
||||
if err != nil {
|
||||
|
@ -171,10 +171,27 @@ class CertEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(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>)
|
||||
}
|
||||
</Select>
|
||||
@ -185,9 +202,15 @@ class CertEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
|
||||
</Col>
|
||||
<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("certificate", "");
|
||||
this.updateCertField("privateKey", "");
|
||||
})}>
|
||||
{
|
||||
Setting.getCryptoAlgorithmOptions(this.state.cert.cryptoAlgorithm).map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -205,14 +228,14 @@ class CertEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
|
||||
</Col>
|
||||
<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);
|
||||
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Copy certificate")}
|
||||
</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"});
|
||||
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"))} :
|
||||
</Col>
|
||||
<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);
|
||||
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
|
||||
}}
|
||||
>
|
||||
{i18next.t("cert:Copy private key")}
|
||||
</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"});
|
||||
FileSaver.saveAs(blob, "token_jwt_key.key");
|
||||
}}
|
||||
@ -265,6 +288,7 @@ class CertEditPage extends React.Component {
|
||||
this.props.history.push("/certs");
|
||||
} else {
|
||||
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
||||
this.getCert();
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
|
@ -239,7 +239,7 @@ class CertListPage extends BaseListPage {
|
||||
value = params.type;
|
||||
}
|
||||
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))
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
|
@ -323,6 +323,16 @@ class OrganizationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</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"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
|
||||
|
@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
|
||||
payment: payment,
|
||||
});
|
||||
if (payment.state === "Created") {
|
||||
if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) {
|
||||
if (["PayPal", "Stripe", "Alipay", "WeChat Pay"].includes(payment.type)) {
|
||||
this.setState({
|
||||
timeout: setTimeout(async() => {
|
||||
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||
|
@ -303,7 +303,7 @@ class PermissionEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select disabled={!this.hasRoleDefinition(this.state.model)} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
|
||||
<Select disabled={!this.hasRoleDefinition(this.state.model)} placeholder={this.hasRoleDefinition(this.state.model) ? "" : "This field is disabled because the model is empty or it doesn't support RBAC (in another word, doesn't contain [role_definition])"} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
|
||||
onChange={(value => {this.updatePermissionField("roles", value);})}
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
@ -323,7 +323,7 @@ class PermissionEditPage extends React.Component {
|
||||
})}
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
...this.state.permission.domains.map((domain) => Setting.getOption(domain, domain)),
|
||||
...this.state.permission.domains.filter(domain => domain !== "*").map((domain) => Setting.getOption(domain, domain)),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
|
@ -44,7 +44,7 @@ class PermissionListPage extends BaseListPage {
|
||||
submitter: this.props.account.name,
|
||||
approver: "",
|
||||
approveTime: "",
|
||||
state: "Pending",
|
||||
state: Setting.isLocalAdminUser(this.props.account) ? "Approved" : "Pending",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ class ProductBuyPage extends React.Component {
|
||||
pricingName: props?.pricingName ?? props?.match?.params?.pricingName ?? null,
|
||||
planName: params.get("plan"),
|
||||
userName: params.get("user"),
|
||||
paymentEnv: "",
|
||||
product: null,
|
||||
pricing: props?.pricing ?? 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() {
|
||||
this.getProduct();
|
||||
this.getPaymentEnv();
|
||||
}
|
||||
|
||||
setStateAsync(state) {
|
||||
@ -127,23 +141,74 @@ class ProductBuyPage extends React.Component {
|
||||
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) {
|
||||
this.setState({
|
||||
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) => {
|
||||
if (res.status === "ok") {
|
||||
const payment = res.data;
|
||||
const attachInfo = res.data2;
|
||||
let payUrl = payment.payUrl;
|
||||
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)}`;
|
||||
}
|
||||
Setting.goToLink(payUrl);
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||
|
||||
this.setState({
|
||||
isPlacingOrder: false,
|
||||
});
|
||||
@ -218,7 +283,7 @@ class ProductBuyPage extends React.Component {
|
||||
return (
|
||||
<div className="login-content">
|
||||
<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}>
|
||||
<span style={{fontSize: 25}}>
|
||||
{Setting.getLanguageText(product?.displayName)}
|
||||
|
@ -197,6 +197,12 @@ class ProviderEditPage extends React.Component {
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
case "Storage":
|
||||
if (provider.type === "Google Cloud Storage") {
|
||||
return Setting.getLabel(i18next.t("provider:Service account JSON"), i18next.t("provider:Service account JSON - Tooltip"));
|
||||
} else {
|
||||
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||
}
|
||||
case "Email":
|
||||
if (provider.type === "Azure ACS") {
|
||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||
@ -521,6 +527,13 @@ class ProviderEditPage extends React.Component {
|
||||
this.updateProviderField("scopes", "openid profile email");
|
||||
this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
|
||||
this.updateProviderField("customUserInfoUrl", "https://door.casdoor.com/api/userinfo");
|
||||
} else if (value === "Custom HTTP SMS") {
|
||||
this.updateProviderField("endpoint", "https://example.com/send-custom-http-sms");
|
||||
this.updateProviderField("method", "GET");
|
||||
this.updateProviderField("title", "code");
|
||||
} else if (value === "Custom HTTP Email") {
|
||||
this.updateProviderField("endpoint", "https://example.com/send-custom-http-email");
|
||||
this.updateProviderField("method", "POST");
|
||||
} else if (value === "Custom HTTP") {
|
||||
this.updateProviderField("method", "GET");
|
||||
this.updateProviderField("title", "");
|
||||
@ -668,9 +681,11 @@ class ProviderEditPage extends React.Component {
|
||||
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
|
||||
(this.state.provider.category === "Web3") ||
|
||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
|
||||
(this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") ||
|
||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP")) ? null : (
|
||||
<React.Fragment>
|
||||
{
|
||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
||||
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
|
||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Line" || this.state.provider.type === "Telegram" || this.state.provider.type === "Bark" || this.state.provider.type === "Discord" || this.state.provider.type === "Slack" || this.state.provider.type === "Pushbullet" || this.state.provider.type === "Pushover" || this.state.provider.type === "Lark" || this.state.provider.type === "Microsoft Teams")) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -756,7 +771,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{this.state.provider.category === "Storage" ? (
|
||||
{this.state.provider.category === "Storage" || ["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? (
|
||||
<div>
|
||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -770,7 +785,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||
@ -782,7 +797,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS", "Local File System"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
||||
@ -794,17 +809,19 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.pathPrefix} onChange={e => {
|
||||
this.updateProviderField("pathPrefix", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{["MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
{["Custom HTTP SMS"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.pathPrefix} onChange={e => {
|
||||
this.updateProviderField("pathPrefix", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
{["Custom HTTP SMS", "MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||
@ -974,7 +991,7 @@ class ProviderEditPage extends React.Component {
|
||||
</React.Fragment>
|
||||
) : this.state.provider.category === "SMS" ? (
|
||||
<React.Fragment>
|
||||
{["Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ?
|
||||
{["Custom HTTP SMS", "Twilio SMS", "Amazon SNS", "Azure ACS", "Msg91 SMS", "Infobip SMS"].includes(this.state.provider.type) ?
|
||||
null :
|
||||
(<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
@ -988,7 +1005,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{["Infobip SMS"].includes(this.state.provider.type) ?
|
||||
{["Custom HTTP SMS", "Infobip SMS"].includes(this.state.provider.type) ?
|
||||
null :
|
||||
(<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
@ -1002,6 +1019,39 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{
|
||||
!["Custom HTTP SMS", "Custom HTTP Email"].includes(this.state.provider.type) ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={2}>
|
||||
{Setting.getLabel(i18next.t("general:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.method} onChange={value => {
|
||||
this.updateProviderField("method", value);
|
||||
}}>
|
||||
{
|
||||
[
|
||||
{id: "GET", name: "GET"},
|
||||
{id: "POST", name: "POST"},
|
||||
].map((method, index) => <Option key={index} value={method.id}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Parameter"), i18next.t("provider:Parameter - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.provider.title} onChange={e => {
|
||||
this.updateProviderField("title", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :
|
||||
@ -1026,7 +1076,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={2} >
|
||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
||||
disabled={!Setting.isValidPhone(this.state.provider.receiver)}
|
||||
disabled={!Setting.isValidPhone(this.state.provider.receiver) && (this.state.provider.type !== "Custom HTTP SMS" || this.state.provider.endpoint === "")}
|
||||
onClick={() => ProviderEditTestSms.sendTestSms(this.state.provider, "+" + Setting.getCountryCode(this.state.provider.content) + this.state.provider.receiver)} >
|
||||
{i18next.t("provider:Send Testing SMS")}
|
||||
</Button>
|
||||
|
@ -143,6 +143,10 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/social_msg91.ico`,
|
||||
url: "https://control.msg91.com/app/",
|
||||
},
|
||||
"Custom HTTP SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||
url: "https://casdoor.org/docs/provider/sms/overview",
|
||||
},
|
||||
"Mock SMS": {
|
||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||
url: "",
|
||||
@ -165,6 +169,10 @@ export const OtherProviderInfo = {
|
||||
logo: `${StaticBaseUrl}/img/social_azure.png`,
|
||||
url: "https://learn.microsoft.com/zh-cn/azure/communication-services",
|
||||
},
|
||||
"Custom HTTP Email": {
|
||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||
url: "https://casdoor.org/docs/provider/email/overview",
|
||||
},
|
||||
},
|
||||
Storage: {
|
||||
"Local File System": {
|
||||
@ -981,6 +989,7 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "SUBMAIL", name: "SUBMAIL"},
|
||||
{id: "Mailtrap", name: "Mailtrap"},
|
||||
{id: "Azure ACS", name: "Azure ACS"},
|
||||
{id: "Custom HTTP Email", name: "Custom HTTP Email"},
|
||||
]
|
||||
);
|
||||
} else if (category === "SMS") {
|
||||
@ -989,6 +998,8 @@ export function getProviderTypeOptions(category) {
|
||||
{id: "Aliyun SMS", name: "Alibaba Cloud SMS"},
|
||||
{id: "Amazon SNS", name: "Amazon SNS"},
|
||||
{id: "Azure ACS", name: "Azure ACS"},
|
||||
{id: "Custom HTTP SMS", name: "Custom HTTP SMS"},
|
||||
{id: "Mock SMS", name: "Mock SMS"},
|
||||
{id: "Infobip SMS", name: "Infobip SMS"},
|
||||
{id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
|
||||
{id: "Baidu Cloud SMS", name: "Baidu Cloud SMS"},
|
||||
@ -1069,6 +1080,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) {
|
||||
if (application === null) {
|
||||
return null;
|
||||
|
@ -374,12 +374,9 @@ class UserEditPage extends React.Component {
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
||||
</Col>
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
<Col>
|
||||
{this.renderImage(this.state.user.avatar, i18next.t("user:Upload a photo"), i18next.t("user:Set new profile picture"), "avatar", false)}
|
||||
</Col>
|
||||
{
|
||||
this.renderImage(this.state.user.avatar, i18next.t("user:Upload a photo"), i18next.t("user:Set new profile picture"), "avatar", false)
|
||||
}
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "User type") {
|
||||
@ -550,9 +547,6 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{i18next.t("general:Preview")}:
|
||||
</Col>
|
||||
{
|
||||
[
|
||||
{name: "ID card front", value: "idCardFront"},
|
||||
@ -975,7 +969,7 @@ class UserEditPage extends React.Component {
|
||||
|
||||
renderImage(imgUrl, title, set, tag, disabled) {
|
||||
return (
|
||||
<Col span={4} style={{textAlign: "center", margin: "auto"}} key={tag}>
|
||||
<Col span={4} style={{textAlign: "center", margin: "auto", marginLeft: "20px"}} key={tag}>
|
||||
{
|
||||
imgUrl ?
|
||||
<div style={{marginBottom: "10px"}}>
|
||||
@ -986,7 +980,7 @@ class UserEditPage extends React.Component {
|
||||
:
|
||||
<Col style={{height: "78%", border: "1px dotted grey", borderRadius: 3, marginBottom: "10px"}}>
|
||||
<div style={{fontSize: 30, margin: 10}}>+</div>
|
||||
<div style={{verticalAlign: "middle", marginBottom: 10}}>{`Upload ${title}...`}</div>
|
||||
<div style={{verticalAlign: "middle", marginBottom: 10}}>{`(${i18next.t("general:empty")})`}</div>
|
||||
</Col>
|
||||
}
|
||||
<CropperDivModal disabled={disabled} tag={tag} setTitle={set} buttonText={`${title}...`} title={title} user={this.state.user} organization={this.state.organizations.find(organization => organization.name === this.state.organizationName)} />
|
||||
|
@ -337,7 +337,7 @@ class LoginPage extends React.Component {
|
||||
const casParams = Util.getCasParameters();
|
||||
values["type"] = this.state.type;
|
||||
AuthBackend.loginCas(values, casParams).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
const loginHandler = (res) => {
|
||||
let msg = "Logged in successfully. ";
|
||||
if (casParams.service === "") {
|
||||
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
|
||||
@ -351,6 +351,28 @@ class LoginPage extends React.Component {
|
||||
newUrl.searchParams.append("ticket", st);
|
||||
window.location.href = newUrl.toString();
|
||||
}
|
||||
};
|
||||
|
||||
if (res.status === "ok") {
|
||||
if (res.data === NextMfa) {
|
||||
this.setState({
|
||||
getVerifyTotp: () => {
|
||||
return (
|
||||
<MfaAuthVerifyForm
|
||||
mfaProps={res.data2}
|
||||
formValues={values}
|
||||
authParams={casParams}
|
||||
application={this.getApplicationObj()}
|
||||
onFail={() => {
|
||||
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
|
||||
}}
|
||||
onSuccess={(res) => loginHandler(res)}
|
||||
/>);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
loginHandler(res);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||
}
|
||||
@ -361,7 +383,7 @@ class LoginPage extends React.Component {
|
||||
this.populateOauthValues(values);
|
||||
AuthBackend.login(values, oAuthParams)
|
||||
.then((res) => {
|
||||
const callback = (res) => {
|
||||
const loginHandler = (res) => {
|
||||
const responseType = values["type"];
|
||||
|
||||
if (responseType === "login") {
|
||||
@ -396,12 +418,12 @@ class LoginPage extends React.Component {
|
||||
<MfaAuthVerifyForm
|
||||
mfaProps={res.data2}
|
||||
formValues={values}
|
||||
oAuthParams={oAuthParams}
|
||||
authParams={oAuthParams}
|
||||
application={this.getApplicationObj()}
|
||||
onFail={() => {
|
||||
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
|
||||
}}
|
||||
onSuccess={(res) => callback(res)}
|
||||
onSuccess={(res) => loginHandler(res)}
|
||||
/>);
|
||||
},
|
||||
});
|
||||
@ -414,7 +436,7 @@ class LoginPage extends React.Component {
|
||||
const sub = res.data2;
|
||||
Setting.goToLink(`/buy-plan/${sub.owner}/${sub.pricing}/result?subscription=${sub.name}`);
|
||||
} else {
|
||||
callback(res);
|
||||
loginHandler(res);
|
||||
}
|
||||
} else {
|
||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||
|
@ -262,7 +262,7 @@ class PromptPage extends React.Component {
|
||||
|
||||
initSteps(user, application) {
|
||||
const steps = [];
|
||||
if (!Setting.isPromptAnswered(user, application) && this.state.promptType === "provider") {
|
||||
if (Setting.hasPromptPage(application)) {
|
||||
steps.push({
|
||||
content: this.renderPromptProvider(application),
|
||||
name: "provider",
|
||||
|
@ -382,7 +382,7 @@ export function getAuthUrl(application, provider, method) {
|
||||
let redirectUri = `${window.location.origin}/callback`;
|
||||
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 codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))
|
||||
|
||||
|
@ -24,7 +24,7 @@ import MfaVerifyTotpForm from "./MfaVerifyTotpForm";
|
||||
export const NextMfa = "NextMfa";
|
||||
export const RequiredMfa = "RequiredMfa";
|
||||
|
||||
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
|
||||
export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application, onSuccess, onFail}) {
|
||||
formValues.password = "";
|
||||
formValues.username = "";
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -34,7 +34,8 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
||||
const verify = ({passcode}) => {
|
||||
setLoading(true);
|
||||
const values = {...formValues, passcode, mfaType};
|
||||
AuthBackend.login(values, oAuthParams).then((res) => {
|
||||
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
|
||||
loginFunction(values, authParams).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
onSuccess(res);
|
||||
} else {
|
||||
@ -49,7 +50,9 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
|
||||
|
||||
const recover = () => {
|
||||
setLoading(true);
|
||||
AuthBackend.login({...formValues, recoveryCode}, oAuthParams).then(res => {
|
||||
const values = {...formValues, recoveryCode};
|
||||
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
|
||||
loginFunction(values, authParams).then((res) => {
|
||||
if (res.status === "ok") {
|
||||
onSuccess(res);
|
||||
} else {
|
||||
|
@ -24,8 +24,8 @@ export function getCerts(owner, page = "", pageSize = "", field = "", value = ""
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getGlobleCerts(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}`, {
|
||||
export function getGlobalCerts(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-global-certs?&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
|
@ -70,8 +70,8 @@ export function deleteProduct(product) {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function buyProduct(owner, name, providerName, pricingName = "", planName = "", userName = "") {
|
||||
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerName=${providerName}&pricingName=${pricingName}&planName=${planName}&userName=${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}&paymentEnv=${paymentEnv}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
|
@ -153,11 +153,12 @@ export function sendCode(captchaType, captchaToken, clientSecret, method, countr
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyCaptcha(captchaType, captchaToken, clientSecret) {
|
||||
export function verifyCaptcha(owner, name, captchaType, captchaToken, clientSecret) {
|
||||
const formData = new FormData();
|
||||
formData.append("captchaType", captchaType);
|
||||
formData.append("captchaToken", captchaToken);
|
||||
formData.append("clientSecret", clientSecret);
|
||||
formData.append("applicationId", `${owner}/${name}`);
|
||||
return fetch(`${Setting.ServerUrl}/api/verify-captcha`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
|
@ -50,7 +50,7 @@ export const CaptchaPreview = (props) => {
|
||||
};
|
||||
|
||||
const onOk = (captchaType, captchaToken, clientSecret) => {
|
||||
UserBackend.verifyCaptcha(captchaType, captchaToken, clientSecret).then(() => {
|
||||
UserBackend.verifyCaptcha(owner, name, captchaType, captchaToken, clientSecret).then(() => {
|
||||
setVisible(false);
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user