mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-25 17:00:29 +08:00
Compare commits
59 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
08864686f3 | ||
![]() |
dc06eb9948 | ||
![]() |
b068202e74 | ||
![]() |
cb16567c7b | ||
![]() |
4eb725d47a | ||
![]() |
ce72a172b0 | ||
![]() |
5521962e0c | ||
![]() |
37b8b09cc0 | ||
![]() |
482eb61168 | ||
![]() |
8819a8697b | ||
![]() |
85cb68eb66 | ||
![]() |
b25b5f0249 | ||
![]() |
947dcf6e75 | ||
![]() |
113c27db73 | ||
![]() |
badfe34755 | ||
![]() |
a5f9f61381 | ||
![]() |
2ce8c93ead | ||
![]() |
da41ac7275 | ||
![]() |
fd0c70a827 | ||
![]() |
c4a6f07672 | ||
![]() |
a67f541171 | ||
![]() |
192968bac8 | ||
![]() |
23d4488b64 | ||
![]() |
23f4684e1d | ||
![]() |
1a91e7b0f9 | ||
![]() |
811999b6cc | ||
![]() |
7786018051 | ||
![]() |
6c72f86d03 | ||
![]() |
5b151f4ec4 | ||
![]() |
e9b7d1266f | ||
![]() |
2d4998228c | ||
![]() |
d3ed6c348b | ||
![]() |
a22e05dcc1 | ||
![]() |
0ac2b69f5a | ||
![]() |
d090e9c860 | ||
![]() |
8ebb158765 | ||
![]() |
ea2f053630 | ||
![]() |
988b14c6b5 | ||
![]() |
a9e72ac3cb | ||
![]() |
498cd02d49 | ||
![]() |
a389842f59 | ||
![]() |
6c69daa666 | ||
![]() |
53c89bbe89 | ||
![]() |
9442aa9f7a | ||
![]() |
8a195715d0 | ||
![]() |
b985bab3f3 | ||
![]() |
477a090aa0 | ||
![]() |
e082cf10e0 | ||
![]() |
3215b88eae | ||
![]() |
9703f3f712 | ||
![]() |
140737b2f6 | ||
![]() |
b285144a64 | ||
![]() |
49c6ce2221 | ||
![]() |
2398e69012 | ||
![]() |
ade9de8256 | ||
![]() |
1bf5497d08 | ||
![]() |
cf10738f45 | ||
![]() |
ac00713c20 | ||
![]() |
febb27f765 |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -127,7 +127,7 @@ jobs:
|
|||||||
release-and-push:
|
release-and-push:
|
||||||
name: Release And Push
|
name: Release And Push
|
||||||
runs-on: ubuntu-latest
|
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 ]
|
needs: [ frontend, backend, linter, e2e ]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -184,14 +184,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v1
|
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:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
- name: Push to Docker Hub
|
- name: Push to Docker Hub
|
||||||
uses: docker/build-push-action@v3
|
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:
|
with:
|
||||||
context: .
|
context: .
|
||||||
target: STANDARD
|
target: STANDARD
|
||||||
@@ -201,7 +201,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Push All In One Version to Docker Hub
|
- name: Push All In One Version to Docker Hub
|
||||||
uses: docker/build-push-action@v3
|
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:
|
with:
|
||||||
context: .
|
context: .
|
||||||
target: ALLINONE
|
target: ALLINONE
|
||||||
|
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
synchronize-with-crowdin:
|
synchronize-with-crowdin:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
<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">
|
<p align="center">
|
||||||
<a href="#badge">
|
<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">
|
<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)
|
sa := stringadapter.NewAdapter(ruleText)
|
||||||
// load all rules from string adapter to enforcer's memory
|
// load all rules from string adapter to enforcer's memory
|
||||||
err := sa.LoadPolicy(Enforcer.GetModel())
|
err = sa.LoadPolicy(Enforcer.GetModel())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -147,11 +147,11 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
|
|
||||||
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||||
if method == "POST" {
|
if method == "POST" {
|
||||||
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" {
|
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" {
|
||||||
return true
|
return true
|
||||||
} else if urlPath == "/api/update-user" {
|
} else if urlPath == "/api/update-user" {
|
||||||
// Allow ordinary users to update their own information
|
// Allow ordinary users to update their own information
|
||||||
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
|
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@@ -282,17 +282,15 @@ func (c *ApiController) Logout() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if token == nil {
|
||||||
if !affected {
|
|
||||||
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil {
|
if application == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
|
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
|
||||||
return
|
return
|
||||||
@@ -319,7 +317,15 @@ func (c *ApiController) Logout() {
|
|||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if application.IsRedirectUriValid(redirectUri) {
|
if application.IsRedirectUriValid(redirectUri) {
|
||||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
redirectUrl := redirectUri
|
||||||
|
if state != "" {
|
||||||
|
if strings.Contains(redirectUri, "?") {
|
||||||
|
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||||
|
} else {
|
||||||
|
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.Ctx.Redirect(http.StatusFound, redirectUrl)
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||||
return
|
return
|
||||||
@@ -473,7 +479,7 @@ func (c *ApiController) GetCaptcha() {
|
|||||||
Type: captchaProvider.Type,
|
Type: captchaProvider.Type,
|
||||||
SubType: captchaProvider.SubType,
|
SubType: captchaProvider.SubType,
|
||||||
ClientId: captchaProvider.ClientId,
|
ClientId: captchaProvider.ClientId,
|
||||||
ClientSecret: captchaProvider.ClientSecret,
|
ClientSecret: "***",
|
||||||
ClientId2: captchaProvider.ClientId2,
|
ClientId2: captchaProvider.ClientId2,
|
||||||
ClientSecret2: captchaProvider.ClientSecret2,
|
ClientSecret2: captchaProvider.ClientSecret2,
|
||||||
})
|
})
|
||||||
|
@@ -173,6 +173,12 @@ func (c *ApiController) GetOrganizationApplications() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applications, err = object.GetAllowedApplications(applications, userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
||||||
} else {
|
} else {
|
||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
|
@@ -34,6 +34,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -154,7 +155,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
||||||
} else {
|
} else {
|
||||||
scope := c.Input().Get("scope")
|
scope := c.Input().Get("scope")
|
||||||
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
|
nonce := c.Input().Get("nonce")
|
||||||
|
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||||
resp = tokenToResponse(token)
|
resp = tokenToResponse(token)
|
||||||
}
|
}
|
||||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||||
@@ -331,8 +333,6 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
var msg string
|
|
||||||
|
|
||||||
if authForm.Password == "" {
|
if authForm.Password == "" {
|
||||||
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
|
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
|
||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
@@ -354,20 +354,21 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check result through Email or Phone
|
// check result through Email or Phone
|
||||||
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||||
if len(checkResult) != 0 {
|
if err != nil {
|
||||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// disable the verification code
|
// disable the verification code
|
||||||
err := object.DisableVerificationCode(checkDest)
|
err = object.DisableVerificationCode(checkDest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
var application *object.Application
|
||||||
|
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error(), nil)
|
c.ResponseError(err.Error(), nil)
|
||||||
return
|
return
|
||||||
@@ -386,7 +387,18 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
} else if enableCaptcha {
|
} else if enableCaptcha {
|
||||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
captchaProvider, err := object.GetCaptchaProviderByApplication(util.GetId(application.Owner, application.Name), "false", c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if captchaProvider.Type != "Default" {
|
||||||
|
authForm.ClientSecret = captchaProvider.ClientSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
var isHuman bool
|
||||||
|
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -399,13 +411,15 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
password := authForm.Password
|
password := authForm.Password
|
||||||
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg != "" {
|
if err != nil {
|
||||||
resp = &Response{Status: "error", Msg: msg}
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
} else {
|
} else {
|
||||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
var application *object.Application
|
||||||
|
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -416,7 +430,8 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
organization, err := object.GetOrganizationByUser(user)
|
var organization *object.Organization
|
||||||
|
organization, err = object.GetOrganizationByUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
}
|
}
|
||||||
@@ -461,12 +476,15 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
|
|
||||||
|
var organization *object.Organization
|
||||||
|
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(c.T(err.Error()))
|
c.ResponseError(c.T(err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := object.GetProvider(util.GetId("admin", authForm.Provider))
|
var provider *object.Provider
|
||||||
|
provider, err = object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -488,7 +506,12 @@ func (c *ApiController) Login() {
|
|||||||
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
||||||
// OAuth
|
// OAuth
|
||||||
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
|
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
|
||||||
idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
var idProvider idp.IdProvider
|
||||||
|
idProvider, err = idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
if idProvider == nil {
|
if idProvider == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
||||||
return
|
return
|
||||||
@@ -502,7 +525,8 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
||||||
token, err := idProvider.GetToken(authForm.Code)
|
var token *oauth2.Token
|
||||||
|
token, err = idProvider.GetToken(authForm.Code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -543,7 +567,12 @@ func (c *ApiController) Login() {
|
|||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
||||||
}
|
}
|
||||||
|
// sync info from 3rd-party if possible
|
||||||
|
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||||
|
|
||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
@@ -584,14 +613,16 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle username conflicts
|
// Handle username conflicts
|
||||||
tmpUser, err := object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
var tmpUser *object.User
|
||||||
|
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if tmpUser != nil {
|
if tmpUser != nil {
|
||||||
uid, err := uuid.NewRandom()
|
var uid uuid.UUID
|
||||||
|
uid, err = uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -602,14 +633,16 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
properties := map[string]string{}
|
properties := map[string]string{}
|
||||||
count, err := object.GetUserCount(application.Organization, "", "", "")
|
var count int64
|
||||||
|
count, err = object.GetUserCount(application.Organization, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
properties["no"] = strconv.Itoa(int(count + 2))
|
properties["no"] = strconv.Itoa(int(count + 2))
|
||||||
initScore, err := organization.GetInitScore()
|
var initScore int
|
||||||
|
initScore, err = organization.GetInitScore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||||
return
|
return
|
||||||
@@ -641,7 +674,8 @@ func (c *ApiController) Login() {
|
|||||||
Properties: properties,
|
Properties: properties,
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := object.AddUser(user)
|
var affected bool
|
||||||
|
affected, err = object.AddUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -663,7 +697,7 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sync info from 3rd-party if possible
|
// sync info from 3rd-party if possible
|
||||||
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -699,7 +733,8 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldUser, err := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
var oldUser *object.User
|
||||||
|
oldUser, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -710,7 +745,8 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := object.GetUser(userId)
|
var user *object.User
|
||||||
|
user, err = object.GetUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -723,7 +759,8 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isLinked, err := object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
var isLinked bool
|
||||||
|
isLinked, err = object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -736,7 +773,8 @@ func (c *ApiController) Login() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if c.getMfaUserSession() != "" {
|
} else if c.getMfaUserSession() != "" {
|
||||||
user, err := object.GetUser(c.getMfaUserSession())
|
var user *object.User
|
||||||
|
user, err = object.GetUser(c.getMfaUserSession())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -769,7 +807,8 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
var application *object.Application
|
||||||
|
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@@ -790,7 +829,8 @@ func (c *ApiController) Login() {
|
|||||||
} else {
|
} else {
|
||||||
if c.GetSessionUsername() != "" {
|
if c.GetSessionUsername() != "" {
|
||||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
var application *object.Application
|
||||||
|
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@@ -243,7 +243,13 @@ func (c *ApiController) GetAllObjects() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(object.GetAllObjects(userId))
|
objects, err := object.GetAllObjects(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetAllActions() {
|
func (c *ApiController) GetAllActions() {
|
||||||
@@ -253,7 +259,13 @@ func (c *ApiController) GetAllActions() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(object.GetAllActions(userId))
|
actions, err := object.GetAllActions(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetAllRoles() {
|
func (c *ApiController) GetAllRoles() {
|
||||||
@@ -263,5 +275,11 @@ func (c *ApiController) GetAllRoles() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(object.GetAllRoles(userId))
|
roles, err := object.GetAllRoles(userId)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(roles)
|
||||||
}
|
}
|
||||||
|
@@ -65,13 +65,13 @@ func (c *ApiController) GetCerts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGlobleCerts
|
// GetGlobalCerts
|
||||||
// @Title GetGlobleCerts
|
// @Title GetGlobalCerts
|
||||||
// @Tag Cert API
|
// @Tag Cert API
|
||||||
// @Description get globle certs
|
// @Description get globle certs
|
||||||
// @Success 200 {array} object.Cert The Response object
|
// @Success 200 {array} object.Cert The Response object
|
||||||
// @router /get-globle-certs [get]
|
// @router /get-global-certs [get]
|
||||||
func (c *ApiController) GetGlobleCerts() {
|
func (c *ApiController) GetGlobalCerts() {
|
||||||
limit := c.Input().Get("pageSize")
|
limit := c.Input().Get("pageSize")
|
||||||
page := c.Input().Get("p")
|
page := c.Input().Get("p")
|
||||||
field := c.Input().Get("field")
|
field := c.Input().Get("field")
|
||||||
@@ -80,7 +80,7 @@ func (c *ApiController) GetGlobleCerts() {
|
|||||||
sortOrder := c.Input().Get("sortOrder")
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
|
||||||
if limit == "" || page == "" {
|
if limit == "" || page == "" {
|
||||||
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts())
|
maskedCerts, err := object.GetMaskedCerts(object.GetGlobalCerts())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@@ -59,6 +59,7 @@ func (c *ApiController) GetLdapUsers() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
|
@@ -163,6 +163,8 @@ func (c *ApiController) BuyProduct() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
providerName := c.Input().Get("providerName")
|
providerName := c.Input().Get("providerName")
|
||||||
|
paymentEnv := c.Input().Get("paymentEnv")
|
||||||
|
|
||||||
// buy `pricingName/planName` for `paidUserName`
|
// buy `pricingName/planName` for `paidUserName`
|
||||||
pricingName := c.Input().Get("pricingName")
|
pricingName := c.Input().Get("pricingName")
|
||||||
planName := c.Input().Get("planName")
|
planName := c.Input().Get("planName")
|
||||||
@@ -187,11 +189,11 @@ func (c *ApiController) BuyProduct() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
|
payment, attachInfo, err := object.BuyProduct(id, user, providerName, pricingName, planName, host, paymentEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(payment)
|
c.ResponseOk(payment, attachInfo)
|
||||||
}
|
}
|
||||||
|
@@ -158,10 +158,9 @@ func (c *ApiController) DeleteToken() {
|
|||||||
// @Success 401 {object} object.TokenError The Response object
|
// @Success 401 {object} object.TokenError The Response object
|
||||||
// @router api/login/oauth/access_token [post]
|
// @router api/login/oauth/access_token [post]
|
||||||
func (c *ApiController) GetOAuthToken() {
|
func (c *ApiController) GetOAuthToken() {
|
||||||
grantType := c.Input().Get("grant_type")
|
|
||||||
refreshToken := c.Input().Get("refresh_token")
|
|
||||||
clientId := c.Input().Get("client_id")
|
clientId := c.Input().Get("client_id")
|
||||||
clientSecret := c.Input().Get("client_secret")
|
clientSecret := c.Input().Get("client_secret")
|
||||||
|
grantType := c.Input().Get("grant_type")
|
||||||
code := c.Input().Get("code")
|
code := c.Input().Get("code")
|
||||||
verifier := c.Input().Get("code_verifier")
|
verifier := c.Input().Get("code_verifier")
|
||||||
scope := c.Input().Get("scope")
|
scope := c.Input().Get("scope")
|
||||||
@@ -169,35 +168,61 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
password := c.Input().Get("password")
|
password := c.Input().Get("password")
|
||||||
tag := c.Input().Get("tag")
|
tag := c.Input().Get("tag")
|
||||||
avatar := c.Input().Get("avatar")
|
avatar := c.Input().Get("avatar")
|
||||||
|
refreshToken := c.Input().Get("refresh_token")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
if clientId == "" && clientSecret == "" {
|
||||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||||
}
|
}
|
||||||
if clientId == "" {
|
|
||||||
// If clientID is empty, try to read data from RequestBody
|
if len(c.Ctx.Input.RequestBody) != 0 {
|
||||||
|
// If clientId is empty, try to read data from RequestBody
|
||||||
var tokenRequest TokenRequest
|
var tokenRequest TokenRequest
|
||||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
||||||
clientId = tokenRequest.ClientId
|
if err == nil {
|
||||||
clientSecret = tokenRequest.ClientSecret
|
if clientId == "" {
|
||||||
grantType = tokenRequest.GrantType
|
clientId = tokenRequest.ClientId
|
||||||
refreshToken = tokenRequest.RefreshToken
|
}
|
||||||
code = tokenRequest.Code
|
if clientSecret == "" {
|
||||||
verifier = tokenRequest.Verifier
|
clientSecret = tokenRequest.ClientSecret
|
||||||
scope = tokenRequest.Scope
|
}
|
||||||
username = tokenRequest.Username
|
if grantType == "" {
|
||||||
password = tokenRequest.Password
|
grantType = tokenRequest.GrantType
|
||||||
tag = tokenRequest.Tag
|
}
|
||||||
avatar = tokenRequest.Avatar
|
if code == "" {
|
||||||
|
code = tokenRequest.Code
|
||||||
|
}
|
||||||
|
if verifier == "" {
|
||||||
|
verifier = tokenRequest.Verifier
|
||||||
|
}
|
||||||
|
if scope == "" {
|
||||||
|
scope = tokenRequest.Scope
|
||||||
|
}
|
||||||
|
if username == "" {
|
||||||
|
username = tokenRequest.Username
|
||||||
|
}
|
||||||
|
if password == "" {
|
||||||
|
password = tokenRequest.Password
|
||||||
|
}
|
||||||
|
if tag == "" {
|
||||||
|
tag = tokenRequest.Tag
|
||||||
|
}
|
||||||
|
if avatar == "" {
|
||||||
|
avatar = tokenRequest.Avatar
|
||||||
|
}
|
||||||
|
if refreshToken == "" {
|
||||||
|
refreshToken = tokenRequest.RefreshToken
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
host := c.Ctx.Request.Host
|
host := c.Ctx.Request.Host
|
||||||
oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = oAuthtoken
|
c.Data["json"] = token
|
||||||
c.SetTokenErrorHttpStatus()
|
c.SetTokenErrorHttpStatus()
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
@@ -15,10 +15,10 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
type TokenRequest struct {
|
type TokenRequest struct {
|
||||||
GrantType string `json:"grant_type"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
ClientId string `json:"client_id"`
|
ClientId string `json:"client_id"`
|
||||||
ClientSecret string `json:"client_secret"`
|
ClientSecret string `json:"client_secret"`
|
||||||
|
GrantType string `json:"grant_type"`
|
||||||
|
Code string `json:"code"`
|
||||||
Verifier string `json:"code_verifier"`
|
Verifier string `json:"code_verifier"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
@@ -161,7 +161,6 @@ func (c *ApiController) GetUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
|
|
||||||
if id == "" && owner == "" {
|
if id == "" && owner == "" {
|
||||||
switch {
|
switch {
|
||||||
case email != "":
|
case email != "":
|
||||||
@@ -176,11 +175,16 @@ func (c *ApiController) GetUser() {
|
|||||||
owner = util.GetOwnerFromId(id)
|
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 {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if organization == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !organization.IsProfilePublic {
|
if !organization.IsProfilePublic {
|
||||||
requestUserId := c.GetSessionUsername()
|
requestUserId := c.GetSessionUsername()
|
||||||
@@ -472,16 +476,16 @@ func (c *ApiController) SetPassword() {
|
|||||||
isAdmin := c.IsAdmin()
|
isAdmin := c.IsAdmin()
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
if oldPassword != "" {
|
if oldPassword != "" {
|
||||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if err != nil {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if code == "" {
|
} else if code == "" {
|
||||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if err != nil {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,11 +518,11 @@ func (c *ApiController) CheckUserPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
|
_, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
|
||||||
if msg == "" {
|
if err != nil {
|
||||||
c.ResponseOk()
|
c.ResponseError(err.Error())
|
||||||
} else {
|
} else {
|
||||||
c.ResponseError(msg)
|
c.ResponseOk()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,11 +576,11 @@ func (c *ApiController) GetUserCount() {
|
|||||||
c.ResponseOk(count)
|
c.ResponseOk(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddUserkeys
|
// AddUserKeys
|
||||||
// @Title AddUserkeys
|
// @Title AddUserKeys
|
||||||
// @router /add-user-keys [post]
|
// @router /add-user-keys [post]
|
||||||
// @Tag User API
|
// @Tag User API
|
||||||
func (c *ApiController) AddUserkeys() {
|
func (c *ApiController) AddUserKeys() {
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -585,7 +589,7 @@ func (c *ApiController) AddUserkeys() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAdmin := c.IsAdmin()
|
isAdmin := c.IsAdmin()
|
||||||
affected, err := object.AddUserkeys(&user, isAdmin)
|
affected, err := object.AddUserKeys(&user, isAdmin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@@ -53,17 +53,34 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if vform.CaptchaType != "none" {
|
provider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
|
||||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
if err != nil {
|
||||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
}
|
||||||
c.ResponseError(err.Error())
|
|
||||||
return
|
if provider != nil {
|
||||||
} else if !isHuman {
|
if vform.CaptchaType != provider.Type {
|
||||||
c.ResponseError(c.T("verification:Turing test failed."))
|
c.ResponseError(c.T("verification:Turing test failed."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if provider.Type != "Default" {
|
||||||
|
vform.ClientSecret = provider.ClientSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
if vform.CaptchaType != "none" {
|
||||||
|
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||||
|
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||||
|
return
|
||||||
|
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
} else if !isHuman {
|
||||||
|
c.ResponseError(c.T("verification:Turing test failed."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
application, err := object.GetApplication(vform.ApplicationId)
|
application, err := object.GetApplication(vform.ApplicationId)
|
||||||
@@ -225,6 +242,16 @@ func (c *ApiController) VerifyCaptcha() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if captchaProvider.Type != "Default" {
|
||||||
|
vform.ClientSecret = captchaProvider.ClientSecret
|
||||||
|
}
|
||||||
|
|
||||||
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||||
if provider == nil {
|
if provider == nil {
|
||||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||||
|
@@ -154,6 +154,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
|||||||
// @router /webauthn/signin/finish [post]
|
// @router /webauthn/signin/finish [post]
|
||||||
func (c *ApiController) WebAuthnSigninFinish() {
|
func (c *ApiController) WebAuthnSigninFinish() {
|
||||||
responseType := c.Input().Get("responseType")
|
responseType := c.Input().Get("responseType")
|
||||||
|
clientId := c.Input().Get("clientId")
|
||||||
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
@@ -182,7 +183,13 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
|||||||
c.SetSessionUsername(userId)
|
c.SetSessionUsername(userId)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] signed in", 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 {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
24
go.mod
24
go.mod
@@ -9,13 +9,12 @@ require (
|
|||||||
github.com/aws/aws-sdk-go v1.45.5
|
github.com/aws/aws-sdk-go v1.45.5
|
||||||
github.com/beego/beego v1.12.12
|
github.com/beego/beego v1.12.12
|
||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin v1.9.1 // indirect
|
|
||||||
github.com/casbin/casbin/v2 v2.77.2
|
github.com/casbin/casbin/v2 v2.77.2
|
||||||
github.com/casdoor/go-sms-sender v0.15.0
|
github.com/casdoor/go-sms-sender v0.17.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.0.1
|
||||||
github.com/casdoor/notify v0.45.0
|
github.com/casdoor/notify v0.45.0
|
||||||
github.com/casdoor/oss v1.3.0
|
github.com/casdoor/oss v1.4.1
|
||||||
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/casvisor/casvisor-go-sdk v1.0.3
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/denisenkom/go-mssqldb v0.9.0
|
github.com/denisenkom/go-mssqldb v0.9.0
|
||||||
@@ -23,15 +22,17 @@ require (
|
|||||||
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||||
github.com/fogleman/gg v1.3.0
|
github.com/fogleman/gg v1.3.0
|
||||||
github.com/forestmgy/ldapserver v1.1.0
|
github.com/forestmgy/ldapserver v1.1.0
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.5
|
||||||
github.com/go-git/go-git/v5 v5.6.0
|
github.com/go-git/go-git/v5 v5.6.0
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-ldap/ldap/v3 v3.4.6
|
||||||
github.com/go-mysql-org/go-mysql v1.7.0
|
github.com/go-mysql-org/go-mysql v1.7.0
|
||||||
github.com/go-pay/gopay v1.5.72
|
github.com/go-pay/gopay v1.5.72
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||||
github.com/go-webauthn/webauthn v0.6.0
|
github.com/go-webauthn/webauthn v0.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||||
github.com/google/uuid v1.3.1
|
github.com/google/uuid v1.4.0
|
||||||
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/lestrrat-go/jwx v1.2.21
|
github.com/lestrrat-go/jwx v1.2.21
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
@@ -42,7 +43,7 @@ require (
|
|||||||
github.com/nyaruka/phonenumbers v1.1.5
|
github.com/nyaruka/phonenumbers v1.1.5
|
||||||
github.com/pquerna/otp v1.4.0
|
github.com/pquerna/otp v1.4.0
|
||||||
github.com/prometheus/client_golang v1.11.1
|
github.com/prometheus/client_golang v1.11.1
|
||||||
github.com/prometheus/client_model v0.3.0
|
github.com/prometheus/client_model v0.4.0
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.9.0
|
github.com/russellhaering/gosaml2 v0.9.0
|
||||||
@@ -61,11 +62,10 @@ require (
|
|||||||
github.com/xorm-io/core v0.7.4
|
github.com/xorm-io/core v0.7.4
|
||||||
github.com/xorm-io/xorm v1.1.6
|
github.com/xorm-io/xorm v1.1.6
|
||||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.14.0
|
||||||
golang.org/x/net v0.14.0
|
golang.org/x/net v0.17.0
|
||||||
golang.org/x/oauth2 v0.11.0
|
golang.org/x/oauth2 v0.13.0
|
||||||
golang.org/x/text v0.13.0 // indirect
|
google.golang.org/api v0.150.0
|
||||||
google.golang.org/api v0.138.0
|
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
|
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
|
||||||
"Unauthorized operation": "Opération non autorisée",
|
"Unauthorized operation": "Opération non autorisée",
|
||||||
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
|
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
|
||||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
"User's tag: %s is not listed in the application's tags": "Le tag de l’utilisateur %s n’est pas répertorié dans les tags de l’application"
|
||||||
},
|
},
|
||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
|
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
||||||
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
|
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
|
||||||
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
|
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
|
||||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
"The user: %s doesn't exist in LDAP server": "L'utilisateur %s n'existe pas sur le serveur LDAP",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
|
||||||
"Username already exists": "Nom d'utilisateur existe déjà",
|
"Username already exists": "Nom d'utilisateur existe déjà",
|
||||||
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
|
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
|
||||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
|
||||||
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
|
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
|
||||||
"password or code is incorrect": "password or code is incorrect",
|
"password or code is incorrect": "mot de passe ou code invalide",
|
||||||
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
|
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
|
||||||
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
||||||
},
|
},
|
||||||
@@ -61,8 +61,8 @@
|
|||||||
"Missing parameter": "Paramètre manquant",
|
"Missing parameter": "Paramètre manquant",
|
||||||
"Please login first": "Veuillez d'abord vous connecter",
|
"Please login first": "Veuillez d'abord vous connecter",
|
||||||
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
|
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
|
||||||
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider",
|
"don't support captchaProvider: ": "ne prend pas en charge captchaProvider: ",
|
||||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
"this operation is not allowed in demo mode": "cette opération n’est pas autorisée en mode démo"
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "Le serveur LDAP existe"
|
"Ldap server exist": "Le serveur LDAP existe"
|
||||||
|
@@ -24,14 +24,6 @@
|
|||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
},
|
},
|
||||||
"chat": {
|
|
||||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
|
||||||
"The chat: %s is not found": "The chat: %s is not found",
|
|
||||||
"The message is invalid": "The message is invalid",
|
|
||||||
"The message: %s is not found": "The message: %s is not found",
|
|
||||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
|
||||||
"The provider: %s is not found": "The provider: %s is not found"
|
|
||||||
},
|
|
||||||
"check": {
|
"check": {
|
||||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
@@ -24,14 +24,6 @@
|
|||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
},
|
},
|
||||||
"chat": {
|
|
||||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
|
||||||
"The chat: %s is not found": "The chat: %s is not found",
|
|
||||||
"The message is invalid": "The message is invalid",
|
|
||||||
"The message: %s is not found": "The message: %s is not found",
|
|
||||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
|
||||||
"The provider: %s is not found": "The provider: %s is not found"
|
|
||||||
},
|
|
||||||
"check": {
|
"check": {
|
||||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
@@ -24,14 +24,6 @@
|
|||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
},
|
},
|
||||||
"chat": {
|
|
||||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
|
||||||
"The chat: %s is not found": "The chat: %s is not found",
|
|
||||||
"The message is invalid": "The message is invalid",
|
|
||||||
"The message: %s is not found": "The message: %s is not found",
|
|
||||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
|
||||||
"The provider: %s is not found": "The provider: %s is not found"
|
|
||||||
},
|
|
||||||
"check": {
|
"check": {
|
||||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
@@ -43,7 +43,7 @@
|
|||||||
"Phone number is invalid": "无效手机号",
|
"Phone number is invalid": "无效手机号",
|
||||||
"Session outdated, please login again": "会话已过期,请重新登录",
|
"Session outdated, please login again": "会话已过期,请重新登录",
|
||||||
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
|
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
|
||||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
"The user: %s doesn't exist in LDAP server": "用户: %s 在LDAP服务器中未找到",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
||||||
"Username already exists": "用户名已存在",
|
"Username already exists": "用户名已存在",
|
||||||
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"Please login first": "请先登录",
|
"Please login first": "请先登录",
|
||||||
"The user: %s doesn't exist": "用户: %s不存在",
|
"The user: %s doesn't exist": "用户: %s不存在",
|
||||||
"don't support captchaProvider: ": "不支持验证码提供商: ",
|
"don't support captchaProvider: ": "不支持验证码提供商: ",
|
||||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
"this operation is not allowed in demo mode": "demo模式下不允许该操作"
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "LDAP服务器已存在"
|
"Ldap server exist": "LDAP服务器已存在"
|
||||||
|
20
idp/adfs.go
20
idp/adfs.go
@@ -19,7 +19,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
@@ -84,6 +83,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
payload.Set("code", code)
|
payload.Set("code", code)
|
||||||
payload.Set("grant_type", "authorization_code")
|
payload.Set("grant_type", "authorization_code")
|
||||||
payload.Set("client_id", idp.Config.ClientID)
|
payload.Set("client_id", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||||
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,11 +118,25 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
keyset, err := jwk.ParseKey(body)
|
var respKeys struct {
|
||||||
|
Keys []interface{} `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &respKeys); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respKey, err := json.Marshal(&(respKeys.Keys[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyset, err := jwk.ParseKey(respKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
tokenSrc := []byte(token.AccessToken)
|
tokenSrc := []byte(token.AccessToken)
|
||||||
publicKey, _ := keyset.PublicKey()
|
publicKey, _ := keyset.PublicKey()
|
||||||
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))
|
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))
|
||||||
|
24
idp/goth.go
24
idp/goth.go
@@ -89,7 +89,7 @@ type GothIdProvider struct {
|
|||||||
Session goth.Session
|
Session goth.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string, hostUrl string) *GothIdProvider {
|
func NewGothIdProvider(providerType string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, redirectUrl string, hostUrl string) (*GothIdProvider, error) {
|
||||||
var idp GothIdProvider
|
var idp GothIdProvider
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case "Amazon":
|
case "Amazon":
|
||||||
@@ -101,8 +101,24 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
if !strings.Contains(redirectUrl, "/api/callback") {
|
if !strings.Contains(redirectUrl, "/api/callback") {
|
||||||
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
|
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iat := time.Now().Unix()
|
||||||
|
exp := iat + 60*60
|
||||||
|
sp := apple.SecretParams{
|
||||||
|
ClientId: clientId,
|
||||||
|
TeamId: clientSecret,
|
||||||
|
KeyId: clientId2,
|
||||||
|
PKCS8PrivateKey: clientSecret2,
|
||||||
|
Iat: int(iat),
|
||||||
|
Exp: int(exp),
|
||||||
|
}
|
||||||
|
secret, err := apple.MakeSecret(sp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
|
Provider: apple.New(clientId, *secret, redirectUrl, nil),
|
||||||
Session: &apple.Session{},
|
Session: &apple.Session{},
|
||||||
}
|
}
|
||||||
case "AzureAD":
|
case "AzureAD":
|
||||||
@@ -386,10 +402,10 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Session: &zoom.Session{},
|
Session: &zoom.Session{},
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &idp
|
return &idp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHttpClient
|
// SetHttpClient
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -30,16 +31,19 @@ type UserInfo struct {
|
|||||||
Phone string
|
Phone string
|
||||||
CountryCode string
|
CountryCode string
|
||||||
AvatarUrl string
|
AvatarUrl string
|
||||||
|
Extra map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderInfo struct {
|
type ProviderInfo struct {
|
||||||
Type string
|
Type string
|
||||||
SubType string
|
SubType string
|
||||||
ClientId string
|
ClientId string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
AppId string
|
ClientId2 string
|
||||||
HostUrl string
|
ClientSecret2 string
|
||||||
RedirectUrl string
|
AppId string
|
||||||
|
HostUrl string
|
||||||
|
RedirectUrl string
|
||||||
|
|
||||||
TokenURL string
|
TokenURL string
|
||||||
AuthURL string
|
AuthURL string
|
||||||
@@ -53,71 +57,71 @@ type IdProvider interface {
|
|||||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
|
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) {
|
||||||
switch idpInfo.Type {
|
switch idpInfo.Type {
|
||||||
case "GitHub":
|
case "GitHub":
|
||||||
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Google":
|
case "Google":
|
||||||
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "QQ":
|
case "QQ":
|
||||||
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "WeChat":
|
case "WeChat":
|
||||||
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Facebook":
|
case "Facebook":
|
||||||
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "DingTalk":
|
case "DingTalk":
|
||||||
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Weibo":
|
case "Weibo":
|
||||||
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Gitee":
|
case "Gitee":
|
||||||
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "LinkedIn":
|
case "LinkedIn":
|
||||||
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "WeCom":
|
case "WeCom":
|
||||||
if idpInfo.SubType == "Internal" {
|
if idpInfo.SubType == "Internal" {
|
||||||
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
} else if idpInfo.SubType == "Third-party" {
|
} else if idpInfo.SubType == "Third-party" {
|
||||||
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
|
||||||
}
|
}
|
||||||
case "Lark":
|
case "Lark":
|
||||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "GitLab":
|
case "GitLab":
|
||||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "ADFS":
|
case "ADFS":
|
||||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Baidu":
|
case "Baidu":
|
||||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Alipay":
|
case "Alipay":
|
||||||
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Custom":
|
case "Custom":
|
||||||
return NewCustomIdProvider(idpInfo, redirectUrl)
|
return NewCustomIdProvider(idpInfo, redirectUrl), nil
|
||||||
case "Infoflow":
|
case "Infoflow":
|
||||||
if idpInfo.SubType == "Internal" {
|
if idpInfo.SubType == "Internal" {
|
||||||
return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
|
return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
|
||||||
} else if idpInfo.SubType == "Third-party" {
|
} else if idpInfo.SubType == "Third-party" {
|
||||||
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
|
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType)
|
||||||
}
|
}
|
||||||
case "Casdoor":
|
case "Casdoor":
|
||||||
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Okta":
|
case "Okta":
|
||||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Douyin":
|
case "Douyin":
|
||||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Bilibili":
|
case "Bilibili":
|
||||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "MetaMask":
|
case "MetaMask":
|
||||||
return NewMetaMaskIdProvider()
|
return NewMetaMaskIdProvider(), nil
|
||||||
case "Web3Onboard":
|
case "Web3Onboard":
|
||||||
return NewWeb3OnboardIdProvider()
|
return NewWeb3OnboardIdProvider(), nil
|
||||||
default:
|
default:
|
||||||
if isGothSupport(idpInfo.Type) {
|
if isGothSupport(idpInfo.Type) {
|
||||||
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.ClientId2, idpInfo.ClientSecret2, redirectUrl, idpInfo.HostUrl)
|
||||||
}
|
}
|
||||||
return nil
|
return nil, fmt.Errorf("OAuth provider type: %s is not supported", idpInfo.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -186,15 +186,24 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
|||||||
id = wechatUserInfo.Openid
|
id = wechatUserInfo.Openid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extra := make(map[string]string)
|
||||||
|
extra["wechat_unionid"] = wechatUserInfo.Openid
|
||||||
|
// For WeChat, different appId corresponds to different openId
|
||||||
|
extra[BuildWechatOpenIdKey(idp.Config.ClientID)] = wechatUserInfo.Openid
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: id,
|
Id: id,
|
||||||
Username: wechatUserInfo.Nickname,
|
Username: wechatUserInfo.Nickname,
|
||||||
DisplayName: wechatUserInfo.Nickname,
|
DisplayName: wechatUserInfo.Nickname,
|
||||||
AvatarUrl: wechatUserInfo.Headimgurl,
|
AvatarUrl: wechatUserInfo.Headimgurl,
|
||||||
|
Extra: extra,
|
||||||
}
|
}
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BuildWechatOpenIdKey(appId string) string {
|
||||||
|
return fmt.Sprintf("wechat_openid_%s", appId)
|
||||||
|
}
|
||||||
|
|
||||||
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
|
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
|
||||||
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
|
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
|
||||||
request, err := http.NewRequest("GET", accessTokenUrl, nil)
|
request, err := http.NewRequest("GET", accessTokenUrl, nil)
|
||||||
|
@@ -16,6 +16,7 @@ package ldap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@@ -49,20 +50,20 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
|||||||
|
|
||||||
if r.AuthenticationChoice() == "simple" {
|
if r.AuthenticationChoice() == "simple" {
|
||||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
|
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
|
||||||
if err != "" {
|
if err != nil {
|
||||||
log.Printf("Bind failed ,ErrMsg=%s", err)
|
log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
|
||||||
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
||||||
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
res.SetDiagnosticMessage(fmt.Sprintf("getNameAndOrgFromDN() error: %s", err.Error()))
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
bindPassword := string(r.AuthenticationSimple())
|
bindPassword := string(r.AuthenticationSimple())
|
||||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
||||||
if err != "" {
|
if err != nil {
|
||||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||||
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
||||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err.Error())
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
|||||||
m.Client.OrgName = bindOrg
|
m.Client.OrgName = bindOrg
|
||||||
} else {
|
} else {
|
||||||
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
|
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
|
||||||
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
res.SetDiagnosticMessage("Authentication method not supported, please use Simple Authentication")
|
||||||
}
|
}
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
}
|
}
|
||||||
@@ -113,10 +114,22 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
|
||||||
e := ldap.NewSearchResultEntry(dn)
|
e := ldap.NewSearchResultEntry(dn)
|
||||||
|
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
|
||||||
for _, attr := range r.Attributes() {
|
e.AddAttribute("uidNumber", message.AttributeValue(uidNumberStr))
|
||||||
|
e.AddAttribute("gidNumber", message.AttributeValue(uidNumberStr))
|
||||||
|
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
|
||||||
|
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||||
|
e.AddAttribute("uid", message.AttributeValue(user.Id))
|
||||||
|
attrs := r.Attributes()
|
||||||
|
for _, attr := range attrs {
|
||||||
|
if string(attr) == "*" {
|
||||||
|
attrs = AdditionalLdapAttributes
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, attr := range attrs {
|
||||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||||
if string(attr) == "cn" {
|
if string(attr) == "cn" {
|
||||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||||
@@ -127,3 +140,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
|||||||
}
|
}
|
||||||
w.Write(res)
|
w.Write(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hash(s string) uint32 {
|
||||||
|
h := fnv.New32a()
|
||||||
|
h.Write([]byte(s))
|
||||||
|
return h.Sum32()
|
||||||
|
}
|
||||||
|
200
ldap/util.go
200
ldap/util.go
@@ -24,9 +24,73 @@ import (
|
|||||||
"github.com/lor00x/goldap/message"
|
"github.com/lor00x/goldap/message"
|
||||||
|
|
||||||
ldap "github.com/forestmgy/ldapserver"
|
ldap "github.com/forestmgy/ldapserver"
|
||||||
|
|
||||||
|
"github.com/xorm-io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getNameAndOrgFromDN(DN string) (string, string, string) {
|
type AttributeMapper func(user *object.User) message.AttributeValue
|
||||||
|
|
||||||
|
type FieldRelation struct {
|
||||||
|
userField string
|
||||||
|
notSearchable bool
|
||||||
|
hideOnStarOp bool
|
||||||
|
fieldMapper AttributeMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rel FieldRelation) GetField() (string, error) {
|
||||||
|
if rel.notSearchable {
|
||||||
|
return "", fmt.Errorf("attribute %s not supported", rel.userField)
|
||||||
|
}
|
||||||
|
return rel.userField, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rel FieldRelation) GetAttributeValue(user *object.User) message.AttributeValue {
|
||||||
|
return rel.fieldMapper(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ldapAttributesMapping = map[string]FieldRelation{
|
||||||
|
"cn": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(user.Name)
|
||||||
|
}},
|
||||||
|
"uid": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(user.Name)
|
||||||
|
}},
|
||||||
|
"displayname": {userField: "displayName", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(user.DisplayName)
|
||||||
|
}},
|
||||||
|
"email": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(user.Email)
|
||||||
|
}},
|
||||||
|
"mail": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(user.Email)
|
||||||
|
}},
|
||||||
|
"mobile": {userField: "phone", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(user.Phone)
|
||||||
|
}},
|
||||||
|
"title": {userField: "tag", fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(user.Tag)
|
||||||
|
}},
|
||||||
|
"userPassword": {
|
||||||
|
userField: "userPassword",
|
||||||
|
notSearchable: true,
|
||||||
|
fieldMapper: func(user *object.User) message.AttributeValue {
|
||||||
|
return message.AttributeValue(getUserPasswordWithType(user))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var AdditionalLdapAttributes []message.LDAPString
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for k, v := range ldapAttributesMapping {
|
||||||
|
if v.hideOnStarOp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
AdditionalLdapAttributes = append(AdditionalLdapAttributes, message.LDAPString(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNameAndOrgFromDN(DN string) (string, string, error) {
|
||||||
DNFields := strings.Split(DN, ",")
|
DNFields := strings.Split(DN, ",")
|
||||||
params := make(map[string]string, len(DNFields))
|
params := make(map[string]string, len(DNFields))
|
||||||
for _, field := range DNFields {
|
for _, field := range DNFields {
|
||||||
@@ -37,12 +101,12 @@ func getNameAndOrgFromDN(DN string) (string, string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if params["cn"] == "" {
|
if params["cn"] == "" {
|
||||||
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
return "", "", fmt.Errorf("please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com")
|
||||||
}
|
}
|
||||||
if params["ou"] == "" {
|
if params["ou"] == "" {
|
||||||
return params["cn"], object.CasdoorOrganization, ""
|
return params["cn"], object.CasdoorOrganization, nil
|
||||||
}
|
}
|
||||||
return params["cn"], params["ou"], ""
|
return params["cn"], params["ou"], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
||||||
@@ -50,7 +114,11 @@ func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
|||||||
return "", "", ldap.LDAPResultInvalidDNSyntax
|
return "", "", ldap.LDAPResultInvalidDNSyntax
|
||||||
}
|
}
|
||||||
|
|
||||||
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
name, org, err := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
return name, org, ldap.LDAPResultSuccess
|
return name, org, ldap.LDAPResultSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +151,92 @@ func stringInSlice(value string, list []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
||||||
|
switch f := filter.(type) {
|
||||||
|
case message.FilterAnd:
|
||||||
|
conditions := make([]builder.Cond, len(f))
|
||||||
|
for i, v := range f {
|
||||||
|
cond, err := buildUserFilterCondition(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conditions[i] = cond
|
||||||
|
}
|
||||||
|
return builder.And(conditions...), nil
|
||||||
|
case message.FilterOr:
|
||||||
|
conditions := make([]builder.Cond, len(f))
|
||||||
|
for i, v := range f {
|
||||||
|
cond, err := buildUserFilterCondition(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conditions[i] = cond
|
||||||
|
}
|
||||||
|
return builder.Or(conditions...), nil
|
||||||
|
case message.FilterNot:
|
||||||
|
cond, err := buildUserFilterCondition(f.Filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return builder.Not{cond}, nil
|
||||||
|
case message.FilterEqualityMatch:
|
||||||
|
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return builder.Eq{field: string(f.AssertionValue())}, nil
|
||||||
|
case message.FilterPresent:
|
||||||
|
field, err := getUserFieldFromAttribute(string(f))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return builder.NotNull{field}, nil
|
||||||
|
case message.FilterGreaterOrEqual:
|
||||||
|
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return builder.Gte{field: string(f.AssertionValue())}, nil
|
||||||
|
case message.FilterLessOrEqual:
|
||||||
|
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return builder.Lte{field: string(f.AssertionValue())}, nil
|
||||||
|
case message.FilterSubstrings:
|
||||||
|
field, err := getUserFieldFromAttribute(string(f.Type_()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var expr string
|
||||||
|
for _, substring := range f.Substrings() {
|
||||||
|
switch s := substring.(type) {
|
||||||
|
case message.SubstringInitial:
|
||||||
|
expr += string(s) + "%"
|
||||||
|
continue
|
||||||
|
case message.SubstringAny:
|
||||||
|
expr += string(s) + "%"
|
||||||
|
continue
|
||||||
|
case message.SubstringFinal:
|
||||||
|
expr += string(s)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.Expr(field+" LIKE ?", expr), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("LDAP filter operation %#v not supported", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildSafeCondition(filter interface{}) builder.Cond {
|
||||||
|
condition, err := buildUserFilterCondition(filter)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("err = %v", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return condition
|
||||||
|
}
|
||||||
|
|
||||||
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
||||||
var err error
|
var err error
|
||||||
r := m.GetSearchRequest()
|
r := m.GetSearchRequest()
|
||||||
@@ -94,15 +248,14 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
|||||||
|
|
||||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||||
if m.Client.IsGlobalAdmin && org == "*" {
|
if m.Client.IsGlobalAdmin && org == "*" {
|
||||||
|
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
||||||
filteredUsers, err = object.GetGlobalUsers()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return filteredUsers, ldap.LDAPResultSuccess
|
return filteredUsers, ldap.LDAPResultSuccess
|
||||||
}
|
}
|
||||||
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
||||||
filteredUsers, err = object.GetUsers(org)
|
filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -144,7 +297,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
|||||||
return nil, ldap.LDAPResultNoSuchObject
|
return nil, ldap.LDAPResultNoSuchObject
|
||||||
}
|
}
|
||||||
|
|
||||||
users, err := object.GetUsersByTag(org, name)
|
users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -178,24 +331,17 @@ func getUserPasswordWithType(user *object.User) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
|
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
|
||||||
switch attributeName {
|
v, ok := ldapAttributesMapping[attributeName]
|
||||||
case "cn":
|
if !ok {
|
||||||
return message.AttributeValue(user.Name)
|
|
||||||
case "uid":
|
|
||||||
return message.AttributeValue(user.Name)
|
|
||||||
case "displayname":
|
|
||||||
return message.AttributeValue(user.DisplayName)
|
|
||||||
case "email":
|
|
||||||
return message.AttributeValue(user.Email)
|
|
||||||
case "mail":
|
|
||||||
return message.AttributeValue(user.Email)
|
|
||||||
case "mobile":
|
|
||||||
return message.AttributeValue(user.Phone)
|
|
||||||
case "title":
|
|
||||||
return message.AttributeValue(user.Tag)
|
|
||||||
case "userPassword":
|
|
||||||
return message.AttributeValue(getUserPasswordWithType(user))
|
|
||||||
default:
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return v.GetAttributeValue(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserFieldFromAttribute(attributeName string) (string, error) {
|
||||||
|
v, ok := ldapAttributesMapping[attributeName]
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("attribute %s not supported", attributeName)
|
||||||
|
}
|
||||||
|
return v.GetField()
|
||||||
}
|
}
|
||||||
|
87
ldap/util_test.go
Normal file
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.InitFlag()
|
||||||
object.InitAdapter()
|
object.InitAdapter()
|
||||||
object.CreateTables()
|
object.CreateTables()
|
||||||
object.DoMigration()
|
|
||||||
|
|
||||||
object.InitDb()
|
object.InitDb()
|
||||||
object.InitFromFile()
|
object.InitFromFile()
|
||||||
|
@@ -15,10 +15,10 @@ type: application
|
|||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# 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.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# 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
|
# 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
|
# 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.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
# It is recommended to use it with quotes.
|
# It is recommended to use it with quotes.
|
||||||
appVersion: "1.16.0"
|
appVersion: "1.17.0"
|
||||||
|
@@ -59,6 +59,9 @@ spec:
|
|||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: config-volume
|
- name: config-volume
|
||||||
mountPath: /conf
|
mountPath: /conf
|
||||||
|
{{ if .Values.extraContainersEnabled }}
|
||||||
|
{{- .Values.extraContainers | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
volumes:
|
volumes:
|
||||||
- name: config-volume
|
- name: config-volume
|
||||||
projected:
|
projected:
|
||||||
|
@@ -108,3 +108,10 @@ nodeSelector: {}
|
|||||||
tolerations: []
|
tolerations: []
|
||||||
|
|
||||||
affinity: {}
|
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 != "" {
|
if application.OrganizationObj.DefaultPassword != "" {
|
||||||
application.OrganizationObj.DefaultPassword = "***"
|
application.OrganizationObj.DefaultPassword = "***"
|
||||||
}
|
}
|
||||||
|
if application.OrganizationObj.MasterVerificationCode != "" {
|
||||||
|
application.OrganizationObj.MasterVerificationCode = "***"
|
||||||
|
}
|
||||||
if application.OrganizationObj.PasswordType != "" {
|
if application.OrganizationObj.PasswordType != "" {
|
||||||
application.OrganizationObj.PasswordType = "***"
|
application.OrganizationObj.PasswordType = "***"
|
||||||
}
|
}
|
||||||
@@ -345,6 +348,34 @@ func GetMaskedApplications(applications []*Application, userId string) []*Applic
|
|||||||
return applications
|
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) {
|
func UpdateApplication(id string, application *Application) (bool, error) {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
oldApplication, err := getApplication(owner, name)
|
oldApplication, err := getApplication(owner, name)
|
||||||
|
@@ -87,7 +87,7 @@ func GetGlobalCertsCount(field, value string) (int64, error) {
|
|||||||
return session.Count(&Cert{})
|
return session.Count(&Cert{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGlobleCerts() ([]*Cert, error) {
|
func GetGlobalCerts() ([]*Cert, error) {
|
||||||
certs := []*Cert{}
|
certs := []*Cert{}
|
||||||
err := ormer.Engine.Desc("created_time").Find(&certs)
|
err := ormer.Engine.Desc("created_time").Find(&certs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -163,6 +163,12 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := cert.populateContent()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -172,10 +178,9 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddCert(cert *Cert) (bool, error) {
|
func AddCert(cert *Cert) (bool, error) {
|
||||||
if cert.Certificate == "" || cert.PrivateKey == "" {
|
err := cert.populateContent()
|
||||||
certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
|
if err != nil {
|
||||||
cert.Certificate = certificate
|
return false, err
|
||||||
cert.PrivateKey = privateKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.Insert(cert)
|
affected, err := ormer.Engine.Insert(cert)
|
||||||
@@ -199,6 +204,20 @@ func (p *Cert) GetId() string {
|
|||||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Cert) populateContent() error {
|
||||||
|
if p.Certificate == "" || p.PrivateKey == "" {
|
||||||
|
certificate, privateKey, err := generateRsaKeys(p.BitSize, p.ExpireInYears, p.Name, p.Owner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Certificate = certificate
|
||||||
|
p.PrivateKey = privateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getCertByApplication(application *Application) (*Cert, error) {
|
func getCertByApplication(application *Application) (*Cert, error) {
|
||||||
if application.Cert != "" {
|
if application.Cert != "" {
|
||||||
return getCertByName(application.Cert)
|
return getCertByName(application.Cert)
|
||||||
|
@@ -142,7 +142,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSigninErrorTimes(user *User, lang string) string {
|
func checkSigninErrorTimes(user *User, lang string) error {
|
||||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||||
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
|
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
|
||||||
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
|
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
|
||||||
@@ -150,37 +150,39 @@ func checkSigninErrorTimes(user *User, lang string) string {
|
|||||||
|
|
||||||
// deny the login if the error times is greater than the limit and the last login time is less than the duration
|
// deny the login if the error times is greater than the limit and the last login time is less than the duration
|
||||||
if minutes > 0 {
|
if minutes > 0 {
|
||||||
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
|
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset the error times
|
// reset the error times
|
||||||
user.SigninWrongTimes = 0
|
user.SigninWrongTimes = 0
|
||||||
|
|
||||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
|
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckPassword(user *User, password string, lang string, options ...bool) string {
|
func CheckPassword(user *User, password string, lang string, options ...bool) error {
|
||||||
enableCaptcha := false
|
enableCaptcha := false
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
enableCaptcha = options[0]
|
enableCaptcha = options[0]
|
||||||
}
|
}
|
||||||
// check the login error times
|
// check the login error times
|
||||||
if !enableCaptcha {
|
if !enableCaptcha {
|
||||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
err := checkSigninErrorTimes(user, lang)
|
||||||
return msg
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
organization, err := GetOrganizationByUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if organization == nil {
|
if organization == nil {
|
||||||
return i18n.Translate(lang, "check:Organization does not exist")
|
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
|
||||||
}
|
}
|
||||||
|
|
||||||
passwordType := user.PasswordType
|
passwordType := user.PasswordType
|
||||||
@@ -191,19 +193,17 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
|
|||||||
if credManager != nil {
|
if credManager != nil {
|
||||||
if organization.MasterPassword != "" {
|
if organization.MasterPassword != "" {
|
||||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||||
resetUserSigninErrorTimes(user)
|
return resetUserSigninErrorTimes(user)
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
||||||
resetUserSigninErrorTimes(user)
|
return resetUserSigninErrorTimes(user)
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return recordSigninErrorInfo(user, lang, enableCaptcha)
|
return recordSigninErrorInfo(user, lang, enableCaptcha)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,10 +217,10 @@ func CheckPasswordComplexity(user *User, password string) string {
|
|||||||
return CheckPasswordComplexityByOrg(organization, password)
|
return CheckPasswordComplexityByOrg(organization, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkLdapUserPassword(user *User, password string, lang string) string {
|
func checkLdapUserPassword(user *User, password string, lang string) error {
|
||||||
ldaps, err := GetLdaps(user.Owner)
|
ldaps, err := GetLdaps(user.Owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapLoginSuccess := false
|
ldapLoginSuccess := false
|
||||||
@@ -237,65 +237,73 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
|
|||||||
|
|
||||||
searchResult, err := conn.Conn.Search(searchReq)
|
searchResult, err := conn.Conn.Search(searchReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err.Error()
|
conn.Close()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(searchResult.Entries) == 0 {
|
if len(searchResult.Entries) == 0 {
|
||||||
|
conn.Close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(searchResult.Entries) > 1 {
|
if len(searchResult.Entries) > 1 {
|
||||||
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
|
conn.Close()
|
||||||
|
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
|
||||||
}
|
}
|
||||||
|
|
||||||
hit = true
|
hit = true
|
||||||
dn := searchResult.Entries[0].DN
|
dn := searchResult.Entries[0].DN
|
||||||
if err := conn.Conn.Bind(dn, password); err == nil {
|
if err = conn.Conn.Bind(dn, password); err == nil {
|
||||||
ldapLoginSuccess = true
|
ldapLoginSuccess = true
|
||||||
|
conn.Close()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ldapLoginSuccess {
|
if !ldapLoginSuccess {
|
||||||
if !hit {
|
if !hit {
|
||||||
return "user not exist"
|
return fmt.Errorf("user not exist")
|
||||||
}
|
}
|
||||||
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
|
return fmt.Errorf(i18n.Translate(lang, "check:LDAP user name or password incorrect"))
|
||||||
}
|
}
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
|
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
|
||||||
enableCaptcha := false
|
enableCaptcha := false
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
enableCaptcha = options[0]
|
enableCaptcha = options[0]
|
||||||
}
|
}
|
||||||
user, err := GetUserByFields(organization, username)
|
user, err := GetUserByFields(organization, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil || user.IsDeleted {
|
if user == nil || user.IsDeleted {
|
||||||
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
return nil, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
return nil, i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator")
|
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Ldap != "" {
|
if user.Ldap != "" {
|
||||||
// ONLY for ldap users
|
// only for LDAP users
|
||||||
if msg := checkLdapUserPassword(user, password, lang); msg != "" {
|
err = checkLdapUserPassword(user, password, lang)
|
||||||
if msg == "user not exist" {
|
if err != nil {
|
||||||
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
if err.Error() == "user not exist" {
|
||||||
|
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
||||||
}
|
}
|
||||||
return nil, msg
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
|
err = CheckPassword(user, password, lang, enableCaptcha)
|
||||||
return nil, msg
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return user, ""
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
|
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
|
||||||
@@ -308,7 +316,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
|||||||
if userId != "" {
|
if userId != "" {
|
||||||
targetUser, err := GetUser(userId)
|
targetUser, err := GetUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetUser == nil {
|
if targetUser == nil {
|
||||||
@@ -351,8 +359,8 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckLoginPermission(userId string, application *Application) (bool, error) {
|
func CheckLoginPermission(userId string, application *Application) (bool, error) {
|
||||||
var err error
|
owner, _ := util.GetOwnerAndNameFromId(userId)
|
||||||
if userId == "built-in/admin" {
|
if owner == "built-in" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +374,7 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
|||||||
allowCount := 0
|
allowCount := 0
|
||||||
denyCount := 0
|
denyCount := 0
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
if !permission.IsEnabled || permission.State != "Approved" || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,20 +36,23 @@ func isValidRealName(s string) bool {
|
|||||||
return reRealName.MatchString(s)
|
return reRealName.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetUserSigninErrorTimes(user *User) {
|
func resetUserSigninErrorTimes(user *User) error {
|
||||||
// if the password is correct and wrong times is not zero, reset the error times
|
// if the password is correct and wrong times is not zero, reset the error times
|
||||||
if user.SigninWrongTimes == 0 {
|
if user.SigninWrongTimes == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
user.SigninWrongTimes = 0
|
user.SigninWrongTimes = 0
|
||||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
|
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||||
enableCaptcha := false
|
enableCaptcha := false
|
||||||
if len(options) > 0 {
|
if len(options) > 0 {
|
||||||
enableCaptcha = options[0]
|
enableCaptcha = options[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// increase failed login count
|
// increase failed login count
|
||||||
if user.SigninWrongTimes < SigninWrongTimesLimit {
|
if user.SigninWrongTimes < SigninWrongTimesLimit {
|
||||||
user.SigninWrongTimes++
|
user.SigninWrongTimes++
|
||||||
@@ -61,13 +64,18 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update user
|
// update user
|
||||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
|
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
|
||||||
if leftChances == 0 && enableCaptcha {
|
if leftChances == 0 && enableCaptcha {
|
||||||
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
|
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||||
} else if leftChances >= 0 {
|
} else if leftChances >= 0 {
|
||||||
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't show the chance error message if the user has no chance left
|
// don't show the chance error message if the user has no chance left
|
||||||
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
|
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
|
||||||
}
|
}
|
||||||
|
@@ -396,15 +396,22 @@ func initBuiltInPermission() {
|
|||||||
Name: "permission-built-in",
|
Name: "permission-built-in",
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
DisplayName: "Built-in Permission",
|
DisplayName: "Built-in Permission",
|
||||||
|
Description: "Built-in Permission",
|
||||||
Users: []string{"built-in/*"},
|
Users: []string{"built-in/*"},
|
||||||
|
Groups: []string{},
|
||||||
Roles: []string{},
|
Roles: []string{},
|
||||||
Domains: []string{},
|
Domains: []string{},
|
||||||
Model: "model-built-in",
|
Model: "model-built-in",
|
||||||
|
Adapter: "",
|
||||||
ResourceType: "Application",
|
ResourceType: "Application",
|
||||||
Resources: []string{"app-built-in"},
|
Resources: []string{"app-built-in"},
|
||||||
Actions: []string{"Read", "Write", "Admin"},
|
Actions: []string{"Read", "Write", "Admin"},
|
||||||
Effect: "Allow",
|
Effect: "Allow",
|
||||||
IsEnabled: true,
|
IsEnabled: true,
|
||||||
|
Submitter: "admin",
|
||||||
|
Approver: "admin",
|
||||||
|
ApproveTime: util.GetCurrentTime(),
|
||||||
|
State: "Approved",
|
||||||
}
|
}
|
||||||
_, err = AddPermission(permission)
|
_, err = AddPermission(permission)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
121
object/init_data_dump.go
Normal file
121
object/init_data_dump.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
// 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/casdoor/casdoor/util"
|
||||||
|
|
||||||
|
func DumpToFile(filePath string) error {
|
||||||
|
return writeInitDataToFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeInitDataToFile(filePath string) error {
|
||||||
|
organizations, err := GetOrganizations("admin")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
applications, err := GetApplications("admin")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
users, err := GetGlobalUsers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certs, err := GetCerts("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
providers, err := GetGlobalProviders()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ldaps, err := GetLdaps("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
models, err := GetModels("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions, err := GetPermissions("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
payments, err := GetPayments("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
products, err := GetProducts("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resources, err := GetResources("", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
roles, err := GetRoles("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
syncers, err := GetSyncers("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := GetTokens("", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
webhooks, err := GetWebhooks("", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &InitData{
|
||||||
|
Organizations: organizations,
|
||||||
|
Applications: applications,
|
||||||
|
Users: users,
|
||||||
|
Certs: certs,
|
||||||
|
Providers: providers,
|
||||||
|
Ldaps: ldaps,
|
||||||
|
Models: models,
|
||||||
|
Permissions: permissions,
|
||||||
|
Payments: payments,
|
||||||
|
Products: products,
|
||||||
|
Resources: resources,
|
||||||
|
Roles: roles,
|
||||||
|
Syncers: syncers,
|
||||||
|
Tokens: tokens,
|
||||||
|
Webhooks: webhooks,
|
||||||
|
}
|
||||||
|
|
||||||
|
text := util.StructToJsonFormatted(data)
|
||||||
|
util.WriteStringToPath(text, filePath)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
29
object/init_data_dump_test.go
Normal file
29
object/init_data_dump_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDumpToFile(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
|
||||||
|
err := DumpToFile("./init_data_dump.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
@@ -100,6 +100,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
|||||||
|
|
||||||
users, err := conn.GetLdapUsers(ldap)
|
users, err := conn.GetLdapUsers(ldap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -111,6 +112,8 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
|||||||
} else {
|
} else {
|
||||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
|
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -81,6 +81,17 @@ func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
|||||||
return &LdapConn{Conn: conn, IsAD: isAD}, nil
|
return &LdapConn{Conn: conn, IsAD: isAD}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LdapConn) Close() {
|
||||||
|
if l.Conn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := l.Conn.Unbind()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||||
SearchFilter := "(objectClass=*)"
|
SearchFilter := "(objectClass=*)"
|
||||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||||
@@ -305,7 +316,7 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := syncUser.buildLdapUserName()
|
name, err := syncUser.buildLdapUserName(owner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -354,10 +365,10 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
|
|||||||
return existUuids, nil
|
return existUuids, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ldapUser *LdapUser) buildLdapUserName() (string, error) {
|
func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) {
|
||||||
user := User{}
|
user := User{}
|
||||||
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
|
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 {
|
if err != nil {
|
||||||
return "", err
|
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"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||||
InitScore int `json:"initScore"`
|
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
InitScore int `json:"initScore"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
|
|
||||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||||
@@ -159,6 +160,9 @@ func GetMaskedOrganization(organization *Organization, errs ...error) (*Organiza
|
|||||||
if organization.DefaultPassword != "" {
|
if organization.DefaultPassword != "" {
|
||||||
organization.DefaultPassword = "***"
|
organization.DefaultPassword = "***"
|
||||||
}
|
}
|
||||||
|
if organization.MasterVerificationCode != "" {
|
||||||
|
organization.MasterVerificationCode = "***"
|
||||||
|
}
|
||||||
return organization, nil
|
return organization, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +217,9 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
|
|||||||
if organization.DefaultPassword == "***" {
|
if organization.DefaultPassword == "***" {
|
||||||
session.Omit("default_password")
|
session.Omit("default_password")
|
||||||
}
|
}
|
||||||
|
if organization.MasterVerificationCode == "***" {
|
||||||
|
session.Omit("master_verification_code")
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := session.Update(organization)
|
affected, err := session.Update(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -64,7 +64,6 @@ func InitConfig() {
|
|||||||
|
|
||||||
InitAdapter()
|
InitAdapter()
|
||||||
CreateTables()
|
CreateTables()
|
||||||
DoMigration()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitAdapter() {
|
func InitAdapter() {
|
||||||
@@ -330,11 +329,6 @@ func (a *Ormer) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(PermissionRule))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(xormadapter.CasbinRule))
|
err = a.Engine.Sync2(new(xormadapter.CasbinRule))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@@ -54,7 +54,7 @@ type Payment struct {
|
|||||||
// Order Info
|
// Order Info
|
||||||
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
||||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||||
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl""` // `successUrl` is redirected from `payUrl` after pay success
|
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl"` // `successUrl` is redirected from `payUrl` after pay success
|
||||||
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
||||||
Message string `xorm:"varchar(2000)" json:"message"`
|
Message string `xorm:"varchar(2000)" json:"message"`
|
||||||
}
|
}
|
||||||
|
@@ -49,17 +49,6 @@ type Permission struct {
|
|||||||
State string `xorm:"varchar(100)" json:"state"`
|
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
|
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) {
|
func GetPermissionCount(owner, field, value string) (int64, error) {
|
||||||
@@ -131,7 +120,11 @@ func checkPermissionValid(permission *Permission) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
groupingPolicies := getGroupingPolicies(permission)
|
groupingPolicies, err := getGroupingPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(groupingPolicies) > 0 {
|
if len(groupingPolicies) > 0 {
|
||||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/casbin/casbin/v2/log"
|
"github.com/casbin/casbin/v2/log"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -137,6 +138,16 @@ func getPolicies(permission *Permission) [][]string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
|
func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
|
||||||
|
roleOwner, roleName := util.GetOwnerAndNameFromId(roleId)
|
||||||
|
if roleName == "*" {
|
||||||
|
roles, err := GetRoles(roleOwner)
|
||||||
|
if err != nil {
|
||||||
|
return []*Role{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles, nil
|
||||||
|
}
|
||||||
|
|
||||||
role, err := GetRole(roleId)
|
role, err := GetRole(roleId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*Role{}, err
|
return []*Role{}, err
|
||||||
@@ -162,7 +173,7 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
|
|||||||
return roles, nil
|
return roles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGroupingPolicies(permission *Permission) [][]string {
|
func getGroupingPolicies(permission *Permission) ([][]string, error) {
|
||||||
var groupingPolicies [][]string
|
var groupingPolicies [][]string
|
||||||
|
|
||||||
domainExist := len(permission.Domains) > 0
|
domainExist := len(permission.Domains) > 0
|
||||||
@@ -170,12 +181,18 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
|||||||
|
|
||||||
for _, roleId := range permission.Roles {
|
for _, roleId := range permission.Roles {
|
||||||
visited := map[string]struct{}{}
|
visited := map[string]struct{}{}
|
||||||
|
|
||||||
|
if roleId == "*" {
|
||||||
|
roleId = util.GetId(permission.Owner, "*")
|
||||||
|
}
|
||||||
|
|
||||||
rolesInRole, err := getRolesInRole(roleId, visited)
|
rolesInRole, err := getRolesInRole(roleId, visited)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, role := range rolesInRole {
|
for _, role := range rolesInRole {
|
||||||
roleId := role.GetId()
|
roleId = role.GetId()
|
||||||
for _, subUser := range role.Users {
|
for _, subUser := range role.Users {
|
||||||
if domainExist {
|
if domainExist {
|
||||||
for _, domain := range permission.Domains {
|
for _, domain := range permission.Domains {
|
||||||
@@ -198,7 +215,7 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupingPolicies
|
return groupingPolicies, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPolicies(permission *Permission) error {
|
func addPolicies(permission *Permission) error {
|
||||||
@@ -231,7 +248,10 @@ func addGroupingPolicies(permission *Permission) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
groupingPolicies := getGroupingPolicies(permission)
|
groupingPolicies, err := getGroupingPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(groupingPolicies) > 0 {
|
if len(groupingPolicies) > 0 {
|
||||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||||
@@ -249,7 +269,10 @@ func removeGroupingPolicies(permission *Permission) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
groupingPolicies := getGroupingPolicies(permission)
|
groupingPolicies, err := getGroupingPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if len(groupingPolicies) > 0 {
|
if len(groupingPolicies) > 0 {
|
||||||
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||||
@@ -287,7 +310,12 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, role := range GetAllRoles(userId) {
|
allRoles, err := GetAllRoles(userId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, role := range allRoles {
|
||||||
permissionsByRole, err := GetPermissionsByRole(role)
|
permissionsByRole, err := GetPermissionsByRole(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -321,17 +349,17 @@ func GetAllActions(userId string) ([]string, error) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllRoles(userId string) []string {
|
func GetAllRoles(userId string) ([]string, error) {
|
||||||
roles, err := getRolesByUser(userId)
|
roles, err := getRolesByUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []string
|
res := []string{}
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
res = append(res, role.Name)
|
res = append(res, role.Name)
|
||||||
}
|
}
|
||||||
return res
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBuiltInModel(modelText string) (model.Model, error) {
|
func GetBuiltInModel(modelText string) (model.Model, error) {
|
||||||
|
@@ -17,6 +17,8 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/idp"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/pp"
|
"github.com/casdoor/casdoor/pp"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
@@ -30,8 +32,8 @@ type Product struct {
|
|||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
Image string `xorm:"varchar(100)" json:"image"`
|
Image string `xorm:"varchar(100)" json:"image"`
|
||||||
Detail string `xorm:"varchar(255)" json:"detail"`
|
Detail string `xorm:"varchar(1000)" json:"detail"`
|
||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(200)" json:"description"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||||
Price float64 `json:"price"`
|
Price float64 `json:"price"`
|
||||||
@@ -158,30 +160,28 @@ func (product *Product) getProvider(providerName string) (*Provider, error) {
|
|||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) {
|
func BuyProduct(id string, user *User, providerName, pricingName, planName, host, paymentEnv string) (payment *Payment, attachInfo map[string]interface{}, err error) {
|
||||||
product, err := GetProduct(id)
|
product, err := GetProduct(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if product == nil {
|
if product == nil {
|
||||||
return nil, fmt.Errorf("the product: %s does not exist", id)
|
return nil, nil, fmt.Errorf("the product: %s does not exist", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := product.getProvider(providerName)
|
provider, err := product.getProvider(providerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pProvider, err := GetPaymentProvider(provider)
|
pProvider, err := GetPaymentProvider(provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
owner := product.Owner
|
owner := product.Owner
|
||||||
productName := product.Name
|
|
||||||
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||||
paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
|
paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
|
||||||
productDisplayName := product.DisplayName
|
|
||||||
|
|
||||||
originFrontend, originBackend := getOriginFromHost(host)
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName)
|
returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName)
|
||||||
@@ -191,26 +191,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
if pricingName != "" && planName != "" {
|
if pricingName != "" && planName != "" {
|
||||||
plan, err := GetPlan(util.GetId(owner, planName))
|
plan, err := GetPlan(util.GetId(owner, planName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if plan == nil {
|
if plan == nil {
|
||||||
return nil, fmt.Errorf("the plan: %s does not exist", planName)
|
return nil, nil, fmt.Errorf("the plan: %s does not exist", planName)
|
||||||
}
|
}
|
||||||
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
|
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
|
||||||
_, err = AddSubscription(sub)
|
_, err = AddSubscription(sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create an OrderId and get the payUrl
|
// Create an order
|
||||||
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
payReq := &pp.PayReq{
|
||||||
|
ProviderName: providerName,
|
||||||
|
ProductName: product.Name,
|
||||||
|
PayerName: payerName,
|
||||||
|
PayerId: user.Id,
|
||||||
|
PaymentName: paymentName,
|
||||||
|
ProductDisplayName: product.DisplayName,
|
||||||
|
Price: product.Price,
|
||||||
|
Currency: product.Currency,
|
||||||
|
ReturnUrl: returnUrl,
|
||||||
|
NotifyUrl: notifyUrl,
|
||||||
|
PaymentEnv: paymentEnv,
|
||||||
|
}
|
||||||
|
// custom process for WeChat & WeChat Pay
|
||||||
|
if provider.Type == "WeChat Pay" {
|
||||||
|
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payResp, err := pProvider.Pay(payReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Create a Payment linked with Product and Order
|
// Create a Payment linked with Product and Order
|
||||||
payment := &Payment{
|
payment = &Payment{
|
||||||
Owner: product.Owner,
|
Owner: product.Owner,
|
||||||
Name: paymentName,
|
Name: paymentName,
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
@@ -219,8 +239,8 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
Provider: provider.Name,
|
Provider: provider.Name,
|
||||||
Type: provider.Type,
|
Type: provider.Type,
|
||||||
|
|
||||||
ProductName: productName,
|
ProductName: product.Name,
|
||||||
ProductDisplayName: productDisplayName,
|
ProductDisplayName: product.DisplayName,
|
||||||
Detail: product.Detail,
|
Detail: product.Detail,
|
||||||
Tag: product.Tag,
|
Tag: product.Tag,
|
||||||
Currency: product.Currency,
|
Currency: product.Currency,
|
||||||
@@ -228,10 +248,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
ReturnUrl: product.ReturnUrl,
|
ReturnUrl: product.ReturnUrl,
|
||||||
|
|
||||||
User: user.Name,
|
User: user.Name,
|
||||||
PayUrl: payUrl,
|
PayUrl: payResp.PayUrl,
|
||||||
SuccessUrl: returnUrl,
|
SuccessUrl: returnUrl,
|
||||||
State: pp.PaymentStateCreated,
|
State: pp.PaymentStateCreated,
|
||||||
OutOrderId: orderId,
|
OutOrderId: payResp.OrderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if provider.Type == "Dummy" {
|
if provider.Type == "Dummy" {
|
||||||
@@ -240,13 +260,13 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
|
|
||||||
affected, err := AddPayment(payment)
|
affected, err := AddPayment(payment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !affected {
|
if !affected {
|
||||||
return nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||||
}
|
}
|
||||||
return payment, err
|
return payment, payResp.AttachInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtendProductWithProviders(product *Product) error {
|
func ExtendProductWithProviders(product *Product) error {
|
||||||
|
@@ -37,9 +37,9 @@ type Provider struct {
|
|||||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||||
Method string `xorm:"varchar(100)" json:"method"`
|
Method string `xorm:"varchar(100)" json:"method"`
|
||||||
ClientId string `xorm:"varchar(200)" json:"clientId"`
|
ClientId string `xorm:"varchar(200)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(3000)" json:"clientSecret"`
|
||||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
ClientSecret2 string `xorm:"varchar(500)" json:"clientSecret2"`
|
||||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
||||||
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
||||||
@@ -398,16 +398,18 @@ func providerChangeTrigger(oldName string, newName string) error {
|
|||||||
|
|
||||||
func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo {
|
func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo {
|
||||||
providerInfo := &idp.ProviderInfo{
|
providerInfo := &idp.ProviderInfo{
|
||||||
Type: provider.Type,
|
Type: provider.Type,
|
||||||
SubType: provider.SubType,
|
SubType: provider.SubType,
|
||||||
ClientId: provider.ClientId,
|
ClientId: provider.ClientId,
|
||||||
ClientSecret: provider.ClientSecret,
|
ClientSecret: provider.ClientSecret,
|
||||||
AppId: provider.AppId,
|
ClientId2: provider.ClientId2,
|
||||||
HostUrl: provider.Host,
|
ClientSecret2: provider.ClientSecret2,
|
||||||
TokenURL: provider.CustomTokenUrl,
|
AppId: provider.AppId,
|
||||||
AuthURL: provider.CustomAuthUrl,
|
HostUrl: provider.Host,
|
||||||
UserInfoURL: provider.CustomUserInfoUrl,
|
TokenURL: provider.CustomTokenUrl,
|
||||||
UserMapping: provider.UserMapping,
|
AuthURL: provider.CustomAuthUrl,
|
||||||
|
UserInfoURL: provider.CustomUserInfoUrl,
|
||||||
|
UserMapping: provider.UserMapping,
|
||||||
}
|
}
|
||||||
|
|
||||||
if provider.Type == "WeChat" {
|
if provider.Type == "WeChat" {
|
||||||
@@ -415,7 +417,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
|||||||
providerInfo.ClientId = provider.ClientId2
|
providerInfo.ClientId = provider.ClientId2
|
||||||
providerInfo.ClientSecret = provider.ClientSecret2
|
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
|
providerInfo.HostUrl = provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,7 +9,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html
|
// https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html
|
||||||
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a
|
|
||||||
type RadiusAccounting struct {
|
type RadiusAccounting struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
@@ -26,6 +26,8 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
|
|||||||
|
|
||||||
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
|
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)
|
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 {
|
} else {
|
||||||
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
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
|
||||||
|
}
|
@@ -112,7 +112,10 @@ func getStorageProvider(provider *Provider, lang string) (oss.StorageInterface,
|
|||||||
|
|
||||||
if provider.Domain == "" {
|
if provider.Domain == "" {
|
||||||
provider.Domain = storageProvider.GetEndpoint()
|
provider.Domain = storageProvider.GetEndpoint()
|
||||||
UpdateProvider(provider.GetId(), provider)
|
_, err := UpdateProvider(provider.GetId(), provider)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return storageProvider, nil
|
return storageProvider, nil
|
||||||
@@ -126,7 +129,12 @@ func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
|
|||||||
|
|
||||||
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
fileUrl, objectKey := GetUploadFileUrl(provider, fullFilePath, true)
|
||||||
|
|
||||||
_, err = storageProvider.Put(objectKey, fileBuffer)
|
objectKeyRefined := objectKey
|
||||||
|
if provider.Type == "Google Cloud Storage" {
|
||||||
|
objectKeyRefined = strings.TrimPrefix(objectKeyRefined, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = storageProvider.Put(objectKeyRefined, fileBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
144
object/token.go
144
object/token.go
@@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -51,15 +52,17 @@ type Token struct {
|
|||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
|
|
||||||
Code string `xorm:"varchar(100) index" json:"code"`
|
Code string `xorm:"varchar(100) index" json:"code"`
|
||||||
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
AccessToken string `xorm:"mediumtext" json:"accessToken"`
|
||||||
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
|
||||||
ExpiresIn int `json:"expiresIn"`
|
AccessTokenHash string `xorm:"varchar(100) index" json:"accessTokenHash"`
|
||||||
Scope string `xorm:"varchar(100)" json:"scope"`
|
RefreshTokenHash string `xorm:"varchar(100) index" json:"refreshTokenHash"`
|
||||||
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
ExpiresIn int `json:"expiresIn"`
|
||||||
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
Scope string `xorm:"varchar(100)" json:"scope"`
|
||||||
CodeIsUsed bool `json:"codeIsUsed"`
|
TokenType string `xorm:"varchar(100)" json:"tokenType"`
|
||||||
CodeExpireIn int64 `json:"codeExpireIn"`
|
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
|
||||||
|
CodeIsUsed bool `json:"codeIsUsed"`
|
||||||
|
CodeExpireIn int64 `json:"codeExpireIn"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenWrapper struct {
|
type TokenWrapper struct {
|
||||||
@@ -141,6 +144,48 @@ func getTokenByCode(code string) (*Token, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTokenByAccessToken(accessToken string) (*Token, error) {
|
||||||
|
token := Token{AccessTokenHash: getTokenHash(accessToken)}
|
||||||
|
existed, err := ormer.Engine.Get(&token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
token = Token{AccessToken: accessToken}
|
||||||
|
existed, err = ormer.Engine.Get(&token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTokenByRefreshToken(refreshToken string) (*Token, error) {
|
||||||
|
token := Token{RefreshTokenHash: getTokenHash(refreshToken)}
|
||||||
|
existed, err := ormer.Engine.Get(&token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
token = Token{RefreshToken: refreshToken}
|
||||||
|
existed, err = ormer.Engine.Get(&token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &token, nil
|
||||||
|
}
|
||||||
|
|
||||||
func updateUsedByCode(token *Token) bool {
|
func updateUsedByCode(token *Token) bool {
|
||||||
affected, err := ormer.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
|
affected, err := ormer.Engine.Where("code=?", token.Code).Cols("code_is_used").Update(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -159,6 +204,24 @@ func (token *Token) GetId() string {
|
|||||||
return fmt.Sprintf("%s/%s", token.Owner, token.Name)
|
return fmt.Sprintf("%s/%s", token.Owner, token.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTokenHash(input string) string {
|
||||||
|
hash := sha256.Sum256([]byte(input))
|
||||||
|
res := hex.EncodeToString(hash[:])
|
||||||
|
if len(res) > 64 {
|
||||||
|
return res[:64]
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (token *Token) popularHashes() {
|
||||||
|
if token.AccessTokenHash == "" && token.AccessToken != "" {
|
||||||
|
token.AccessTokenHash = getTokenHash(token.AccessToken)
|
||||||
|
}
|
||||||
|
if token.RefreshTokenHash == "" && token.RefreshToken != "" {
|
||||||
|
token.RefreshTokenHash = getTokenHash(token.RefreshToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateToken(id string, token *Token) (bool, error) {
|
func UpdateToken(id string, token *Token) (bool, error) {
|
||||||
owner, name := util.GetOwnerAndNameFromId(id)
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
if t, err := getToken(owner, name); err != nil {
|
if t, err := getToken(owner, name); err != nil {
|
||||||
@@ -167,6 +230,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token.popularHashes()
|
||||||
|
|
||||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(token)
|
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -176,6 +241,8 @@ func UpdateToken(id string, token *Token) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddToken(token *Token) (bool, error) {
|
func AddToken(token *Token) (bool, error) {
|
||||||
|
token.popularHashes()
|
||||||
|
|
||||||
affected, err := ormer.Engine.Insert(token)
|
affected, err := ormer.Engine.Insert(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -194,18 +261,16 @@ func DeleteToken(token *Token) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
|
||||||
token := Token{AccessToken: accessToken}
|
token, err := GetTokenByAccessToken(accessToken)
|
||||||
existed, err := ormer.Engine.Get(&token)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
}
|
}
|
||||||
|
if token == nil {
|
||||||
if !existed {
|
|
||||||
return false, nil, nil, nil
|
return false, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
token.ExpiresIn = 0
|
token.ExpiresIn = 0
|
||||||
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(&token)
|
affected, err := ormer.Engine.ID(core.PK{token.Owner, token.Name}).Cols("expires_in").Update(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -215,22 +280,7 @@ func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, e
|
|||||||
return false, nil, nil, err
|
return false, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0, application, &token, nil
|
return affected != 0, application, token, nil
|
||||||
}
|
|
||||||
|
|
||||||
func GetTokenByAccessToken(accessToken string) (*Token, error) {
|
|
||||||
// Check if the accessToken is in the database
|
|
||||||
token := Token{AccessToken: accessToken}
|
|
||||||
existed, err := ormer.Engine.Get(&token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !existed {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &token, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTokenByTokenAndApplication(token string, application string) (*Token, error) {
|
func GetTokenByTokenAndApplication(token string, application string) (*Token, error) {
|
||||||
@@ -432,16 +482,17 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
ErrorDescription: "client_id is invalid",
|
ErrorDescription: "client_id is invalid",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientSecret != "" && application.ClientSecret != clientSecret {
|
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: InvalidClient,
|
Error: InvalidClient,
|
||||||
ErrorDescription: "client_secret is invalid",
|
ErrorDescription: "client_secret is invalid",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether the refresh token is valid, and has not expired.
|
// check whether the refresh token is valid, and has not expired.
|
||||||
token := Token{RefreshToken: refreshToken}
|
token, err := GetTokenByRefreshToken(refreshToken)
|
||||||
existed, err := ormer.Engine.Get(&token)
|
if err != nil || token == nil {
|
||||||
if err != nil || !existed {
|
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "refresh token is invalid, expired or revoked",
|
ErrorDescription: "refresh token is invalid, expired or revoked",
|
||||||
@@ -452,6 +503,12 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if cert == nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("cert: %s cannot be found", application.Cert),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
_, err = ParseJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -460,6 +517,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new token
|
// generate a new token
|
||||||
user, err := getUser(application.Organization, token.User)
|
user, err := getUser(application.Organization, token.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -477,6 +535,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &TokenError{
|
return &TokenError{
|
||||||
@@ -504,7 +563,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = DeleteToken(&token)
|
_, err = DeleteToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -517,7 +576,6 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
ExpiresIn: newToken.ExpiresIn,
|
ExpiresIn: newToken.ExpiresIn,
|
||||||
Scope: newToken.Scope,
|
Scope: newToken.Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenWrapper, nil
|
return tokenWrapper, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,25 +679,25 @@ func GetPasswordToken(application *Application, username string, password string
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "the user does not exist",
|
ErrorDescription: "the user does not exist",
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
var msg string
|
|
||||||
if user.Ldap != "" {
|
if user.Ldap != "" {
|
||||||
msg = checkLdapUserPassword(user, password, "en")
|
err = checkLdapUserPassword(user, password, "en")
|
||||||
} else {
|
} else {
|
||||||
msg = CheckPassword(user, password, "en")
|
err = CheckPassword(user, password, "en")
|
||||||
}
|
}
|
||||||
if msg != "" {
|
if err != nil {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
ErrorDescription: "invalid username or password",
|
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
return nil, &TokenError{
|
return nil, &TokenError{
|
||||||
Error: InvalidGrant,
|
Error: InvalidGrant,
|
||||||
@@ -729,13 +787,13 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
|
|||||||
|
|
||||||
// GetTokenByUser
|
// GetTokenByUser
|
||||||
// Implicit flow
|
// Implicit flow
|
||||||
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
|
func GetTokenByUser(application *Application, user *User, scope string, nonce string, host string) (*Token, error) {
|
||||||
err := ExtendUserWithRolesAndPermissions(user)
|
err := ExtendUserWithRolesAndPermissions(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", scope, host)
|
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, nonce, scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -195,6 +195,9 @@ func GenerateCasToken(userId string, service string) (string, error) {
|
|||||||
|
|
||||||
user, _ = GetMaskedUser(user, false)
|
user, _ = GetMaskedUser(user, false)
|
||||||
|
|
||||||
|
user.WebauthnCredentials = nil
|
||||||
|
user.Properties = nil
|
||||||
|
|
||||||
authenticationSuccess := CasAuthenticationSuccess{
|
authenticationSuccess := CasAuthenticationSuccess{
|
||||||
User: user.Name,
|
User: user.Name,
|
||||||
Attributes: &CasAttributes{
|
Attributes: &CasAttributes{
|
||||||
|
@@ -34,6 +34,12 @@ type Claims struct {
|
|||||||
type UserShort struct {
|
type UserShort struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
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 {
|
type UserWithoutThirdIdp struct {
|
||||||
@@ -144,6 +150,12 @@ func getShortUser(user *User) *UserShort {
|
|||||||
res := &UserShort{
|
res := &UserShort{
|
||||||
Owner: user.Owner,
|
Owner: user.Owner,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
|
|
||||||
|
Id: user.Id,
|
||||||
|
DisplayName: user.DisplayName,
|
||||||
|
Avatar: user.Avatar,
|
||||||
|
Email: user.Email,
|
||||||
|
Phone: user.Phone,
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@@ -24,14 +24,14 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string) {
|
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string, error) {
|
||||||
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
|
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
|
||||||
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
|
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
|
||||||
|
|
||||||
// Generate RSA key.
|
// Generate RSA key.
|
||||||
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
key, err := rsa.GenerateKey(rand.Reader, bitSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode private key to PKCS#1 ASN.1 PEM.
|
// Encode private key to PKCS#1 ASN.1 PEM.
|
||||||
@@ -54,9 +54,10 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
|
|||||||
},
|
},
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
|
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a pem block with the certificate
|
// Generate a pem block with the certificate
|
||||||
@@ -65,5 +66,5 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
|
|||||||
Bytes: cert,
|
Bytes: cert,
|
||||||
})
|
})
|
||||||
|
|
||||||
return string(certPem), string(privateKeyPem)
|
return string(certPem), string(privateKeyPem), nil
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,10 @@ import (
|
|||||||
|
|
||||||
func TestGenerateRsaKeys(t *testing.T) {
|
func TestGenerateRsaKeys(t *testing.T) {
|
||||||
fileId := "token_jwt_key"
|
fileId := "token_jwt_key"
|
||||||
certificate, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
|
certificate, privateKey, err := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
// Write certificate (aka certificate) to file.
|
// Write certificate (aka certificate) to file.
|
||||||
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
|
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
"github.com/xorm-io/builder"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -231,6 +232,20 @@ func GetGlobalUsers() ([]*User, error) {
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetGlobalUsersWithFilter(cond builder.Cond) ([]*User, error) {
|
||||||
|
users := []*User{}
|
||||||
|
session := ormer.Engine.Desc("created_time")
|
||||||
|
if cond != nil {
|
||||||
|
session = session.Where(cond)
|
||||||
|
}
|
||||||
|
err := session.Find(&users)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
|
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) ([]*User, error) {
|
||||||
users := []*User{}
|
users := []*User{}
|
||||||
session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder)
|
session := GetSessionForUser("", offset, limit, field, value, sortField, sortOrder)
|
||||||
@@ -266,9 +281,27 @@ func GetUsers(owner string) ([]*User, error) {
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUsersByTag(owner string, tag string) ([]*User, error) {
|
func GetUsersWithFilter(owner string, cond builder.Cond) ([]*User, error) {
|
||||||
users := []*User{}
|
users := []*User{}
|
||||||
err := ormer.Engine.Desc("created_time").Find(&users, &User{Owner: owner, Tag: tag})
|
session := ormer.Engine.Desc("created_time")
|
||||||
|
if cond != nil {
|
||||||
|
session = session.Where(cond)
|
||||||
|
}
|
||||||
|
err := session.Find(&users, &User{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUsersByTagWithFilter(owner string, tag string, cond builder.Cond) ([]*User, error) {
|
||||||
|
users := []*User{}
|
||||||
|
session := ormer.Engine.Desc("created_time")
|
||||||
|
if cond != nil {
|
||||||
|
session = session.Where(cond)
|
||||||
|
}
|
||||||
|
err := session.Find(&users, &User{Owner: owner, Tag: tag})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -561,7 +594,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if oldUser == nil {
|
if oldUser == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user: %s is not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if name != user.Name {
|
if name != user.Name {
|
||||||
@@ -642,7 +675,7 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if oldUser == nil {
|
if oldUser == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user: %s is not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if name != user.Name {
|
if name != user.Name {
|
||||||
@@ -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)
|
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -688,12 +723,15 @@ func AddUser(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.Owner == "" || user.Name == "" {
|
if user.Owner == "" || user.Name == "" {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user's owner and name should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
organization, _ := GetOrganizationByUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
if organization == nil {
|
if organization == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the organization: %s is not found", user.Owner)
|
||||||
}
|
}
|
||||||
|
|
||||||
if organization.DefaultPassword != "" && user.Password == "123" {
|
if organization.DefaultPassword != "" && user.Password == "123" {
|
||||||
@@ -704,7 +742,7 @@ func AddUser(user *User) (bool, error) {
|
|||||||
user.UpdateUserPassword(organization)
|
user.UpdateUserPassword(organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := user.UpdateUserHash()
|
err = user.UpdateUserHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -738,9 +776,8 @@ func AddUser(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddUsers(users []*User) (bool, error) {
|
func AddUsers(users []*User) (bool, error) {
|
||||||
var err error
|
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
return false, nil
|
return false, fmt.Errorf("no users are provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
// organization := GetOrganizationByUser(users[0])
|
// organization := GetOrganizationByUser(users[0])
|
||||||
@@ -748,7 +785,7 @@ func AddUsers(users []*User) (bool, error) {
|
|||||||
// this function is only used for syncer or batch upload, so no need to encrypt the password
|
// this function is only used for syncer or batch upload, so no need to encrypt the password
|
||||||
// user.UpdateUserPassword(organization)
|
// user.UpdateUserPassword(organization)
|
||||||
|
|
||||||
err = user.UpdateUserHash()
|
err := user.UpdateUserHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -772,12 +809,12 @@ func AddUsers(users []*User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddUsersInBatch(users []*User) (bool, error) {
|
func AddUsersInBatch(users []*User) (bool, error) {
|
||||||
batchSize := conf.GetConfigBatchSize()
|
|
||||||
|
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
return false, nil
|
return false, fmt.Errorf("no users are provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
batchSize := conf.GetConfigBatchSize()
|
||||||
|
|
||||||
affected := false
|
affected := false
|
||||||
for i := 0; i < len(users); i += batchSize {
|
for i := 0; i < len(users); i += batchSize {
|
||||||
start := i
|
start := i
|
||||||
@@ -849,7 +886,7 @@ func (user *User) GetId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isUserIdGlobalAdmin(userId string) bool {
|
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) {
|
func ExtendUserWithRolesAndPermissions(user *User) (err error) {
|
||||||
@@ -945,9 +982,9 @@ func (user *User) GetPreferredMfaProps(masked bool) *MfaProps {
|
|||||||
return user.GetMfaProps(user.PreferredMfaType, masked)
|
return user.GetMfaProps(user.PreferredMfaType, masked)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddUserkeys(user *User, isAdmin bool) (bool, error) {
|
func AddUserKeys(user *User, isAdmin bool) (bool, error) {
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user is not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.AccessKey = util.GenerateId()
|
user.AccessKey = util.GenerateId()
|
||||||
|
@@ -35,11 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
|
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
|
||||||
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") || strings.Contains(err.Error(), "unrecognized name") {
|
return nil, "", nil
|
||||||
return nil, "", nil
|
|
||||||
} else {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -58,6 +54,8 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
|||||||
|
|
||||||
if strings.Contains(contentType, "text/html") {
|
if strings.Contains(contentType, "text/html") {
|
||||||
fileExtension = ".html"
|
fileExtension = ".html"
|
||||||
|
} else if contentType == "image/vnd.microsoft.icon" {
|
||||||
|
fileExtension = ".ico"
|
||||||
} else {
|
} else {
|
||||||
fileExtensions, err := mime.ExtensionsByType(contentType)
|
fileExtensions, err := mime.ExtensionsByType(contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -186,10 +186,47 @@ func parseSize(sizes string) []int {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var publicEmailDomains = map[string]int{
|
||||||
|
"gmail.com": 1,
|
||||||
|
"163.com": 1,
|
||||||
|
"qq.com": 1,
|
||||||
|
"yahoo.com": 1,
|
||||||
|
"hotmail.com": 1,
|
||||||
|
"outlook.com": 1,
|
||||||
|
"icloud.com": 1,
|
||||||
|
"mail.com": 1,
|
||||||
|
"aol.com": 1,
|
||||||
|
"live.com": 1,
|
||||||
|
"yandex.com": 1,
|
||||||
|
"yahoo.co.jp": 1,
|
||||||
|
"yahoo.co.in": 1,
|
||||||
|
"yahoo.co.uk": 1,
|
||||||
|
"me.com": 1,
|
||||||
|
"msn.com": 1,
|
||||||
|
"comcast.net": 1,
|
||||||
|
"sbcglobal.net": 1,
|
||||||
|
"verizon.net": 1,
|
||||||
|
"earthlink.net": 1,
|
||||||
|
"cox.net": 1,
|
||||||
|
"rediffmail.com": 1,
|
||||||
|
"in.com": 1,
|
||||||
|
"hotmail.co.uk": 1,
|
||||||
|
"hotmail.fr": 1,
|
||||||
|
"zoho.com": 1,
|
||||||
|
"gmx.com": 1,
|
||||||
|
"gmx.de": 1,
|
||||||
|
"gmx.net": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPublicEmailDomain(domain string) bool {
|
||||||
|
_, exists := publicEmailDomains[domain]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
|
func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
|
||||||
tokens := strings.Split(email, "@")
|
tokens := strings.Split(email, "@")
|
||||||
domain := tokens[1]
|
domain := tokens[1]
|
||||||
if domain == "gmail.com" || domain == "163.com" || domain == "qq.com" {
|
if isPublicEmailDomain(domain) {
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,11 +240,11 @@ func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, str
|
|||||||
if buffer != nil {
|
if buffer != nil {
|
||||||
faviconUrl, err = GetFaviconUrl(buffer.String())
|
faviconUrl, err = GetFaviconUrl(buffer.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
fmt.Printf("getFaviconFileBuffer() error, faviconUrl is empty, error = %s\n", err.Error())
|
||||||
}
|
} else {
|
||||||
|
if !strings.HasPrefix(faviconUrl, "http") {
|
||||||
if !strings.HasPrefix(faviconUrl, "http") {
|
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
|
||||||
faviconUrl = util.UrlJoin(htmlUrl, faviconUrl)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,10 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/idp"
|
"github.com/casdoor/casdoor/idp"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -110,6 +113,10 @@ func SetUserField(user *User, field string, value string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user != nil {
|
||||||
|
user.UpdatedTime = util.GetCurrentTime()
|
||||||
|
}
|
||||||
|
|
||||||
_, err = ormer.Engine.ID(core.PK{user.Owner, user.Name}).Cols("hash").Update(user)
|
_, err = ormer.Engine.ID(core.PK{user.Owner, user.Name}).Cols("hash").Update(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
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) {
|
func SetUserOAuthProperties(organization *Organization, user *User, providerType string, userInfo *idp.UserInfo) (bool, error) {
|
||||||
if userInfo.Id != "" {
|
if userInfo.Id != "" {
|
||||||
propertyName := fmt.Sprintf("oauth_%s_id", providerType)
|
propertyName := fmt.Sprintf("oauth_%s_id", providerType)
|
||||||
@@ -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)
|
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 {
|
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
|
||||||
sender := organization.DisplayName
|
sender := organization.DisplayName
|
||||||
title := provider.Title
|
title := provider.Title
|
||||||
|
|
||||||
code := getRandomCode(6)
|
code := getRandomCode(6)
|
||||||
|
if organization.MasterVerificationCode != "" {
|
||||||
|
code = organization.MasterVerificationCode
|
||||||
|
}
|
||||||
|
|
||||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||||
content := fmt.Sprintf(provider.Content, code)
|
content := fmt.Sprintf(provider.Content, code)
|
||||||
|
|
||||||
@@ -107,6 +112,10 @@ func SendVerificationCodeToPhone(organization *Organization, user *User, provide
|
|||||||
}
|
}
|
||||||
|
|
||||||
code := getRandomCode(6)
|
code := getRandomCode(6)
|
||||||
|
if organization.MasterVerificationCode != "" {
|
||||||
|
code = organization.MasterVerificationCode
|
||||||
|
}
|
||||||
|
|
||||||
if err := SendSms(provider, code, dest); err != nil {
|
if err := SendSms(provider, code, dest); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -156,7 +165,7 @@ func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
|||||||
return &record, nil
|
return &record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckVerificationCode(dest, code, lang string) *VerifyResult {
|
func CheckVerificationCode(dest string, code string, lang string) *VerifyResult {
|
||||||
record, err := getVerificationRecord(dest)
|
record, err := getVerificationRecord(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -183,32 +192,32 @@ func CheckVerificationCode(dest, code, lang string) *VerifyResult {
|
|||||||
return &VerifyResult{VerificationSuccess, ""}
|
return &VerifyResult{VerificationSuccess, ""}
|
||||||
}
|
}
|
||||||
|
|
||||||
func DisableVerificationCode(dest string) (err error) {
|
func DisableVerificationCode(dest string) error {
|
||||||
record, err := getVerificationRecord(dest)
|
record, err := getVerificationRecord(dest)
|
||||||
if record == nil || err != nil {
|
if record == nil || err != nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
record.IsUsed = true
|
record.IsUsed = true
|
||||||
_, err = ormer.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record)
|
_, err = ormer.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record)
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckSigninCode(user *User, dest, code, lang string) string {
|
func CheckSigninCode(user *User, dest, code, lang string) error {
|
||||||
// check the login error times
|
// check the login error times
|
||||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
err := checkSigninErrorTimes(user, lang)
|
||||||
return msg
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := CheckVerificationCode(dest, code, lang)
|
result := CheckVerificationCode(dest, code, lang)
|
||||||
switch result.Code {
|
switch result.Code {
|
||||||
case VerificationSuccess:
|
case VerificationSuccess:
|
||||||
resetUserSigninErrorTimes(user)
|
return resetUserSigninErrorTimes(user)
|
||||||
return ""
|
|
||||||
case wrongCodeError:
|
case wrongCodeError:
|
||||||
return recordSigninErrorInfo(user, lang)
|
return recordSigninErrorInfo(user, lang)
|
||||||
default:
|
default:
|
||||||
return result.Msg
|
return fmt.Errorf(result.Msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
pp/alipay.go
20
pp/alipay.go
@@ -49,20 +49,24 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
|
|||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *AlipayPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
// pp.Client.DebugSwitch = gopay.DebugOn
|
// pp.Client.DebugSwitch = gopay.DebugOn
|
||||||
bm := gopay.BodyMap{}
|
bm := gopay.BodyMap{}
|
||||||
pp.Client.SetReturnUrl(returnUrl)
|
pp.Client.SetReturnUrl(r.ReturnUrl)
|
||||||
pp.Client.SetNotifyUrl(notifyUrl)
|
pp.Client.SetNotifyUrl(r.NotifyUrl)
|
||||||
bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName}))
|
bm.Set("subject", joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName}))
|
||||||
bm.Set("out_trade_no", paymentName)
|
bm.Set("out_trade_no", r.PaymentName)
|
||||||
bm.Set("total_amount", priceFloat64ToString(price))
|
bm.Set("total_amount", priceFloat64ToString(r.Price))
|
||||||
|
|
||||||
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return payUrl, paymentName, nil
|
payResp := &PayResp{
|
||||||
|
PayUrl: payUrl,
|
||||||
|
OrderId: r.PaymentName,
|
||||||
|
}
|
||||||
|
return payResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
|
@@ -21,8 +21,10 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
|
|||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *DummyPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
return returnUrl, "", nil
|
return &PayResp{
|
||||||
|
PayUrl: r.ReturnUrl,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
|
36
pp/gc.go
36
pp/gc.go
@@ -153,22 +153,22 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
|||||||
return respBytes, nil
|
return respBytes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *GcPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
payReqInfo := GcPayReqInfo{
|
payReqInfo := GcPayReqInfo{
|
||||||
OrderDate: util.GenerateSimpleTimeId(),
|
OrderDate: util.GenerateSimpleTimeId(),
|
||||||
OrderNo: paymentName,
|
OrderNo: r.PaymentName,
|
||||||
Amount: getPriceString(price),
|
Amount: getPriceString(r.Price),
|
||||||
Xmpch: pp.Xmpch,
|
Xmpch: pp.Xmpch,
|
||||||
Body: productDisplayName,
|
Body: r.ProductDisplayName,
|
||||||
ReturnUrl: returnUrl,
|
ReturnUrl: r.ReturnUrl,
|
||||||
NotifyUrl: notifyUrl,
|
NotifyUrl: r.NotifyUrl,
|
||||||
Remark1: payerName,
|
Remark1: r.PayerName,
|
||||||
Remark2: productName,
|
Remark2: r.ProductName,
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(payReqInfo)
|
b, err := json.Marshal(payReqInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
body := GcRequestBody{
|
body := GcRequestBody{
|
||||||
@@ -184,36 +184,38 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
|
|||||||
|
|
||||||
bodyBytes, err := json.Marshal(body)
|
bodyBytes, err := json.Marshal(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
respBytes, err := pp.doPost(bodyBytes)
|
respBytes, err := pp.doPost(bodyBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var respBody GcResponseBody
|
var respBody GcResponseBody
|
||||||
err = json.Unmarshal(respBytes, &respBody)
|
err = json.Unmarshal(respBytes, &respBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if respBody.ReturnCode != "SUCCESS" {
|
if respBody.ReturnCode != "SUCCESS" {
|
||||||
return "", "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
return nil, fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var payRespInfo GcPayRespInfo
|
var payRespInfo GcPayRespInfo
|
||||||
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
|
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
payResp := &PayResp{
|
||||||
return payRespInfo.PayUrl, "", nil
|
PayUrl: payRespInfo.PayUrl,
|
||||||
|
}
|
||||||
|
return payResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
|
22
pp/paypal.go
22
pp/paypal.go
@@ -49,16 +49,16 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
|
|||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *PaypalPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
// https://github.com/go-pay/gopay/blob/main/doc/paypal.md
|
// https://github.com/go-pay/gopay/blob/main/doc/paypal.md
|
||||||
units := make([]*paypal.PurchaseUnit, 0, 1)
|
units := make([]*paypal.PurchaseUnit, 0, 1)
|
||||||
unit := &paypal.PurchaseUnit{
|
unit := &paypal.PurchaseUnit{
|
||||||
ReferenceId: util.GetRandomString(16),
|
ReferenceId: util.GetRandomString(16),
|
||||||
Amount: &paypal.Amount{
|
Amount: &paypal.Amount{
|
||||||
CurrencyCode: currency, // e.g."USD"
|
CurrencyCode: r.Currency, // e.g."USD"
|
||||||
Value: priceFloat64ToString(price), // e.g."100.00"
|
Value: priceFloat64ToString(r.Price), // e.g."100.00"
|
||||||
},
|
},
|
||||||
Description: joinAttachString([]string{productDisplayName, productName, providerName}),
|
Description: joinAttachString([]string{r.ProductDisplayName, r.ProductName, r.ProviderName}),
|
||||||
}
|
}
|
||||||
units = append(units, unit)
|
units = append(units, unit)
|
||||||
|
|
||||||
@@ -68,23 +68,27 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
|
|||||||
bm.SetBodyMap("application_context", func(b gopay.BodyMap) {
|
bm.SetBodyMap("application_context", func(b gopay.BodyMap) {
|
||||||
b.Set("brand_name", "Casdoor")
|
b.Set("brand_name", "Casdoor")
|
||||||
b.Set("locale", "en-PT")
|
b.Set("locale", "en-PT")
|
||||||
b.Set("return_url", returnUrl)
|
b.Set("return_url", r.ReturnUrl)
|
||||||
b.Set("cancel_url", returnUrl)
|
b.Set("cancel_url", r.ReturnUrl)
|
||||||
})
|
})
|
||||||
|
|
||||||
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
|
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
if ppRsp.Code != paypal.Success {
|
if ppRsp.Code != paypal.Success {
|
||||||
return "", "", errors.New(ppRsp.Error)
|
return nil, errors.New(ppRsp.Error)
|
||||||
}
|
}
|
||||||
// {"id":"9BR68863NE220374S","status":"CREATED",
|
// {"id":"9BR68863NE220374S","status":"CREATED",
|
||||||
// "links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"self","method":"GET"},
|
// "links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"self","method":"GET"},
|
||||||
// {"href":"https://www.sandbox.paypal.com/checkoutnow?token=9BR68863NE220374S","rel":"approve","method":"GET"},
|
// {"href":"https://www.sandbox.paypal.com/checkoutnow?token=9BR68863NE220374S","rel":"approve","method":"GET"},
|
||||||
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"update","method":"PATCH"},
|
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"update","method":"PATCH"},
|
||||||
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S/capture","rel":"capture","method":"POST"}]}
|
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S/capture","rel":"capture","method":"POST"}]}
|
||||||
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
|
payResp := &PayResp{
|
||||||
|
PayUrl: ppRsp.Response.Links[1].Href,
|
||||||
|
OrderId: ppRsp.Response.Id,
|
||||||
|
}
|
||||||
|
return payResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
|
@@ -24,6 +24,32 @@ const (
|
|||||||
PaymentStateError PaymentState = "Error"
|
PaymentStateError PaymentState = "Error"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PaymentEnvWechatBrowser = "WechatBrowser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PayReq struct {
|
||||||
|
ProviderName string
|
||||||
|
ProductName string
|
||||||
|
PayerName string
|
||||||
|
PayerId string
|
||||||
|
PaymentName string
|
||||||
|
ProductDisplayName string
|
||||||
|
Price float64
|
||||||
|
Currency string
|
||||||
|
|
||||||
|
ReturnUrl string
|
||||||
|
NotifyUrl string
|
||||||
|
|
||||||
|
PaymentEnv string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PayResp struct {
|
||||||
|
PayUrl string
|
||||||
|
OrderId string
|
||||||
|
AttachInfo map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
type NotifyResult struct {
|
type NotifyResult struct {
|
||||||
PaymentName string
|
PaymentName string
|
||||||
PaymentStatus PaymentState
|
PaymentStatus PaymentState
|
||||||
@@ -39,7 +65,7 @@ type NotifyResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PaymentProvider interface {
|
type PaymentProvider interface {
|
||||||
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
|
Pay(req *PayReq) (*PayResp, error)
|
||||||
Notify(body []byte, orderId string) (*NotifyResult, error)
|
Notify(body []byte, orderId string) (*NotifyResult, error)
|
||||||
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
|
||||||
GetResponseError(err error) string
|
GetResponseError(err error) string
|
||||||
|
35
pp/stripe.go
35
pp/stripe.go
@@ -46,30 +46,30 @@ func NewStripePaymentProvider(PublishableKey, SecretKey string) (*StripePaymentP
|
|||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *StripePaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (payUrl string, orderId string, err error) {
|
func (pp *StripePaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
// Create a temp product
|
// Create a temp product
|
||||||
description := joinAttachString([]string{productName, productDisplayName, providerName})
|
description := joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName})
|
||||||
productParams := &stripe.ProductParams{
|
productParams := &stripe.ProductParams{
|
||||||
Name: stripe.String(productDisplayName),
|
Name: stripe.String(r.ProductDisplayName),
|
||||||
Description: stripe.String(description),
|
Description: stripe.String(description),
|
||||||
DefaultPriceData: &stripe.ProductDefaultPriceDataParams{
|
DefaultPriceData: &stripe.ProductDefaultPriceDataParams{
|
||||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
|
UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
|
||||||
Currency: stripe.String(currency),
|
Currency: stripe.String(r.Currency),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
sProduct, err := stripeProduct.New(productParams)
|
sProduct, err := stripeProduct.New(productParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create a price for an existing product
|
// Create a price for an existing product
|
||||||
priceParams := &stripe.PriceParams{
|
priceParams := &stripe.PriceParams{
|
||||||
Currency: stripe.String(currency),
|
Currency: stripe.String(r.Currency),
|
||||||
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
|
UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
|
||||||
Product: stripe.String(sProduct.ID),
|
Product: stripe.String(sProduct.ID),
|
||||||
}
|
}
|
||||||
sPrice, err := stripePrice.New(priceParams)
|
sPrice, err := stripePrice.New(priceParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create a Checkout Session
|
// Create a Checkout Session
|
||||||
checkoutParams := &stripe.CheckoutSessionParams{
|
checkoutParams := &stripe.CheckoutSessionParams{
|
||||||
@@ -80,17 +80,21 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
|
||||||
SuccessURL: stripe.String(returnUrl),
|
SuccessURL: stripe.String(r.ReturnUrl),
|
||||||
CancelURL: stripe.String(returnUrl),
|
CancelURL: stripe.String(r.ReturnUrl),
|
||||||
ClientReferenceID: stripe.String(paymentName),
|
ClientReferenceID: stripe.String(r.PaymentName),
|
||||||
ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()),
|
ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()),
|
||||||
}
|
}
|
||||||
checkoutParams.AddMetadata("product_description", description)
|
checkoutParams.AddMetadata("product_description", description)
|
||||||
sCheckout, err := stripeCheckout.New(checkoutParams)
|
sCheckout, err := stripeCheckout.New(checkoutParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return sCheckout.URL, sCheckout.ID, nil
|
payResp := &PayResp{
|
||||||
|
PayUrl: sCheckout.URL,
|
||||||
|
OrderId: sCheckout.ID,
|
||||||
|
}
|
||||||
|
return payResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
@@ -128,6 +132,9 @@ func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyRes
|
|||||||
}
|
}
|
||||||
// Once payment is successful, the Checkout Session will contain a reference to the successful `PaymentIntent`
|
// Once payment is successful, the Checkout Session will contain a reference to the successful `PaymentIntent`
|
||||||
sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil)
|
sIntent, err := stripeIntent.Get(sCheckout.PaymentIntent.ID, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
productName string
|
productName string
|
||||||
productDisplayName string
|
productDisplayName string
|
||||||
|
@@ -63,27 +63,66 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, seria
|
|||||||
return pp, nil
|
return pp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
|
func (pp *WechatPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
bm := gopay.BodyMap{}
|
bm := gopay.BodyMap{}
|
||||||
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
|
desc := joinAttachString([]string{r.ProductDisplayName, r.ProductName, r.ProviderName})
|
||||||
|
bm.Set("attach", desc)
|
||||||
bm.Set("appid", pp.AppId)
|
bm.Set("appid", pp.AppId)
|
||||||
bm.Set("description", productDisplayName)
|
bm.Set("description", r.ProductDisplayName)
|
||||||
bm.Set("notify_url", notifyUrl)
|
bm.Set("notify_url", r.NotifyUrl)
|
||||||
bm.Set("out_trade_no", paymentName)
|
bm.Set("out_trade_no", r.PaymentName)
|
||||||
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
|
||||||
bm.Set("total", priceFloat64ToInt64(price))
|
bm.Set("total", priceFloat64ToInt64(r.Price))
|
||||||
bm.Set("currency", currency)
|
bm.Set("currency", r.Currency)
|
||||||
})
|
})
|
||||||
|
// In Wechat browser, we use JSAPI
|
||||||
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
if r.PaymentEnv == PaymentEnvWechatBrowser {
|
||||||
if err != nil {
|
if r.PayerId == "" {
|
||||||
return "", "", err
|
return nil, errors.New("failed to get the payer's openid, please retry login")
|
||||||
|
}
|
||||||
|
bm.SetBodyMap("payer", func(bm gopay.BodyMap) {
|
||||||
|
bm.Set("openid", r.PayerId) // If the account is signup via Wechat, the PayerId is the Wechat OpenId e.g.oxW9O1ZDvgreSHuBSQDiQ2F055PI
|
||||||
|
})
|
||||||
|
jsapiRsp, err := pp.Client.V3TransactionJsapi(context.Background(), bm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if jsapiRsp.Code != wechat.Success {
|
||||||
|
return nil, errors.New(jsapiRsp.Error)
|
||||||
|
}
|
||||||
|
// use RSA256 to sign the pay request
|
||||||
|
params, err := pp.Client.PaySignOfJSAPI(pp.AppId, jsapiRsp.Response.PrepayId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
payResp := &PayResp{
|
||||||
|
PayUrl: "",
|
||||||
|
OrderId: r.PaymentName, // Wechat can use paymentName as the OutTradeNo to query order status
|
||||||
|
AttachInfo: map[string]interface{}{
|
||||||
|
"appId": params.AppId,
|
||||||
|
"timeStamp": params.TimeStamp,
|
||||||
|
"nonceStr": params.NonceStr,
|
||||||
|
"package": params.Package,
|
||||||
|
"signType": "RSA",
|
||||||
|
"paySign": params.PaySign,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return payResp, nil
|
||||||
|
} else {
|
||||||
|
// In other case, we use NativeAPI
|
||||||
|
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if nativeRsp.Code != wechat.Success {
|
||||||
|
return nil, errors.New(nativeRsp.Error)
|
||||||
|
}
|
||||||
|
payResp := &PayResp{
|
||||||
|
PayUrl: nativeRsp.Response.CodeUrl,
|
||||||
|
OrderId: r.PaymentName, // Wechat can use paymentName as the OutTradeNo to query order status
|
||||||
|
}
|
||||||
|
return payResp, nil
|
||||||
}
|
}
|
||||||
if nativeRsp.Code != wechat.Success {
|
|
||||||
return "", "", errors.New(nativeRsp.Error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
|
@@ -17,15 +17,16 @@ package radius
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
"layeh.com/radius"
|
"layeh.com/radius"
|
||||||
"layeh.com/radius/rfc2865"
|
"layeh.com/radius/rfc2865"
|
||||||
"layeh.com/radius/rfc2866"
|
"layeh.com/radius/rfc2866"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3
|
|
||||||
func StartRadiusServer() {
|
func StartRadiusServer() {
|
||||||
secret := conf.GetConfigString("radiusSecret")
|
secret := conf.GetConfigString("radiusSecret")
|
||||||
server := radius.PacketServer{
|
server := radius.PacketServer{
|
||||||
@@ -55,15 +56,18 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
|||||||
password := rfc2865.UserPassword_GetString(r.Packet)
|
password := rfc2865.UserPassword_GetString(r.Packet)
|
||||||
organization := rfc2865.Class_GetString(r.Packet)
|
organization := rfc2865.Class_GetString(r.Packet)
|
||||||
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
||||||
|
|
||||||
if organization == "" {
|
if organization == "" {
|
||||||
w.Write(r.Response(radius.CodeAccessReject))
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, msg := object.CheckUserPassword(organization, username, password, "en")
|
|
||||||
if msg != "" {
|
_, err := object.CheckUserPassword(organization, username, password, "en")
|
||||||
|
if err != nil {
|
||||||
w.Write(r.Response(radius.CodeAccessReject))
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write(r.Response(radius.CodeAccessAccept))
|
w.Write(r.Response(radius.CodeAccessAccept))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +75,11 @@ func handleAccountingRequest(w radius.ResponseWriter, r *radius.Request) {
|
|||||||
statusType := rfc2866.AcctStatusType_Get(r.Packet)
|
statusType := rfc2866.AcctStatusType_Get(r.Packet)
|
||||||
username := rfc2865.UserName_GetString(r.Packet)
|
username := rfc2865.UserName_GetString(r.Packet)
|
||||||
organization := rfc2865.Class_GetString(r.Packet)
|
organization := rfc2865.Class_GetString(r.Packet)
|
||||||
|
|
||||||
|
if strings.Contains(username, "/") {
|
||||||
|
organization, username = util.GetOwnerAndNameFromId(username)
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("handleAccountingRequest() username=%v, org=%v, statusType=%v", username, organization, statusType)
|
log.Printf("handleAccountingRequest() username=%v, org=%v, statusType=%v", username, organization, statusType)
|
||||||
w.Write(r.Response(radius.CodeAccountingResponse))
|
w.Write(r.Response(radius.CodeAccountingResponse))
|
||||||
var err error
|
var err error
|
||||||
|
@@ -83,13 +83,12 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
password := ctx.Input.Query("password")
|
password := ctx.Input.Query("password")
|
||||||
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
|
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
|
||||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||||
_, msg := object.CheckUserPassword(owner, name, password, "en")
|
_, err = object.CheckUserPassword(owner, name, password, "en")
|
||||||
if msg != "" {
|
if err != nil {
|
||||||
responseError(ctx, msg)
|
responseError(ctx, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setSessionUser(ctx, userId)
|
setSessionUser(ctx, userId)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,6 +51,11 @@ func CorsFilter(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if originHostname == "appleid.apple.com" {
|
||||||
|
setCorsHeaders(ctx, origin)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Request.Method == "POST" && ctx.Request.RequestURI == "/api/login/oauth/access_token" {
|
if ctx.Request.Method == "POST" && ctx.Request.RequestURI == "/api/login/oauth/access_token" {
|
||||||
setCorsHeaders(ctx, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
return
|
return
|
||||||
|
@@ -79,7 +79,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-user-count", &controllers.ApiController{}, "GET:GetUserCount")
|
beego.Router("/api/get-user-count", &controllers.ApiController{}, "GET:GetUserCount")
|
||||||
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
|
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
|
||||||
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
||||||
beego.Router("/api/add-user-keys", &controllers.ApiController{}, "POST:AddUserkeys")
|
beego.Router("/api/add-user-keys", &controllers.ApiController{}, "POST:AddUserKeys")
|
||||||
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
||||||
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
||||||
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
||||||
@@ -204,7 +204,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
|
||||||
|
|
||||||
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
|
||||||
beego.Router("/api/get-globle-certs", &controllers.ApiController{}, "GET:GetGlobleCerts")
|
beego.Router("/api/get-global-certs", &controllers.ApiController{}, "GET:GetGlobalCerts")
|
||||||
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
|
||||||
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
|
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
|
||||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||||
|
@@ -83,7 +83,11 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
|
|||||||
return "", fmt.Errorf(code.Message)
|
return "", fmt.Errorf(code.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
res := fmt.Sprintf("%s?code=%s&state=%s", redirectUri, code.Code, state)
|
sep := "?"
|
||||||
|
if strings.Contains(redirectUri, "?") {
|
||||||
|
sep = "&"
|
||||||
|
}
|
||||||
|
res := fmt.Sprintf("%s%scode=%s&state=%s", redirectUri, sep, code.Code, state)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +129,7 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
path += urlPath
|
path += urlPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if !util.FileExist(path) {
|
if strings.Contains(path, "/../") || !util.FileExist(path) {
|
||||||
path = webBuildFolder + "/index.html"
|
path = webBuildFolder + "/index.html"
|
||||||
}
|
}
|
||||||
if !util.FileExist(path) {
|
if !util.FileExist(path) {
|
||||||
|
@@ -19,13 +19,15 @@ import (
|
|||||||
"github.com/casdoor/oss/googlecloud"
|
"github.com/casdoor/oss/googlecloud"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewGoogleCloudStorageProvider(clientId string, clientSecret string, bucket string, endpoint string) oss.StorageInterface {
|
func NewGoogleCloudStorageProvider(clientSecret string, bucket string, endpoint string) oss.StorageInterface {
|
||||||
sp, _ := googlecloud.New(&googlecloud.Config{
|
sp, err := googlecloud.New(&googlecloud.Config{
|
||||||
AccessID: clientId,
|
ServiceAccountJson: clientSecret,
|
||||||
AccessKey: clientSecret,
|
Bucket: bucket,
|
||||||
Bucket: bucket,
|
Endpoint: endpoint,
|
||||||
Endpoint: endpoint,
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
return sp
|
return sp
|
||||||
}
|
}
|
||||||
|
@@ -33,7 +33,7 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
|
|||||||
case "Qiniu Cloud Kodo":
|
case "Qiniu Cloud Kodo":
|
||||||
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
|
||||||
case "Google Cloud Storage":
|
case "Google Cloud Storage":
|
||||||
return NewGoogleCloudStorageProvider(clientId, clientSecret, bucket, endpoint)
|
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -2023,13 +2023,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/get-globle-certs": {
|
"/api/get-global-certs": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Cert API"
|
"Cert API"
|
||||||
],
|
],
|
||||||
"description": "get globle certs",
|
"description": "get globle certs",
|
||||||
"operationId": "ApiController.GetGlobleCerts",
|
"operationId": "ApiController.GetGlobalCerts",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "The Response object",
|
"description": "The Response object",
|
||||||
|
@@ -1311,12 +1311,12 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/definitions/object.User'
|
$ref: '#/definitions/object.User'
|
||||||
/api/get-globle-certs:
|
/api/get-global-certs:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- Cert API
|
- Cert API
|
||||||
description: get globle certs
|
description: get globle certs
|
||||||
operationId: ApiController.GetGlobleCerts
|
operationId: ApiController.GetGlobalCerts
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: The Response object
|
description: The Response object
|
||||||
|
116
sync_v2/cmd_test.go
Normal file
116
sync_v2/cmd_test.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
The following config should be added to my.cnf:
|
||||||
|
|
||||||
|
gtid_mode=on
|
||||||
|
enforce_gtid_consistency=on
|
||||||
|
binlog-format=ROW
|
||||||
|
server-id = 1 # this should be different for each mysql instance (1,2)
|
||||||
|
auto_increment_offset = 1 # this is same as server-id
|
||||||
|
auto_increment_increment = 2 # this is same as the number of mysql instances (2)
|
||||||
|
log-bin = mysql-bin
|
||||||
|
replicate-do-db = casdoor # this is the database name
|
||||||
|
binlog-do-db = casdoor # this is the database name
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Configs = []Database{
|
||||||
|
{
|
||||||
|
host: "test-db.v2tl.com",
|
||||||
|
port: 3306,
|
||||||
|
username: "root",
|
||||||
|
password: "password",
|
||||||
|
database: "casdoor",
|
||||||
|
// the following two fields are used to create replication user, you don't need to change them
|
||||||
|
slaveUser: "repl_user",
|
||||||
|
slavePassword: "repl_user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host: "localhost",
|
||||||
|
port: 3306,
|
||||||
|
username: "root",
|
||||||
|
password: "password",
|
||||||
|
database: "casdoor",
|
||||||
|
// the following two fields are used to create replication user, you don't need to change them
|
||||||
|
slaveUser: "repl_user",
|
||||||
|
slavePassword: "repl_user",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartMasterSlaveSync(t *testing.T) {
|
||||||
|
// for example, this is aliyun rds
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
// for example, this is local mysql instance
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
|
||||||
|
createSlaveUser(db0)
|
||||||
|
// db0 is master, db1 is slave
|
||||||
|
startSlave(db0, db1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopMasterSlaveSync(t *testing.T) {
|
||||||
|
// for example, this is aliyun rds
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
// for example, this is local mysql instance
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
|
||||||
|
stopSlave(db1)
|
||||||
|
deleteSlaveUser(db0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartMasterMasterSync(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
createSlaveUser(db0)
|
||||||
|
createSlaveUser(db1)
|
||||||
|
// db0 is master, db1 is slave
|
||||||
|
startSlave(db0, db1)
|
||||||
|
// db1 is master, db0 is slave
|
||||||
|
startSlave(db1, db0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopMasterMasterSync(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
stopSlave(db0)
|
||||||
|
stopSlave(db1)
|
||||||
|
deleteSlaveUser(db0)
|
||||||
|
deleteSlaveUser(db1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShowSlaveStatus(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
slaveStatus(db0)
|
||||||
|
slaveStatus(db1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShowMasterStatus(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
masterStatus(db0)
|
||||||
|
masterStatus(db1)
|
||||||
|
}
|
70
sync_v2/db.go
Normal file
70
sync_v2/db.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// 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 sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/xorm-io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
database string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
slaveUser string
|
||||||
|
slavePassword string
|
||||||
|
engine *xorm.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) exec(format string, args ...interface{}) []map[string]string {
|
||||||
|
sql := fmt.Sprintf(format, args...)
|
||||||
|
res, err := db.engine.QueryString(sql)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEngine(dataSourceName string) (*xorm.Engine, error) {
|
||||||
|
engine, err := xorm.NewEngine("mysql", dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping mysql
|
||||||
|
err = engine.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.ShowSQL(true)
|
||||||
|
log.Println("mysql connection success")
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDatabase(db *Database) *Database {
|
||||||
|
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", db.username, db.password, db.host, db.port, db.database)
|
||||||
|
engine, err := createEngine(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.engine = engine
|
||||||
|
return db
|
||||||
|
}
|
89
sync_v2/master.go
Normal file
89
sync_v2/master.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
// 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 sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deleteSlaveUser(masterdb *Database) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
masterdb.exec("delete from mysql.user where user = '%v'", masterdb.slaveUser)
|
||||||
|
masterdb.exec("flush privileges")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSlaveUser(masterdb *Database) {
|
||||||
|
res := make([]map[string]string, 0)
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
res = masterdb.exec("show databases")
|
||||||
|
dbNames := make([]string, 0, len(res))
|
||||||
|
for _, dbInfo := range res {
|
||||||
|
dbName := dbInfo["Database"]
|
||||||
|
dbNames = append(dbNames, dbName)
|
||||||
|
}
|
||||||
|
log.Println("dbs in mysql: ", dbNames)
|
||||||
|
res = masterdb.exec("show tables")
|
||||||
|
tableNames := make([]string, 0, len(res))
|
||||||
|
for _, table := range res {
|
||||||
|
tableName := table[fmt.Sprintf("Tables_in_%v", masterdb.database)]
|
||||||
|
tableNames = append(tableNames, tableName)
|
||||||
|
}
|
||||||
|
log.Printf("tables in %v: %v", masterdb.database, tableNames)
|
||||||
|
|
||||||
|
// delete user to prevent user already exists
|
||||||
|
res = masterdb.exec("delete from mysql.user where user = '%v'", masterdb.slaveUser)
|
||||||
|
res = masterdb.exec("flush privileges")
|
||||||
|
|
||||||
|
// create replication user
|
||||||
|
res = masterdb.exec("create user '%s'@'%s' identified by '%s'", masterdb.slaveUser, "%", masterdb.slavePassword)
|
||||||
|
res = masterdb.exec("select host, user from mysql.user where user = '%v'", masterdb.slaveUser)
|
||||||
|
log.Println("user: ", res[0])
|
||||||
|
res = masterdb.exec("grant replication slave on *.* to '%s'@'%s'", masterdb.slaveUser, "%")
|
||||||
|
res = masterdb.exec("flush privileges")
|
||||||
|
res = masterdb.exec("show grants for '%s'@'%s'", masterdb.slaveUser, "%")
|
||||||
|
log.Println("grants: ", res[0])
|
||||||
|
|
||||||
|
// check env
|
||||||
|
res = masterdb.exec("show variables like 'server_id'")
|
||||||
|
log.Println("server_id: ", res[0]["Value"])
|
||||||
|
res = masterdb.exec("show variables like 'log_bin'")
|
||||||
|
log.Println("log_bin: ", res[0]["Value"])
|
||||||
|
res = masterdb.exec("show variables like 'binlog_format'")
|
||||||
|
log.Println("binlog_format: ", res[0]["Value"])
|
||||||
|
res = masterdb.exec("show variables like 'binlog_row_image'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func masterStatus(masterdb *Database) {
|
||||||
|
res := masterdb.exec("show master status")
|
||||||
|
if len(res) == 0 {
|
||||||
|
log.Printf("no master status for master [%v:%v]\n", masterdb.host, masterdb.port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pos := res[0]["Position"]
|
||||||
|
file := res[0]["File"]
|
||||||
|
log.Println("*****check master status*****")
|
||||||
|
log.Println("master:", masterdb.host, ":", masterdb.port)
|
||||||
|
log.Println("file:", file, ", position:", pos, ", master status:", res)
|
||||||
|
log.Println("*****************************")
|
||||||
|
}
|
84
sync_v2/slave.go
Normal file
84
sync_v2/slave.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// 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 sync_v2
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
// slaveStatus shows slave status
|
||||||
|
func slaveStatus(slavedb *Database) {
|
||||||
|
res := slavedb.exec("show slave status")
|
||||||
|
if len(res) == 0 {
|
||||||
|
log.Printf("no slave status for slave [%v:%v]\n", slavedb.host, slavedb.port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("*****check slave status*****")
|
||||||
|
log.Println("slave:", slavedb.host, ":", slavedb.port)
|
||||||
|
masterServerId := res[0]["Master_Server_Id"]
|
||||||
|
log.Println("master server id:", masterServerId)
|
||||||
|
lastError := res[0]["Last_Error"]
|
||||||
|
log.Println("last error:", lastError) // this should be empty
|
||||||
|
lastIoError := res[0]["Last_IO_Error"]
|
||||||
|
log.Println("last io error:", lastIoError) // this should be empty
|
||||||
|
slaveIoState := res[0]["Slave_IO_State"]
|
||||||
|
log.Println("slave io state:", slaveIoState)
|
||||||
|
slaveIoRunning := res[0]["Slave_IO_Running"]
|
||||||
|
log.Println("slave io running:", slaveIoRunning) // this should be Yes
|
||||||
|
slaveSqlRunning := res[0]["Slave_SQL_Running"]
|
||||||
|
log.Println("slave sql running:", slaveSqlRunning) // this should be Yes
|
||||||
|
slaveSqlRunningState := res[0]["Slave_SQL_Running_State"]
|
||||||
|
log.Println("slave sql running state:", slaveSqlRunningState)
|
||||||
|
slaveSecondsBehindMaster := res[0]["Seconds_Behind_Master"]
|
||||||
|
log.Println("seconds behind master:", slaveSecondsBehindMaster) // this should be 0, if not, it means the slave is behind the master
|
||||||
|
log.Println("slave status:", res)
|
||||||
|
log.Println("****************************")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopSlave stops slave
|
||||||
|
func stopSlave(slavedb *Database) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
slavedb.exec("stop slave")
|
||||||
|
slaveStatus(slavedb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// startSlave starts slave
|
||||||
|
func startSlave(masterdb *Database, slavedb *Database) {
|
||||||
|
res := make([]map[string]string, 0)
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
stopSlave(slavedb)
|
||||||
|
// get the info about master
|
||||||
|
res = masterdb.exec("show master status")
|
||||||
|
if len(res) == 0 {
|
||||||
|
log.Println("no master status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pos := res[0]["Position"]
|
||||||
|
file := res[0]["File"]
|
||||||
|
log.Println("file:", file, ", position:", pos, ", master status:", res)
|
||||||
|
res = slavedb.exec("stop slave")
|
||||||
|
res = slavedb.exec(
|
||||||
|
"change master to master_host='%v', master_port=%v, master_user='%v', master_password='%v', master_log_file='%v', master_log_pos=%v;",
|
||||||
|
masterdb.host, masterdb.port, masterdb.slaveUser, masterdb.slavePassword, file, pos,
|
||||||
|
)
|
||||||
|
res = slavedb.exec("start slave")
|
||||||
|
slaveStatus(slavedb)
|
||||||
|
}
|
66
sync_v2/table_test.go
Normal file
66
sync_v2/table_test.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestUser struct {
|
||||||
|
Id int64 `xorm:"pk autoincr"`
|
||||||
|
Username string `xorm:"varchar(50)"`
|
||||||
|
Address string `xorm:"varchar(50)"`
|
||||||
|
Card string `xorm:"varchar(50)"`
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateUserTable(t *testing.T) {
|
||||||
|
db := newDatabase(&Configs[0])
|
||||||
|
err := db.engine.Sync2(new(TestUser))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertUser(t *testing.T) {
|
||||||
|
db := newDatabase(&Configs[0])
|
||||||
|
// random generate user
|
||||||
|
user := &TestUser{
|
||||||
|
Username: util.GetRandomName(),
|
||||||
|
Age: rand.Intn(100) + 10,
|
||||||
|
}
|
||||||
|
_, err := db.engine.Insert(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteUser(t *testing.T) {
|
||||||
|
db := newDatabase(&Configs[0])
|
||||||
|
user := &TestUser{
|
||||||
|
Id: 10,
|
||||||
|
}
|
||||||
|
_, err := db.engine.Delete(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
@@ -171,10 +171,27 @@ class CertEditPage extends React.Component {
|
|||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
|
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
|
||||||
this.updateCertField("cryptoAlgorithm", value);
|
this.updateCertField("cryptoAlgorithm", value);
|
||||||
|
if (value === "RS256") {
|
||||||
|
this.updateCertField("bitSize", 2048);
|
||||||
|
} else if (value === "HS256" || value === "ES256") {
|
||||||
|
this.updateCertField("bitSize", 256);
|
||||||
|
} else if (value === "ES384") {
|
||||||
|
this.updateCertField("bitSize", 384);
|
||||||
|
} else if (value === "ES521") {
|
||||||
|
this.updateCertField("bitSize", 521);
|
||||||
|
} else {
|
||||||
|
this.updateCertField("bitSize", 0);
|
||||||
|
}
|
||||||
|
this.updateCertField("certificate", "");
|
||||||
|
this.updateCertField("privateKey", "");
|
||||||
})}>
|
})}>
|
||||||
{
|
{
|
||||||
[
|
[
|
||||||
{id: "RS256", name: "RS256"},
|
{id: "RS256", name: "RS256 (RSA + SHA256)"},
|
||||||
|
{id: "HS256", name: "HS256 (HMAC + SHA256)"},
|
||||||
|
{id: "ES256", name: "ES256 (ECDSA using P-256 + SHA256)"},
|
||||||
|
{id: "ES384", name: "ES384 (ECDSA using P-384 + SHA256)"},
|
||||||
|
{id: "ES521", name: "ES521 (ECDSA using P-521 + SHA256)"},
|
||||||
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
}
|
}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -185,9 +202,15 @@ class CertEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
|
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<InputNumber value={this.state.cert.bitSize} onChange={value => {
|
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.bitSize} onChange={(value => {
|
||||||
this.updateCertField("bitSize", value);
|
this.updateCertField("bitSize", value);
|
||||||
}} />
|
this.updateCertField("certificate", "");
|
||||||
|
this.updateCertField("privateKey", "");
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
Setting.getCryptoAlgorithmOptions(this.state.cert.cryptoAlgorithm).map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
@@ -205,14 +228,14 @@ class CertEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
|
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={editorWidth} >
|
<Col span={editorWidth} >
|
||||||
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
|
<Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.certificate === ""} onClick={() => {
|
||||||
copy(this.state.cert.certificate);
|
copy(this.state.cert.certificate);
|
||||||
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
|
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("cert:Copy certificate")}
|
{i18next.t("cert:Copy certificate")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" onClick={() => {
|
<Button type="primary" disabled={this.state.cert.certificate === ""} onClick={() => {
|
||||||
const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
|
const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
|
||||||
FileSaver.saveAs(blob, "token_jwt_key.pem");
|
FileSaver.saveAs(blob, "token_jwt_key.pem");
|
||||||
}}
|
}}
|
||||||
@@ -228,14 +251,14 @@ class CertEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
|
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={editorWidth} >
|
<Col span={editorWidth} >
|
||||||
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
|
<Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.privateKey === ""} onClick={() => {
|
||||||
copy(this.state.cert.privateKey);
|
copy(this.state.cert.privateKey);
|
||||||
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
|
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{i18next.t("cert:Copy private key")}
|
{i18next.t("cert:Copy private key")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" onClick={() => {
|
<Button type="primary" disabled={this.state.cert.privateKey === ""} onClick={() => {
|
||||||
const blob = new Blob([this.state.cert.privateKey], {type: "text/plain;charset=utf-8"});
|
const blob = new Blob([this.state.cert.privateKey], {type: "text/plain;charset=utf-8"});
|
||||||
FileSaver.saveAs(blob, "token_jwt_key.key");
|
FileSaver.saveAs(blob, "token_jwt_key.key");
|
||||||
}}
|
}}
|
||||||
@@ -265,6 +288,7 @@ class CertEditPage extends React.Component {
|
|||||||
this.props.history.push("/certs");
|
this.props.history.push("/certs");
|
||||||
} else {
|
} else {
|
||||||
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
||||||
|
this.getCert();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
@@ -239,7 +239,7 @@ class CertListPage extends BaseListPage {
|
|||||||
value = params.type;
|
value = params.type;
|
||||||
}
|
}
|
||||||
this.setState({loading: true});
|
this.setState({loading: true});
|
||||||
(Setting.isDefaultOrganizationSelected(this.props.account) ? CertBackend.getGlobleCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
(Setting.isDefaultOrganizationSelected(this.props.account) ? CertBackend.getGlobalCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
|
||||||
: CertBackend.getCerts(Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
: CertBackend.getCerts(Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
@@ -323,6 +323,16 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Master verification code"), i18next.t("general:Master verification code - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.organization.masterVerificationCode} onChange={e => {
|
||||||
|
this.updateOrganizationField("masterVerificationCode", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
|
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
|
||||||
|
@@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
|
|||||||
payment: payment,
|
payment: payment,
|
||||||
});
|
});
|
||||||
if (payment.state === "Created") {
|
if (payment.state === "Created") {
|
||||||
if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) {
|
if (["PayPal", "Stripe", "Alipay", "WeChat Pay"].includes(payment.type)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
timeout: setTimeout(async() => {
|
timeout: setTimeout(async() => {
|
||||||
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||||
|
@@ -303,7 +303,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select disabled={!this.hasRoleDefinition(this.state.model)} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
|
<Select disabled={!this.hasRoleDefinition(this.state.model)} placeholder={this.hasRoleDefinition(this.state.model) ? "" : "This field is disabled because the model is empty or it doesn't support RBAC (in another word, doesn't contain [role_definition])"} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
|
||||||
onChange={(value => {this.updatePermissionField("roles", value);})}
|
onChange={(value => {this.updatePermissionField("roles", value);})}
|
||||||
options={[
|
options={[
|
||||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||||
@@ -323,7 +323,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
})}
|
})}
|
||||||
options={[
|
options={[
|
||||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||||
...this.state.permission.domains.map((domain) => Setting.getOption(domain, domain)),
|
...this.state.permission.domains.filter(domain => domain !== "*").map((domain) => Setting.getOption(domain, domain)),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
@@ -44,7 +44,7 @@ class PermissionListPage extends BaseListPage {
|
|||||||
submitter: this.props.account.name,
|
submitter: this.props.account.name,
|
||||||
approver: "",
|
approver: "",
|
||||||
approveTime: "",
|
approveTime: "",
|
||||||
state: "Pending",
|
state: Setting.isLocalAdminUser(this.props.account) ? "Approved" : "Pending",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -31,6 +31,7 @@ class ProductBuyPage extends React.Component {
|
|||||||
pricingName: props?.pricingName ?? props?.match?.params?.pricingName ?? null,
|
pricingName: props?.pricingName ?? props?.match?.params?.pricingName ?? null,
|
||||||
planName: params.get("plan"),
|
planName: params.get("plan"),
|
||||||
userName: params.get("user"),
|
userName: params.get("user"),
|
||||||
|
paymentEnv: "",
|
||||||
product: null,
|
product: null,
|
||||||
pricing: props?.pricing ?? null,
|
pricing: props?.pricing ?? null,
|
||||||
plan: null,
|
plan: null,
|
||||||
@@ -38,8 +39,21 @@ class ProductBuyPage extends React.Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPaymentEnv() {
|
||||||
|
let env = "";
|
||||||
|
const ua = navigator.userAgent.toLocaleLowerCase();
|
||||||
|
// Only support Wechat Pay in Wechat Browser for mobile devices
|
||||||
|
if (ua.indexOf("micromessenger") !== -1 && ua.indexOf("mobile") !== -1) {
|
||||||
|
env = "WechatBrowser";
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
paymentEnv: env,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
this.getProduct();
|
this.getProduct();
|
||||||
|
this.getPaymentEnv();
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateAsync(state) {
|
setStateAsync(state) {
|
||||||
@@ -127,23 +141,74 @@ class ProductBuyPage extends React.Component {
|
|||||||
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
|
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call Weechat Pay via jsapi
|
||||||
|
onBridgeReady(attachInfo) {
|
||||||
|
const {WeixinJSBridge} = window;
|
||||||
|
// Setting.showMessage("success", "attachInfo is " + JSON.stringify(attachInfo));
|
||||||
|
this.setState({
|
||||||
|
isPlacingOrder: false,
|
||||||
|
});
|
||||||
|
WeixinJSBridge.invoke(
|
||||||
|
"getBrandWCPayRequest", {
|
||||||
|
"appId": attachInfo.appId,
|
||||||
|
"timeStamp": attachInfo.timeStamp,
|
||||||
|
"nonceStr": attachInfo.nonceStr,
|
||||||
|
"package": attachInfo.package,
|
||||||
|
"signType": attachInfo.signType,
|
||||||
|
"paySign": attachInfo.paySign,
|
||||||
|
},
|
||||||
|
function(res) {
|
||||||
|
if (res.err_msg === "get_brand_wcpay_request:ok") {
|
||||||
|
Setting.goToLink(attachInfo.payment.successUrl);
|
||||||
|
return ;
|
||||||
|
} else {
|
||||||
|
if (res.err_msg === "get_brand_wcpay_request:cancel") {
|
||||||
|
Setting.showMessage("error", i18next.t("product:Payment cancelled"));
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", i18next.t("product:Payment failed"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Wechat browser, call this function to pay via jsapi
|
||||||
|
callWechatPay(attachInfo) {
|
||||||
|
const {WeixinJSBridge} = window;
|
||||||
|
if (typeof WeixinJSBridge === "undefined") {
|
||||||
|
if (document.addEventListener) {
|
||||||
|
document.addEventListener("WeixinJSBridgeReady", () => this.onBridgeReady(attachInfo), false);
|
||||||
|
} else if (document.attachEvent) {
|
||||||
|
document.attachEvent("WeixinJSBridgeReady", () => this.onBridgeReady(attachInfo));
|
||||||
|
document.attachEvent("onWeixinJSBridgeReady", () => this.onBridgeReady(attachInfo));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.onBridgeReady(attachInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buyProduct(product, provider) {
|
buyProduct(product, provider) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isPlacingOrder: true,
|
isPlacingOrder: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "")
|
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "", this.state.paymentEnv)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const payment = res.data;
|
const payment = res.data;
|
||||||
|
const attachInfo = res.data2;
|
||||||
let payUrl = payment.payUrl;
|
let payUrl = payment.payUrl;
|
||||||
if (provider.type === "WeChat Pay") {
|
if (provider.type === "WeChat Pay") {
|
||||||
|
if (this.state.paymentEnv === "WechatBrowser") {
|
||||||
|
attachInfo.payment = payment;
|
||||||
|
this.callWechatPay(attachInfo);
|
||||||
|
return ;
|
||||||
|
}
|
||||||
payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`;
|
payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`;
|
||||||
}
|
}
|
||||||
Setting.goToLink(payUrl);
|
Setting.goToLink(payUrl);
|
||||||
} else {
|
} else {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isPlacingOrder: false,
|
isPlacingOrder: false,
|
||||||
});
|
});
|
||||||
@@ -218,7 +283,7 @@ class ProductBuyPage extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="login-content">
|
<div className="login-content">
|
||||||
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
|
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
|
||||||
<Descriptions title={<span style={{fontSize: 28}}>{i18next.t("product:Buy Product")}</span>} bordered>
|
<Descriptions title={<span style={Setting.isMobile() ? {fontSize: 20} : {fontSize: 28}}>{i18next.t("product:Buy Product")}</span>} bordered>
|
||||||
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
|
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
|
||||||
<span style={{fontSize: 25}}>
|
<span style={{fontSize: 25}}>
|
||||||
{Setting.getLanguageText(product?.displayName)}
|
{Setting.getLanguageText(product?.displayName)}
|
||||||
|
@@ -152,6 +152,12 @@ class ProviderEditPage extends React.Component {
|
|||||||
}
|
}
|
||||||
getClientIdLabel(provider) {
|
getClientIdLabel(provider) {
|
||||||
switch (provider.category) {
|
switch (provider.category) {
|
||||||
|
case "OAuth":
|
||||||
|
if (provider.type === "Apple") {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Service ID identifier"), i18next.t("provider:Service ID identifier - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
|
||||||
|
}
|
||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
|
return Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"));
|
||||||
case "SMS":
|
case "SMS":
|
||||||
@@ -185,6 +191,18 @@ class ProviderEditPage extends React.Component {
|
|||||||
|
|
||||||
getClientSecretLabel(provider) {
|
getClientSecretLabel(provider) {
|
||||||
switch (provider.category) {
|
switch (provider.category) {
|
||||||
|
case "OAuth":
|
||||||
|
if (provider.type === "Apple") {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Team ID"), i18next.t("provider:Team ID - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
|
}
|
||||||
|
case "Storage":
|
||||||
|
if (provider.type === "Google Cloud Storage") {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Service account JSON"), i18next.t("provider:Service account JSON - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
|
||||||
|
}
|
||||||
case "Email":
|
case "Email":
|
||||||
if (provider.type === "Azure ACS") {
|
if (provider.type === "Azure ACS") {
|
||||||
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
|
||||||
@@ -226,6 +244,12 @@ class ProviderEditPage extends React.Component {
|
|||||||
|
|
||||||
getClientId2Label(provider) {
|
getClientId2Label(provider) {
|
||||||
switch (provider.category) {
|
switch (provider.category) {
|
||||||
|
case "OAuth":
|
||||||
|
if (provider.type === "Apple") {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Key ID"), i18next.t("provider:Key ID - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client ID 2"), i18next.t("provider:Client ID 2 - Tooltip"));
|
||||||
|
}
|
||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("provider:From address"), i18next.t("provider:From address - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:From address"), i18next.t("provider:From address - Tooltip"));
|
||||||
default:
|
default:
|
||||||
@@ -241,6 +265,12 @@ class ProviderEditPage extends React.Component {
|
|||||||
|
|
||||||
getClientSecret2Label(provider) {
|
getClientSecret2Label(provider) {
|
||||||
switch (provider.category) {
|
switch (provider.category) {
|
||||||
|
case "OAuth":
|
||||||
|
if (provider.type === "Apple") {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Key text"), i18next.t("provider:Key text - Tooltip"));
|
||||||
|
} else {
|
||||||
|
return Setting.getLabel(i18next.t("provider:Client secret 2"), i18next.t("provider:Client secret 2 - Tooltip"));
|
||||||
|
}
|
||||||
case "Email":
|
case "Email":
|
||||||
return Setting.getLabel(i18next.t("provider:From name"), i18next.t("provider:From name - Tooltip"));
|
return Setting.getLabel(i18next.t("provider:From name"), i18next.t("provider:From name - Tooltip"));
|
||||||
default:
|
default:
|
||||||
@@ -497,6 +527,10 @@ class ProviderEditPage extends React.Component {
|
|||||||
this.updateProviderField("scopes", "openid profile email");
|
this.updateProviderField("scopes", "openid profile email");
|
||||||
this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
|
this.updateProviderField("customTokenUrl", "https://door.casdoor.com/api/login/oauth/access_token");
|
||||||
this.updateProviderField("customUserInfoUrl", "https://door.casdoor.com/api/userinfo");
|
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") {
|
} else if (value === "Custom HTTP") {
|
||||||
this.updateProviderField("method", "GET");
|
this.updateProviderField("method", "GET");
|
||||||
this.updateProviderField("title", "");
|
this.updateProviderField("title", "");
|
||||||
@@ -644,9 +678,11 @@ class ProviderEditPage extends React.Component {
|
|||||||
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
|
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
|
||||||
(this.state.provider.category === "Web3") ||
|
(this.state.provider.category === "Web3") ||
|
||||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
|
(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 : (
|
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP")) ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{
|
{
|
||||||
|
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
||||||
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
|
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
|
||||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Line" || this.state.provider.type === "Telegram" || this.state.provider.type === "Bark" || this.state.provider.type === "Discord" || this.state.provider.type === "Slack" || this.state.provider.type === "Pushbullet" || this.state.provider.type === "Pushover" || this.state.provider.type === "Lark" || this.state.provider.type === "Microsoft Teams")) ? null : (
|
(this.state.provider.category === "Notification" && (this.state.provider.type === "Line" || this.state.provider.type === "Telegram" || this.state.provider.type === "Bark" || this.state.provider.type === "Discord" || this.state.provider.type === "Slack" || this.state.provider.type === "Pushbullet" || this.state.provider.type === "Pushover" || this.state.provider.type === "Lark" || this.state.provider.type === "Microsoft Teams")) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
@@ -675,7 +711,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (
|
this.state.provider.category !== "Email" && this.state.provider.type !== "WeChat" && this.state.provider.type !== "Apple" && this.state.provider.type !== "Aliyun Captcha" && this.state.provider.type !== "WeChat Pay" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
@@ -732,7 +768,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{this.state.provider.category === "Storage" ? (
|
{this.state.provider.category === "Storage" || this.state.provider.type === "Custom HTTP SMS" ? (
|
||||||
<div>
|
<div>
|
||||||
{["Local File System"].includes(this.state.provider.type) ? null : (
|
{["Local File System"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
@@ -746,7 +782,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
|
||||||
@@ -758,7 +794,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</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"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
|
||||||
@@ -770,17 +806,19 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
<Row style={{marginTop: "20px"}} >
|
{["Custom HTTP SMS"].includes(this.state.provider.type) ? null : (
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Row style={{marginTop: "20px"}} >
|
||||||
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
</Col>
|
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
|
||||||
<Col span={22} >
|
</Col>
|
||||||
<Input value={this.state.provider.pathPrefix} onChange={e => {
|
<Col span={22} >
|
||||||
this.updateProviderField("pathPrefix", e.target.value);
|
<Input value={this.state.provider.pathPrefix} onChange={e => {
|
||||||
}} />
|
this.updateProviderField("pathPrefix", e.target.value);
|
||||||
</Col>
|
}} />
|
||||||
</Row>
|
</Col>
|
||||||
{["MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
</Row>
|
||||||
|
)}
|
||||||
|
{["Custom HTTP SMS", "MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={2}>
|
<Col style={{marginTop: "5px"}} span={2}>
|
||||||
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
|
||||||
@@ -950,7 +988,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
) : this.state.provider.category === "SMS" ? (
|
) : this.state.provider.category === "SMS" ? (
|
||||||
<React.Fragment>
|
<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 :
|
null :
|
||||||
(<Row style={{marginTop: "20px"}} >
|
(<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
@@ -964,7 +1002,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{["Infobip SMS"].includes(this.state.provider.type) ?
|
{["Custom HTTP SMS", "Infobip SMS"].includes(this.state.provider.type) ?
|
||||||
null :
|
null :
|
||||||
(<Row style={{marginTop: "20px"}} >
|
(<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
@@ -978,6 +1016,39 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Row>
|
</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"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:SMS Test"), i18next.t("provider:SMS Test - Tooltip"))} :
|
||||||
@@ -1002,7 +1073,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={2} >
|
<Col span={2} >
|
||||||
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary"
|
<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)} >
|
onClick={() => ProviderEditTestSms.sendTestSms(this.state.provider, "+" + Setting.getCountryCode(this.state.provider.content) + this.state.provider.receiver)} >
|
||||||
{i18next.t("provider:Send Testing SMS")}
|
{i18next.t("provider:Send Testing SMS")}
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -143,6 +143,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/social_msg91.ico`,
|
logo: `${StaticBaseUrl}/img/social_msg91.ico`,
|
||||||
url: "https://control.msg91.com/app/",
|
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": {
|
"Mock SMS": {
|
||||||
logo: `${StaticBaseUrl}/img/social_default.png`,
|
logo: `${StaticBaseUrl}/img/social_default.png`,
|
||||||
url: "",
|
url: "",
|
||||||
@@ -989,6 +993,8 @@ export function getProviderTypeOptions(category) {
|
|||||||
{id: "Aliyun SMS", name: "Alibaba Cloud SMS"},
|
{id: "Aliyun SMS", name: "Alibaba Cloud SMS"},
|
||||||
{id: "Amazon SNS", name: "Amazon SNS"},
|
{id: "Amazon SNS", name: "Amazon SNS"},
|
||||||
{id: "Azure ACS", name: "Azure ACS"},
|
{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: "Infobip SMS", name: "Infobip SMS"},
|
||||||
{id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
|
{id: "Tencent Cloud SMS", name: "Tencent Cloud SMS"},
|
||||||
{id: "Baidu Cloud SMS", name: "Baidu 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) {
|
export function renderLogo(application) {
|
||||||
if (application === null) {
|
if (application === null) {
|
||||||
return null;
|
return null;
|
||||||
|
@@ -374,12 +374,9 @@ class UserEditPage extends React.Component {
|
|||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
{Setting.getLabel(i18next.t("general:Avatar"), i18next.t("general:Avatar - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
{
|
||||||
{i18next.t("general:Preview")}:
|
this.renderImage(this.state.user.avatar, i18next.t("user:Upload a photo"), i18next.t("user:Set new profile picture"), "avatar", false)
|
||||||
</Col>
|
}
|
||||||
<Col>
|
|
||||||
{this.renderImage(this.state.user.avatar, i18next.t("user:Upload a photo"), i18next.t("user:Set new profile picture"), "avatar", false)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
} else if (accountItem.name === "User type") {
|
} else if (accountItem.name === "User type") {
|
||||||
@@ -550,9 +547,6 @@ class UserEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{i18next.t("general:Preview")}:
|
|
||||||
</Col>
|
|
||||||
{
|
{
|
||||||
[
|
[
|
||||||
{name: "ID card front", value: "idCardFront"},
|
{name: "ID card front", value: "idCardFront"},
|
||||||
@@ -975,7 +969,7 @@ class UserEditPage extends React.Component {
|
|||||||
|
|
||||||
renderImage(imgUrl, title, set, tag, disabled) {
|
renderImage(imgUrl, title, set, tag, disabled) {
|
||||||
return (
|
return (
|
||||||
<Col span={4} style={{textAlign: "center", margin: "auto"}} key={tag}>
|
<Col span={4} style={{textAlign: "center", margin: "auto", marginLeft: "20px"}} key={tag}>
|
||||||
{
|
{
|
||||||
imgUrl ?
|
imgUrl ?
|
||||||
<div style={{marginBottom: "10px"}}>
|
<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"}}>
|
<Col style={{height: "78%", border: "1px dotted grey", borderRadius: 3, marginBottom: "10px"}}>
|
||||||
<div style={{fontSize: 30, margin: 10}}>+</div>
|
<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>
|
</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)} />
|
<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)} />
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user