mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-14 08:03:23 +08:00
Compare commits
149 Commits
Author | SHA1 | Date | |
---|---|---|---|
cad9c28e92 | |||
524cf4dda5 | |||
077a1cb8b7 | |||
00efdf1d03 | |||
aa543f1abb | |||
1d1d3049bd | |||
4f497d44a5 | |||
369de36987 | |||
e3f28e8b4c | |||
3373174c65 | |||
2fb79e4092 | |||
5846e337c7 | |||
44f4de1440 | |||
27adeb4620 | |||
5c107db43b | |||
27187b3a54 | |||
14fcedcc5d | |||
e7c015f288 | |||
c4819602ec | |||
dea03cdd15 | |||
21f394847e | |||
9bef9691fb | |||
141f22a707 | |||
02329d342a | |||
b9d3e2184c | |||
28caf8550e | |||
79159dc809 | |||
63081641d6 | |||
698f24f762 | |||
5499e62d7f | |||
f8905ae64c | |||
a42594859f | |||
46e0bc1a39 | |||
ffe2330238 | |||
ec53616dc8 | |||
067276d739 | |||
468ceb6b71 | |||
b31a317585 | |||
396b6fb65f | |||
be637fca81 | |||
374928e719 | |||
5c103e8cd3 | |||
85b86e8831 | |||
08864686f3 | |||
dc06eb9948 | |||
b068202e74 | |||
cb16567c7b | |||
4eb725d47a | |||
ce72a172b0 | |||
5521962e0c | |||
37b8b09cc0 | |||
482eb61168 | |||
8819a8697b | |||
85cb68eb66 | |||
b25b5f0249 | |||
947dcf6e75 | |||
113c27db73 | |||
badfe34755 | |||
a5f9f61381 | |||
2ce8c93ead | |||
da41ac7275 | |||
fd0c70a827 | |||
c4a6f07672 | |||
a67f541171 | |||
192968bac8 | |||
23d4488b64 | |||
23f4684e1d | |||
1a91e7b0f9 | |||
811999b6cc | |||
7786018051 | |||
6c72f86d03 | |||
5b151f4ec4 | |||
e9b7d1266f | |||
2d4998228c | |||
d3ed6c348b | |||
a22e05dcc1 | |||
0ac2b69f5a | |||
d090e9c860 | |||
8ebb158765 | |||
ea2f053630 | |||
988b14c6b5 | |||
a9e72ac3cb | |||
498cd02d49 | |||
a389842f59 | |||
6c69daa666 | |||
53c89bbe89 | |||
9442aa9f7a | |||
8a195715d0 | |||
b985bab3f3 | |||
477a090aa0 | |||
e082cf10e0 | |||
3215b88eae | |||
9703f3f712 | |||
140737b2f6 | |||
b285144a64 | |||
49c6ce2221 | |||
2398e69012 | |||
ade9de8256 | |||
1bf5497d08 | |||
cf10738f45 | |||
ac00713c20 | |||
febb27f765 | |||
49a981f787 | |||
34b1945180 | |||
b320cca789 | |||
b38654a45a | |||
f77fafae24 | |||
8b6b5ffe81 | |||
a147fa3e0b | |||
9d03665523 | |||
0106c7f7fa | |||
6713dad0af | |||
6ef2b51782 | |||
1732cd8538 | |||
a10548fe73 | |||
f6a7888f83 | |||
93efaa5459 | |||
0bfe683108 | |||
8a4758c22d | |||
ee3b46e91c | |||
37744d6cd7 | |||
98defe617b | |||
96cbf51ca0 | |||
22b57fdd23 | |||
b68e291f37 | |||
9960b4933b | |||
432a5496f2 | |||
45db4deb6b | |||
3f53591751 | |||
d7569684f6 | |||
a616127909 | |||
f2e2b960ff | |||
fbc603876f | |||
9ea77c63d1 | |||
53243a30f3 | |||
cbdeb91ee8 | |||
2dd1dc582f | |||
f3d4b45a0f | |||
2ee4aebd96 | |||
150e3e30d5 | |||
1055d7781b | |||
1c296e9b6f | |||
3d80ec721f | |||
43d849086f | |||
69b144d80f | |||
52a66ef044 | |||
ec0a8e16f7 | |||
80a8000057 | |||
77091a3ae5 |
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install && CI=false yarn run build
|
||||
@ -101,7 +101,7 @@ jobs:
|
||||
working-directory: ./
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: ./web/yarn.lock
|
||||
- run: yarn install
|
||||
@ -127,7 +127,7 @@ jobs:
|
||||
release-and-push:
|
||||
name: Release And Push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend, linter, e2e ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -137,7 +137,7 @@ jobs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
node-version: 18
|
||||
|
||||
- name: Fetch Previous version
|
||||
id: get-previous-tag
|
||||
@ -184,14 +184,14 @@ jobs:
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Push to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
context: .
|
||||
target: STANDARD
|
||||
@ -201,7 +201,7 @@ jobs:
|
||||
|
||||
- name: Push All In One Version to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
|
||||
with:
|
||||
context: .
|
||||
target: ALLINONE
|
||||
|
40
.github/workflows/helm.yml
vendored
Normal file
40
.github/workflows/helm.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Helm Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'manifests/casdoor/Chart.yaml'
|
||||
|
||||
jobs:
|
||||
release-helm-chart:
|
||||
name: Release Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
- name: Release Helm Chart
|
||||
run: |
|
||||
cd manifests/casdoor
|
||||
REGISTRY=oci://registry-1.docker.io/casbin
|
||||
helm package .
|
||||
PKG_NAME=$(ls *.tgz)
|
||||
helm repo index . --url $REGISTRY --merge index.yaml
|
||||
helm push $PKG_NAME $REGISTRY
|
||||
rm $PKG_NAME
|
||||
|
||||
- name: Commit updated helm index.yaml
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: 'ci: update helm index.yaml'
|
2
.github/workflows/sync.yml
vendored
2
.github/workflows/sync.yml
vendored
@ -7,7 +7,7 @@ on:
|
||||
jobs:
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
|
@ -1,10 +1,10 @@
|
||||
FROM node:16.18.0 AS FRONT
|
||||
FROM node:18.19.0 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
||||
|
||||
|
||||
FROM golang:1.19.9 AS BACK
|
||||
FROM golang:1.20.12 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN ./build.sh
|
||||
|
16
README.md
16
README.md
@ -1,5 +1,5 @@
|
||||
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
|
||||
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
|
||||
<h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
|
||||
<p align="center">
|
||||
<a href="#badge">
|
||||
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
|
||||
@ -42,6 +42,20 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<sup>Sponsored by</sup>
|
||||
<br>
|
||||
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.casbin.org/img/stytch-white.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://cdn.casbin.org/img/stytch-charcoal.png">
|
||||
<img src="https://cdn.casbin.org/img/stytch-charcoal.png" width="275">
|
||||
</picture>
|
||||
</a><br/>
|
||||
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin"><b>Build auth with fraud prevention, faster.</b><br/> Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.</a>
|
||||
<br>
|
||||
</p>
|
||||
|
||||
## Online demo
|
||||
|
||||
- Read-only site: https://door.casdoor.com (any modification operation will fail)
|
||||
|
@ -92,11 +92,14 @@ p, *, *, GET, /api/get-plan, *, *
|
||||
p, *, *, GET, /api/get-subscription, *, *
|
||||
p, *, *, GET, /api/get-provider, *, *
|
||||
p, *, *, GET, /api/get-organization-names, *, *
|
||||
p, *, *, GET, /api/get-all-objects, *, *
|
||||
p, *, *, GET, /api/get-all-actions, *, *
|
||||
p, *, *, GET, /api/get-all-roles, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
// load all rules from string adapter to enforcer's memory
|
||||
err := sa.LoadPolicy(Enforcer.GetModel())
|
||||
err = sa.LoadPolicy(Enforcer.GetModel())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -127,8 +130,14 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
return true
|
||||
}
|
||||
|
||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||
return true
|
||||
if user != nil {
|
||||
if user.IsDeleted {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||
@ -141,11 +150,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 {
|
||||
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" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") {
|
||||
return true
|
||||
} else if urlPath == "/api/update-user" {
|
||||
// Allow ordinary users to update their own information
|
||||
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
|
||||
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -16,9 +16,11 @@ verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
origin =
|
||||
originFrontend =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
enableGzip = true
|
||||
ldapServerPort = 389
|
||||
radiusServerPort = 1812
|
||||
radiusSecret = "secret"
|
||||
|
@ -56,6 +56,17 @@ type Captcha struct {
|
||||
SubType string `json:"subType"`
|
||||
}
|
||||
|
||||
// this API is used by "Api URL" of Flarum's FoF Passport plugin
|
||||
// https://github.com/FriendsOfFlarum/passport
|
||||
type LaravelResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
EmailVerifiedAt string `json:"email_verified_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Signup
|
||||
// @Tag Login API
|
||||
// @Title Signup
|
||||
@ -238,7 +249,7 @@ func (c *ApiController) Signup() {
|
||||
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
|
||||
// @Param state query string false "state"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /logout [get,post]
|
||||
// @router /logout [post]
|
||||
func (c *ApiController) Logout() {
|
||||
// https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
|
||||
accessToken := c.Input().Get("id_token_hint")
|
||||
@ -282,17 +293,15 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
}
|
||||
|
||||
affected, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !affected {
|
||||
if token == nil {
|
||||
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
|
||||
return
|
||||
@ -319,7 +328,15 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
} else {
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
||||
redirectUrl := redirectUri
|
||||
if state != "" {
|
||||
if strings.Contains(redirectUri, "?") {
|
||||
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
} else {
|
||||
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
|
||||
}
|
||||
}
|
||||
c.Ctx.Redirect(http.StatusFound, redirectUrl)
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
@ -412,7 +429,7 @@ func (c *ApiController) GetUserinfo() {
|
||||
// @Title UserInfo2
|
||||
// @Tag Account API
|
||||
// @Description return Laravel compatible user information according to OAuth 2.0
|
||||
// @Success 200 {object} LaravelResponse The Response object
|
||||
// @Success 200 {object} controllers.LaravelResponse The Response object
|
||||
// @router /user [get]
|
||||
func (c *ApiController) GetUserinfo2() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
@ -420,17 +437,6 @@ func (c *ApiController) GetUserinfo2() {
|
||||
return
|
||||
}
|
||||
|
||||
// this API is used by "Api URL" of Flarum's FoF Passport plugin
|
||||
// https://github.com/FriendsOfFlarum/passport
|
||||
type LaravelResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
EmailVerifiedAt string `json:"email_verified_at"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
response := LaravelResponse{
|
||||
Id: user.Id,
|
||||
Name: user.Name,
|
||||
@ -448,6 +454,7 @@ func (c *ApiController) GetUserinfo2() {
|
||||
// @Tag Login API
|
||||
// @Title GetCaptcha
|
||||
// @router /api/get-captcha [get]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) GetCaptcha() {
|
||||
applicationId := c.Input().Get("applicationId")
|
||||
isCurrentProvider := c.Input().Get("isCurrentProvider")
|
||||
@ -473,7 +480,7 @@ func (c *ApiController) GetCaptcha() {
|
||||
Type: captchaProvider.Type,
|
||||
SubType: captchaProvider.SubType,
|
||||
ClientId: captchaProvider.ClientId,
|
||||
ClientSecret: captchaProvider.ClientSecret,
|
||||
ClientSecret: "***",
|
||||
ClientId2: captchaProvider.ClientId2,
|
||||
ClientSecret2: captchaProvider.ClientSecret2,
|
||||
})
|
||||
|
@ -110,6 +110,14 @@ func (c *ApiController) GetApplication() {
|
||||
}
|
||||
}
|
||||
|
||||
// 0 as an initialization value, corresponding to the default configuration parameters
|
||||
if application.FailedSigninLimit == 0 {
|
||||
application.FailedSigninLimit = object.DefaultFailedSigninLimit
|
||||
}
|
||||
if application.FailedSigninfrozenTime == 0 {
|
||||
application.FailedSigninfrozenTime = object.DefaultFailedSigninfrozenTime
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplication(application, userId))
|
||||
}
|
||||
|
||||
@ -173,6 +181,12 @@ func (c *ApiController) GetOrganizationApplications() {
|
||||
return
|
||||
}
|
||||
|
||||
applications, err = object.GetAllowedApplications(applications, userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetMaskedApplications(applications, userId))
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
|
@ -34,6 +34,7 @@ import (
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -154,7 +155,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
||||
} else {
|
||||
scope := c.Input().Get("scope")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
|
||||
nonce := c.Input().Get("nonce")
|
||||
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
|
||||
resp = tokenToResponse(token)
|
||||
}
|
||||
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||
@ -220,7 +222,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
// @Param redirectUri query string true "redirect uri"
|
||||
// @Param scope query string true "scope"
|
||||
// @Param state query string true "state"
|
||||
// @Success 200 {object} Response The Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-app-login [get]
|
||||
func (c *ApiController) GetApplicationLogin() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
@ -331,8 +333,6 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if authForm.Password == "" {
|
||||
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
@ -342,7 +342,28 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
|
||||
verificationCodeType := object.GetVerifyType(authForm.Username)
|
||||
if verificationCodeType == object.VerifyTypeEmail && !application.IsCodeSigninViaEmailEnabled() {
|
||||
c.ResponseError(c.T("auth:The login method: login with email is not enabled for the application"))
|
||||
return
|
||||
}
|
||||
if verificationCodeType == object.VerifyTypePhone && !application.IsCodeSigninViaSmsEnabled() {
|
||||
c.ResponseError(c.T("auth:The login method: login with SMS is not enabled for the application"))
|
||||
return
|
||||
}
|
||||
|
||||
var checkDest string
|
||||
if verificationCodeType == object.VerifyTypePhone {
|
||||
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
|
||||
@ -354,20 +375,21 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// check result through Email or Phone
|
||||
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if len(checkResult) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
|
||||
err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
// disable the verification code
|
||||
err := object.DisableVerificationCode(checkDest)
|
||||
err = object.DisableVerificationCode(checkDest)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
return
|
||||
@ -377,7 +399,7 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
if !application.EnablePassword {
|
||||
if !application.IsPasswordEnabled() {
|
||||
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
|
||||
return
|
||||
}
|
||||
@ -386,7 +408,18 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if enableCaptcha {
|
||||
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
captchaProvider, err := object.GetCaptchaProviderByApplication(util.GetId(application.Owner, application.Name), "false", c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
authForm.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
var isHuman bool
|
||||
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -399,13 +432,15 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
password := authForm.Password
|
||||
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
|
||||
}
|
||||
|
||||
if msg != "" {
|
||||
resp = &Response{Status: "error", Msg: msg}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else {
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -416,7 +451,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
}
|
||||
@ -461,12 +497,15 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
|
||||
return
|
||||
}
|
||||
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
|
||||
if err != nil {
|
||||
c.ResponseError(c.T(err.Error()))
|
||||
}
|
||||
|
||||
provider, err := object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
var provider *object.Provider
|
||||
provider, err = object.GetProvider(util.GetId("admin", authForm.Provider))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -477,11 +516,10 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
|
||||
return
|
||||
}
|
||||
|
||||
userInfo := &idp.UserInfo{}
|
||||
if provider.Category == "SAML" {
|
||||
// SAML
|
||||
userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
|
||||
userInfo, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -489,7 +527,12 @@ func (c *ApiController) Login() {
|
||||
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
||||
// OAuth
|
||||
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 {
|
||||
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
||||
return
|
||||
@ -503,7 +546,8 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
|
||||
token, err := idProvider.GetToken(authForm.Code)
|
||||
var token *oauth2.Token
|
||||
token, err = idProvider.GetToken(authForm.Code)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -524,7 +568,8 @@ func (c *ApiController) Login() {
|
||||
if authForm.Method == "signup" {
|
||||
user := &object.User{}
|
||||
if provider.Category == "SAML" {
|
||||
user, err = object.GetUser(util.GetId(application.Organization, userInfo.Id))
|
||||
// The userInfo.Id is the NameID in SAML response, it could be name / email / phone
|
||||
user, err = object.GetUserByFields(application.Organization, userInfo.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -543,7 +588,12 @@ func (c *ApiController) Login() {
|
||||
if user.IsForbidden {
|
||||
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
|
||||
}
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||
|
||||
record := object.NewRecord(c.Ctx)
|
||||
@ -584,14 +634,16 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
// Handle username conflicts
|
||||
tmpUser, err := object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||
var tmpUser *object.User
|
||||
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if tmpUser != nil {
|
||||
uid, err := uuid.NewRandom()
|
||||
var uid uuid.UUID
|
||||
uid, err = uuid.NewRandom()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -602,14 +654,16 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
|
||||
properties := map[string]string{}
|
||||
count, err := object.GetUserCount(application.Organization, "", "", "")
|
||||
var count int64
|
||||
count, err = object.GetUserCount(application.Organization, "", "", "")
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
properties["no"] = strconv.Itoa(int(count + 2))
|
||||
initScore, err := organization.GetInitScore()
|
||||
var initScore int
|
||||
initScore, err = organization.GetInitScore()
|
||||
if err != nil {
|
||||
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
|
||||
return
|
||||
@ -641,7 +695,8 @@ func (c *ApiController) Login() {
|
||||
Properties: properties,
|
||||
}
|
||||
|
||||
affected, err := object.AddUser(user)
|
||||
var affected bool
|
||||
affected, err = object.AddUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -651,10 +706,19 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user)))
|
||||
return
|
||||
}
|
||||
|
||||
if providerItem.SignupGroup != "" {
|
||||
user.Groups = []string{providerItem.SignupGroup}
|
||||
_, err = object.UpdateUser(user.GetId(), user, []string{"groups"}, false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -679,6 +743,7 @@ func (c *ApiController) Login() {
|
||||
record2.User = user.Name
|
||||
util.SafeGoroutine(func() { object.AddRecord(record2) })
|
||||
} else if provider.Category == "SAML" {
|
||||
// TODO: since we get the user info from SAML response, we can try to create the user
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
|
||||
}
|
||||
// resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||
@ -689,7 +754,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
oldUser, err := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
var oldUser *object.User
|
||||
oldUser, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -700,7 +766,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
var user *object.User
|
||||
user, err = object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -713,7 +780,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
isLinked, err := object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
var isLinked bool
|
||||
isLinked, err = object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -726,7 +794,8 @@ func (c *ApiController) Login() {
|
||||
}
|
||||
}
|
||||
} else if c.getMfaUserSession() != "" {
|
||||
user, err := object.GetUser(c.getMfaUserSession())
|
||||
var user *object.User
|
||||
user, err = object.GetUser(c.getMfaUserSession())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -759,7 +828,8 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -780,7 +850,8 @@ func (c *ApiController) Login() {
|
||||
} else {
|
||||
if c.GetSessionUsername() != "" {
|
||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
var application *object.Application
|
||||
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -837,6 +908,7 @@ func (c *ApiController) HandleSamlLogin() {
|
||||
// @Tag HandleOfficialAccountEvent API
|
||||
// @Title HandleOfficialAccountEvent
|
||||
// @router /api/webhook [POST]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) HandleOfficialAccountEvent() {
|
||||
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
@ -867,6 +939,7 @@ func (c *ApiController) HandleOfficialAccountEvent() {
|
||||
// @Tag GetWebhookEventType API
|
||||
// @Title GetWebhookEventType
|
||||
// @router /api/get-webhook-event [GET]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) GetWebhookEventType() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
@ -896,8 +969,14 @@ func (c *ApiController) GetCaptchaStatus() {
|
||||
return
|
||||
}
|
||||
|
||||
failedSigninLimit, _, err := object.GetFailedSigninConfigByUser(user)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var captchaEnabled bool
|
||||
if user != nil && user.SigninWrongTimes >= object.SigninWrongTimesLimit {
|
||||
if user != nil && user.SigninWrongTimes >= failedSigninLimit {
|
||||
captchaEnabled = true
|
||||
}
|
||||
c.ResponseOk(captchaEnabled)
|
||||
@ -908,6 +987,7 @@ func (c *ApiController) GetCaptchaStatus() {
|
||||
// @Tag Callback API
|
||||
// @Description Get Login Error Counts
|
||||
// @router /api/Callback [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) Callback() {
|
||||
code := c.GetString("code")
|
||||
state := c.GetString("state")
|
||||
|
@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -25,10 +26,11 @@ import (
|
||||
// @Title Enforce
|
||||
// @Tag Enforce API
|
||||
// @Description Call Casbin Enforce API
|
||||
// @Param body body object.CasbinRequest true "Casbin request"
|
||||
// @Param body body []string true "Casbin request"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Param resourceId query string false "resource id"
|
||||
// @Param owner query string false "owner"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /enforce [post]
|
||||
func (c *ApiController) Enforce() {
|
||||
@ -36,8 +38,14 @@ func (c *ApiController) Enforce() {
|
||||
modelId := c.Input().Get("modelId")
|
||||
resourceId := c.Input().Get("resourceId")
|
||||
enforcerId := c.Input().Get("enforcerId")
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
var request object.CasbinRequest
|
||||
if len(c.Ctx.Input.RequestBody) == 0 {
|
||||
c.ResponseError("The request body should not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
var request []string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -51,13 +59,22 @@ func (c *ApiController) Enforce() {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := enforcer.Enforce(request...)
|
||||
res := []bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
// type transformation
|
||||
interfaceRequest := util.StringToInterfaceArray(request)
|
||||
|
||||
enforceResult, err := enforcer.Enforce(interfaceRequest...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -67,22 +84,24 @@ func (c *ApiController) Enforce() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
|
||||
if permission == nil {
|
||||
res = append(res, false)
|
||||
} else {
|
||||
enforceResult, err := object.Enforce(permission, &request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
c.ResponseError(fmt.Sprintf("permission: %s doesn't exist", permissionId))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res := []bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
enforceResult, err := object.Enforce(permission, request)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -100,48 +119,53 @@ func (c *ApiController) Enforce() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if owner != "" {
|
||||
permissions, err = object.GetPermissions(owner)
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
res := []bool{}
|
||||
|
||||
keyRes := []string{}
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for _, permissionIds := range listPermissionIdMap {
|
||||
for key, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...)
|
||||
enforceResult, err := object.Enforce(firstPermission, request, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, key)
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
c.ResponseOk(res, keyRes)
|
||||
}
|
||||
|
||||
// BatchEnforce
|
||||
// @Title BatchEnforce
|
||||
// @Tag Enforce API
|
||||
// @Description Call Casbin BatchEnforce API
|
||||
// @Param body body object.CasbinRequest true "array of casbin requests"
|
||||
// @Param body body []string true "array of casbin requests"
|
||||
// @Param permissionId query string false "permission id"
|
||||
// @Param modelId query string false "model id"
|
||||
// @Param owner query string false "owner"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /batch-enforce [post]
|
||||
func (c *ApiController) BatchEnforce() {
|
||||
permissionId := c.Input().Get("permissionId")
|
||||
modelId := c.Input().Get("modelId")
|
||||
enforcerId := c.Input().Get("enforcerId")
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
var requests []object.CasbinRequest
|
||||
var requests [][]string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -155,13 +179,22 @@ func (c *ApiController) BatchEnforce() {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := enforcer.BatchEnforce(requests)
|
||||
res := [][]bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
// type transformation
|
||||
interfaceRequests := util.StringToInterfaceArray2d(requests)
|
||||
|
||||
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -171,28 +204,24 @@ func (c *ApiController) BatchEnforce() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
|
||||
if permission == nil {
|
||||
l := len(requests)
|
||||
resRequest := make([]bool, l)
|
||||
for i := 0; i < l; i++ {
|
||||
resRequest[i] = false
|
||||
}
|
||||
|
||||
res = append(res, resRequest)
|
||||
} else {
|
||||
enforceResult, err := object.BatchEnforce(permission, &requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
c.ResponseError(fmt.Sprintf("permission: %s doesn't exist", permissionId))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
res := [][]bool{}
|
||||
keyRes := []string{}
|
||||
|
||||
enforceResult, err := object.BatchEnforce(permission, requests)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, permission.GetModelAndAdapter())
|
||||
|
||||
c.ResponseOk(res, keyRes)
|
||||
return
|
||||
}
|
||||
|
||||
@ -204,13 +233,15 @@ func (c *ApiController) BatchEnforce() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
} else if owner != "" {
|
||||
permissions, err = object.GetPermissions(owner)
|
||||
} else {
|
||||
c.ResponseError(c.T("general:Missing parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
res := [][]bool{}
|
||||
|
||||
keyRes := []string{}
|
||||
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
|
||||
for _, permissionIds := range listPermissionIdMap {
|
||||
firstPermission, err := object.GetPermission(permissionIds[0])
|
||||
@ -219,16 +250,17 @@ func (c *ApiController) BatchEnforce() {
|
||||
return
|
||||
}
|
||||
|
||||
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...)
|
||||
enforceResult, err := object.BatchEnforce(firstPermission, requests, permissionIds...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res = append(res, enforceResult)
|
||||
keyRes = append(keyRes, firstPermission.GetModelAndAdapter())
|
||||
}
|
||||
|
||||
c.ResponseOk(res)
|
||||
c.ResponseOk(res, keyRes)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllObjects() {
|
||||
@ -238,7 +270,13 @@ func (c *ApiController) GetAllObjects() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllObjects(userId))
|
||||
objects, err := object.GetAllObjects(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(objects)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllActions() {
|
||||
@ -248,7 +286,13 @@ func (c *ApiController) GetAllActions() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllActions(userId))
|
||||
actions, err := object.GetAllActions(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(actions)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetAllRoles() {
|
||||
@ -258,5 +302,11 @@ func (c *ApiController) GetAllRoles() {
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(object.GetAllRoles(userId))
|
||||
roles, err := object.GetAllRoles(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(roles)
|
||||
}
|
||||
|
@ -65,13 +65,13 @@ func (c *ApiController) GetCerts() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobleCerts
|
||||
// @Title GetGlobleCerts
|
||||
// GetGlobalCerts
|
||||
// @Title GetGlobalCerts
|
||||
// @Tag Cert API
|
||||
// @Description get globle certs
|
||||
// @Success 200 {array} object.Cert The Response object
|
||||
// @router /get-globle-certs [get]
|
||||
func (c *ApiController) GetGlobleCerts() {
|
||||
// @router /get-global-certs [get]
|
||||
func (c *ApiController) GetGlobalCerts() {
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
@ -80,7 +80,7 @@ func (c *ApiController) GetGlobleCerts() {
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts())
|
||||
maskedCerts, err := object.GetMaskedCerts(object.GetGlobalCerts())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -71,7 +71,7 @@ func (c *ApiController) GetEnforcers() {
|
||||
// @Tag Enforcer API
|
||||
// @Description get enforcer
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Success 200 {object} object
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /get-enforcer [get]
|
||||
func (c *ApiController) GetEnforcer() {
|
||||
id := c.Input().Get("id")
|
||||
@ -99,7 +99,7 @@ func (c *ApiController) GetEnforcer() {
|
||||
// @Description update enforcer
|
||||
// @Param id query string true "The id ( owner/name ) of enforcer"
|
||||
// @Param enforcer body object true "The enforcer object"
|
||||
// @Success 200 {object} object
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /update-enforcer [post]
|
||||
func (c *ApiController) UpdateEnforcer() {
|
||||
id := c.Input().Get("id")
|
||||
@ -120,7 +120,7 @@ func (c *ApiController) UpdateEnforcer() {
|
||||
// @Tag Enforcer API
|
||||
// @Description add enforcer
|
||||
// @Param enforcer body object true "The enforcer object"
|
||||
// @Success 200 {object} object
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /add-enforcer [post]
|
||||
func (c *ApiController) AddEnforcer() {
|
||||
enforcer := object.Enforcer{}
|
||||
@ -138,8 +138,8 @@ func (c *ApiController) AddEnforcer() {
|
||||
// @Title DeleteEnforcer
|
||||
// @Tag Enforcer API
|
||||
// @Description delete enforcer
|
||||
// @Param body body object.Enforce true "The enforcer object"
|
||||
// @Success 200 {object} object
|
||||
// @Param body body object.Enforcer true "The enforcer object"
|
||||
// @Success 200 {object} object.Enforcer
|
||||
// @router /delete-enforcer [post]
|
||||
func (c *ApiController) DeleteEnforcer() {
|
||||
var enforcer object.Enforcer
|
||||
|
164
controllers/invitation.go
Normal file
164
controllers/invitation.go
Normal file
@ -0,0 +1,164 @@
|
||||
// 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 controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
// GetInvitations
|
||||
// @Title GetInvitations
|
||||
// @Tag Invitation API
|
||||
// @Description get invitations
|
||||
// @Param owner query string true "The owner of invitations"
|
||||
// @Success 200 {array} object.Invitation The Response object
|
||||
// @router /get-invitations [get]
|
||||
func (c *ApiController) GetInvitations() {
|
||||
owner := c.Input().Get("owner")
|
||||
limit := c.Input().Get("pageSize")
|
||||
page := c.Input().Get("p")
|
||||
field := c.Input().Get("field")
|
||||
value := c.Input().Get("value")
|
||||
sortField := c.Input().Get("sortField")
|
||||
sortOrder := c.Input().Get("sortOrder")
|
||||
|
||||
if limit == "" || page == "" {
|
||||
invitations, err := object.GetInvitations(owner)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(invitations)
|
||||
} else {
|
||||
limit := util.ParseInt(limit)
|
||||
count, err := object.GetInvitationCount(owner, field, value)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paginator := pagination.SetPaginator(c.Ctx, limit, count)
|
||||
invitations, err := object.GetPaginationInvitations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(invitations, paginator.Nums())
|
||||
}
|
||||
}
|
||||
|
||||
// GetInvitation
|
||||
// @Title GetInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description get invitation
|
||||
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||
// @Success 200 {object} object.Invitation The Response object
|
||||
// @router /get-invitation [get]
|
||||
func (c *ApiController) GetInvitation() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
invitation, err := object.GetInvitation(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(invitation)
|
||||
}
|
||||
|
||||
// UpdateInvitation
|
||||
// @Title UpdateInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description update invitation
|
||||
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||
// @Param body body object.Invitation true "The details of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-invitation [post]
|
||||
func (c *ApiController) UpdateInvitation() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var invitation object.Invitation
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// AddInvitation
|
||||
// @Title AddInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description add invitation
|
||||
// @Param body body object.Invitation true "The details of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-invitation [post]
|
||||
func (c *ApiController) AddInvitation() {
|
||||
var invitation object.Invitation
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteInvitation
|
||||
// @Title DeleteInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description delete invitation
|
||||
// @Param body body object.Invitation true "The details of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-invitation [post]
|
||||
func (c *ApiController) DeleteInvitation() {
|
||||
var invitation object.Invitation
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteInvitation(&invitation))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// VerifyInvitation
|
||||
// @Title VerifyInvitation
|
||||
// @Tag Invitation API
|
||||
// @Description verify invitation
|
||||
// @Param id query string true "The id ( owner/name ) of the invitation"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /verify-invitation [get]
|
||||
func (c *ApiController) VerifyInvitation() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
payment, attachInfo, err := object.VerifyInvitation(id)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment, attachInfo)
|
||||
}
|
@ -42,7 +42,7 @@ type LdapSyncResp struct {
|
||||
// @Tag Account API
|
||||
// @Description get ldap users
|
||||
// Param id string true "id"
|
||||
// @Success 200 {object} LdapResp The Response object
|
||||
// @Success 200 {object} controllers.LdapResp The Response object
|
||||
// @router /get-ldap-users [get]
|
||||
func (c *ApiController) GetLdapUsers() {
|
||||
id := c.Input().Get("id")
|
||||
@ -59,6 +59,7 @@ func (c *ApiController) GetLdapUsers() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
//if err != nil {
|
||||
@ -249,7 +250,7 @@ func (c *ApiController) DeleteLdap() {
|
||||
// @Tag Account API
|
||||
// @Description sync ldap users
|
||||
// @Param id query string true "id"
|
||||
// @Success 200 {object} LdapSyncResp The Response object
|
||||
// @Success 200 {object} controllers.LdapSyncResp The Response object
|
||||
// @router /sync-ldap-users [post]
|
||||
func (c *ApiController) SyncLdapUsers() {
|
||||
id := c.Input().Get("id")
|
||||
|
@ -26,8 +26,10 @@ type LinkForm struct {
|
||||
}
|
||||
|
||||
// Unlink ...
|
||||
// @router /unlink [post]
|
||||
// @Tag Login API
|
||||
// @Title Unlink
|
||||
// @router /unlink [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) Unlink() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
|
@ -73,7 +73,7 @@ func (c *ApiController) MfaSetupInitiate() {
|
||||
// @Description setup verify totp
|
||||
// @param secret form string true "MFA secret"
|
||||
// @param passcode form string true "MFA passcode"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /mfa/setup/verify [post]
|
||||
func (c *ApiController) MfaSetupVerify() {
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
@ -104,7 +104,7 @@ func (c *ApiController) MfaSetupVerify() {
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @param type form string true "MFA auth type"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /mfa/setup/enable [post]
|
||||
func (c *ApiController) MfaSetupEnable() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
@ -143,7 +143,7 @@ func (c *ApiController) MfaSetupEnable() {
|
||||
// @Description: Delete MFA
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-mfa/ [post]
|
||||
func (c *ApiController) DeleteMfa() {
|
||||
owner := c.Ctx.Request.Form.Get("owner")
|
||||
@ -176,7 +176,7 @@ func (c *ApiController) DeleteMfa() {
|
||||
// @param owner form string true "owner of user"
|
||||
// @param name form string true "name of user"
|
||||
// @param id form string true "id of user's MFA props"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /set-preferred-mfa [post]
|
||||
func (c *ApiController) SetPreferredMfa() {
|
||||
mfaType := c.Ctx.Request.Form.Get("mfaType")
|
||||
|
@ -178,7 +178,7 @@ func (c *ApiController) DeleteOrganization() {
|
||||
// @Tag Organization API
|
||||
// @Description get default application
|
||||
// @Param id query string true "organization id"
|
||||
// @Success 200 {object} Response The Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-default-application [get]
|
||||
func (c *ApiController) GetDefaultApplication() {
|
||||
userId := c.GetSessionUsername()
|
||||
|
@ -163,6 +163,8 @@ func (c *ApiController) BuyProduct() {
|
||||
id := c.Input().Get("id")
|
||||
host := c.Ctx.Request.Host
|
||||
providerName := c.Input().Get("providerName")
|
||||
paymentEnv := c.Input().Get("paymentEnv")
|
||||
|
||||
// buy `pricingName/planName` for `paidUserName`
|
||||
pricingName := c.Input().Get("pricingName")
|
||||
planName := c.Input().Get("planName")
|
||||
@ -187,11 +189,11 @@ func (c *ApiController) BuyProduct() {
|
||||
return
|
||||
}
|
||||
|
||||
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
|
||||
payment, attachInfo, err := object.BuyProduct(id, user, providerName, pricingName, planName, host, paymentEnv)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(payment)
|
||||
c.ResponseOk(payment, attachInfo)
|
||||
}
|
||||
|
@ -33,7 +33,13 @@ func (c *ApiController) GetSamlMeta() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
||||
return
|
||||
}
|
||||
metadata, _ := object.GetSamlMeta(application, host)
|
||||
|
||||
metadata, err := object.GetSamlMeta(application, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["xml"] = metadata
|
||||
c.ServeXML()
|
||||
}
|
||||
|
@ -51,9 +51,14 @@ type NotificationForm struct {
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.EmailForm true "Details of the email request"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var emailForm EmailForm
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||
@ -108,8 +113,13 @@ func (c *ApiController) SendEmail() {
|
||||
}
|
||||
|
||||
code := "123456"
|
||||
|
||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||
content := fmt.Sprintf(emailForm.Content, code)
|
||||
content := strings.Replace(provider.Content, "%s", code, 1)
|
||||
if user != nil {
|
||||
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
|
||||
}
|
||||
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
|
||||
if err != nil {
|
||||
@ -128,7 +138,7 @@ func (c *ApiController) SendEmail() {
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param from body controllers.SmsForm true "Details of the sms request"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
provider, err := c.GetProviderFromContext("SMS")
|
||||
@ -166,7 +176,7 @@ func (c *ApiController) SendSms() {
|
||||
// @Tag Service API
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param from body controllers.NotificationForm true "Details of the notification request"
|
||||
// @Success 200 {object} Response object
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /api/send-notification [post]
|
||||
func (c *ApiController) SendNotification() {
|
||||
provider, err := c.GetProviderFromContext("Notification")
|
||||
|
@ -158,10 +158,9 @@ func (c *ApiController) DeleteToken() {
|
||||
// @Success 401 {object} object.TokenError The Response object
|
||||
// @router api/login/oauth/access_token [post]
|
||||
func (c *ApiController) GetOAuthToken() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
refreshToken := c.Input().Get("refresh_token")
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
grantType := c.Input().Get("grant_type")
|
||||
code := c.Input().Get("code")
|
||||
verifier := c.Input().Get("code_verifier")
|
||||
scope := c.Input().Get("scope")
|
||||
@ -169,35 +168,61 @@ func (c *ApiController) GetOAuthToken() {
|
||||
password := c.Input().Get("password")
|
||||
tag := c.Input().Get("tag")
|
||||
avatar := c.Input().Get("avatar")
|
||||
refreshToken := c.Input().Get("refresh_token")
|
||||
|
||||
if clientId == "" && clientSecret == "" {
|
||||
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
|
||||
}
|
||||
if clientId == "" {
|
||||
// If clientID is empty, try to read data from RequestBody
|
||||
|
||||
if len(c.Ctx.Input.RequestBody) != 0 {
|
||||
// If clientId is empty, try to read data from RequestBody
|
||||
var tokenRequest TokenRequest
|
||||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
|
||||
clientId = tokenRequest.ClientId
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
grantType = tokenRequest.GrantType
|
||||
refreshToken = tokenRequest.RefreshToken
|
||||
code = tokenRequest.Code
|
||||
verifier = tokenRequest.Verifier
|
||||
scope = tokenRequest.Scope
|
||||
username = tokenRequest.Username
|
||||
password = tokenRequest.Password
|
||||
tag = tokenRequest.Tag
|
||||
avatar = tokenRequest.Avatar
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
|
||||
if err == nil {
|
||||
if clientId == "" {
|
||||
clientId = tokenRequest.ClientId
|
||||
}
|
||||
if clientSecret == "" {
|
||||
clientSecret = tokenRequest.ClientSecret
|
||||
}
|
||||
if grantType == "" {
|
||||
grantType = tokenRequest.GrantType
|
||||
}
|
||||
if code == "" {
|
||||
code = tokenRequest.Code
|
||||
}
|
||||
if verifier == "" {
|
||||
verifier = tokenRequest.Verifier
|
||||
}
|
||||
if scope == "" {
|
||||
scope = tokenRequest.Scope
|
||||
}
|
||||
if username == "" {
|
||||
username = tokenRequest.Username
|
||||
}
|
||||
if password == "" {
|
||||
password = tokenRequest.Password
|
||||
}
|
||||
if tag == "" {
|
||||
tag = tokenRequest.Tag
|
||||
}
|
||||
if avatar == "" {
|
||||
avatar = tokenRequest.Avatar
|
||||
}
|
||||
if refreshToken == "" {
|
||||
refreshToken = tokenRequest.RefreshToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
host := c.Ctx.Request.Host
|
||||
oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
||||
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = oAuthtoken
|
||||
c.Data["json"] = token
|
||||
c.SetTokenErrorHttpStatus()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@ -15,10 +15,10 @@
|
||||
package controllers
|
||||
|
||||
type TokenRequest struct {
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
ClientId string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
GrantType string `json:"grant_type"`
|
||||
Code string `json:"code"`
|
||||
Verifier string `json:"code_verifier"`
|
||||
Scope string `json:"scope"`
|
||||
Username string `json:"username"`
|
||||
|
@ -161,7 +161,6 @@ func (c *ApiController) GetUser() {
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
|
||||
if id == "" && owner == "" {
|
||||
switch {
|
||||
case email != "":
|
||||
@ -176,11 +175,16 @@ func (c *ApiController) GetUser() {
|
||||
owner = util.GetOwnerFromId(id)
|
||||
}
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
||||
var organization *object.Organization
|
||||
organization, err = object.GetOrganization(util.GetId("admin", owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
|
||||
return
|
||||
}
|
||||
|
||||
if !organization.IsProfilePublic {
|
||||
requestUserId := c.GetSessionUsername()
|
||||
@ -472,16 +476,16 @@ func (c *ApiController) SetPassword() {
|
||||
isAdmin := c.IsAdmin()
|
||||
if isAdmin {
|
||||
if oldPassword != "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
} else if code == "" {
|
||||
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -506,6 +510,7 @@ func (c *ApiController) SetPassword() {
|
||||
// @Title CheckUserPassword
|
||||
// @router /check-user-password [post]
|
||||
// @Tag User API
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) CheckUserPassword() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
@ -514,11 +519,11 @@ func (c *ApiController) CheckUserPassword() {
|
||||
return
|
||||
}
|
||||
|
||||
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
|
||||
if msg == "" {
|
||||
c.ResponseOk()
|
||||
_, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
} else {
|
||||
c.ResponseError(msg)
|
||||
c.ResponseOk()
|
||||
}
|
||||
}
|
||||
|
||||
@ -572,11 +577,12 @@ func (c *ApiController) GetUserCount() {
|
||||
c.ResponseOk(count)
|
||||
}
|
||||
|
||||
// AddUserkeys
|
||||
// @Title AddUserkeys
|
||||
// AddUserKeys
|
||||
// @Title AddUserKeys
|
||||
// @router /add-user-keys [post]
|
||||
// @Tag User API
|
||||
func (c *ApiController) AddUserkeys() {
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) AddUserKeys() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
@ -585,7 +591,7 @@ func (c *ApiController) AddUserkeys() {
|
||||
}
|
||||
|
||||
isAdmin := c.IsAdmin()
|
||||
affected, err := object.AddUserkeys(&user, isAdmin)
|
||||
affected, err := object.AddUserKeys(&user, isAdmin)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -96,6 +96,13 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(userId, "app/") {
|
||||
tmpUserId := c.Input().Get("userId")
|
||||
if tmpUserId != "" {
|
||||
userId = tmpUserId
|
||||
}
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
|
@ -39,6 +39,7 @@ const (
|
||||
// @Title SendVerificationCode
|
||||
// @Tag Verification API
|
||||
// @router /send-verification-code [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
@ -53,17 +54,34 @@ func (c *ApiController) SendVerificationCode() {
|
||||
return
|
||||
}
|
||||
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
provider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if provider != nil {
|
||||
if vform.CaptchaType != provider.Type {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
|
||||
if provider.Type != "Default" {
|
||||
vform.ClientSecret = provider.ClientSecret
|
||||
}
|
||||
|
||||
if vform.CaptchaType != "none" {
|
||||
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
|
||||
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
|
||||
return
|
||||
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
} else if !isHuman {
|
||||
c.ResponseError(c.T("verification:Turing test failed."))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application, err := object.GetApplication(vform.ApplicationId)
|
||||
@ -142,6 +160,10 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("please add an Email provider to the \"Providers\" list for the application: %s", application.Name))
|
||||
return
|
||||
}
|
||||
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
|
||||
case object.VerifyTypePhone:
|
||||
@ -184,6 +206,10 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("please add a SMS provider to the \"Providers\" list for the application: %s", application.Name))
|
||||
return
|
||||
}
|
||||
|
||||
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
|
||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
||||
@ -204,6 +230,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
// @Title VerifyCaptcha
|
||||
// @Tag Verification API
|
||||
// @router /verify-captcha [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) VerifyCaptcha() {
|
||||
var vform form.VerificationForm
|
||||
err := c.ParseForm(&vform)
|
||||
@ -217,6 +244,16 @@ func (c *ApiController) VerifyCaptcha() {
|
||||
return
|
||||
}
|
||||
|
||||
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if captchaProvider.Type != "Default" {
|
||||
vform.ClientSecret = captchaProvider.ClientSecret
|
||||
}
|
||||
|
||||
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
|
||||
if provider == nil {
|
||||
c.ResponseError(c.T("verification:Invalid captcha provider."))
|
||||
@ -236,6 +273,7 @@ func (c *ApiController) VerifyCaptcha() {
|
||||
// @Tag Account API
|
||||
// @Title ResetEmailOrPhone
|
||||
// @router /api/reset-email-or-phone [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) ResetEmailOrPhone() {
|
||||
user, ok := c.RequireSignedInUser()
|
||||
if !ok {
|
||||
@ -330,6 +368,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
||||
// @Tag Verification API
|
||||
// @Title VerifyCode
|
||||
// @router /api/verify-code [post]
|
||||
// @Success 200 {object} object.Userinfo The Response object
|
||||
func (c *ApiController) VerifyCode() {
|
||||
var authForm form.AuthForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
|
||||
|
@ -146,7 +146,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
||||
}
|
||||
|
||||
// WebAuthnSigninFinish
|
||||
// @Title WebAuthnSigninBegin
|
||||
// @Title WebAuthnSigninFinish
|
||||
// @Tag Login API
|
||||
// @Description WebAuthn Login Flow 2nd stage
|
||||
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
|
||||
@ -154,6 +154,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
|
||||
// @router /webauthn/signin/finish [post]
|
||||
func (c *ApiController) WebAuthnSigninFinish() {
|
||||
responseType := c.Input().Get("responseType")
|
||||
clientId := c.Input().Get("clientId")
|
||||
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -182,7 +183,13 @@ func (c *ApiController) WebAuthnSigninFinish() {
|
||||
c.SetSessionUsername(userId)
|
||||
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
|
||||
|
||||
application, err := object.GetApplicationByUser(user)
|
||||
var application *object.Application
|
||||
|
||||
if clientId != "" && (responseType == ResponseTypeCode) {
|
||||
application, err = object.GetApplicationByClientId(clientId)
|
||||
} else {
|
||||
application, err = object.GetApplicationByUser(user)
|
||||
}
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
82
email/custom_http.go
Normal file
82
email/custom_http.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
|
||||
type HttpEmailProvider struct {
|
||||
endpoint string
|
||||
method string
|
||||
}
|
||||
|
||||
func NewHttpEmailProvider(endpoint string, method string) *HttpEmailProvider {
|
||||
client := &HttpEmailProvider{
|
||||
endpoint: endpoint,
|
||||
method: method,
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
|
||||
var req *http.Request
|
||||
var err error
|
||||
if c.method == "POST" {
|
||||
formValues := url.Values{}
|
||||
formValues.Set("fromName", fromName)
|
||||
formValues.Set("toAddress", toAddress)
|
||||
formValues.Set("subject", subject)
|
||||
formValues.Set("content", content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("fromName", fromName)
|
||||
q.Add("toAddress", toAddress)
|
||||
q.Add("subject", subject)
|
||||
q.Add("content", content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
} else {
|
||||
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HttpEmailProvider's Send() error, custom HTTP Email request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -18,9 +18,11 @@ type EmailProvider interface {
|
||||
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
||||
}
|
||||
|
||||
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool) EmailProvider {
|
||||
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool, endpoint string, method string) EmailProvider {
|
||||
if typ == "Azure ACS" {
|
||||
return NewAzureACSEmailProvider(clientSecret, host)
|
||||
} else if typ == "Custom HTTP Email" {
|
||||
return NewHttpEmailProvider(endpoint, method)
|
||||
} else {
|
||||
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||
}
|
||||
|
29
go.mod
29
go.mod
@ -4,18 +4,16 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/Masterminds/squirrel v1.5.3
|
||||
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
|
||||
github.com/aws/aws-sdk-go v1.45.5
|
||||
github.com/beego/beego v1.12.12
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin v1.9.1 // indirect
|
||||
github.com/casbin/casbin/v2 v2.77.2
|
||||
github.com/casdoor/go-sms-sender v0.15.0
|
||||
github.com/casdoor/go-sms-sender v0.19.0
|
||||
github.com/casdoor/gomail/v2 v2.0.1
|
||||
github.com/casdoor/notify v0.44.0
|
||||
github.com/casdoor/oss v1.3.0
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||
github.com/casdoor/notify v0.45.0
|
||||
github.com/casdoor/oss v1.4.1
|
||||
github.com/casdoor/xorm-adapter/v3 v3.1.0
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/denisenkom/go-mssqldb v0.9.0
|
||||
@ -23,26 +21,26 @@ require (
|
||||
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||
github.com/fogleman/gg v1.3.0
|
||||
github.com/forestmgy/ldapserver v1.1.0
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.5
|
||||
github.com/go-git/go-git/v5 v5.6.0
|
||||
github.com/go-ldap/ldap/v3 v3.3.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.6
|
||||
github.com/go-mysql-org/go-mysql v1.7.0
|
||||
github.com/go-pay/gopay v1.5.72
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/go-webauthn/webauthn v0.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/lestrrat-go/jwx v1.2.21
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||
github.com/markbates/goth v1.75.2
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||
github.com/nyaruka/phonenumbers v1.1.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.11.1
|
||||
github.com/prometheus/client_model v0.3.0
|
||||
github.com/prometheus/client_model v0.4.0
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/gosaml2 v0.9.0
|
||||
@ -61,11 +59,10 @@ require (
|
||||
github.com/xorm-io/core v0.7.4
|
||||
github.com/xorm-io/xorm v1.1.6
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/api v0.138.0
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.13.0
|
||||
google.golang.org/api v0.150.0
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Das Konto für den Anbieter %s und Benutzernamen %s (%s) existiert nicht und es ist nicht erlaubt, ein neues Konto anzumelden. Bitte wenden Sie sich an Ihren IT-Support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Das Konto für den Anbieter %s und Benutzernamen %s (%s) ist bereits mit einem anderen Konto verknüpft: %s (%s)",
|
||||
"The application: %s does not exist": "Die Anwendung: %s existiert nicht",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "Die Anmeldeart \"Anmeldung mit Passwort\" ist für die Anwendung nicht aktiviert",
|
||||
"The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert",
|
||||
"Unauthorized operation": "Nicht autorisierte Operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unbekannter Authentifizierungstyp (nicht Passwort oder Anbieter), Formular = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "E-Mail ist ungültig",
|
||||
"Empty username.": "Leerer Benutzername.",
|
||||
"FirstName cannot be blank": "Vorname darf nicht leer sein",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
|
||||
"LastName cannot be blank": "Nachname darf nicht leer sein",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Mehrere Konten mit derselben uid, bitte überprüfen Sie Ihren LDAP-Server",
|
||||
"Organization does not exist": "Organisation existiert nicht",
|
||||
"Password must have at least 6 characters": "Das Passwort muss mindestens 6 Zeichen enthalten",
|
||||
"Phone already exists": "Telefon existiert bereits",
|
||||
"Phone cannot be empty": "Das Telefon darf nicht leer sein",
|
||||
"Phone number is invalid": "Die Telefonnummer ist ungültig",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "La cuenta para el proveedor: %s y el nombre de usuario: %s (%s) no existe y no se permite registrarse como una nueva cuenta, por favor contacte a su soporte de TI",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "La cuenta para proveedor: %s y nombre de usuario: %s (%s) ya está vinculada a otra cuenta: %s (%s)",
|
||||
"The application: %s does not exist": "La aplicación: %s no existe",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "El método de inicio de sesión: inicio de sesión con contraseña no está habilitado para la aplicación",
|
||||
"The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación",
|
||||
"Unauthorized operation": "Operación no autorizada",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Tipo de autenticación desconocido (no es contraseña o proveedor), formulario = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Los servicios %s y %s no coinciden"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "El correo electrónico no es válido",
|
||||
"Empty username.": "Nombre de usuario vacío.",
|
||||
"FirstName cannot be blank": "El nombre no puede estar en blanco",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "Nombre de usuario o contraseña de Ldap incorrectos",
|
||||
"LastName cannot be blank": "El apellido no puede estar en blanco",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Cuentas múltiples con el mismo uid, por favor revise su servidor ldap",
|
||||
"Organization does not exist": "La organización no existe",
|
||||
"Password must have at least 6 characters": "La contraseña debe tener al menos 6 caracteres",
|
||||
"Phone already exists": "El teléfono ya existe",
|
||||
"Phone cannot be empty": "Teléfono no puede estar vacío",
|
||||
"Phone number is invalid": "El número de teléfono no es válido",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Le compte pour le fournisseur : %s et le nom d'utilisateur : %s (%s) n'existe pas et n'est pas autorisé à s'inscrire comme nouveau compte, veuillez contacter votre support informatique",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Le compte du fournisseur : %s et le nom d'utilisateur : %s (%s) sont déjà liés à un autre compte : %s (%s)",
|
||||
"The application: %s does not exist": "L'application : %s n'existe pas",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "La méthode de connexion : connexion avec mot de passe n'est pas activée 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",
|
||||
"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",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
|
||||
@ -33,17 +36,18 @@
|
||||
"Email is invalid": "L'adresse e-mail est invalide",
|
||||
"Empty username.": "Nom d'utilisateur vide.",
|
||||
"FirstName cannot be blank": "Le prénom ne peut pas être laissé vide",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "Nom d'utilisateur ou mot de passe LDAP incorrect",
|
||||
"LastName cannot be blank": "Le nom de famille ne peut pas être vide",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Plusieurs comptes avec le même identifiant d'utilisateur, veuillez vérifier votre serveur LDAP",
|
||||
"Organization does not exist": "L'organisation n'existe pas",
|
||||
"Password must have at least 6 characters": "Le mot de passe doit comporter au moins 6 caractères",
|
||||
"Phone already exists": "Le téléphone existe déjà",
|
||||
"Phone cannot be empty": "Le téléphone ne peut pas être vide",
|
||||
"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",
|
||||
"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é.",
|
||||
"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",
|
||||
@ -53,7 +57,7 @@
|
||||
"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",
|
||||
"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",
|
||||
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
||||
},
|
||||
@ -61,8 +65,8 @@
|
||||
"Missing parameter": "Paramètre manquant",
|
||||
"Please login first": "Veuillez d'abord vous connecter",
|
||||
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
|
||||
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
"don't support captchaProvider: ": "ne prend pas en charge captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "cette opération n’est pas autorisée en mode démo"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Le serveur LDAP existe"
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Akun untuk penyedia: %s dan nama pengguna: %s (%s) tidak ada dan tidak diizinkan untuk mendaftar sebagai akun baru, silakan hubungi dukungan IT Anda",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Akun untuk provider: %s dan username: %s (%s) sudah terhubung dengan akun lain: %s (%s)",
|
||||
"The application: %s does not exist": "Aplikasi: %s tidak ada",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut",
|
||||
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
|
||||
"Unauthorized operation": "Operasi tidak sah",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Jenis otentikasi tidak diketahui (bukan kata sandi atau pemberi), formulir = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email tidak valid",
|
||||
"Empty username.": "Nama pengguna kosong.",
|
||||
"FirstName cannot be blank": "Nama depan tidak boleh kosong",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
|
||||
"LastName cannot be blank": "Nama belakang tidak boleh kosong",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Beberapa akun dengan uid yang sama, harap periksa server ldap Anda",
|
||||
"Organization does not exist": "Organisasi tidak ada",
|
||||
"Password must have at least 6 characters": "Kata sandi harus memiliki minimal 6 karakter",
|
||||
"Phone already exists": "Telepon sudah ada",
|
||||
"Phone cannot be empty": "Telepon tidak boleh kosong",
|
||||
"Phone number is invalid": "Nomor telepon tidak valid",
|
||||
|
@ -15,23 +15,18 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"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": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
@ -41,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "プロバイダー名:%sとユーザー名:%s(%s)のアカウントは存在しません。新しいアカウントとしてサインアップすることはできません。 ITサポートに連絡してください",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "プロバイダのアカウント:%s とユーザー名:%s (%s) は既に別のアカウント:%s (%s) にリンクされています",
|
||||
"The application: %s does not exist": "アプリケーション: %sは存在しません",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "ログイン方法:パスワードでのログインはアプリケーションで有効になっていません",
|
||||
"The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません",
|
||||
"Unauthorized operation": "不正操作",
|
||||
"Unknown authentication type (not password or provider), form = %s": "不明な認証タイプ(パスワードまたはプロバイダーではない)フォーム=%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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "サービス%sと%sは一致しません"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "電子メールは無効です",
|
||||
"Empty username.": "空のユーザー名。",
|
||||
"FirstName cannot be blank": "ファーストネームは空白にできません",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
|
||||
"LastName cannot be blank": "姓は空白にできません",
|
||||
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
|
||||
"Organization does not exist": "組織は存在しません",
|
||||
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
|
||||
"Phone already exists": "電話はすでに存在しています",
|
||||
"Phone cannot be empty": "電話は空っぽにできません",
|
||||
"Phone number is invalid": "電話番号が無効です",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "공급자 계정 %s과 사용자 이름 %s (%s)는 존재하지 않으며 새 계정으로 등록할 수 없습니다. IT 지원팀에 문의하십시오",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "공급자 계정 %s과 사용자 이름 %s(%s)는 이미 다른 계정 %s(%s)에 연결되어 있습니다",
|
||||
"The application: %s does not exist": "해당 애플리케이션(%s)이 존재하지 않습니다",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "어플리케이션에서는 암호를 사용한 로그인 방법이 활성화되어 있지 않습니다",
|
||||
"The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다",
|
||||
"Unauthorized operation": "무단 조작",
|
||||
"Unknown authentication type (not password or provider), form = %s": "알 수 없는 인증 유형(암호 또는 공급자가 아님), 폼 = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "이메일이 유효하지 않습니다",
|
||||
"Empty username.": "빈 사용자 이름.",
|
||||
"FirstName cannot be blank": "이름은 공백일 수 없습니다",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
|
||||
"LastName cannot be blank": "성은 비어 있을 수 없습니다",
|
||||
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
|
||||
"Organization does not exist": "조직은 존재하지 않습니다",
|
||||
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
|
||||
"Phone already exists": "전화기는 이미 존재합니다",
|
||||
"Phone cannot be empty": "전화는 비워 둘 수 없습니다",
|
||||
"Phone number is invalid": "전화번호가 유효하지 않습니다",
|
||||
|
@ -15,23 +15,18 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"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": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
@ -41,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
|
||||
"The application: %s does not exist": "Приложение: %s не существует",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
|
||||
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
|
||||
"Unauthorized operation": "Несанкционированная операция",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Сервисы %s и %s не совпадают"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Адрес электронной почты недействительный",
|
||||
"Empty username.": "Пустое имя пользователя.",
|
||||
"FirstName cannot be blank": "Имя не может быть пустым",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
|
||||
"LastName cannot be blank": "Фамилия не может быть пустой",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
||||
"Organization does not exist": "Организация не существует",
|
||||
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
|
||||
"Phone already exists": "Телефон уже существует",
|
||||
"Phone cannot be empty": "Телефон не может быть пустым",
|
||||
"Phone number is invalid": "Номер телефона является недействительным",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,23 +15,18 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"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": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
@ -41,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
|
||||
"The application: %s does not exist": "The application: %s does not exist",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
|
||||
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
|
||||
"Unauthorized operation": "Unauthorized operation",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Email is invalid",
|
||||
"Empty username.": "Empty username.",
|
||||
"FirstName cannot be blank": "FirstName cannot be blank",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
|
||||
"LastName cannot be blank": "LastName cannot be blank",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
|
||||
"Organization does not exist": "Organization does not exist",
|
||||
"Password must have at least 6 characters": "Password must have at least 6 characters",
|
||||
"Phone already exists": "Phone already exists",
|
||||
"Phone cannot be empty": "Phone cannot be empty",
|
||||
"Phone number is invalid": "Phone number is invalid",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Tài khoản cho nhà cung cấp: %s và tên người dùng: %s (%s) không tồn tại và không được phép đăng ký như một tài khoản mới, vui lòng liên hệ với bộ phận hỗ trợ công nghệ thông tin của bạn",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Tài khoản cho nhà cung cấp: %s và tên người dùng: %s (%s) đã được liên kết với tài khoản khác: %s (%s)",
|
||||
"The application: %s does not exist": "Ứng dụng: %s không tồn tại",
|
||||
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
|
||||
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
|
||||
"The login method: login with password is not enabled for the application": "Phương thức đăng nhập: đăng nhập bằng mật khẩu không được kích hoạt cho ứng dụng",
|
||||
"The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng",
|
||||
"Unauthorized operation": "Hoạt động không được ủy quyền",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Loại xác thực không xác định (không phải mật khẩu hoặc nhà cung cấp), biểu mẫu = %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": "User's tag: %s is not listed in the application's tags",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp"
|
||||
@ -33,11 +36,12 @@
|
||||
"Email is invalid": "Địa chỉ email không hợp lệ",
|
||||
"Empty username.": "Tên đăng nhập trống.",
|
||||
"FirstName cannot be blank": "Tên không được để trống",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "Tên người dùng hoặc mật khẩu Ldap không chính xác",
|
||||
"LastName cannot be blank": "Họ không thể để trống",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Nhiều tài khoản với cùng một uid, vui lòng kiểm tra máy chủ ldap của bạn",
|
||||
"Organization does not exist": "Tổ chức không tồn tại",
|
||||
"Password must have at least 6 characters": "Mật khẩu phải ít nhất 6 ký tự",
|
||||
"Phone already exists": "Điện thoại đã tồn tại",
|
||||
"Phone cannot be empty": "Điện thoại không thể để trống",
|
||||
"Phone number is invalid": "Số điện thoại không hợp lệ",
|
||||
|
@ -15,11 +15,14 @@
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "提供商账户: %s 与用户名: %s (%s) 不存在且 不允许注册新账户, 请联系IT支持",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "提供商账户: %s与用户名: %s (%s)已经与其他账户绑定: %s (%s)",
|
||||
"The application: %s does not exist": "应用%s不存在",
|
||||
"The login method: login with SMS is not enabled for the application": "该应用禁止采用短信登录方式",
|
||||
"The login method: login with email is not enabled for the application": "该应用禁止采用邮箱登录方式",
|
||||
"The login method: login with password is not enabled for the application": "该应用禁止采用密码登录方式",
|
||||
"The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用",
|
||||
"Unauthorized operation": "未授权的操作",
|
||||
"Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s",
|
||||
"User's tag: %s is not listed in the application's tags": "用户的标签: %s不在该应用的标签列表中"
|
||||
"User's tag: %s is not listed in the application's tags": "用户的标签: %s不在该应用的标签列表中",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "服务%s与%s不匹配"
|
||||
@ -33,17 +36,18 @@
|
||||
"Email is invalid": "无效邮箱",
|
||||
"Empty username.": "用户名不可为空",
|
||||
"FirstName cannot be blank": "名不可以为空",
|
||||
"Invitation code cannot be blank": "Invitation code cannot be blank",
|
||||
"Invitation code is invalid": "Invitation code is invalid",
|
||||
"LDAP user name or password incorrect": "LDAP密码错误",
|
||||
"LastName cannot be blank": "姓不可以为空",
|
||||
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid,请检查您的 LDAP 服务器",
|
||||
"Organization does not exist": "组织不存在",
|
||||
"Password must have at least 6 characters": "新密码至少为6位",
|
||||
"Phone already exists": "该手机号已存在",
|
||||
"Phone cannot be empty": "手机号不可为空",
|
||||
"Phone number is invalid": "无效手机号",
|
||||
"Session outdated, please login again": "会话已过期,请重新登录",
|
||||
"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.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
||||
"Username already exists": "用户名已存在",
|
||||
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
||||
@ -62,7 +66,7 @@
|
||||
"Please login first": "请先登录",
|
||||
"The user: %s doesn't exist": "用户: %s不存在",
|
||||
"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 server exist": "LDAP服务器已存在"
|
||||
|
26
idp/adfs.go
26
idp/adfs.go
@ -19,7 +19,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
@ -84,11 +83,14 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
payload.Set("code", code)
|
||||
payload.Set("grant_type", "authorization_code")
|
||||
payload.Set("client_id", idp.Config.ClientID)
|
||||
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -97,10 +99,10 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
pToken := &AdfsToken{}
|
||||
err = json.Unmarshal(data, pToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
if pToken.ErrMsg != "" {
|
||||
return nil, fmt.Errorf("pToken.Errmsg = %s", pToken.ErrMsg)
|
||||
return nil, fmt.Errorf(pToken.ErrMsg)
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
@ -118,11 +120,25 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
keyset, err := jwk.ParseKey(body)
|
||||
body, err := io.ReadAll(resp.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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyset, err := jwk.ParseKey(respKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenSrc := []byte(token.AccessToken)
|
||||
publicKey, _ := keyset.PublicKey()
|
||||
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))
|
||||
|
126
idp/azuread_b2c.go
Normal file
126
idp/azuread_b2c.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 idp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type AzureADB2CProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
Tenant string
|
||||
UserFlow string
|
||||
}
|
||||
|
||||
func NewAzureAdB2cProvider(clientId, clientSecret, redirectUrl, tenant string, userFlow string) *AzureADB2CProvider {
|
||||
return &AzureADB2CProvider{
|
||||
Config: &oauth2.Config{
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("https://%s.b2clogin.com/%s.onmicrosoft.com/%s/oauth2/v2.0/authorize", tenant, tenant, userFlow),
|
||||
TokenURL: fmt.Sprintf("https://%s.b2clogin.com/%s.onmicrosoft.com/%s/oauth2/v2.0/token", tenant, tenant, userFlow),
|
||||
},
|
||||
Scopes: []string{"openid", "email"},
|
||||
},
|
||||
Tenant: tenant,
|
||||
UserFlow: userFlow,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AzureADB2CProvider) SetHttpClient(client *http.Client) {
|
||||
p.Client = client
|
||||
}
|
||||
|
||||
type AzureadB2cToken struct {
|
||||
IdToken string `json:"id_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
NotBefore int `json:"not_before"`
|
||||
IdTokenExpiresIn int `json:"id_token_expires_in"`
|
||||
ProfileInfo string `json:"profile_info"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
func (p *AzureADB2CProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
payload := url.Values{}
|
||||
payload.Set("code", code)
|
||||
payload.Set("grant_type", "authorization_code")
|
||||
payload.Set("client_id", p.Config.ClientID)
|
||||
payload.Set("client_secret", p.Config.ClientSecret)
|
||||
payload.Set("redirect_uri", p.Config.RedirectURL)
|
||||
|
||||
resp, err := p.Client.PostForm(p.Config.Endpoint.TokenURL, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pToken := &AzureadB2cToken{}
|
||||
err = json.Unmarshal(data, pToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: pToken.IdToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(pToken.IdTokenExpiresIn), 0),
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (p *AzureADB2CProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
userInfoEndpoint := fmt.Sprintf("https://%s.b2clogin.com/%s.onmicrosoft.com/%s/openid/v2.0/userinfo", p.Tenant, p.Tenant, p.UserFlow)
|
||||
req, err := http.NewRequest("GET", userInfoEndpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Authorization", "Bearer "+token.AccessToken)
|
||||
|
||||
resp, err := p.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("error fetching user info: status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userInfo UserInfo
|
||||
err = json.Unmarshal(bodyBytes, &userInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
24
idp/goth.go
24
idp/goth.go
@ -89,7 +89,7 @@ type GothIdProvider struct {
|
||||
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
|
||||
switch providerType {
|
||||
case "Amazon":
|
||||
@ -101,8 +101,24 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
if !strings.Contains(redirectUrl, "/api/callback") {
|
||||
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{
|
||||
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
|
||||
Provider: apple.New(clientId, *secret, redirectUrl, nil),
|
||||
Session: &apple.Session{},
|
||||
}
|
||||
case "AzureAD":
|
||||
@ -386,10 +402,10 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
Session: &zoom.Session{},
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType)
|
||||
}
|
||||
|
||||
return &idp
|
||||
return &idp, nil
|
||||
}
|
||||
|
||||
// SetHttpClient
|
||||
|
@ -15,6 +15,7 @@
|
||||
package idp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -30,16 +31,19 @@ type UserInfo struct {
|
||||
Phone string
|
||||
CountryCode string
|
||||
AvatarUrl string
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
type ProviderInfo struct {
|
||||
Type string
|
||||
SubType string
|
||||
ClientId string
|
||||
ClientSecret string
|
||||
AppId string
|
||||
HostUrl string
|
||||
RedirectUrl string
|
||||
Type string
|
||||
SubType string
|
||||
ClientId string
|
||||
ClientSecret string
|
||||
ClientId2 string
|
||||
ClientSecret2 string
|
||||
AppId string
|
||||
HostUrl string
|
||||
RedirectUrl string
|
||||
|
||||
TokenURL string
|
||||
AuthURL string
|
||||
@ -53,71 +57,73 @@ type IdProvider interface {
|
||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
}
|
||||
|
||||
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
|
||||
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) {
|
||||
switch idpInfo.Type {
|
||||
case "GitHub":
|
||||
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Google":
|
||||
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "QQ":
|
||||
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "WeChat":
|
||||
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Facebook":
|
||||
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "DingTalk":
|
||||
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Weibo":
|
||||
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Gitee":
|
||||
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "LinkedIn":
|
||||
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "WeCom":
|
||||
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" {
|
||||
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
} else {
|
||||
return nil
|
||||
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
|
||||
}
|
||||
case "Lark":
|
||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "GitLab":
|
||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
case "Adfs":
|
||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "ADFS":
|
||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||
case "AzureADB2C":
|
||||
return NewAzureAdB2cProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl, idpInfo.AppId), nil
|
||||
case "Baidu":
|
||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Alipay":
|
||||
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Custom":
|
||||
return NewCustomIdProvider(idpInfo, redirectUrl)
|
||||
return NewCustomIdProvider(idpInfo, redirectUrl), nil
|
||||
case "Infoflow":
|
||||
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" {
|
||||
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
|
||||
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
|
||||
} else {
|
||||
return nil
|
||||
return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType)
|
||||
}
|
||||
case "Casdoor":
|
||||
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
||||
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||
case "Okta":
|
||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||
case "Douyin":
|
||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Bilibili":
|
||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "MetaMask":
|
||||
return NewMetaMaskIdProvider()
|
||||
return NewMetaMaskIdProvider(), nil
|
||||
case "Web3Onboard":
|
||||
return NewWeb3OnboardIdProvider()
|
||||
return NewWeb3OnboardIdProvider(), nil
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
extra := make(map[string]string)
|
||||
extra["wechat_unionid"] = wechatUserInfo.Openid
|
||||
// For WeChat, different appId corresponds to different openId
|
||||
extra[BuildWechatOpenIdKey(idp.Config.ClientID)] = wechatUserInfo.Openid
|
||||
userInfo := UserInfo{
|
||||
Id: id,
|
||||
Username: wechatUserInfo.Nickname,
|
||||
DisplayName: wechatUserInfo.Nickname,
|
||||
AvatarUrl: wechatUserInfo.Headimgurl,
|
||||
Extra: extra,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func BuildWechatOpenIdKey(appId string) string {
|
||||
return fmt.Sprintf("wechat_openid_%s", appId)
|
||||
}
|
||||
|
||||
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
|
||||
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
|
||||
request, err := http.NewRequest("GET", accessTokenUrl, nil)
|
||||
|
@ -15,6 +15,7 @@
|
||||
"tags": [],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
|
||||
"masterPassword": "",
|
||||
"defaultPassword": "",
|
||||
"initScore": 2000,
|
||||
"enableSoftDeletion": false,
|
||||
"isProfilePublic": true,
|
||||
@ -44,6 +45,23 @@
|
||||
"alertType": "None"
|
||||
}
|
||||
],
|
||||
"signinMethods": [
|
||||
{
|
||||
"name": "Password",
|
||||
"displayName": "Password",
|
||||
"rule": "None",
|
||||
},
|
||||
{
|
||||
"name": "Verification code",
|
||||
"displayName": "Verification code",
|
||||
"rule": "All",
|
||||
},
|
||||
{
|
||||
"name": "WebAuthn",
|
||||
"displayName": "WebAuthn",
|
||||
"rule": "None",
|
||||
},
|
||||
],
|
||||
"signupItems": [
|
||||
{
|
||||
"name": "ID",
|
||||
@ -103,7 +121,9 @@
|
||||
}
|
||||
],
|
||||
"redirectUris": [""],
|
||||
"expireInHours": 168
|
||||
"expireInHours": 168,
|
||||
"failedSigninLimit": 5,
|
||||
"failedSigninfrozenTime": 15
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
|
163
ldap/server.go
163
ldap/server.go
@ -16,7 +16,9 @@ package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -25,6 +27,11 @@ import (
|
||||
)
|
||||
|
||||
func StartLdapServer() {
|
||||
ldapServerPort := conf.GetConfigString("ldapServerPort")
|
||||
if ldapServerPort == "" || ldapServerPort == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
server := ldap.NewServer()
|
||||
routes := ldap.NewRouteMux()
|
||||
|
||||
@ -32,7 +39,7 @@ func StartLdapServer() {
|
||||
routes.Search(handleSearch).Label(" SEARCH****")
|
||||
|
||||
server.Handle(routes)
|
||||
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
||||
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
|
||||
if err != nil {
|
||||
log.Printf("StartLdapServer() failed, err = %s", err.Error())
|
||||
}
|
||||
@ -43,21 +50,30 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
res := ldap.NewBindResponse(ldap.LDAPResultSuccess)
|
||||
|
||||
if r.AuthenticationChoice() == "simple" {
|
||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
|
||||
if err != "" {
|
||||
log.Printf("Bind failed ,ErrMsg=%s", err)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
||||
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
||||
bindDN := string(r.Name())
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
|
||||
if bindDN == "" && bindPassword == "" {
|
||||
res.SetResultCode(ldap.LDAPResultInappropriateAuthentication)
|
||||
res.SetDiagnosticMessage("Anonymous bind disallowed")
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
bindUsername, bindOrg, err := getNameAndOrgFromDN(bindDN)
|
||||
if err != nil {
|
||||
log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
|
||||
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
|
||||
res.SetDiagnosticMessage(fmt.Sprintf("getNameAndOrgFromDN() error: %s", err.Error()))
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
bindPassword := string(r.AuthenticationSimple())
|
||||
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
|
||||
if err != "" {
|
||||
if err != nil {
|
||||
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
|
||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
||||
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err.Error())
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
@ -73,7 +89,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
m.Client.OrgName = bindOrg
|
||||
} else {
|
||||
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
|
||||
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
||||
res.SetDiagnosticMessage("Authentication method not supported, please use Simple Authentication")
|
||||
}
|
||||
w.Write(res)
|
||||
}
|
||||
@ -87,7 +103,46 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
}
|
||||
|
||||
r := m.GetSearchRequest()
|
||||
if r.FilterString() == "(objectClass=*)" {
|
||||
|
||||
// case insensitive match
|
||||
if strings.EqualFold(r.FilterString(), "(objectClass=*)") {
|
||||
if len(r.Attributes()) == 0 {
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
first_attr := string(r.Attributes()[0])
|
||||
|
||||
if string(r.BaseObject()) == "" {
|
||||
// handle special search requests
|
||||
|
||||
if first_attr == "namingContexts" {
|
||||
orgs, code := GetFilteredOrganizations(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
e := ldap.NewSearchResultEntry(string(r.BaseObject()))
|
||||
dnlist := make([]message.AttributeValue, len(orgs))
|
||||
for i, org := range orgs {
|
||||
dnlist[i] = message.AttributeValue(fmt.Sprintf("ou=%s", org.Name))
|
||||
}
|
||||
e.AddAttribute("namingContexts", dnlist...)
|
||||
w.Write(e)
|
||||
} else if first_attr == "subschemaSubentry" {
|
||||
e := ldap.NewSearchResultEntry(string(r.BaseObject()))
|
||||
e.AddAttribute("subschemaSubentry", message.AttributeValue("cn=Subschema"))
|
||||
w.Write(e)
|
||||
}
|
||||
} else if strings.EqualFold(first_attr, "objectclasses") && string(r.BaseObject()) == "cn=Subschema" {
|
||||
e := ldap.NewSearchResultEntry(string(r.BaseObject()))
|
||||
e.AddAttribute("objectClasses", []message.AttributeValue{
|
||||
"( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) )",
|
||||
"( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) )",
|
||||
}...)
|
||||
w.Write(e)
|
||||
}
|
||||
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
@ -100,25 +155,77 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
|
||||
default:
|
||||
}
|
||||
|
||||
users, code := GetFilteredUsers(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
|
||||
for _, attr := range r.Attributes() {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
|
||||
if string(attr) == "cn" {
|
||||
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
|
||||
}
|
||||
objectClass := searchFilterForEquality(r.Filter(), "objectClass", "posixAccount", "posixGroup")
|
||||
switch objectClass {
|
||||
case "posixAccount":
|
||||
users, code := GetFilteredUsers(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(e)
|
||||
// log.Printf("Handling posixAccount filter=%s", r.FilterString())
|
||||
for _, user := range users {
|
||||
dn := fmt.Sprintf("uid=%s,cn=users,%s", user.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
attrs = AdditionalLdapUserAttributes
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
if strings.HasSuffix(string(attr), ";binary") {
|
||||
// unsupported: userCertificate;binary
|
||||
continue
|
||||
}
|
||||
field, ok := ldapUserAttributesMapping.CaseInsensitiveGet(string(attr))
|
||||
if ok {
|
||||
e.AddAttribute(message.AttributeDescription(attr), field.GetAttributeValues(user)...)
|
||||
}
|
||||
}
|
||||
w.Write(e)
|
||||
}
|
||||
|
||||
case "posixGroup":
|
||||
// log.Printf("Handling posixGroup filter=%s", r.FilterString())
|
||||
groups, code := GetFilteredGroups(m)
|
||||
if code != ldap.LDAPResultSuccess {
|
||||
res.SetResultCode(code)
|
||||
w.Write(res)
|
||||
return
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
dn := fmt.Sprintf("cn=%s,cn=groups,%s", group.Name, string(r.BaseObject()))
|
||||
e := ldap.NewSearchResultEntry(dn)
|
||||
attrs := r.Attributes()
|
||||
for _, attr := range attrs {
|
||||
if string(attr) == "*" {
|
||||
attrs = AdditionalLdapGroupAttributes
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
field, ok := ldapGroupAttributesMapping.CaseInsensitiveGet(string(attr))
|
||||
if ok {
|
||||
e.AddAttribute(message.AttributeDescription(attr), field.GetAttributeValues(group)...)
|
||||
}
|
||||
}
|
||||
w.Write(e)
|
||||
}
|
||||
|
||||
case "":
|
||||
log.Printf("Unmatched search request. filter=%s", r.FilterString())
|
||||
}
|
||||
|
||||
w.Write(res)
|
||||
}
|
||||
|
||||
func hash(s string) uint32 {
|
||||
h := fnv.New32a()
|
||||
h.Write([]byte(s))
|
||||
return h.Sum32()
|
||||
}
|
||||
|
476
ldap/util.go
476
ldap/util.go
@ -18,15 +18,274 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/lor00x/goldap/message"
|
||||
|
||||
ldap "github.com/forestmgy/ldapserver"
|
||||
|
||||
"github.com/xorm-io/builder"
|
||||
)
|
||||
|
||||
func getNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
type V = message.AttributeValue
|
||||
|
||||
type UserAttributeMapper func(user *object.User) []V
|
||||
|
||||
type UserFieldRelation struct {
|
||||
userField string
|
||||
ldapField string
|
||||
notSearchable bool
|
||||
hideOnStarOp bool
|
||||
fieldMapper UserAttributeMapper
|
||||
constantValue []V
|
||||
}
|
||||
|
||||
func (rel UserFieldRelation) GetField() (string, error) {
|
||||
if rel.notSearchable {
|
||||
return "", fmt.Errorf("attribute %s not supported", rel.userField)
|
||||
}
|
||||
return rel.userField, nil
|
||||
}
|
||||
|
||||
func (rel UserFieldRelation) GetAttributeValues(user *object.User) []V {
|
||||
if rel.constantValue != nil && rel.fieldMapper == nil {
|
||||
return rel.constantValue
|
||||
}
|
||||
return rel.fieldMapper(user)
|
||||
}
|
||||
|
||||
type UserFieldRelationMap map[string]UserFieldRelation
|
||||
|
||||
func (m UserFieldRelationMap) CaseInsensitiveGet(key string) (UserFieldRelation, bool) {
|
||||
lowerKey := strings.ToLower(key)
|
||||
ret, ok := m[lowerKey]
|
||||
return ret, ok
|
||||
}
|
||||
|
||||
type GroupAttributeMapper func(group *object.Group) []V
|
||||
|
||||
type GroupFieldRelation struct {
|
||||
groupField string
|
||||
ldapField string
|
||||
notSearchable bool
|
||||
hideOnStarOp bool
|
||||
fieldMapper GroupAttributeMapper
|
||||
constantValue []V
|
||||
}
|
||||
|
||||
func (rel GroupFieldRelation) GetField() (string, error) {
|
||||
if rel.notSearchable {
|
||||
return "", fmt.Errorf("attribute %s not supported", rel.groupField)
|
||||
}
|
||||
return rel.groupField, nil
|
||||
}
|
||||
|
||||
func (rel GroupFieldRelation) GetAttributeValues(group *object.Group) []V {
|
||||
if rel.constantValue != nil && rel.fieldMapper == nil {
|
||||
return rel.constantValue
|
||||
}
|
||||
return rel.fieldMapper(group)
|
||||
}
|
||||
|
||||
type GroupFieldRelationMap map[string]GroupFieldRelation
|
||||
|
||||
func (m GroupFieldRelationMap) CaseInsensitiveGet(key string) (GroupFieldRelation, bool) {
|
||||
lowerKey := strings.ToLower(key)
|
||||
ret, ok := m[lowerKey]
|
||||
return ret, ok
|
||||
}
|
||||
|
||||
var ldapUserAttributesMapping = UserFieldRelationMap{
|
||||
"cn": {ldapField: "cn", userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Name)}
|
||||
}},
|
||||
"uid": {ldapField: "uid", userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Name)}
|
||||
}},
|
||||
"displayname": {ldapField: "displayName", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.DisplayName)}
|
||||
}},
|
||||
"email": {ldapField: "email", userField: "email", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Email)}
|
||||
}},
|
||||
"mail": {ldapField: "mail", userField: "email", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Email)}
|
||||
}},
|
||||
"mobile": {ldapField: "mobile", userField: "phone", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Phone)}
|
||||
}},
|
||||
"telephonenumber": {ldapField: "telephoneNumber", userField: "phone", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Phone)}
|
||||
}},
|
||||
"postaladdress": {ldapField: "postalAddress", userField: "address", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(strings.Join(user.Address, " "))}
|
||||
}},
|
||||
"title": {ldapField: "title", userField: "title", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.Title)}
|
||||
}},
|
||||
"gecos": {ldapField: "gecos", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.DisplayName)}
|
||||
}},
|
||||
"description": {ldapField: "description", userField: "displayName", fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(user.DisplayName)}
|
||||
}},
|
||||
"logindisabled": {ldapField: "loginDisabled", userField: "isForbidden", fieldMapper: func(user *object.User) []V {
|
||||
if user.IsForbidden {
|
||||
return []V{V("1")}
|
||||
} else {
|
||||
return []V{V("0")}
|
||||
}
|
||||
}},
|
||||
"userpassword": {
|
||||
ldapField: "userPassword",
|
||||
userField: "userPassword",
|
||||
notSearchable: true,
|
||||
fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(getUserPasswordWithType(user))}
|
||||
},
|
||||
},
|
||||
"uidnumber": {ldapField: "uidNumber", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V(fmt.Sprintf("%v", hash(user.Name)))}
|
||||
}},
|
||||
"gidnumber": {ldapField: "gidNumber", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
if len(user.Groups) == 0 {
|
||||
return []V{V("")}
|
||||
}
|
||||
group, err := object.GetGroup(user.Groups[0])
|
||||
if err != nil {
|
||||
log.Printf("gidnumber object.GetGroup error: %s", err)
|
||||
return []V{V("")}
|
||||
}
|
||||
return []V{V(fmt.Sprintf("%v", hash(group.Name)))}
|
||||
}},
|
||||
"homedirectory": {ldapField: "homeDirectory", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
return []V{V("/home/" + user.Name)}
|
||||
}},
|
||||
"loginshell": {ldapField: "loginShell", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
if user.IsForbidden || user.IsDeleted {
|
||||
return []V{V("/sbin/nologin")}
|
||||
} else {
|
||||
return []V{V("/bin/bash")}
|
||||
}
|
||||
}},
|
||||
"shadowlastchange": {ldapField: "shadowLastChange", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
// "this attribute specifies number of days between January 1, 1970, and the date that the password was last modified"
|
||||
updatedTime, err := time.Parse(time.RFC3339, user.UpdatedTime)
|
||||
if err != nil {
|
||||
log.Printf("shadowlastchange time.Parse error: %s", err)
|
||||
updatedTime = time.Now()
|
||||
}
|
||||
return []V{V(fmt.Sprint(updatedTime.Unix() / 86400))}
|
||||
}},
|
||||
"pwdchangedtime": {ldapField: "pwdChangedTime", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
updatedTime, err := time.Parse(time.RFC3339, user.UpdatedTime)
|
||||
if err != nil {
|
||||
log.Printf("pwdchangedtime time.Parse error: %s", err)
|
||||
updatedTime = time.Now()
|
||||
}
|
||||
return []V{V(updatedTime.UTC().Format("20060102030405Z"))}
|
||||
}},
|
||||
"shadowmin": {ldapField: "shadowMin", notSearchable: true, constantValue: []V{V("0")}},
|
||||
"shadowmax": {ldapField: "shadowMax", notSearchable: true, constantValue: []V{V("99999")}},
|
||||
"shadowwarning": {ldapField: "shadowWarning", notSearchable: true, constantValue: []V{V("7")}},
|
||||
"shadowexpire": {ldapField: "shadowExpire", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
if user.IsForbidden {
|
||||
return []V{V("1")}
|
||||
} else {
|
||||
return []V{V("-1")}
|
||||
}
|
||||
}},
|
||||
"shadowinactive": {ldapField: "shadowInactive", notSearchable: true, constantValue: []V{V("0")}},
|
||||
"shadowflag": {ldapField: "shadowFlag", notSearchable: true, constantValue: []V{V("0")}},
|
||||
"memberof": {ldapField: "memberOf", notSearchable: true, fieldMapper: func(user *object.User) []V {
|
||||
var groupdn []V
|
||||
for _, groupId := range user.Groups {
|
||||
group, err := object.GetGroup(groupId)
|
||||
if err != nil {
|
||||
log.Printf("memberOf object.GetGroup error: %s", err)
|
||||
continue
|
||||
}
|
||||
groupdn = append(groupdn, V(fmt.Sprintf("cn=%s,cn=groups,ou=%s", group.Name, group.Owner)))
|
||||
}
|
||||
return groupdn
|
||||
}},
|
||||
"objectclass": {ldapField: "objectClass", notSearchable: true, constantValue: []V{
|
||||
V("top"),
|
||||
V("posixAccount"),
|
||||
V("shadowAccount"),
|
||||
V("person"),
|
||||
V("organizationalPerson"),
|
||||
V("inetOrgPerson"),
|
||||
V("apple-user"),
|
||||
V("sambaSamAccount"),
|
||||
V("sambaIdmapEntry"),
|
||||
V("extensibleObject"),
|
||||
}},
|
||||
}
|
||||
|
||||
var ldapGroupAttributesMapping = GroupFieldRelationMap{
|
||||
"cn": {ldapField: "cn", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
||||
return []V{V(group.Name)}
|
||||
}},
|
||||
"gidnumber": {ldapField: "gidNumber", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
||||
return []V{V(fmt.Sprintf("%v", hash(group.Name)))}
|
||||
}},
|
||||
"member": {ldapField: "member", fieldMapper: func(group *object.Group) []V {
|
||||
users, err := object.GetGroupUsers(group.GetId())
|
||||
if err != nil {
|
||||
log.Printf("member object.GetGroupUsers error: %s", err)
|
||||
return []V{V("")}
|
||||
}
|
||||
var members []V
|
||||
for _, user := range users {
|
||||
members = append(members, V(fmt.Sprintf("uid=%s,cn=users,ou=%s", user.Name, user.Owner)))
|
||||
}
|
||||
return members
|
||||
}},
|
||||
"memberuid": {ldapField: "memberUid", fieldMapper: func(group *object.Group) []V {
|
||||
users, err := object.GetGroupUsers(group.GetId())
|
||||
if err != nil {
|
||||
log.Printf("member object.GetGroupUsers error: %s", err)
|
||||
return []V{V("")}
|
||||
}
|
||||
var members []message.AttributeValue
|
||||
for _, user := range users {
|
||||
members = append(members, message.AttributeValue(user.Name))
|
||||
}
|
||||
return members
|
||||
}},
|
||||
"description": {ldapField: "description", hideOnStarOp: true, fieldMapper: func(group *object.Group) []V {
|
||||
return []V{V(group.DisplayName)}
|
||||
}},
|
||||
"objectclass": {ldapField: "objectClass", hideOnStarOp: true, constantValue: []V{
|
||||
V("top"),
|
||||
V("posixGroup"),
|
||||
}},
|
||||
}
|
||||
|
||||
var (
|
||||
AdditionalLdapUserAttributes []message.LDAPString
|
||||
AdditionalLdapGroupAttributes []message.LDAPString
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, v := range ldapUserAttributesMapping {
|
||||
if v.hideOnStarOp {
|
||||
continue
|
||||
}
|
||||
AdditionalLdapUserAttributes = append(AdditionalLdapUserAttributes, message.LDAPString(v.ldapField))
|
||||
}
|
||||
for _, v := range ldapGroupAttributesMapping {
|
||||
if v.hideOnStarOp {
|
||||
continue
|
||||
}
|
||||
AdditionalLdapGroupAttributes = append(AdditionalLdapGroupAttributes, message.LDAPString(v.ldapField))
|
||||
}
|
||||
}
|
||||
|
||||
func getNameAndOrgFromDN(DN string) (string, string, error) {
|
||||
DNFields := strings.Split(DN, ",")
|
||||
params := make(map[string]string, len(DNFields))
|
||||
for _, field := range DNFields {
|
||||
@ -37,12 +296,12 @@ func getNameAndOrgFromDN(DN string) (string, string, string) {
|
||||
}
|
||||
|
||||
if params["cn"] == "" {
|
||||
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
||||
return "", "", fmt.Errorf("please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com")
|
||||
}
|
||||
if params["ou"] == "" {
|
||||
return params["cn"], object.CasdoorOrganization, ""
|
||||
return params["cn"], object.CasdoorOrganization, nil
|
||||
}
|
||||
return params["cn"], params["ou"], ""
|
||||
return params["cn"], params["ou"], nil
|
||||
}
|
||||
|
||||
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
||||
@ -50,7 +309,11 @@ func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
|
||||
return "", "", ldap.LDAPResultInvalidDNSyntax
|
||||
}
|
||||
|
||||
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
||||
name, org, err := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return name, org, ldap.LDAPResultSuccess
|
||||
}
|
||||
|
||||
@ -83,6 +346,92 @@ func stringInSlice(value string, list []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
|
||||
switch f := filter.(type) {
|
||||
case message.FilterAnd:
|
||||
conditions := make([]builder.Cond, len(f))
|
||||
for i, v := range f {
|
||||
cond, err := buildUserFilterCondition(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = cond
|
||||
}
|
||||
return builder.And(conditions...), nil
|
||||
case message.FilterOr:
|
||||
conditions := make([]builder.Cond, len(f))
|
||||
for i, v := range f {
|
||||
cond, err := buildUserFilterCondition(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditions[i] = cond
|
||||
}
|
||||
return builder.Or(conditions...), nil
|
||||
case message.FilterNot:
|
||||
cond, err := buildUserFilterCondition(f.Filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Not{cond}, nil
|
||||
case message.FilterEqualityMatch:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Eq{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterPresent:
|
||||
field, err := getUserFieldFromAttribute(string(f))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.NotNull{field}, nil
|
||||
case message.FilterGreaterOrEqual:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Gte{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterLessOrEqual:
|
||||
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builder.Lte{field: string(f.AssertionValue())}, nil
|
||||
case message.FilterSubstrings:
|
||||
field, err := getUserFieldFromAttribute(string(f.Type_()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var expr string
|
||||
for _, substring := range f.Substrings() {
|
||||
switch s := substring.(type) {
|
||||
case message.SubstringInitial:
|
||||
expr += string(s) + "%"
|
||||
continue
|
||||
case message.SubstringAny:
|
||||
expr += string(s) + "%"
|
||||
continue
|
||||
case message.SubstringFinal:
|
||||
expr += string(s)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return builder.Expr(field+" LIKE ?", expr), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("LDAP filter operation %#v not supported", f)
|
||||
}
|
||||
}
|
||||
|
||||
func buildSafeCondition(filter interface{}) builder.Cond {
|
||||
condition, err := buildUserFilterCondition(filter)
|
||||
if err != nil {
|
||||
log.Printf("err = %v", err.Error())
|
||||
return nil
|
||||
}
|
||||
return condition
|
||||
}
|
||||
|
||||
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
|
||||
var err error
|
||||
r := m.GetSearchRequest()
|
||||
@ -94,15 +443,14 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
|
||||
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||
if m.Client.IsGlobalAdmin && org == "*" {
|
||||
|
||||
filteredUsers, err = object.GetGlobalUsers()
|
||||
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return filteredUsers, ldap.LDAPResultSuccess
|
||||
}
|
||||
if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
|
||||
filteredUsers, err = object.GetUsers(org)
|
||||
filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -144,7 +492,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
return nil, ldap.LDAPResultNoSuchObject
|
||||
}
|
||||
|
||||
users, err := object.GetUsersByTag(org, name)
|
||||
users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -154,6 +502,52 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
||||
}
|
||||
}
|
||||
|
||||
func GetFilteredOrganizations(m *ldap.Message) ([]*object.Organization, int) {
|
||||
if m.Client.IsGlobalAdmin {
|
||||
organizations, err := object.GetOrganizations("")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return organizations, ldap.LDAPResultSuccess
|
||||
} else if m.Client.IsOrgAdmin {
|
||||
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
||||
user, err := object.GetUser(requestUserId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
organization, err := object.GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return []*object.Organization{organization}, ldap.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
}
|
||||
|
||||
func GetFilteredGroups(m *ldap.Message) ([]*object.Group, int) {
|
||||
if m.Client.IsGlobalAdmin {
|
||||
groups, err := object.GetGroups("")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return groups, ldap.LDAPResultSuccess
|
||||
} else if m.Client.IsOrgAdmin {
|
||||
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
|
||||
user, err := object.GetUser(requestUserId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
groups, err := object.GetGroups(user.Owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return groups, ldap.LDAPResultSuccess
|
||||
} else {
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
}
|
||||
|
||||
// get user password with hash type prefix
|
||||
// TODO not handle salt yet
|
||||
// @return {md5}5f4dcc3b5aa765d61d8327deb882cf99
|
||||
@ -177,25 +571,49 @@ func getUserPasswordWithType(user *object.User) string {
|
||||
return fmt.Sprintf("{%s}%s", prefix, user.Password)
|
||||
}
|
||||
|
||||
func getAttribute(attributeName string, user *object.User) message.AttributeValue {
|
||||
switch attributeName {
|
||||
case "cn":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "uid":
|
||||
return message.AttributeValue(user.Name)
|
||||
case "displayname":
|
||||
return message.AttributeValue(user.DisplayName)
|
||||
case "email":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mail":
|
||||
return message.AttributeValue(user.Email)
|
||||
case "mobile":
|
||||
return message.AttributeValue(user.Phone)
|
||||
case "title":
|
||||
return message.AttributeValue(user.Tag)
|
||||
case "userPassword":
|
||||
return message.AttributeValue(getUserPasswordWithType(user))
|
||||
default:
|
||||
return ""
|
||||
func getUserFieldFromAttribute(attributeName string) (string, error) {
|
||||
v, ok := ldapUserAttributesMapping.CaseInsensitiveGet(attributeName)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("attribute %s not supported", attributeName)
|
||||
}
|
||||
return v.GetField()
|
||||
}
|
||||
|
||||
func searchFilterForEquality(filter message.Filter, desc string, values ...string) string {
|
||||
switch f := filter.(type) {
|
||||
case message.FilterAnd:
|
||||
for _, child := range f {
|
||||
if val := searchFilterForEquality(child, desc, values...); val != "" {
|
||||
return val
|
||||
}
|
||||
}
|
||||
case message.FilterOr:
|
||||
for _, child := range f {
|
||||
if val := searchFilterForEquality(child, desc, values...); val != "" {
|
||||
return val
|
||||
}
|
||||
}
|
||||
case message.FilterNot:
|
||||
return searchFilterForEquality(f.Filter, desc, values...)
|
||||
case message.FilterSubstrings:
|
||||
// Handle FilterSubstrings case if needed
|
||||
case message.FilterEqualityMatch:
|
||||
if strings.EqualFold(string(f.AttributeDesc()), desc) {
|
||||
for _, value := range values {
|
||||
if val := string(f.AssertionValue()); val == value {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
case message.FilterGreaterOrEqual:
|
||||
// Handle FilterGreaterOrEqual case if needed
|
||||
case message.FilterLessOrEqual:
|
||||
// Handle FilterLessOrEqual case if needed
|
||||
case message.FilterPresent:
|
||||
// Handle FilterPresent case if needed
|
||||
case message.FilterApproxMatch:
|
||||
// Handle FilterApproxMatch case if needed
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
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
|
||||
}
|
3
main.go
3
main.go
@ -34,7 +34,6 @@ func main() {
|
||||
object.InitFlag()
|
||||
object.InitAdapter()
|
||||
object.CreateTables()
|
||||
object.DoMigration()
|
||||
|
||||
object.InitDb()
|
||||
object.InitFromFile()
|
||||
@ -59,7 +58,7 @@ func main() {
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
||||
beego.InsertFilter("*", beego.AfterExec, routers.RecordMessage, false)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
|
||||
|
@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
name: casdoor
|
||||
name: casdoor-helm-charts
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
@ -15,10 +15,10 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.0
|
||||
version: 0.3.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "1.16.0"
|
||||
appVersion: "1.18.0"
|
||||
|
@ -59,6 +59,9 @@ spec:
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /conf
|
||||
{{ if .Values.extraContainersEnabled }}
|
||||
{{- .Values.extraContainers | nindent 8 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: config-volume
|
||||
projected:
|
||||
|
@ -1,5 +1,5 @@
|
||||
{{- if .Values.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2beta1
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "casdoor.fullname" . }}
|
||||
@ -17,12 +17,15 @@ spec:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
target:
|
||||
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
@ -22,14 +22,15 @@ config: |
|
||||
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
|
||||
dbName = casdoor
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = ""
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
origin = "https://door.casbin.com"
|
||||
origin =
|
||||
enableGzip = true
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
@ -107,3 +108,10 @@ nodeSelector: {}
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
||||
|
||||
# -- Optionally add extra sidecar containers.
|
||||
extraContainersEnabled: false
|
||||
extraContainers: ""
|
||||
# extraContainers: |
|
||||
# - name: ...
|
||||
# image: ...
|
@ -15,10 +15,11 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
)
|
||||
@ -39,26 +40,29 @@ func NewCustomHttpProvider(endpoint string, method string, paramName string) (*H
|
||||
}
|
||||
|
||||
func (c *HttpNotificationClient) Send(ctx context.Context, subject string, content string) error {
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
|
||||
req, err := http.NewRequest(c.method, c.endpoint, bytes.NewBufferString(content))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.method == "POST" {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.PostForm = map[string][]string{
|
||||
c.paramName: {content},
|
||||
formValues := url.Values{}
|
||||
formValues.Set(c.paramName, content)
|
||||
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else if c.method == "GET" {
|
||||
req, err = http.NewRequest(c.method, c.endpoint, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add(c.paramName, content)
|
||||
req.URL.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
httpClient := proxy.DefaultHttpClient
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -66,7 +70,7 @@ func (c *HttpNotificationClient) Send(ctx context.Context, subject string, conte
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
|
||||
return fmt.Errorf("HttpNotificationClient's SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -24,12 +24,26 @@ import (
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type SigninMethod struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
type SignupItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Rule string `json:"rule"`
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Label string `json:"label"`
|
||||
Placeholder string `json:"placeholder"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
||||
|
||||
type SamlItem struct {
|
||||
Name string `json:"name"`
|
||||
NameFormat string `json:"nameformat"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type Application struct {
|
||||
@ -49,17 +63,20 @@ type Application struct {
|
||||
EnableAutoSignin bool `json:"enableAutoSignin"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
|
||||
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
@ -80,6 +97,9 @@ type Application struct {
|
||||
FormOffset int `json:"formOffset"`
|
||||
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
|
||||
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
|
||||
|
||||
FailedSigninLimit int `json:"failedSigninLimit"`
|
||||
FailedSigninfrozenTime int `json:"failedSigninfrozenTime"`
|
||||
}
|
||||
|
||||
func GetApplicationCount(owner, field, value string) (int64, error) {
|
||||
@ -178,6 +198,30 @@ func extendApplicationWithOrg(application *Application) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func extendApplicationWithSigninMethods(application *Application) (err error) {
|
||||
if len(application.SigninMethods) == 0 {
|
||||
if application.EnablePassword {
|
||||
signinMethod := &SigninMethod{Name: "Password", DisplayName: "Password", Rule: "None"}
|
||||
application.SigninMethods = append(application.SigninMethods, signinMethod)
|
||||
}
|
||||
if application.EnableCodeSignin {
|
||||
signinMethod := &SigninMethod{Name: "Verification code", DisplayName: "Verification code", Rule: "All"}
|
||||
application.SigninMethods = append(application.SigninMethods, signinMethod)
|
||||
}
|
||||
if application.EnableWebAuthn {
|
||||
signinMethod := &SigninMethod{Name: "WebAuthn", DisplayName: "WebAuthn", Rule: "None"}
|
||||
application.SigninMethods = append(application.SigninMethods, signinMethod)
|
||||
}
|
||||
}
|
||||
|
||||
if len(application.SigninMethods) == 0 {
|
||||
signinMethod := &SigninMethod{Name: "Password", DisplayName: "Password", Rule: "None"}
|
||||
application.SigninMethods = append(application.SigninMethods, signinMethod)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getApplication(owner string, name string) (*Application, error) {
|
||||
if owner == "" || name == "" {
|
||||
return nil, nil
|
||||
@ -200,6 +244,11 @@ func getApplication(owner string, name string) (*Application, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = extendApplicationWithSigninMethods(&application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &application, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
@ -224,6 +273,11 @@ func GetApplicationByOrganizationName(organization string) (*Application, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = extendApplicationWithSigninMethods(&application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &application, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
@ -271,6 +325,11 @@ func GetApplicationByClientId(clientId string) (*Application, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = extendApplicationWithSigninMethods(&application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &application, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
@ -306,6 +365,12 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
||||
if application.OrganizationObj.MasterPassword != "" {
|
||||
application.OrganizationObj.MasterPassword = "***"
|
||||
}
|
||||
if application.OrganizationObj.DefaultPassword != "" {
|
||||
application.OrganizationObj.DefaultPassword = "***"
|
||||
}
|
||||
if application.OrganizationObj.MasterVerificationCode != "" {
|
||||
application.OrganizationObj.MasterVerificationCode = "***"
|
||||
}
|
||||
if application.OrganizationObj.PasswordType != "" {
|
||||
application.OrganizationObj.PasswordType = "***"
|
||||
}
|
||||
@ -332,6 +397,34 @@ func GetMaskedApplications(applications []*Application, userId string) []*Applic
|
||||
return applications
|
||||
}
|
||||
|
||||
func GetAllowedApplications(applications []*Application, userId string) ([]*Application, error) {
|
||||
if userId == "" || isUserIdGlobalAdmin(userId) {
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
user, err := GetUser(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user != nil && user.IsAdmin {
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
res := []*Application{}
|
||||
for _, application := range applications {
|
||||
var allowed bool
|
||||
allowed, err = CheckLoginPermission(userId, application)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if allowed {
|
||||
res = append(res, application)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func UpdateApplication(id string, application *Application) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
oldApplication, err := getApplication(owner, name)
|
||||
@ -438,6 +531,45 @@ func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (application *Application) IsPasswordEnabled() bool {
|
||||
if len(application.SigninMethods) == 0 {
|
||||
return application.EnablePassword
|
||||
} else {
|
||||
for _, signinMethod := range application.SigninMethods {
|
||||
if signinMethod.Name == "Password" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (application *Application) IsCodeSigninViaEmailEnabled() bool {
|
||||
if len(application.SigninMethods) == 0 {
|
||||
return application.EnableCodeSignin
|
||||
} else {
|
||||
for _, signinMethod := range application.SigninMethods {
|
||||
if signinMethod.Name == "Verification code" && signinMethod.Rule != "Phone only" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (application *Application) IsCodeSigninViaSmsEnabled() bool {
|
||||
if len(application.SigninMethods) == 0 {
|
||||
return application.EnableCodeSignin
|
||||
} else {
|
||||
for _, signinMethod := range application.SigninMethods {
|
||||
if signinMethod.Name == "Verification code" && signinMethod.Rule != "Email only" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func IsOriginAllowed(origin string) (bool, error) {
|
||||
applications, err := GetApplications("")
|
||||
if err != nil {
|
||||
@ -532,7 +664,7 @@ func applicationChangeTrigger(oldName string, newName string) error {
|
||||
}
|
||||
}
|
||||
permissions[i].Resources = permissionResoureces
|
||||
_, err = session.Where("name=?", permissions[i].Name).Update(permissions[i])
|
||||
_, err = session.Where("owner=?", permissions[i].Owner).Where("name=?", permissions[i].Name).Update(permissions[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ func GetGlobalCertsCount(field, value string) (int64, error) {
|
||||
return session.Count(&Cert{})
|
||||
}
|
||||
|
||||
func GetGlobleCerts() ([]*Cert, error) {
|
||||
func GetGlobalCerts() ([]*Cert, error) {
|
||||
certs := []*Cert{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&certs)
|
||||
if err != nil {
|
||||
@ -163,6 +163,12 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
err := cert.populateContent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -172,10 +178,9 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
|
||||
}
|
||||
|
||||
func AddCert(cert *Cert) (bool, error) {
|
||||
if cert.Certificate == "" || cert.PrivateKey == "" {
|
||||
certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
|
||||
cert.Certificate = certificate
|
||||
cert.PrivateKey = privateKey
|
||||
err := cert.populateContent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(cert)
|
||||
@ -199,6 +204,20 @@ func (p *Cert) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func (p *Cert) populateContent() error {
|
||||
if p.Certificate == "" || p.PrivateKey == "" {
|
||||
certificate, privateKey, err := generateRsaKeys(p.BitSize, p.ExpireInYears, p.Name, p.Owner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.Certificate = certificate
|
||||
p.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCertByApplication(application *Application) (*Cert, error) {
|
||||
if application.Cert != "" {
|
||||
return getCertByName(application.Cert)
|
||||
|
133
object/check.go
133
object/check.go
@ -28,8 +28,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SigninWrongTimesLimit = 5
|
||||
LastSignWrongTimeDuration = time.Minute * 15
|
||||
DefaultFailedSigninLimit = 5
|
||||
// DefaultFailedSigninfrozenTime The unit of frozen time is minutes
|
||||
DefaultFailedSigninfrozenTime = 15
|
||||
)
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
|
||||
@ -142,45 +143,52 @@ func CheckUserSignup(application *Application, organization *Organization, form
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkSigninErrorTimes(user *User, lang string) string {
|
||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||
func checkSigninErrorTimes(user *User, lang string) error {
|
||||
failedSigninLimit, failedSigninfrozenTime, err := GetFailedSigninConfigByUser(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user.SigninWrongTimes >= failedSigninLimit {
|
||||
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
|
||||
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
|
||||
minutes := int(LastSignWrongTimeDuration.Minutes() - passedTime.Minutes())
|
||||
minutes := failedSigninfrozenTime - int(passedTime.Minutes())
|
||||
|
||||
// deny the login if the error times is greater than the limit and the last login time is less than the duration
|
||||
if minutes > 0 {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
|
||||
}
|
||||
|
||||
// reset the error times
|
||||
user.SigninWrongTimes = 0
|
||||
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
|
||||
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckPassword(user *User, password string, lang string, options ...bool) string {
|
||||
func CheckPassword(user *User, password string, lang string, options ...bool) error {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
// check the login error times
|
||||
if !enableCaptcha {
|
||||
if msg := checkSigninErrorTimes(user, lang); msg != "" {
|
||||
return msg
|
||||
err := checkSigninErrorTimes(user, lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
organization, err := GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if organization == nil {
|
||||
return i18n.Translate(lang, "check:Organization does not exist")
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
|
||||
}
|
||||
|
||||
passwordType := user.PasswordType
|
||||
@ -191,19 +199,17 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
|
||||
if credManager != nil {
|
||||
if organization.MasterPassword != "" {
|
||||
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
|
||||
resetUserSigninErrorTimes(user)
|
||||
return ""
|
||||
return resetUserSigninErrorTimes(user)
|
||||
}
|
||||
}
|
||||
|
||||
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
|
||||
resetUserSigninErrorTimes(user)
|
||||
return ""
|
||||
return resetUserSigninErrorTimes(user)
|
||||
}
|
||||
|
||||
return recordSigninErrorInfo(user, lang, enableCaptcha)
|
||||
} else {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,10 +223,10 @@ func CheckPasswordComplexity(user *User, password string) string {
|
||||
return CheckPasswordComplexityByOrg(organization, password)
|
||||
}
|
||||
|
||||
func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||
func checkLdapUserPassword(user *User, password string, lang string) error {
|
||||
ldaps, err := GetLdaps(user.Owner)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
return err
|
||||
}
|
||||
|
||||
ldapLoginSuccess := false
|
||||
@ -237,65 +243,73 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
|
||||
|
||||
searchResult, err := conn.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
conn.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
if len(searchResult.Entries) > 1 {
|
||||
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
|
||||
conn.Close()
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
|
||||
}
|
||||
|
||||
hit = true
|
||||
dn := searchResult.Entries[0].DN
|
||||
if err := conn.Conn.Bind(dn, password); err == nil {
|
||||
if err = conn.Conn.Bind(dn, password); err == nil {
|
||||
ldapLoginSuccess = true
|
||||
conn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
if !ldapLoginSuccess {
|
||||
if !hit {
|
||||
return "user not exist"
|
||||
return fmt.Errorf("user not exist")
|
||||
}
|
||||
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:LDAP user name or password incorrect"))
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
|
||||
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
user, err := GetUserByFields(organization, username)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if user == nil || user.IsDeleted {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||
return nil, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
|
||||
}
|
||||
|
||||
if user.IsForbidden {
|
||||
return nil, i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator")
|
||||
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator"))
|
||||
}
|
||||
|
||||
if user.Ldap != "" {
|
||||
// ONLY for ldap users
|
||||
if msg := checkLdapUserPassword(user, password, lang); msg != "" {
|
||||
if msg == "user not exist" {
|
||||
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
||||
// only for LDAP users
|
||||
err = checkLdapUserPassword(user, password, lang)
|
||||
if err != nil {
|
||||
if err.Error() == "user not exist" {
|
||||
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
|
||||
}
|
||||
return nil, msg
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
|
||||
return nil, msg
|
||||
err = CheckPassword(user, password, lang, enableCaptcha)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return user, ""
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
|
||||
@ -308,7 +322,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
||||
if userId != "" {
|
||||
targetUser, err := GetUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if targetUser == nil {
|
||||
@ -351,8 +365,8 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
||||
}
|
||||
|
||||
func CheckLoginPermission(userId string, application *Application) (bool, error) {
|
||||
var err error
|
||||
if userId == "built-in/admin" {
|
||||
owner, _ := util.GetOwnerAndNameFromId(userId)
|
||||
if owner == "built-in" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@ -361,18 +375,28 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
||||
return false, err
|
||||
}
|
||||
|
||||
allowPermissionCount := 0
|
||||
denyPermissionCount := 0
|
||||
allowCount := 0
|
||||
denyCount := 0
|
||||
for _, permission := range permissions {
|
||||
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||
if !permission.IsEnabled || permission.State != "Approved" || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
if permission.isUserHit(userId) {
|
||||
allowCount += 1
|
||||
if !permission.isUserHit(userId) && !permission.isRoleHit(userId) {
|
||||
if permission.Effect == "Allow" {
|
||||
allowPermissionCount += 1
|
||||
} else {
|
||||
denyPermissionCount += 1
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var isAllowed bool
|
||||
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
|
||||
@ -391,8 +415,18 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Deny-override, if one deny is found, then deny
|
||||
if denyCount > 0 {
|
||||
return false, nil
|
||||
} else if allowCount > 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// For no-allow and no-deny condition
|
||||
// If only allow permissions exist, we suppose it's Deny-by-default, aka no-allow means deny
|
||||
// Otherwise, it's Allow-by-default, aka no-deny means allow
|
||||
if allowPermissionCount > 0 && denyPermissionCount == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@ -451,7 +485,14 @@ func CheckToEnableCaptcha(application *Application, organization, username strin
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return user != nil && user.SigninWrongTimes >= SigninWrongTimesLimit, nil
|
||||
|
||||
var failedSigninLimit int
|
||||
if application.FailedSigninLimit == 0 {
|
||||
failedSigninLimit = 5
|
||||
} else {
|
||||
failedSigninLimit = application.FailedSigninLimit
|
||||
}
|
||||
return user != nil && user.SigninWrongTimes >= failedSigninLimit, nil
|
||||
}
|
||||
return providerItem.Rule == "Always", nil
|
||||
}
|
||||
|
@ -36,38 +36,70 @@ func isValidRealName(s string) bool {
|
||||
return reRealName.MatchString(s)
|
||||
}
|
||||
|
||||
func resetUserSigninErrorTimes(user *User) {
|
||||
func resetUserSigninErrorTimes(user *User) error {
|
||||
// if the password is correct and wrong times is not zero, reset the error times
|
||||
if user.SigninWrongTimes == 0 {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
user.SigninWrongTimes = 0
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
|
||||
func GetFailedSigninConfigByUser(user *User) (int, int, error) {
|
||||
application, err := GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
failedSigninLimit := application.FailedSigninLimit
|
||||
failedSigninfrozenTime := application.FailedSigninfrozenTime
|
||||
|
||||
// 0 as an initialization value, corresponding to the default configuration parameters
|
||||
if failedSigninLimit == 0 {
|
||||
failedSigninLimit = DefaultFailedSigninLimit
|
||||
}
|
||||
if failedSigninfrozenTime == 0 {
|
||||
failedSigninfrozenTime = DefaultFailedSigninfrozenTime
|
||||
}
|
||||
|
||||
return failedSigninLimit, failedSigninfrozenTime, nil
|
||||
}
|
||||
|
||||
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
|
||||
enableCaptcha := false
|
||||
if len(options) > 0 {
|
||||
enableCaptcha = options[0]
|
||||
}
|
||||
|
||||
failedSigninLimit, failedSigninfrozenTime, errSignin := GetFailedSigninConfigByUser(user)
|
||||
if errSignin != nil {
|
||||
return errSignin
|
||||
}
|
||||
|
||||
// increase failed login count
|
||||
if user.SigninWrongTimes < SigninWrongTimesLimit {
|
||||
if user.SigninWrongTimes < failedSigninLimit {
|
||||
user.SigninWrongTimes++
|
||||
}
|
||||
|
||||
if user.SigninWrongTimes >= SigninWrongTimesLimit {
|
||||
if user.SigninWrongTimes >= failedSigninLimit {
|
||||
// record the latest failed login time
|
||||
user.LastSigninWrongTime = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// update user
|
||||
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
|
||||
if leftChances == 0 && enableCaptcha {
|
||||
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||
} else if leftChances >= 0 {
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
||||
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
leftChances := failedSigninLimit - user.SigninWrongTimes
|
||||
if leftChances == 0 && enableCaptcha {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect"))
|
||||
} else if leftChances >= 0 {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
|
||||
}
|
||||
|
||||
// don't show the chance error message if the user has no chance left
|
||||
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), failedSigninfrozenTime)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
|
||||
}
|
||||
|
||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl)
|
||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl, provider.Endpoint, provider.Method)
|
||||
|
||||
fromAddress := provider.ClientId2
|
||||
if fromAddress == "" {
|
||||
|
@ -125,6 +125,10 @@ func (enforcer *Enforcer) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", enforcer.Owner, enforcer.Name)
|
||||
}
|
||||
|
||||
func (enforcer *Enforcer) GetModelAndAdapter() string {
|
||||
return util.GetId(enforcer.Model, enforcer.Adapter)
|
||||
}
|
||||
|
||||
func (enforcer *Enforcer) InitEnforcer() error {
|
||||
if enforcer.Enforcer != nil {
|
||||
return nil
|
||||
@ -246,7 +250,7 @@ func (enforcer *Enforcer) LoadModelCfg() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
model, err := GetModel(enforcer.Model)
|
||||
model, err := GetModelEx(enforcer.Model)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if model == nil {
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/builder"
|
||||
"github.com/xorm-io/core"
|
||||
@ -224,7 +225,8 @@ func GetGroupUserCount(groupId string, field, value string) (int64, error) {
|
||||
if field == "" && value == "" {
|
||||
return int64(len(names)), nil
|
||||
} else {
|
||||
return ormer.Engine.Table("user").
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
return ormer.Engine.Table(tableNamePrefix+"user").
|
||||
Where("owner = ?", owner).In("name", names).
|
||||
And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%").
|
||||
Count()
|
||||
@ -239,7 +241,8 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session := ormer.Engine.Table("user").
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
session := ormer.Engine.Table(tableNamePrefix+"user").
|
||||
Where("owner = ?", owner).In("name", names)
|
||||
|
||||
if offset != -1 && limit != -1 {
|
||||
@ -271,7 +274,9 @@ func GetGroupUsers(groupId string) ([]*User, error) {
|
||||
users := []*User{}
|
||||
owner, _ := util.GetOwnerAndNameFromId(groupId)
|
||||
names, err := userEnforcer.GetUserNamesByGroupName(groupId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -303,6 +308,9 @@ func GroupChangeTrigger(oldName, newName string) error {
|
||||
|
||||
groups := []*Group{}
|
||||
err = session.Where("parent_id = ?", oldName).Find(&groups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, group := range groups {
|
||||
group.ParentId = newName
|
||||
_, err := session.ID(core.PK{group.Owner, group.Name}).Cols("parent_id").Update(group)
|
||||
|
@ -178,7 +178,12 @@ func initBuiltInApplication() {
|
||||
EnablePassword: true,
|
||||
EnableSignUp: true,
|
||||
Providers: []*ProviderItem{
|
||||
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Rule: "None", Provider: nil},
|
||||
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, SignupGroup: "", Rule: "None", Provider: nil},
|
||||
},
|
||||
SigninMethods: []*SigninMethod{
|
||||
{Name: "Password", DisplayName: "Password", Rule: "None"},
|
||||
{Name: "Verification code", DisplayName: "Verification code", Rule: "All"},
|
||||
{Name: "WebAuthn", DisplayName: "WebAuthn", Rule: "None"},
|
||||
},
|
||||
SignupItems: []*SignupItem{
|
||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||
@ -396,15 +401,22 @@ func initBuiltInPermission() {
|
||||
Name: "permission-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Permission",
|
||||
Description: "Built-in Permission",
|
||||
Users: []string{"built-in/*"},
|
||||
Groups: []string{},
|
||||
Roles: []string{},
|
||||
Domains: []string{},
|
||||
Model: "model-built-in",
|
||||
Adapter: "",
|
||||
ResourceType: "Application",
|
||||
Resources: []string{"app-built-in"},
|
||||
Actions: []string{"Read", "Write", "Admin"},
|
||||
Effect: "Allow",
|
||||
IsEnabled: true,
|
||||
Submitter: "admin",
|
||||
Approver: "admin",
|
||||
ApproveTime: util.GetCurrentTime(),
|
||||
State: "Approved",
|
||||
}
|
||||
_, err = AddPermission(permission)
|
||||
if err != nil {
|
||||
|
@ -136,6 +136,9 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
|
||||
if application.Providers == nil {
|
||||
application.Providers = []*ProviderItem{}
|
||||
}
|
||||
if application.SigninMethods == nil {
|
||||
application.SigninMethods = []*SigninMethod{}
|
||||
}
|
||||
if application.SignupItems == nil {
|
||||
application.SignupItems = []*SignupItem{}
|
||||
}
|
||||
|
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)
|
||||
}
|
||||
}
|
134
object/invitation.go
Normal file
134
object/invitation.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type Invitation 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"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Code string `xorm:"varchar(100)" json:"code"`
|
||||
Quota int `json:"quota"`
|
||||
UsedCount int `json:"usedCount"`
|
||||
|
||||
Application string `xorm:"varchar(100)" json:"application"`
|
||||
Username string `xorm:"varchar(100)" json:"username"`
|
||||
Email string `xorm:"varchar(100)" json:"email"`
|
||||
Phone string `xorm:"varchar(100)" json:"phone"`
|
||||
|
||||
SignupGroup string `xorm:"varchar(100)" json:"signupGroup"`
|
||||
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
|
||||
func GetInvitationCount(owner, field, value string) (int64, error) {
|
||||
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||
return session.Count(&Invitation{})
|
||||
}
|
||||
|
||||
func GetInvitations(owner string) ([]*Invitation, error) {
|
||||
invitations := []*Invitation{}
|
||||
err := ormer.Engine.Desc("created_time").Find(&invitations, &Invitation{Owner: owner})
|
||||
if err != nil {
|
||||
return invitations, err
|
||||
}
|
||||
|
||||
return invitations, nil
|
||||
}
|
||||
|
||||
func GetPaginationInvitations(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Invitation, error) {
|
||||
invitations := []*Invitation{}
|
||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||
err := session.Find(&invitations)
|
||||
if err != nil {
|
||||
return invitations, err
|
||||
}
|
||||
|
||||
return invitations, nil
|
||||
}
|
||||
|
||||
func getInvitation(owner string, name string) (*Invitation, error) {
|
||||
if owner == "" || name == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
invitation := Invitation{Owner: owner, Name: name}
|
||||
existed, err := ormer.Engine.Get(&invitation)
|
||||
if err != nil {
|
||||
return &invitation, nil
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &invitation, nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetInvitation(id string) (*Invitation, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getInvitation(owner, name)
|
||||
}
|
||||
|
||||
func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if p, err := getInvitation(owner, name); err != nil {
|
||||
return false, err
|
||||
} else if p == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(invitation)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddInvitation(invitation *Invitation) (bool, error) {
|
||||
affected, err := ormer.Engine.Insert(invitation)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func DeleteInvitation(invitation *Invitation) (bool, error) {
|
||||
affected, err := ormer.Engine.ID(core.PK{invitation.Owner, invitation.Name}).Delete(&Invitation{})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (invitation *Invitation) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", invitation.Owner, invitation.Name)
|
||||
}
|
||||
|
||||
func VerifyInvitation(id string) (payment *Payment, attachInfo map[string]interface{}, err error) {
|
||||
return nil, nil, fmt.Errorf("the invitation: %s does not exist", id)
|
||||
}
|
@ -100,6 +100,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
||||
|
||||
users, err := conn.GetLdapUsers(ldap)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
|
||||
continue
|
||||
}
|
||||
@ -111,6 +112,8 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
|
||||
} else {
|
||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
@ -81,6 +82,17 @@ func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
|
||||
return &LdapConn{Conn: conn, IsAD: isAD}, nil
|
||||
}
|
||||
|
||||
func (l *LdapConn) Close() {
|
||||
if l.Conn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := l.Conn.Unbind()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
|
||||
SearchFilter := "(objectClass=*)"
|
||||
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
|
||||
@ -305,7 +317,7 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
name, err := syncUser.buildLdapUserName()
|
||||
name, err := syncUser.buildLdapUserName(owner)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -345,7 +357,8 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
|
||||
func GetExistUuids(owner string, uuids []string) ([]string, error) {
|
||||
var existUuids []string
|
||||
|
||||
err := ormer.Engine.Table("user").Where("owner = ?", owner).Cols("ldap").
|
||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||
err := ormer.Engine.Table(tableNamePrefix+"user").Where("owner = ?", owner).Cols("ldap").
|
||||
In("ldap", uuids).Select("DISTINCT ldap").Find(&existUuids)
|
||||
if err != nil {
|
||||
return existUuids, err
|
||||
@ -354,10 +367,10 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
|
||||
return existUuids, nil
|
||||
}
|
||||
|
||||
func (ldapUser *LdapUser) buildLdapUserName() (string, error) {
|
||||
func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) {
|
||||
user := User{}
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
|
||||
has, err := ormer.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
|
||||
has, err := ormer.Engine.Where("owner = ? and (name = ? or name = ?)", owner, ldapUser.Uid, uidWithNumber).Get(&user)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -1,51 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/xorm-io/xorm/migrate"
|
||||
|
||||
type Migrator interface {
|
||||
IsMigrationNeeded() bool
|
||||
DoMigration() *migrate.Migration
|
||||
}
|
||||
|
||||
func DoMigration() {
|
||||
migrators := []Migrator{
|
||||
&Migrator_1_101_0_PR_1083{},
|
||||
&Migrator_1_235_0_PR_1530{},
|
||||
&Migrator_1_240_0_PR_1539{},
|
||||
&Migrator_1_314_0_PR_1841{},
|
||||
// more migrators add here in chronological order...
|
||||
}
|
||||
|
||||
migrations := []*migrate.Migration{}
|
||||
|
||||
for _, migrator := range migrators {
|
||||
if migrator.IsMigrationNeeded() {
|
||||
migrations = append(migrations, migrator.DoMigration())
|
||||
}
|
||||
}
|
||||
|
||||
options := &migrate.Options{
|
||||
TableName: "migration",
|
||||
IDColumnName: "id",
|
||||
}
|
||||
|
||||
m := migrate.New(ormer.Engine, options, migrations)
|
||||
err := m.Migrate()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_101_0_PR_1083 struct{}
|
||||
|
||||
func (*Migrator_1_101_0_PR_1083) IsMigrationNeeded() bool {
|
||||
exist1, _ := ormer.Engine.IsTableExist("model")
|
||||
exist2, _ := ormer.Engine.IsTableExist("permission")
|
||||
exist3, _ := ormer.Engine.IsTableExist("permission_rule")
|
||||
|
||||
if exist1 && exist2 && exist3 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (*Migrator_1_101_0_PR_1083) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230209MigratePermissionRule--Use V5 instead of V1 to store permissionID",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
models := []*Model{}
|
||||
err := engine.Table("model").Find(&models, &Model{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
isHit := false
|
||||
for _, model := range models {
|
||||
if strings.Contains(model.ModelText, "permission") {
|
||||
// update model table
|
||||
model.ModelText = strings.Replace(model.ModelText, "permission,", "", -1)
|
||||
UpdateModel(model.GetId(), model)
|
||||
isHit = true
|
||||
}
|
||||
}
|
||||
|
||||
if isHit {
|
||||
// update permission_rule table
|
||||
sql := "UPDATE `permission_rule`SET V0 = V1, V1 = V2, V2 = V3, V3 = V4, V4 = V5 WHERE V0 IN (SELECT CONCAT(owner, '/', name) AS permission_id FROM `permission`)"
|
||||
_, err = engine.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_235_0_PR_1530 struct{}
|
||||
|
||||
func (*Migrator_1_235_0_PR_1530) IsMigrationNeeded() bool {
|
||||
exist, _ := ormer.Engine.IsTableExist("casbin_rule")
|
||||
|
||||
return exist
|
||||
}
|
||||
|
||||
func (*Migrator_1_235_0_PR_1530) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20221015CasbinRule--fill ptype field with p",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
_, err := engine.Cols("ptype").Update(&xormadapter.CasbinRule{
|
||||
Ptype: "p",
|
||||
})
|
||||
return err
|
||||
},
|
||||
Rollback: func(engine *xorm.Engine) error {
|
||||
return engine.DropTables(&xormadapter.CasbinRule{})
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_240_0_PR_1539 struct{}
|
||||
|
||||
func (*Migrator_1_240_0_PR_1539) IsMigrationNeeded() bool {
|
||||
exist, _ := ormer.Engine.IsTableExist("session")
|
||||
err := ormer.Engine.Table("session").Find(&[]*Session{})
|
||||
|
||||
if exist && err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (*Migrator_1_240_0_PR_1539) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230211MigrateSession--Create a new field 'application' for table `session`",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
if alreadyCreated, _ := engine.IsTableExist("session_tmp"); alreadyCreated {
|
||||
return errors.New("there is already a table called 'session_tmp', please rename or delete it for casdoor version migration and restart")
|
||||
}
|
||||
|
||||
type oldSession struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
SessionId []string `json:"sessionId"`
|
||||
}
|
||||
|
||||
tx := engine.NewSession()
|
||||
|
||||
defer tx.Close()
|
||||
|
||||
err := tx.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.Table("session_tmp").CreateTable(&Session{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
oldSessions := []*oldSession{}
|
||||
newSessions := []*Session{}
|
||||
|
||||
err = tx.Table("session").Find(&oldSessions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, oldSession := range oldSessions {
|
||||
newApplication := "null"
|
||||
if oldSession.Owner == "built-in" {
|
||||
newApplication = "app-built-in"
|
||||
}
|
||||
newSessions = append(newSessions, &Session{
|
||||
Owner: oldSession.Owner,
|
||||
Name: oldSession.Name,
|
||||
Application: newApplication,
|
||||
CreatedTime: oldSession.CreatedTime,
|
||||
SessionId: oldSession.SessionId,
|
||||
})
|
||||
}
|
||||
|
||||
rollbackFlag := false
|
||||
_, err = tx.Table("session_tmp").Insert(newSessions)
|
||||
count1, _ := tx.Table("session_tmp").Count()
|
||||
count2, _ := tx.Table("session").Count()
|
||||
|
||||
if err != nil || count1 != count2 {
|
||||
rollbackFlag = true
|
||||
}
|
||||
|
||||
delete := &Session{
|
||||
Application: "null",
|
||||
}
|
||||
_, err = tx.Table("session_tmp").Delete(*delete)
|
||||
if err != nil {
|
||||
rollbackFlag = true
|
||||
}
|
||||
|
||||
if rollbackFlag {
|
||||
tx.DropTable("session_tmp")
|
||||
return errors.New("there is something wrong with data migration for table `session`, if there is a table called `session_tmp` not created by you in casdoor, please drop it, then restart anyhow")
|
||||
}
|
||||
|
||||
err = tx.DropTable("session")
|
||||
if err != nil {
|
||||
return errors.New("fail to drop table `session` for casdoor, please drop it and rename the table `session_tmp` to `session` manually and restart")
|
||||
}
|
||||
|
||||
// Already drop table `session`
|
||||
// Can't find an api from xorm for altering table name
|
||||
err = tx.Table("session").CreateTable(&Session{})
|
||||
if err != nil {
|
||||
return errors.New("there is something wrong with data migration for table `session`, please restart")
|
||||
}
|
||||
|
||||
sessions := []*Session{}
|
||||
tx.Table("session_tmp").Find(&sessions)
|
||||
_, err = tx.Table("session").Insert(sessions)
|
||||
if err != nil {
|
||||
return errors.New("there is something wrong with data migration for table `session`, please drop table `session` and rename table `session_tmp` to `session` and restart")
|
||||
}
|
||||
|
||||
err = tx.DropTable("session_tmp")
|
||||
if err != nil {
|
||||
return errors.New("fail to drop table `session_tmp` for casdoor, please drop it manually and restart")
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"github.com/xorm-io/xorm"
|
||||
"github.com/xorm-io/xorm/migrate"
|
||||
)
|
||||
|
||||
type Migrator_1_314_0_PR_1841 struct{}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) IsMigrationNeeded() bool {
|
||||
count, err := ormer.Engine.Where("password_type=?", "").Count(&User{})
|
||||
if err != nil {
|
||||
// table doesn't exist
|
||||
return false
|
||||
}
|
||||
|
||||
return count > 100
|
||||
}
|
||||
|
||||
func (*Migrator_1_314_0_PR_1841) DoMigration() *migrate.Migration {
|
||||
migration := migrate.Migration{
|
||||
ID: "20230515MigrateUser--Create a new field 'passwordType' for table `user`",
|
||||
Migrate: func(engine *xorm.Engine) error {
|
||||
tx := engine.NewSession()
|
||||
|
||||
defer tx.Close()
|
||||
|
||||
err := tx.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
organizations := []*Organization{}
|
||||
err = tx.Table("organization").Find(&organizations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, organization := range organizations {
|
||||
user := &User{PasswordType: organization.PasswordType}
|
||||
_, err = tx.Where("owner = ?", organization.Name).Cols("password_type").Update(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
tx.Commit()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return &migration
|
||||
}
|
@ -84,6 +84,19 @@ func GetModel(id string) (*Model, error) {
|
||||
return getModel(owner, name)
|
||||
}
|
||||
|
||||
func GetModelEx(id string) (*Model, error) {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
model, err := getModel(owner, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if model != nil {
|
||||
return model, nil
|
||||
}
|
||||
|
||||
return getModel("built-in", name)
|
||||
}
|
||||
|
||||
func UpdateModelWithCheck(id string, modelObj *Model) error {
|
||||
// check model grammar
|
||||
_, err := model.NewModelFromString(modelObj.ModelText)
|
||||
|
@ -59,7 +59,7 @@ func isIpAddress(host string) bool {
|
||||
return ip != nil
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
func getOriginFromHostInternal(host string) (string, string) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
if origin != "" {
|
||||
return origin, origin
|
||||
@ -82,6 +82,17 @@ func getOriginFromHost(host string) (string, string) {
|
||||
}
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
originF, originB := getOriginFromHostInternal(host)
|
||||
|
||||
originFrontend := conf.GetConfigString("originFrontend")
|
||||
if originFrontend != "" {
|
||||
originF = originFrontend
|
||||
}
|
||||
|
||||
return originF, originB
|
||||
}
|
||||
|
||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
|
||||
|
@ -51,22 +51,24 @@ type Organization struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
InitScore int `json:"initScore"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
|
||||
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
|
||||
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
|
||||
InitScore int `json:"initScore"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
|
||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||
@ -155,6 +157,12 @@ func GetMaskedOrganization(organization *Organization, errs ...error) (*Organiza
|
||||
if organization.MasterPassword != "" {
|
||||
organization.MasterPassword = "***"
|
||||
}
|
||||
if organization.DefaultPassword != "" {
|
||||
organization.DefaultPassword = "***"
|
||||
}
|
||||
if organization.MasterVerificationCode != "" {
|
||||
organization.MasterVerificationCode = "***"
|
||||
}
|
||||
return organization, nil
|
||||
}
|
||||
|
||||
@ -202,9 +210,17 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
|
||||
}
|
||||
|
||||
session := ormer.Engine.ID(core.PK{owner, name}).AllCols()
|
||||
|
||||
if organization.MasterPassword == "***" {
|
||||
session.Omit("master_password")
|
||||
}
|
||||
if organization.DefaultPassword == "***" {
|
||||
session.Omit("default_password")
|
||||
}
|
||||
if organization.MasterVerificationCode == "***" {
|
||||
session.Omit("master_verification_code")
|
||||
}
|
||||
|
||||
affected, err := session.Update(organization)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
115
object/ormer.go
115
object/ormer.go
@ -64,7 +64,6 @@ func InitConfig() {
|
||||
|
||||
InitAdapter()
|
||||
CreateTables()
|
||||
DoMigration()
|
||||
}
|
||||
|
||||
func InitAdapter() {
|
||||
@ -235,12 +234,37 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Group))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(User))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Group))
|
||||
err = a.Engine.Sync2(new(Invitation))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Resource))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Cert))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -270,17 +294,7 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Resource))
|
||||
err = a.Engine.Sync2(new(Session))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -290,26 +304,6 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(VerificationRecord))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Webhook))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Syncer))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Cert))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Product))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -320,6 +314,36 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Plan))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Pricing))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Subscription))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Syncer))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Webhook))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(VerificationRecord))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Ldap))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -330,33 +354,8 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(PermissionRule))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(xormadapter.CasbinRule))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Session))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Subscription))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Plan))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Pricing))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ type Payment struct {
|
||||
// Order Info
|
||||
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
||||
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"`
|
||||
Message string `xorm:"varchar(2000)" json:"message"`
|
||||
}
|
||||
|
@ -49,17 +49,6 @@ type Permission struct {
|
||||
State string `xorm:"varchar(100)" json:"state"`
|
||||
}
|
||||
|
||||
type PermissionRule struct {
|
||||
Ptype string `xorm:"varchar(100) index not null default ''" json:"ptype"`
|
||||
V0 string `xorm:"varchar(100) index not null default ''" json:"v0"`
|
||||
V1 string `xorm:"varchar(100) index not null default ''" json:"v1"`
|
||||
V2 string `xorm:"varchar(100) index not null default ''" json:"v2"`
|
||||
V3 string `xorm:"varchar(100) index not null default ''" json:"v3"`
|
||||
V4 string `xorm:"varchar(100) index not null default ''" json:"v4"`
|
||||
V5 string `xorm:"varchar(100) index not null default ''" json:"v5"`
|
||||
Id string `xorm:"varchar(100) index not null default ''" json:"id"`
|
||||
}
|
||||
|
||||
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
|
||||
|
||||
func GetPermissionCount(owner, field, value string) (int64, error) {
|
||||
@ -113,11 +102,15 @@ func GetPermission(id string) (*Permission, error) {
|
||||
|
||||
// checkPermissionValid verifies if the permission is valid
|
||||
func checkPermissionValid(permission *Permission) error {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enforcer.EnableAutoSave(false)
|
||||
|
||||
policies := getPolicies(permission)
|
||||
_, err := enforcer.AddPolicies(policies)
|
||||
_, err = enforcer.AddPolicies(policies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -127,9 +120,13 @@ func checkPermissionValid(permission *Permission) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -150,18 +147,21 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if permission.ResourceType == "Application" {
|
||||
model, err := GetModel(util.GetId(owner, permission.Model))
|
||||
if permission.ResourceType == "Application" && permission.Model != "" {
|
||||
model, err := GetModelEx(util.GetId(owner, permission.Model))
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if model == nil {
|
||||
return false, fmt.Errorf("the model: %s for permission: %s is not found", permission.Model, permission.GetId())
|
||||
}
|
||||
|
||||
modelCfg, err := getModelCfg(model)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(strings.Split(modelCfg["p"], ",")) != 3 {
|
||||
return false, fmt.Errorf("the model: %s for permission: %s is not valid, application type resources need 3 size [policy_defination] model", permission.Model, permission.GetId())
|
||||
return false, fmt.Errorf("the model: %s for permission: %s is not valid, Casbin model's [policy_defination] section should have 3 elements", permission.Model, permission.GetId())
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,8 +171,16 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
removeGroupingPolicies(oldPermission)
|
||||
removePolicies(oldPermission)
|
||||
err = removeGroupingPolicies(oldPermission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = removePolicies(oldPermission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
||||
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
||||
if isEmpty {
|
||||
@ -182,8 +190,16 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
addGroupingPolicies(permission)
|
||||
addPolicies(permission)
|
||||
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
@ -196,40 +212,54 @@ func AddPermission(permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
addGroupingPolicies(permission)
|
||||
addPolicies(permission)
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddPermissions(permissions []*Permission) bool {
|
||||
func AddPermissions(permissions []*Permission) (bool, error) {
|
||||
if len(permissions) == 0 {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(permissions)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||
panic(err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
// add using for loop
|
||||
if affected != 0 {
|
||||
addGroupingPolicies(permission)
|
||||
addPolicies(permission)
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return affected != 0
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddPermissionsInBatch(permissions []*Permission) bool {
|
||||
func AddPermissionsInBatch(permissions []*Permission) (bool, error) {
|
||||
batchSize := conf.GetConfigBatchSize()
|
||||
|
||||
if len(permissions) == 0 {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
affected := false
|
||||
@ -242,12 +272,18 @@ func AddPermissionsInBatch(permissions []*Permission) bool {
|
||||
|
||||
tmp := permissions[start:end]
|
||||
fmt.Printf("The syncer adds permissions: [%d - %d]\n", start, end)
|
||||
if AddPermissions(tmp) {
|
||||
|
||||
b, err := AddPermissions(tmp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if b {
|
||||
affected = true
|
||||
}
|
||||
}
|
||||
|
||||
return affected
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
func DeletePermission(permission *Permission) (bool, error) {
|
||||
@ -257,8 +293,16 @@ func DeletePermission(permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
removeGroupingPolicies(permission)
|
||||
removePolicies(permission)
|
||||
err = removeGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = removePolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
||||
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
||||
if isEmpty {
|
||||
@ -402,9 +446,8 @@ func GetMaskedPermissions(permissions []*Permission) []*Permission {
|
||||
// as the policyFilter when the enforcer load policy).
|
||||
func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]string {
|
||||
m := make(map[string][]string)
|
||||
|
||||
for _, permission := range permissions {
|
||||
key := permission.Model + permission.Adapter
|
||||
key := permission.GetModelAndAdapter()
|
||||
permissionIds, ok := m[key]
|
||||
if !ok {
|
||||
m[key] = []string{permission.GetId()}
|
||||
@ -420,20 +463,48 @@ func (p *Permission) GetId() string {
|
||||
return util.GetId(p.Owner, p.Name)
|
||||
}
|
||||
|
||||
func (p *Permission) GetModelAndAdapter() string {
|
||||
return util.GetId(p.Model, p.Adapter)
|
||||
}
|
||||
|
||||
func (p *Permission) isUserHit(name string) bool {
|
||||
targetOrg, _ := util.GetOwnerAndNameFromId(name)
|
||||
targetOrg, targetName := util.GetOwnerAndNameFromId(name)
|
||||
for _, user := range p.Users {
|
||||
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
||||
if userOrg == targetOrg && userName == "*" {
|
||||
if user == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
||||
if userOrg == targetOrg && (userName == "*" || userName == targetName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Permission) isRoleHit(userId string) bool {
|
||||
targetRoles, err := getRolesByUser(userId)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range p.Roles {
|
||||
if role == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, targetRole := range targetRoles {
|
||||
if role == targetRole.GetId() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Permission) isResourceHit(name string) bool {
|
||||
for _, resource := range p.Resources {
|
||||
if name == resource {
|
||||
if resource == "*" || resource == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -23,26 +23,27 @@ import (
|
||||
"github.com/casbin/casbin/v2/log"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
)
|
||||
|
||||
func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enforcer {
|
||||
func getPermissionEnforcer(p *Permission, permissionIDs ...string) (*casbin.Enforcer, error) {
|
||||
// Init an enforcer instance without specifying a model or adapter.
|
||||
// If you specify an adapter, it will load all policies, which is a
|
||||
// heavy process that can slow down the application.
|
||||
enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.setEnforcerModel(enforcer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.setEnforcerAdapter(enforcer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policyFilterV5 := []string{p.GetId()}
|
||||
@ -60,10 +61,10 @@ func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enfor
|
||||
|
||||
err = enforcer.LoadFilteredPolicy(policyFilter)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return enforcer
|
||||
return enforcer, nil
|
||||
}
|
||||
|
||||
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
|
||||
@ -137,6 +138,16 @@ func getPolicies(permission *Permission) [][]string {
|
||||
}
|
||||
|
||||
func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
|
||||
roleOwner, roleName := util.GetOwnerAndNameFromId(roleId)
|
||||
if roleName == "*" {
|
||||
roles, err := GetRoles(roleOwner)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
role, err := GetRole(roleId)
|
||||
if err != nil {
|
||||
return []*Role{}, err
|
||||
@ -162,7 +173,7 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func getGroupingPolicies(permission *Permission) [][]string {
|
||||
func getGroupingPolicies(permission *Permission) ([][]string, error) {
|
||||
var groupingPolicies [][]string
|
||||
|
||||
domainExist := len(permission.Domains) > 0
|
||||
@ -170,12 +181,18 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
|
||||
for _, roleId := range permission.Roles {
|
||||
visited := map[string]struct{}{}
|
||||
|
||||
if roleId == "*" {
|
||||
roleId = util.GetId(permission.Owner, "*")
|
||||
}
|
||||
|
||||
rolesInRole, err := getRolesInRole(roleId, visited)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range rolesInRole {
|
||||
roleId := role.GetId()
|
||||
roleId = role.GetId()
|
||||
for _, subUser := range role.Users {
|
||||
if domainExist {
|
||||
for _, domain := range permission.Domains {
|
||||
@ -198,75 +215,114 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
}
|
||||
}
|
||||
|
||||
return groupingPolicies
|
||||
return groupingPolicies, nil
|
||||
}
|
||||
|
||||
func addPolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
func addPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policies := getPolicies(permission)
|
||||
|
||||
_, err := enforcer.AddPolicies(policies)
|
||||
_, err = enforcer.AddPolicies(policies)
|
||||
return err
|
||||
}
|
||||
|
||||
func removePolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func addGroupingPolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeGroupingPolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err := enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removePolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
policies := getPolicies(permission)
|
||||
|
||||
_, err := enforcer.RemovePolicies(policies)
|
||||
_, err = enforcer.RemovePolicies(policies)
|
||||
return err
|
||||
}
|
||||
|
||||
func addGroupingPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CasbinRequest = []interface{}
|
||||
func removeGroupingPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
|
||||
enforcer := getPermissionEnforcer(permission, permissionIds...)
|
||||
return enforcer.Enforce(*request...)
|
||||
groupingPolicies, err := getGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
|
||||
enforcer := getPermissionEnforcer(permission, permissionIds...)
|
||||
return enforcer.BatchEnforce(*requests)
|
||||
func Enforce(permission *Permission, request []string, permissionIds ...string) (bool, error) {
|
||||
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// type transformation
|
||||
interfaceRequest := util.StringToInterfaceArray(request)
|
||||
|
||||
return enforcer.Enforce(interfaceRequest...)
|
||||
}
|
||||
|
||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
|
||||
func BatchEnforce(permission *Permission, requests [][]string, permissionIds ...string) ([]bool, error) {
|
||||
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// type transformation
|
||||
interfaceRequests := util.StringToInterfaceArray2d(requests)
|
||||
|
||||
return enforcer.BatchEnforce(interfaceRequests)
|
||||
}
|
||||
|
||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([]string, error) {
|
||||
permissions, _, err := getPermissionsAndRolesByUser(userId)
|
||||
if err != nil {
|
||||
panic(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)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
permissions = append(permissions, permissionsByRole...)
|
||||
@ -274,35 +330,40 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []
|
||||
|
||||
var values []string
|
||||
for _, permission := range permissions {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
values = append(values, fn(enforcer)...)
|
||||
}
|
||||
return values
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func GetAllObjects(userId string) []string {
|
||||
func GetAllObjects(userId string) ([]string, error) {
|
||||
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
||||
return enforcer.GetAllObjects()
|
||||
})
|
||||
}
|
||||
|
||||
func GetAllActions(userId string) []string {
|
||||
func GetAllActions(userId string) ([]string, error) {
|
||||
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
||||
return enforcer.GetAllActions()
|
||||
})
|
||||
}
|
||||
|
||||
func GetAllRoles(userId string) []string {
|
||||
func GetAllRoles(userId string) ([]string, error) {
|
||||
roles, err := getRolesByUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res []string
|
||||
res := []string{}
|
||||
for _, role := range roles {
|
||||
res = append(res, role.Name)
|
||||
}
|
||||
return res
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func GetBuiltInModel(modelText string) (model.Model, error) {
|
||||
@ -330,17 +391,23 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
|
||||
|
||||
// load [policy_definition]
|
||||
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
|
||||
|
||||
fieldsNum := len(policyDefinition)
|
||||
if fieldsNum > builtInAvailableField {
|
||||
panic(fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum))
|
||||
return nil, fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum)
|
||||
}
|
||||
|
||||
// filled empty field with "" and V5 with "permissionId"
|
||||
for i := builtInAvailableField - fieldsNum; i > 0; i-- {
|
||||
policyDefinition = append(policyDefinition, "")
|
||||
}
|
||||
policyDefinition = append(policyDefinition, "permissionId")
|
||||
|
||||
m, _ := model.NewModelFromString(modelText)
|
||||
m, err := model.NewModelFromString(modelText)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.AddDef("p", "p", strings.Join(policyDefinition, ","))
|
||||
|
||||
return m, err
|
||||
|
@ -83,5 +83,10 @@ func UploadPermissions(owner string, path string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return AddPermissionsInBatch(newPermissions), nil
|
||||
affected, err := AddPermissionsInBatch(newPermissions)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return affected, nil
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
|
||||
"github.com/casdoor/casdoor/pp"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -30,8 +32,8 @@ type Product struct {
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
|
||||
Image string `xorm:"varchar(100)" json:"image"`
|
||||
Detail string `xorm:"varchar(255)" json:"detail"`
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
Detail string `xorm:"varchar(1000)" json:"detail"`
|
||||
Description string `xorm:"varchar(200)" json:"description"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||
Price float64 `json:"price"`
|
||||
@ -158,30 +160,28 @@ func (product *Product) getProvider(providerName string) (*Provider, error) {
|
||||
return provider, nil
|
||||
}
|
||||
|
||||
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) {
|
||||
func BuyProduct(id string, user *User, providerName, pricingName, planName, host, paymentEnv string) (payment *Payment, attachInfo map[string]interface{}, err error) {
|
||||
product, err := GetProduct(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if product == nil {
|
||||
return nil, fmt.Errorf("the product: %s does not exist", id)
|
||||
return nil, nil, fmt.Errorf("the product: %s does not exist", id)
|
||||
}
|
||||
|
||||
provider, err := product.getProvider(providerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pProvider, err := GetPaymentProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
owner := product.Owner
|
||||
productName := product.Name
|
||||
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||
paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
|
||||
productDisplayName := product.DisplayName
|
||||
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName)
|
||||
@ -191,26 +191,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
if pricingName != "" && planName != "" {
|
||||
plan, err := GetPlan(util.GetId(owner, planName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if plan == nil {
|
||||
return nil, fmt.Errorf("the plan: %s does not exist", planName)
|
||||
return nil, nil, fmt.Errorf("the plan: %s does not exist", planName)
|
||||
}
|
||||
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
|
||||
_, err = AddSubscription(sub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
|
||||
}
|
||||
}
|
||||
// Create an OrderId and get the payUrl
|
||||
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
|
||||
// Create an order
|
||||
payReq := &pp.PayReq{
|
||||
ProviderName: providerName,
|
||||
ProductName: product.Name,
|
||||
PayerName: payerName,
|
||||
PayerId: user.Id,
|
||||
PaymentName: paymentName,
|
||||
ProductDisplayName: product.DisplayName,
|
||||
Price: product.Price,
|
||||
Currency: product.Currency,
|
||||
ReturnUrl: returnUrl,
|
||||
NotifyUrl: notifyUrl,
|
||||
PaymentEnv: paymentEnv,
|
||||
}
|
||||
// custom process for WeChat & WeChat Pay
|
||||
if provider.Type == "WeChat Pay" {
|
||||
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
payResp, err := pProvider.Pay(payReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
// Create a Payment linked with Product and Order
|
||||
payment := &Payment{
|
||||
payment = &Payment{
|
||||
Owner: product.Owner,
|
||||
Name: paymentName,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
@ -219,8 +239,8 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
Provider: provider.Name,
|
||||
Type: provider.Type,
|
||||
|
||||
ProductName: productName,
|
||||
ProductDisplayName: productDisplayName,
|
||||
ProductName: product.Name,
|
||||
ProductDisplayName: product.DisplayName,
|
||||
Detail: product.Detail,
|
||||
Tag: product.Tag,
|
||||
Currency: product.Currency,
|
||||
@ -228,10 +248,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
ReturnUrl: product.ReturnUrl,
|
||||
|
||||
User: user.Name,
|
||||
PayUrl: payUrl,
|
||||
PayUrl: payResp.PayUrl,
|
||||
SuccessUrl: returnUrl,
|
||||
State: pp.PaymentStateCreated,
|
||||
OutOrderId: orderId,
|
||||
OutOrderId: payResp.OrderId,
|
||||
}
|
||||
|
||||
if provider.Type == "Dummy" {
|
||||
@ -240,13 +260,13 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
||||
|
||||
affected, err := AddPayment(payment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if !affected {
|
||||
return nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||
}
|
||||
return payment, err
|
||||
return payment, payResp.AttachInfo, nil
|
||||
}
|
||||
|
||||
func ExtendProductWithProviders(product *Product) error {
|
||||
|
@ -37,9 +37,9 @@ type Provider struct {
|
||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(200)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||
ClientSecret string `xorm:"varchar(3000)" json:"clientSecret"`
|
||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||
ClientSecret2 string `xorm:"varchar(500)" json:"clientSecret2"`
|
||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
||||
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
||||
@ -52,7 +52,7 @@ type Provider struct {
|
||||
Port int `json:"port"`
|
||||
DisableSsl bool `json:"disableSsl"` // If the provider type is WeChat, DisableSsl means EnableQRCode
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
Content string `xorm:"varchar(1000)" json:"content"` // If provider type is WeChat, Content means QRCode string by Base64 encoding
|
||||
Content string `xorm:"varchar(2000)" json:"content"` // If provider type is WeChat, Content means QRCode string by Base64 encoding
|
||||
Receiver string `xorm:"varchar(100)" json:"receiver"`
|
||||
|
||||
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
||||
@ -398,16 +398,18 @@ func providerChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo {
|
||||
providerInfo := &idp.ProviderInfo{
|
||||
Type: provider.Type,
|
||||
SubType: provider.SubType,
|
||||
ClientId: provider.ClientId,
|
||||
ClientSecret: provider.ClientSecret,
|
||||
AppId: provider.AppId,
|
||||
HostUrl: provider.Host,
|
||||
TokenURL: provider.CustomTokenUrl,
|
||||
AuthURL: provider.CustomAuthUrl,
|
||||
UserInfoURL: provider.CustomUserInfoUrl,
|
||||
UserMapping: provider.UserMapping,
|
||||
Type: provider.Type,
|
||||
SubType: provider.SubType,
|
||||
ClientId: provider.ClientId,
|
||||
ClientSecret: provider.ClientSecret,
|
||||
ClientId2: provider.ClientId2,
|
||||
ClientSecret2: provider.ClientSecret2,
|
||||
AppId: provider.AppId,
|
||||
HostUrl: provider.Host,
|
||||
TokenURL: provider.CustomTokenUrl,
|
||||
AuthURL: provider.CustomAuthUrl,
|
||||
UserInfoURL: provider.CustomUserInfoUrl,
|
||||
UserMapping: provider.UserMapping,
|
||||
}
|
||||
|
||||
if provider.Type == "WeChat" {
|
||||
@ -415,7 +417,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
||||
providerInfo.ClientId = provider.ClientId2
|
||||
providerInfo.ClientSecret = provider.ClientSecret2
|
||||
}
|
||||
} else if provider.Type == "AzureAD" {
|
||||
} else if provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "ADFS" || provider.Type == "Okta" {
|
||||
providerInfo.HostUrl = provider.Domain
|
||||
}
|
||||
|
||||
|
@ -18,13 +18,13 @@ type ProviderItem struct {
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
|
||||
CanSignUp bool `json:"canSignUp"`
|
||||
CanSignIn bool `json:"canSignIn"`
|
||||
CanUnlink bool `json:"canUnlink"`
|
||||
Prompted bool `json:"prompted"`
|
||||
AlertType string `json:"alertType"`
|
||||
Rule string `json:"rule"`
|
||||
Provider *Provider `json:"provider"`
|
||||
CanSignUp bool `json:"canSignUp"`
|
||||
CanSignIn bool `json:"canSignIn"`
|
||||
CanUnlink bool `json:"canUnlink"`
|
||||
Prompted bool `json:"prompted"`
|
||||
SignupGroup string `json:"signupGroup"`
|
||||
Rule string `json:"rule"`
|
||||
Provider *Provider `json:"provider"`
|
||||
}
|
||||
|
||||
func (application *Application) GetProviderItem(providerName string) *ProviderItem {
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
)
|
||||
|
||||
// https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html
|
||||
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a
|
||||
type RadiusAccounting struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user