mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-20 06:53:50 +08:00
Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
8ebb158765 | |||
ea2f053630 | |||
988b14c6b5 | |||
a9e72ac3cb | |||
498cd02d49 | |||
a389842f59 | |||
6c69daa666 | |||
53c89bbe89 | |||
9442aa9f7a | |||
8a195715d0 | |||
b985bab3f3 | |||
477a090aa0 | |||
e082cf10e0 |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -127,7 +127,7 @@ jobs:
|
||||
release-and-push:
|
||||
name: Release And Push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend, linter, e2e ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -184,14 +184,14 @@ jobs:
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
context: .
|
||||
target: STANDARD
|
||||
@ -201,7 +201,7 @@ jobs:
|
||||
|
||||
- name: Push All In One Version to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
context: .
|
||||
target: ALLINONE
|
||||
|
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
|
@ -1,5 +1,5 @@
|
||||
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
||||
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
|
||||
<h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
|
||||
<p align="center">
|
||||
<a href="#badge">
|
||||
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
||||
|
@ -96,7 +96,7 @@ p, *, *, GET, /api/get-organization-names, *, *
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
// load all rules from string adapter to enforcer's memory
|
||||
err := sa.LoadPolicy(Enforcer.GetModel())
|
||||
err = sa.LoadPolicy(Enforcer.GetModel())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -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
|
||||
|
@ -173,6 +173,12 @@ func (c *ApiController) GetOrganizationApplications() {
|
||||
return
|
||||
}
|
||||
|
||||
applications, err = object.GetAllowedApplications(applications, userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -154,7 +155,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
||||
} else {
|
||||
scope := c.Input().Get("scope")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
|
||||
nonce := c.Input().Get("nonce")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||
resp = tokenToResponse(token)
|
||||
}
|
||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||
@ -331,8 +333,6 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if authForm.Password == "" {
|
||||
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
@ -354,20 +354,21 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||
err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// disable the verification code
|
||||
err := object.DisableVerificationCode(checkDest)
|
||||
err = object.DisableVerificationCode(checkDest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
@ -386,7 +387,8 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if enableCaptcha {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
var isHuman bool
|
||||
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -399,13 +401,15 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
password := authForm.Password
|
||||
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
resp = &Response{Status: "error", Msg: msg}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else {
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -416,7 +420,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
@ -461,12 +466,15 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
}
|
||||
|
||||
provider, err := object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
var provider *object.Provider
|
||||
provider, err = object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -488,7 +496,8 @@ func (c *ApiController) Login() {
|
||||
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
||||
// OAuth
|
||||
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
|
||||
idProvider, err := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
||||
var idProvider idp.IdProvider
|
||||
idProvider, err = idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -506,7 +515,8 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
||||
token, err := idProvider.GetToken(authForm.Code)
|
||||
var token *oauth2.Token
|
||||
token, err = idProvider.GetToken(authForm.Code)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -547,7 +557,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 +603,16 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// Handle username conflicts
|
||||
tmpUser, err := object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||
var tmpUser *object.User
|
||||
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if tmpUser != nil {
|
||||
uid, err := uuid.NewRandom()
|
||||
var uid uuid.UUID
|
||||
uid, err = uuid.NewRandom()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -606,14 +623,16 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
properties := map[string]string{}
|
||||
count, err := object.GetUserCount(application.Organization, "", "", "")
|
||||
var count int64
|
||||
count, err = object.GetUserCount(application.Organization, "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
properties["no"] = strconv.Itoa(int(count + 2))
|
||||
initScore, err := organization.GetInitScore()
|
||||
var initScore int
|
||||
initScore, err = organization.GetInitScore()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
@ -645,7 +664,8 @@ func (c *ApiController) Login() {
|
||||
Properties: properties,
|
||||
}
|
||||
|
||||
affected, err := object.AddUser(user)
|
||||
var affected bool
|
||||
affected, err = object.AddUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -667,7 +687,7 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -703,7 +723,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
oldUser, err := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
var oldUser *object.User
|
||||
oldUser, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -714,7 +735,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
var user *object.User
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -727,7 +749,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
isLinked, err := object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
var isLinked bool
|
||||
isLinked, err = object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -740,7 +763,8 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
} else if c.getMfaUserSession() != "" {
|
||||
user, err := object.GetUser(c.getMfaUserSession())
|
||||
var user *object.User
|
||||
user, err = object.GetUser(c.getMfaUserSession())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -773,7 +797,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -794,7 +819,8 @@ func (c *ApiController) Login() {
|
||||
} else {
|
||||
if c.GetSessionUsername() != "" {
|
||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -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"`
|
||||
|
@ -161,7 +161,6 @@ func (c *ApiController) GetUser() {
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
|
||||
if id == "" && owner == "" {
|
||||
switch {
|
||||
case email != "":
|
||||
@ -176,7 +175,8 @@ func (c *ApiController) GetUser() {
|
||||
owner = util.GetOwnerFromId(id)
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganization(util.GetId("admin", owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +154,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
||||
// @router /webauthn/signin/finish [post]
|
||||
func (c *ApiController) WebAuthnSigninFinish() {
|
||||
responseType := c.Input().Get("responseType")
|
||||
clientId := c.Input().Get("clientId")
|
||||
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -182,7 +183,13 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
||||
c.SetSessionUsername(userId)
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
|
||||
application, err := object.GetApplicationByUser(user)
|
||||
var application *object.Application
|
||||
|
||||
if clientId != "" && (responseType == ResponseTypeCode) {
|
||||
application, err = object.GetApplicationByClientId(clientId)
|
||||
} else {
|
||||
application, err = object.GetApplicationByUser(user)
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
12
go.mod
12
go.mod
@ -9,13 +9,12 @@ 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/xorm-adapter/v3 v3.0.4
|
||||
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
|
||||
github.com/denisenkom/go-mssqldb v0.9.0
|
||||
@ -23,8 +22,9 @@ 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
|
||||
@ -32,6 +32,7 @@ require (
|
||||
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/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
|
||||
@ -61,10 +62,9 @@ require (
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/crypto v0.13.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/api v0.138.0
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
|
35
go.sum
35
go.sum
@ -778,8 +778,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
|
||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@ -823,6 +823,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
|
||||
github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
|
||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
@ -912,9 +914,8 @@ github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQ
|
||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
|
||||
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||
github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM=
|
||||
github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
|
||||
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
@ -922,16 +923,16 @@ github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3
|
||||
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
||||
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
||||
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
||||
github.com/casdoor/go-sms-sender v0.15.0 h1:9SWj/jd5c7jIteTRUrqbkpWbtIXMDv+t1CEfDhO06m0=
|
||||
github.com/casdoor/go-sms-sender v0.15.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs=
|
||||
github.com/casdoor/go-sms-sender v0.17.0 h1:72vfaNt9EEFkm+nMgZLMw5MsNoiHJDV62Pp9axLCRK0=
|
||||
github.com/casdoor/go-sms-sender v0.17.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs=
|
||||
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
|
||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||
github.com/casdoor/oss v1.3.0 h1:D5pcz65tJRqJrWY11Ks7D9LUsmlhqqMHugjDhSxWTvk=
|
||||
github.com/casdoor/oss v1.3.0/go.mod h1:YOi6KpG1pZHTkiy9AYaqI0UaPfE7YkaA07d89f1idqY=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4 h1:vB04Ao8n2jA7aFBI9F+gGXo9+Aa1IQP6mTdo50913DM=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3 h1:TKJQWKnhtznEBhzLPEdNsp7nJK2GgdD8JsB0lFPMW7U=
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3/go.mod h1:frnNtH5GA0wxzAQLyZxxfL0RSsSub9GQPi2Ybe86ocE=
|
||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||
@ -1081,8 +1082,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
||||
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g=
|
||||
github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks=
|
||||
@ -1110,8 +1111,8 @@ github.com/go-lark/lark v1.9.0 h1:FX21osIw6ssBH4hc4yO83AJrkRZONPji2jp5y8xQJZo=
|
||||
github.com/go-lark/lark v1.9.0/go.mod h1:6ltbSztPZRT6IaO9ZIQyVaY5pVp/KeMizDYtfZkU+vM=
|
||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
||||
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
|
||||
github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
@ -1916,7 +1917,6 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@ -1947,8 +1947,9 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20181106170214-d68db9428509/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@ -2271,8 +2272,9 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@ -2287,8 +2289,9 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -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
|
||||
}
|
1
main.go
1
main.go
@ -34,7 +34,6 @@ func main() {
|
||||
object.InitFlag()
|
||||
object.InitAdapter()
|
||||
object.CreateTables()
|
||||
object.DoMigration()
|
||||
|
||||
object.InitDb()
|
||||
object.InitFromFile()
|
||||
|
@ -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: ...
|
@ -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 = "***"
|
||||
}
|
||||
@ -345,6 +348,34 @@ func GetMaskedApplications(applications []*Application, userId string) []*Applic
|
||||
return applications
|
||||
}
|
||||
|
||||
func GetAllowedApplications(applications []*Application, userId string) ([]*Application, error) {
|
||||
if userId == "" || isUserIdGlobalAdmin(userId) {
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
user, err := GetUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user != nil && user.IsAdmin {
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
res := []*Application{}
|
||||
for _, application := range applications {
|
||||
var allowed bool
|
||||
allowed, err = CheckLoginPermission(userId, application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if allowed {
|
||||
res = append(res, application)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func UpdateApplication(id string, application *Application) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
oldApplication, err := getApplication(owner, name)
|
||||
|
@ -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()))
|
||||
}
|
||||
|
@ -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"}
|
||||
@ -305,7 +316,7 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
name, err := syncUser.buildLdapUserName()
|
||||
name, err := syncUser.buildLdapUserName(owner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -354,10 +365,10 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
|
||||
return existUuids, nil
|
||||
}
|
||||
|
||||
func (ldapUser *LdapUser) buildLdapUserName() (string, error) {
|
||||
func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) {
|
||||
user := User{}
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
|
||||
has, err := ormer.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
|
||||
has, err := ormer.Engine.Where("owner = ? and (name = ? or name = ?)", owner, ldapUser.Uid, uidWithNumber).Get(&user)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -1,51 +0,0 @@
|
||||
// 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 "github.com/xorm-io/xorm/migrate"
|
||||
|
||||
type Migrator interface {
|
||||
IsMigrationNeeded() bool
|
||||
DoMigration() *migrate.Migration
|
||||
}
|
||||
|
||||
func DoMigration() {
|
||||
migrators := []Migrator{
|
||||
&Migrator_1_101_0_PR_1083{},
|
||||
&Migrator_1_235_0_PR_1530{},
|
||||
&Migrator_1_240_0_PR_1539{},
|
||||
&Migrator_1_314_0_PR_1841{},
|
||||
// more migrators add here in chronological order...
|
||||
}
|
||||
|
||||
migrations := []*migrate.Migration{}
|
||||
|
||||
for _, migrator := range migrators {
|
||||
if migrator.IsMigrationNeeded() {
|
||||
migrations = append(migrations, migrator.DoMigration())
|
||||
}
|
||||
}
|
||||
|
||||
options := &migrate.Options{
|
||||
TableName: "migration",
|
||||
IDColumnName: "id",
|
||||
}
|
||||
|
||||
m := migrate.New(ormer.Engine, options, migrations)
|
||||
err := m.Migrate()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
// 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 (
|
||||
"strings"
|
||||
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_101_0_PR_1083 struct{}
|
||||
|
||||
func (*Migrator_1_101_0_PR_1083) IsMigrationNeeded() bool {
|
||||
exist1, _ := ormer.Engine.IsTableExist("model")
|
||||
exist2, _ := ormer.Engine.IsTableExist("permission")
|
||||
exist3, _ := ormer.Engine.IsTableExist("permission_rule")
|
||||
|
||||
if exist1 && exist2 && exist3 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (*Migrator_1_101_0_PR_1083) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230209MigratePermissionRule--Use V5 instead of V1 to store permissionID",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
models := []*Model{}
|
||||
err := engine.Table("model").Find(&models, &Model{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isHit := false
|
||||
for _, model := range models {
|
||||
if strings.Contains(model.ModelText, "permission") {
|
||||
// update model table
|
||||
model.ModelText = strings.Replace(model.ModelText, "permission,", "", -1)
|
||||
UpdateModel(model.GetId(), model)
|
||||
isHit = true
|
||||
}
|
||||
}
|
||||
|
||||
if isHit {
|
||||
// update permission_rule table
|
||||
sql := "UPDATE `permission_rule`SET V0 = V1, V1 = V2, V2 = V3, V3 = V4, V4 = V5 WHERE V0 IN (SELECT CONCAT(owner, '/', name) AS permission_id FROM `permission`)"
|
||||
_, err = engine.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// 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 (
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_235_0_PR_1530 struct{}
|
||||
|
||||
func (*Migrator_1_235_0_PR_1530) IsMigrationNeeded() bool {
|
||||
exist, _ := ormer.Engine.IsTableExist("casbin_rule")
|
||||
|
||||
return exist
|
||||
}
|
||||
|
||||
func (*Migrator_1_235_0_PR_1530) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20221015CasbinRule--fill ptype field with p",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
_, err := engine.Cols("ptype").Update(&xormadapter.CasbinRule{
|
||||
Ptype: "p",
|
||||
})
|
||||
return err
|
||||
},
|
||||
Rollback: func(engine *xorm.Engine) error {
|
||||
return engine.DropTables(&xormadapter.CasbinRule{})
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_240_0_PR_1539 struct{}
|
||||
|
||||
func (*Migrator_1_240_0_PR_1539) IsMigrationNeeded() bool {
|
||||
exist, _ := ormer.Engine.IsTableExist("session")
|
||||
err := ormer.Engine.Table("session").Find(&[]*Session{})
|
||||
|
||||
if exist && err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (*Migrator_1_240_0_PR_1539) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230211MigrateSession--Create a new field 'application' for table `session`",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
if alreadyCreated, _ := engine.IsTableExist("session_tmp"); alreadyCreated {
|
||||
return errors.New("there is already a table called 'session_tmp', please rename or delete it for casdoor version migration and restart")
|
||||
}
|
||||
|
||||
type oldSession struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
SessionId []string `json:"sessionId"`
|
||||
}
|
||||
|
||||
tx := engine.NewSession()
|
||||
|
||||
defer tx.Close()
|
||||
|
||||
err := tx.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Table("session_tmp").CreateTable(&Session{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldSessions := []*oldSession{}
|
||||
newSessions := []*Session{}
|
||||
|
||||
err = tx.Table("session").Find(&oldSessions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, oldSession := range oldSessions {
|
||||
newApplication := "null"
|
||||
if oldSession.Owner == "built-in" {
|
||||
newApplication = "app-built-in"
|
||||
}
|
||||
newSessions = append(newSessions, &Session{
|
||||
Owner: oldSession.Owner,
|
||||
Name: oldSession.Name,
|
||||
Application: newApplication,
|
||||
CreatedTime: oldSession.CreatedTime,
|
||||
SessionId: oldSession.SessionId,
|
||||
})
|
||||
}
|
||||
|
||||
rollbackFlag := false
|
||||
_, err = tx.Table("session_tmp").Insert(newSessions)
|
||||
count1, _ := tx.Table("session_tmp").Count()
|
||||
count2, _ := tx.Table("session").Count()
|
||||
|
||||
if err != nil || count1 != count2 {
|
||||
rollbackFlag = true
|
||||
}
|
||||
|
||||
delete := &Session{
|
||||
Application: "null",
|
||||
}
|
||||
_, err = tx.Table("session_tmp").Delete(*delete)
|
||||
if err != nil {
|
||||
rollbackFlag = true
|
||||
}
|
||||
|
||||
if rollbackFlag {
|
||||
tx.DropTable("session_tmp")
|
||||
return errors.New("there is something wrong with data migration for table `session`, if there is a table called `session_tmp` not created by you in casdoor, please drop it, then restart anyhow")
|
||||
}
|
||||
|
||||
err = tx.DropTable("session")
|
||||
if err != nil {
|
||||
return errors.New("fail to drop table `session` for casdoor, please drop it and rename the table `session_tmp` to `session` manually and restart")
|
||||
}
|
||||
|
||||
// Already drop table `session`
|
||||
// Can't find an api from xorm for altering table name
|
||||
err = tx.Table("session").CreateTable(&Session{})
|
||||
if err != nil {
|
||||
return errors.New("there is something wrong with data migration for table `session`, please restart")
|
||||
}
|
||||
|
||||
sessions := []*Session{}
|
||||
tx.Table("session_tmp").Find(&sessions)
|
||||
_, err = tx.Table("session").Insert(sessions)
|
||||
if err != nil {
|
||||
return errors.New("there is something wrong with data migration for table `session`, please drop table `session` and rename table `session_tmp` to `session` and restart")
|
||||
}
|
||||
|
||||
err = tx.DropTable("session_tmp")
|
||||
if err != nil {
|
||||
return errors.New("fail to drop table `session_tmp` for casdoor, please drop it manually and restart")
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// 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 (
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_314_0_PR_1841 struct{}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) IsMigrationNeeded() bool {
|
||||
count, err := ormer.Engine.Where("password_type=?", "").Count(&User{})
|
||||
if err != nil {
|
||||
// table doesn't exist
|
||||
return false
|
||||
}
|
||||
|
||||
return count > 100
|
||||
}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230515MigrateUser--Create a new field 'passwordType' for table `user`",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
tx := engine.NewSession()
|
||||
|
||||
defer tx.Close()
|
||||
|
||||
err := tx.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
organizations := []*Organization{}
|
||||
err = tx.Table("organization").Find(&organizations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, organization := range organizations {
|
||||
user := &User{PasswordType: organization.PasswordType}
|
||||
_, err = tx.Where("owner = ?", organization.Name).Cols("password_type").Update(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -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 {
|
||||
|
@ -64,7 +64,6 @@ func InitConfig() {
|
||||
|
||||
InitAdapter()
|
||||
CreateTables()
|
||||
DoMigration()
|
||||
}
|
||||
|
||||
func InitAdapter() {
|
||||
@ -330,11 +329,6 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(PermissionRule))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(xormadapter.CasbinRule))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -49,17 +49,6 @@ type Permission struct {
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
|
||||
type PermissionRule struct {
|
||||
Ptype string `xorm:"varchar(100) index not null default ''" json:"ptype"`
|
||||
V0 string `xorm:"varchar(100) index not null default ''" json:"v0"`
|
||||
V1 string `xorm:"varchar(100) index not null default ''" json:"v1"`
|
||||
V2 string `xorm:"varchar(100) index not null default ''" json:"v2"`
|
||||
V3 string `xorm:"varchar(100) index not null default ''" json:"v3"`
|
||||
V4 string `xorm:"varchar(100) index not null default ''" json:"v4"`
|
||||
V5 string `xorm:"varchar(100) index not null default ''" json:"v5"`
|
||||
Id string `xorm:"varchar(100) index not null default ''" json:"id"`
|
||||
}
|
||||
|
||||
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
|
||||
|
||||
func GetPermissionCount(owner, field, value string) (int64, error) {
|
||||
@ -131,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"
|
||||
@ -30,8 +32,8 @@ type Product struct {
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Image string `xorm:"varchar(100)" json:"image"`
|
||||
Detail string `xorm:"varchar(255)" json:"detail"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Detail string `xorm:"varchar(1000)" json:"detail"`
|
||||
Description string `xorm:"varchar(200)" json:"description"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
@ -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 {
|
||||
|
@ -417,7 +417,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
||||
providerInfo.ClientId = provider.ClientId2
|
||||
providerInfo.ClientSecret = provider.ClientSecret2
|
||||
}
|
||||
} else if provider.Type == "AzureAD" || provider.Type == "ADFS" {
|
||||
} else if provider.Type == "AzureAD" || provider.Type == "ADFS" || provider.Type == "Okta" {
|
||||
providerInfo.HostUrl = provider.Domain
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
76
object/sms_custom.go
Normal file
76
object/sms_custom.go
Normal file
@ -0,0 +1,76 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"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"]
|
||||
|
||||
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{
|
||||
"phoneNumber": targetPhoneNumber,
|
||||
c.paramName: {content},
|
||||
}
|
||||
} else if c.method == "GET" {
|
||||
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
|
||||
}
|
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
|
||||
}
|
||||
|
@ -34,6 +34,12 @@ type Claims struct {
|
||||
type UserShort struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
|
||||
Id string `xorm:"varchar(100) index" json:"id"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
}
|
||||
|
||||
type UserWithoutThirdIdp struct {
|
||||
@ -144,6 +150,12 @@ func getShortUser(user *User) *UserShort {
|
||||
res := &UserShort{
|
||||
Owner: user.Owner,
|
||||
Name: user.Name,
|
||||
|
||||
Id: user.Id,
|
||||
DisplayName: user.DisplayName,
|
||||
Avatar: user.Avatar,
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
@ -664,6 +697,8 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
user.UpdatedTime = util.GetCurrentTime()
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -851,7 +886,7 @@ func (user *User) GetId() string {
|
||||
}
|
||||
|
||||
func isUserIdGlobalAdmin(userId string) bool {
|
||||
return strings.HasPrefix(userId, "built-in/")
|
||||
return strings.HasPrefix(userId, "built-in/") || strings.HasPrefix(userId, "app/")
|
||||
}
|
||||
|
||||
func ExtendUserWithRolesAndPermissions(user *User) (err error) {
|
||||
|
@ -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,7 +20,10 @@ import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
@ -110,6 +113,10 @@ func SetUserField(user *User, field string, value string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
user.UpdatedTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
_, err = ormer.Engine.ID(core.PK{user.Owner, user.Name}).Cols("hash").Update(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -137,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)
|
||||
@ -180,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) {
|
||||
|
@ -55,15 +55,18 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
||||
password := rfc2865.UserPassword_GetString(r.Packet)
|
||||
organization := rfc2865.Class_GetString(r.Packet)
|
||||
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
||||
|
||||
if organization == "" {
|
||||
w.Write(r.Response(radius.CodeAccessReject))
|
||||
return
|
||||
}
|
||||
_, msg := object.CheckUserPassword(organization, username, password, "en")
|
||||
if msg != "" {
|
||||
|
||||
_, err := object.CheckUserPassword(organization, username, password, "en")
|
||||
if err != nil {
|
||||
w.Write(r.Response(radius.CodeAccessReject))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(r.Response(radius.CodeAccessAccept))
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)}
|
||||
|
@ -521,6 +521,10 @@ 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");
|
||||
this.updateProviderField("method", "GET");
|
||||
this.updateProviderField("title", "code");
|
||||
} else if (value === "Custom HTTP") {
|
||||
this.updateProviderField("method", "GET");
|
||||
this.updateProviderField("title", "");
|
||||
@ -668,6 +672,7 @@ 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>
|
||||
{
|
||||
@ -756,7 +761,7 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{this.state.provider.category === "Storage" ? (
|
||||
{this.state.provider.category === "Storage" || this.state.provider.type === "Custom HTTP SMS" ? (
|
||||
<div>
|
||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -770,7 +775,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 +787,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 +799,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 +981,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 +995,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 +1009,39 @@ class ProviderEditPage extends React.Component {
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
{
|
||||
!["Custom HTTP SMS"].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 +1066,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: "",
|
||||
@ -989,6 +993,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 +1075,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: {
|
||||
|
Reference in New Issue
Block a user