Compare commits

..

128 Commits

Author SHA1 Message Date
Yang Luo
5c107db43b fix: fix i18n typo 2023-12-30 00:49:39 +08:00
Yang Luo
27187b3a54 feat: add "Reset to Default HTML" button 2023-12-30 00:47:10 +08:00
Yang Luo
14fcedcc5d feat: support HTML in Email content 2023-12-29 23:31:50 +08:00
xiao-kong-long
e7c015f288 feat: fix comment and configs for successfully generating OpenAPI typescript-axios sdk (#2560)
* fix: fix swagger.json, successfully generate java sdk

* fix:fix comment and change some content for successfully generating typescript-axios sdk
2023-12-29 15:12:40 +08:00
Yang Luo
c4819602ec fix: add mfa API to isAllowedInDemoMode() 2023-12-26 20:06:27 +08:00
Eng Zer Jun
dea03cdd15 feat: replace deprecated github.com/RobotsAndPencils/go-saml (#2558)
The `github.com/RobotsAndPencils/go-saml` has been officially deprecated
and archived on 7 June 2023.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-12-25 21:15:53 +08:00
xiao-kong-long
21f394847e fix: fix Swagger docs by improving comments 2023-12-23 11:57:18 +08:00
Yang Luo
9bef9691fb feat: fix volcengine SMS provider error handling 2023-12-22 20:49:45 +08:00
Yang Luo
141f22a707 feat: upgrade to Node.js 18 and Go 1.20 in Dockerfile 2023-12-22 14:46:41 +08:00
Yang Luo
02329d342a feat: fix bug in "*" users and roles in permission edit page. 2023-12-22 14:16:00 +08:00
Yang Luo
b9d3e2184c fix: update CI node version from 16 to 18 2023-12-22 09:28:45 +08:00
Yang Luo
28caf8550e Support token parsed result 2023-12-22 02:04:25 +08:00
Yang Luo
79159dc809 Improve TokenEditPage 2023-12-22 00:44:34 +08:00
Yang Luo
63081641d6 Improve i18n text 2023-12-22 00:25:46 +08:00
Yang Luo
698f24f762 feat: fix template code bug in SMS provider of Amazon SNS 2023-12-21 23:32:55 +08:00
HGZ-20
5499e62d7f feat: add the FailedSigninLimit and FailedSigninfrozenTime configuration options to the application (#2552)
Add configuration items to the application to limit the number of logins and the login wait time after the maximum number of errors is reached
feat: #2272

fix: fixed the issue where the token parameter could be set to a negative value
2023-12-20 22:29:53 +08:00
Yang Luo
f8905ae64c Fix S3-compliant storage providers support 2023-12-20 14:38:32 +08:00
Yang Luo
a42594859f feat: improve enforce() and batchEnforce() API response 2023-12-20 11:41:54 +08:00
Yang Luo
46e0bc1a39 Improve i18n texts 2023-12-20 10:09:00 +08:00
Gucheng Wang
ffe2330238 Fix tag field in user list page 2023-12-20 01:57:56 +08:00
Gucheng
ec53616dc8 Update README.md 2023-12-20 01:52:29 +08:00
Gucheng Wang
067276d739 Add new B2C provider 2023-12-17 16:29:29 +08:00
Yang Luo
468ceb6b71 Fix get-all-objects API 403 issue 2023-12-15 21:32:45 +08:00
Satinder Singh
b31a317585 feat: add helm release github action (#2546) 2023-12-15 19:30:10 +08:00
Yang Luo
396b6fb65f feat: refactor custom HTTP related filenames 2023-12-15 00:06:05 +08:00
Yang Luo
be637fca81 fix: fix wrong POST param logic in custom HTTP providers 2023-12-15 00:00:47 +08:00
link89
374928e719 feat: add custom HTTP Email provider (#2542)
* feat: implement Custom HTTP Email provider

* Update Setting.js

* Update ProviderEditPage.js

* Update http.go

* Update provider.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-12-14 22:35:25 +08:00
Yang Luo
5c103e8cd3 Improve error handling in GenerateIdForNewUser() 2023-12-14 10:12:00 +08:00
Lars Lehtonen
85b86e8831 fix: dropped object group errors (#2545) 2023-12-14 09:00:25 +08:00
Yang Luo
08864686f3 feat: fix Google cloud storage provider bug 2023-12-14 00:25:50 +08:00
HGZ-20
dc06eb9948 feat: fix secret information issue in the CAPTCHA provider code (#2531) 2023-12-11 18:01:56 +08:00
Yang Luo
b068202e74 Improve Radius username handling 2023-12-11 18:01:28 +08:00
Satinder Singh
cb16567c7b feat: helm support extra containers (#2530) 2023-12-10 14:41:56 +08:00
Yang Luo
4eb725d47a Improve image upload UI 2023-12-08 19:42:20 +08:00
Yang Luo
ce72a172b0 feat: add back Custom HTTP SMS provider 2023-12-07 16:59:41 +08:00
Yang Luo
5521962e0c feat: update go-sms-sender to v0.17.0 to improve error handling 2023-12-07 14:25:21 +08:00
Yang Luo
37b8b09cc0 feat: update go-sms-sender to v0.16.0 to fix first number missing bug in AmazonSNSClient.SendMessage 2023-12-06 20:05:48 +08:00
Yang Luo
482eb61168 feat: improve StaticFilter() 2023-12-05 18:33:06 +08:00
Lars Lehtonen
8819a8697b feat: fix dropped error in stripe.go (#2525) 2023-12-05 16:02:33 +08:00
Yang Luo
85cb68eb66 feat: unbind LDAP clients if not used any more 2023-12-02 17:51:25 +08:00
Yang Luo
b25b5f0249 Support original accessToken in token APIs 2023-12-02 16:56:18 +08:00
Yang Luo
947dcf6e75 Fix "All" roles bug in permission edit page 2023-12-02 15:26:52 +08:00
Yang Luo
113c27db73 Improve logout's id_token_hint logic 2023-12-02 02:13:34 +08:00
Nex Zhu
badfe34755 feat: add "nonce" into the OAuth and OIDC tokens, for some apps require "nonce" to integrate (#2522) 2023-12-01 18:29:39 +08:00
Yang Luo
a5f9f61381 feat: add token hash to improve performance 2023-11-30 18:05:30 +08:00
Daniil Mikhaylov
2ce8c93ead feat: Improve LDAP filter support (#2519) 2023-11-26 23:11:49 +08:00
Yang Luo
da41ac7275 Improve error handling in getFaviconFileBuffer() 2023-11-25 18:31:33 +08:00
hsluoyz
fd0c70a827 feat: Revert "feat: fix login page path after logout" (#2516)
This reverts commit 23d4488b64.
2023-11-24 15:52:59 +08:00
Yang Luo
c4a6f07672 Allow app user in demo mode 2023-11-24 01:04:23 +08:00
Nex Zhu
a67f541171 feat: in LDAP, search '*' should return all properties (#2511) 2023-11-22 23:52:40 +08:00
Yang Luo
192968bac8 Improve permission.State 2023-11-22 00:03:33 +08:00
aiden
23d4488b64 feat: fix login page path after logout (#2493)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-11-21 23:37:35 +08:00
songjf
23f4684e1d feat: make MFA works for CAS login (#2506)
* feat: make MFA works for CAS login

* fix: Reduced code redundancy

* fix: Modified the format of the code.

* fix: fix an error with the 'res' variable

* Update LoginPage.js

* Update LoginPage.js

* Update LoginPage.js

* Update MfaAuthVerifyForm.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-11-21 21:35:19 +08:00
xzgan
1a91e7b0f9 feat: support LDAP in Linux (#2508) 2023-11-21 14:01:27 +08:00
Yang Luo
811999b6cc feat: fix error handling in CheckPassword() related functions 2023-11-20 21:49:19 +08:00
Jiankun Yang
7786018051 feat: use short state for OAuth provider (#2504)
* fix: use fixed length of state

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

* feat: add log

* feat: update sign

* feat: process wechat pay result

* feat: process wechat pay result

* feat: save wechat openid for different app

* feat: save wechat openid for different app

* feat: add SetUserOAuthProperties for signup

* feat: fix openid for wechat

* feat: get user extra property in buyproduct

* feat: remove log

* feat: remove log

* feat: gofumpt code

* feat: change lr->crlf

* feat: change crlf->lf

* feat: improve code
2023-11-11 17:16:57 +08:00
Yang Luo
d090e9c860 Improve downloadImage() 2023-11-10 08:35:21 +08:00
Yang Luo
8ebb158765 feat: improve README 2023-11-09 21:52:52 +08:00
Yang Luo
ea2f053630 feat: add fields like Email to user profile in JWT-Empty mode 2023-11-09 20:20:42 +08:00
Yang Luo
988b14c6b5 Fix user's UpdatedTime in other APIs 2023-11-08 20:22:28 +08:00
Yang Luo
a9e72ac3cb feat: fix bug in GetAllowedApplications() 2023-11-08 10:31:24 +08:00
Yang Luo
498cd02d49 feat: add GetAllowedApplications() in user's app homepage 2023-11-08 09:48:31 +08:00
Yang Luo
a389842f59 Improve Product fields 2023-11-06 19:44:21 +08:00
aiden
6c69daa666 feat: fix search for ldap users' name within an organization (#2476)
* fix: #2304

* fix: when logging in with OAuth2 and authenticating via WebAuthn, retrieve the application from the clientId.

* fix: search for ldap users' name within an organization

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-11-06 11:48:23 +08:00
Yang Luo
53c89bbe89 feat: upgrade xorm-adapter to add id to CasbinRule 2023-11-03 02:48:01 +08:00
Yang Luo
9442aa9f7a Remove useless PermissionRule 2023-11-03 00:39:16 +08:00
Yang Luo
8a195715d0 Remove migrator code 2023-11-03 00:25:09 +08:00
Lars Lehtonen
b985bab3f3 fix: fix dropped errors in GetUser() (#2470)
* controllers: fix dropped errors

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-11-01 23:07:24 +08:00
aiden
477a090aa0 feat: when logging in with OAuth2 and authenticating via WebAuthn, retrieve the application from the clientId (#2469)
* fix: #2304

* fix: when logging in with OAuth2 and authenticating via WebAuthn, retrieve the application from the clientId.

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-11-01 18:40:05 +08:00
songjf
e082cf10e0 fix: fix Okta provider no host issue (#2467) 2023-11-01 18:14:39 +08:00
吃着土豆坐地铁
3215b88eae fix: ADFS GetToken() and GetUserInfo() bug (#2468)
* fix adfs bug

* Update adfs.go

---------

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2023-11-01 17:58:17 +08:00
Yang Luo
9703f3f712 Support Apple OAuth login now 2023-10-31 23:10:36 +08:00
Yang Luo
140737b2f6 Fix some bugs in Apple OAuth login path 2023-10-31 23:10:36 +08:00
haiwu
b285144a64 ci: support MySQL data sync (#2443)
* feat: support tool for mysql master-slave sync

* feat: support mysql master-master sync

* feat: improve log

* feat: improve code

* fix: fix bug when len(res) ==0

* fix: fix bug when len(res) ==0

* feat: support master-slave sync

* feat: add deleteSlaveUser for TestStopMasterSlaveSync

* feat: add deleteSlaveUser for TestStopMasterSlaveSync
2023-10-31 21:00:09 +08:00
github-actions[bot]
49c6ce2221 refactor: New Crowdin translations (#1667)
* refactor: New Crowdin translations by Github Action

* refactor: New Crowdin Backend translations by Github Action

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-10-31 18:11:05 +08:00
Yang Luo
2398e69012 Improve fastAutoSignin() 2023-10-31 16:54:30 +08:00
Yang Luo
ade9de8256 Add DumpToFile() to export init_data.json 2023-10-31 14:39:50 +08:00
Yang Luo
1bf5497d08 Improve error handling for GetUser() 2023-10-31 14:01:37 +08:00
Yang Luo
cf10738f45 Fix typo in AddUserKeys() 2023-10-31 13:31:12 +08:00
Yang Luo
ac00713c20 Improve error handling for object/user.go 2023-10-31 13:20:44 +08:00
Yang Luo
febb27f765 Remove useless fields in GenerateCasToken() 2023-10-30 18:45:34 +08:00
aiden
49a981f787 fix: fix that GROUPS is a reserved keyword introduced in MySQL 8.0 (#2458)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-30 10:59:48 +08:00
aiden
34b1945180 feat: fix bugs in custom app sso login with WebAuthn authentication (#2457)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-30 10:54:34 +08:00
Yang Luo
b320cca789 Can disable ldapServerPort by setting to empty string 2023-10-29 23:55:08 +08:00
Yang Luo
b38654a45a Add renderAiAssistant() 2023-10-28 23:58:51 +08:00
Yang Luo
f77fafae24 Fix hidden top navbar item 2023-10-28 17:07:29 +08:00
songjf
8b6b5ffe81 feat: fix go-reddit module checksum mismatch (#2451) 2023-10-28 15:32:36 +08:00
Chao
a147fa3e0b feat: fix bug that tableNamePrefix caused getRolesByUserInternal() to fail (#2450)
If set tableNamePrefix in app.conf, while cause sql error
2023-10-28 09:45:54 +08:00
Yang Luo
9d03665523 Fix FromProviderToIdpInfo() bug 2023-10-27 18:10:22 +08:00
Yang Luo
0106c7f7fa Fix GetIdProvider() bug 2023-10-27 17:03:37 +08:00
Yang Luo
6713dad0af Fix this.props.account null issue 2023-10-27 02:13:23 +08:00
Yang Luo
6ef2b51782 Support fastAutoSignin by backend redirection 2023-10-27 00:44:50 +08:00
Yang Luo
1732cd8538 Fix the bug that sometimes cannot auto login with enableAutoSignin = true 2023-10-27 00:06:17 +08:00
Yang Luo
a10548fe73 Fix org admin's enforcer policy APIs 2023-10-26 23:31:36 +08:00
Yang Luo
f6a7888f83 Deleted user cannot perform actions 2023-10-26 10:41:38 +08:00
Yang Luo
93efaa5459 Fix FileExist() error handling 2023-10-26 10:40:28 +08:00
jump2cn
0bfe683108 feat: change canonicalizer algorithm to xml-exc-c14n# (#2440) 2023-10-24 14:13:09 +08:00
Yang Luo
8a4758c22d Update sync code 2023-10-22 11:56:56 +08:00
Yang Luo
ee3b46e91c Allow permission.Model to be empty 2023-10-22 02:35:51 +08:00
Yang Luo
37744d6cd7 Improve permission error handling 2023-10-22 02:30:29 +08:00
Yang Luo
98defe617b Add providerItem.SignupGroup 2023-10-20 23:10:43 +08:00
Yang Luo
96cbf51ca0 Remove useless alertType field 2023-10-20 23:01:11 +08:00
Yang Luo
22b57fdd23 Add application.EnableSamlC14n10 2023-10-20 22:37:23 +08:00
haiwu
b68e291f37 feat: support SAML Custom provider (#2430)
* 111

* feat: support custom saml provider

* feat: gofumpt code

* feat: gofumpt code

* feat: remove comment

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-20 21:11:36 +08:00
aiden
9960b4933b feat: respect isReadOnly in the syncer (#2427)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-19 18:57:12 +08:00
aiden
432a5496f2 fix: skip checking password when the code is provided (#2425)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-19 18:25:25 +08:00
aiden
45db4deb6b feat: support checking permissions for group roles (#2422)
* fix(permission): fix CheckLoginPermission() logic

* style: fix code format

* feat: support settting roles for groups

* fix: fix field name

* style: format codes

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-19 15:33:45 +08:00
Yang Luo
3f53591751 Improve verification no provider error message 2023-10-18 15:32:12 +08:00
Yang Luo
d7569684f6 Local admin can edit its org user's other fields now 2023-10-18 12:16:05 +08:00
Yang Luo
a616127909 Add organization.DefaultPassword 2023-10-18 11:58:25 +08:00
Yang Luo
f2e2b960ff Improve downloadImage() error handling 2023-10-18 02:25:22 +08:00
Yang Luo
fbc603876f feat: add originFrontend to app.conf 2023-10-17 21:47:18 +08:00
Yang Luo
9ea77c63d1 Local admin can edit its org users now 2023-10-17 18:23:39 +08:00
songjf
53243a30f3 feat: support tencent cloud SAML SSO authentication with casdoor (#2409)
* feat: Support Tencent Cloud SAML SSO authentication with Casdoor

* feat: support SamlAttributeTable in the frontend

* fix:fixed the error where frontend fields did not match the database fields

* fix:fix lint error

* fix:fixed non-standard naming

* fix:remove if conditional statement

* feat:Add Saml Attribute format select

* fix:fix typo

* fix:fix typo

* fix:fix typo

* Update SamlAttributeTable.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-17 15:40:41 +08:00
aiden
cbdeb91ee8 feat: support groups in app login permissions (#2413)
* fix(permission): fix CheckLoginPermission() logic

* style: fix code format

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-17 14:35:13 +08:00
Yang Luo
2dd1dc582f Add text to app's signup table 2023-10-15 18:17:50 +08:00
Yang Luo
f3d4b45a0f Add label and placeholder to app's signup table 2023-10-15 17:24:38 +08:00
Yang Luo
2ee4aebd96 Fix error handling in GetSamlMeta() 2023-10-15 17:02:40 +08:00
Yang Luo
150e3e30d5 Support app user in API authentication 2023-10-15 15:20:57 +08:00
Yang Luo
1055d7781b Improve error handling in AutoSigninFilter 2023-10-15 12:43:36 +08:00
Yang Luo
1c296e9b6f feat: activate enableGzip by default in app.conf 2023-10-15 01:27:42 +08:00
209 changed files with 8044 additions and 3516 deletions

View File

@@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18
cache: 'yarn' cache: 'yarn'
cache-dependency-path: ./web/yarn.lock cache-dependency-path: ./web/yarn.lock
- run: yarn install && CI=false yarn run build - run: yarn install && CI=false yarn run build
@@ -101,7 +101,7 @@ jobs:
working-directory: ./ working-directory: ./
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18
cache: 'yarn' cache: 'yarn'
cache-dependency-path: ./web/yarn.lock cache-dependency-path: ./web/yarn.lock
- run: yarn install - run: yarn install
@@ -127,7 +127,7 @@ jobs:
release-and-push: release-and-push:
name: Release And Push name: Release And Push
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
needs: [ frontend, backend, linter, e2e ] needs: [ frontend, backend, linter, e2e ]
steps: steps:
- name: Checkout - name: Checkout
@@ -137,7 +137,7 @@ jobs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 18
- name: Fetch Previous version - name: Fetch Previous version
id: get-previous-tag id: get-previous-tag
@@ -184,14 +184,14 @@ jobs:
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v1
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true' if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }} password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push to Docker Hub - name: Push to Docker Hub
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true' if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with: with:
context: . context: .
target: STANDARD target: STANDARD
@@ -201,7 +201,7 @@ jobs:
- name: Push All In One Version to Docker Hub - name: Push All In One Version to Docker Hub
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true' if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with: with:
context: . context: .
target: ALLINONE target: ALLINONE

40
.github/workflows/helm.yml vendored Normal file
View 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'

View File

@@ -7,7 +7,7 @@ on:
jobs: jobs:
synchronize-with-crowdin: synchronize-with-crowdin:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
steps: steps:
- name: Checkout - name: Checkout

View File

@@ -1,10 +1,10 @@
FROM node:16.18.0 AS FRONT FROM node:18.19.0 AS FRONT
WORKDIR /web WORKDIR /web
COPY ./web . COPY ./web .
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build 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 WORKDIR /go/src/casdoor
COPY . . COPY . .
RUN ./build.sh RUN ./build.sh

View File

@@ -1,5 +1,5 @@
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1> <h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3> <h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
<p align="center"> <p align="center">
<a href="#badge"> <a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg"> <img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
@@ -42,6 +42,20 @@
</a> </a>
</p> </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 ## Online demo
- Read-only site: https://door.casdoor.com (any modification operation will fail) - Read-only site: https://door.casdoor.com (any modification operation will fail)

View File

@@ -92,11 +92,14 @@ p, *, *, GET, /api/get-plan, *, *
p, *, *, GET, /api/get-subscription, *, * p, *, *, GET, /api/get-subscription, *, *
p, *, *, GET, /api/get-provider, *, * p, *, *, GET, /api/get-provider, *, *
p, *, *, GET, /api/get-organization-names, *, * 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) sa := stringadapter.NewAdapter(ruleText)
// load all rules from string adapter to enforcer's memory // load all rules from string adapter to enforcer's memory
err := sa.LoadPolicy(Enforcer.GetModel()) err = sa.LoadPolicy(Enforcer.GetModel())
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -127,8 +130,14 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
return true return true
} }
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) { if user != nil {
return true if user.IsDeleted {
return false
}
if user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
return true
}
} }
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName) 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 { func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
if method == "POST" { if method == "POST" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" { if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") {
return true return true
} else if urlPath == "/api/update-user" { } else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information // Allow ordinary users to update their own information
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") { if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
return true return true
} }
return false return false

View File

@@ -16,9 +16,11 @@ verificationCodeTimeout = 10
initScore = 0 initScore = 0
logPostOnly = true logPostOnly = true
origin = origin =
originFrontend =
staticBaseUrl = "https://cdn.casbin.org" staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false isDemoMode = false
batchSize = 100 batchSize = 100
enableGzip = true
ldapServerPort = 389 ldapServerPort = 389
radiusServerPort = 1812 radiusServerPort = 1812
radiusSecret = "secret" radiusSecret = "secret"

View File

@@ -56,6 +56,17 @@ type Captcha struct {
SubType string `json:"subType"` 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 // Signup
// @Tag Login API // @Tag Login API
// @Title Signup // @Title Signup
@@ -238,7 +249,7 @@ func (c *ApiController) Signup() {
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri" // @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
// @Param state query string false "state" // @Param state query string false "state"
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /logout [get,post] // @router /logout [post]
func (c *ApiController) Logout() { func (c *ApiController) Logout() {
// https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html // https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
accessToken := c.Input().Get("id_token_hint") accessToken := c.Input().Get("id_token_hint")
@@ -282,17 +293,15 @@ func (c *ApiController) Logout() {
return return
} }
affected, application, token, err := object.ExpireTokenByAccessToken(accessToken) _, application, token, err := object.ExpireTokenByAccessToken(accessToken)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if token == nil {
if !affected {
c.ResponseError(c.T("token:Token not found, invalid accessToken")) c.ResponseError(c.T("token:Token not found, invalid accessToken"))
return return
} }
if application == nil { if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application) c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
return return
@@ -319,7 +328,15 @@ func (c *ApiController) Logout() {
return return
} else { } else {
if application.IsRedirectUriValid(redirectUri) { if application.IsRedirectUriValid(redirectUri) {
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state)) redirectUrl := redirectUri
if state != "" {
if strings.Contains(redirectUri, "?") {
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
} else {
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
}
}
c.Ctx.Redirect(http.StatusFound, redirectUrl)
} else { } else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri)) c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return return
@@ -412,7 +429,7 @@ func (c *ApiController) GetUserinfo() {
// @Title UserInfo2 // @Title UserInfo2
// @Tag Account API // @Tag Account API
// @Description return Laravel compatible user information according to OAuth 2.0 // @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] // @router /user [get]
func (c *ApiController) GetUserinfo2() { func (c *ApiController) GetUserinfo2() {
user, ok := c.RequireSignedInUser() user, ok := c.RequireSignedInUser()
@@ -420,17 +437,6 @@ func (c *ApiController) GetUserinfo2() {
return 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{ response := LaravelResponse{
Id: user.Id, Id: user.Id,
Name: user.Name, Name: user.Name,
@@ -448,6 +454,7 @@ func (c *ApiController) GetUserinfo2() {
// @Tag Login API // @Tag Login API
// @Title GetCaptcha // @Title GetCaptcha
// @router /api/get-captcha [get] // @router /api/get-captcha [get]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) GetCaptcha() { func (c *ApiController) GetCaptcha() {
applicationId := c.Input().Get("applicationId") applicationId := c.Input().Get("applicationId")
isCurrentProvider := c.Input().Get("isCurrentProvider") isCurrentProvider := c.Input().Get("isCurrentProvider")
@@ -473,7 +480,7 @@ func (c *ApiController) GetCaptcha() {
Type: captchaProvider.Type, Type: captchaProvider.Type,
SubType: captchaProvider.SubType, SubType: captchaProvider.SubType,
ClientId: captchaProvider.ClientId, ClientId: captchaProvider.ClientId,
ClientSecret: captchaProvider.ClientSecret, ClientSecret: "***",
ClientId2: captchaProvider.ClientId2, ClientId2: captchaProvider.ClientId2,
ClientSecret2: captchaProvider.ClientSecret2, ClientSecret2: captchaProvider.ClientSecret2,
}) })

View File

@@ -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)) c.ResponseOk(object.GetMaskedApplication(application, userId))
} }
@@ -173,6 +181,12 @@ func (c *ApiController) GetOrganizationApplications() {
return return
} }
applications, err = object.GetAllowedApplications(applications, userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedApplications(applications, userId)) c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else { } else {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)

View File

@@ -34,6 +34,7 @@ import (
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/google/uuid" "github.com/google/uuid"
"golang.org/x/oauth2"
) )
var ( var (
@@ -154,7 +155,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""} resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
} else { } else {
scope := c.Input().Get("scope") scope := c.Input().Get("scope")
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host) nonce := c.Input().Get("nonce")
token, _ := object.GetTokenByUser(application, user, scope, nonce, c.Ctx.Request.Host)
resp = tokenToResponse(token) resp = tokenToResponse(token)
} }
} else if form.Type == ResponseTypeSaml { // saml flow } else if form.Type == ResponseTypeSaml { // saml flow
@@ -220,7 +222,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// @Param redirectUri query string true "redirect uri" // @Param redirectUri query string true "redirect uri"
// @Param scope query string true "scope" // @Param scope query string true "scope"
// @Param state query string true "state" // @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] // @router /get-app-login [get]
func (c *ApiController) GetApplicationLogin() { func (c *ApiController) GetApplicationLogin() {
clientId := c.Input().Get("clientId") clientId := c.Input().Get("clientId")
@@ -331,8 +333,6 @@ func (c *ApiController) Login() {
} }
var user *object.User var user *object.User
var msg string
if authForm.Password == "" { if authForm.Password == "" {
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil { if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
@@ -354,20 +354,21 @@ func (c *ApiController) Login() {
} }
// check result through Email or Phone // check result through Email or Phone
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage()) err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
if len(checkResult) != 0 { if err != nil {
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult)) c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
return return
} }
// disable the verification code // disable the verification code
err := object.DisableVerificationCode(checkDest) err = object.DisableVerificationCode(checkDest)
if err != nil { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
} }
} else { } else {
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
@@ -386,7 +387,18 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} else if enableCaptcha { } else if enableCaptcha {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret) captchaProvider, err := object.GetCaptchaProviderByApplication(util.GetId(application.Owner, application.Name), "false", c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
if captchaProvider.Type != "Default" {
authForm.ClientSecret = captchaProvider.ClientSecret
}
var isHuman bool
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -399,13 +411,15 @@ func (c *ApiController) Login() {
} }
password := authForm.Password password := authForm.Password
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha) user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
} }
if msg != "" { if err != nil {
resp = &Response{Status: "error", Msg: msg} c.ResponseError(err.Error())
return
} else { } else {
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -416,7 +430,8 @@ func (c *ApiController) Login() {
return return
} }
organization, err := object.GetOrganizationByUser(user) var organization *object.Organization
organization, err = object.GetOrganizationByUser(user)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
} }
@@ -461,12 +476,15 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application)) c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return return
} }
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
var organization *object.Organization
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
if err != nil { if err != nil {
c.ResponseError(c.T(err.Error())) c.ResponseError(c.T(err.Error()))
} }
provider, err := object.GetProvider(util.GetId("admin", authForm.Provider)) var provider *object.Provider
provider, err = object.GetProvider(util.GetId("admin", authForm.Provider))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -477,11 +495,10 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name)) c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
return return
} }
userInfo := &idp.UserInfo{} userInfo := &idp.UserInfo{}
if provider.Category == "SAML" { if provider.Category == "SAML" {
// 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -489,7 +506,12 @@ func (c *ApiController) Login() {
} else if provider.Category == "OAuth" || provider.Category == "Web3" { } else if provider.Category == "OAuth" || provider.Category == "Web3" {
// OAuth // OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider) idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri) var idProvider idp.IdProvider
idProvider, err = idp.GetIdProvider(idpInfo, authForm.RedirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
if idProvider == nil { if idProvider == nil {
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type)) c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
return return
@@ -503,7 +525,8 @@ func (c *ApiController) Login() {
} }
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338 // https://github.com/golang/oauth2/issues/123#issuecomment-103715338
token, err := idProvider.GetToken(authForm.Code) var token *oauth2.Token
token, err = idProvider.GetToken(authForm.Code)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -524,7 +547,8 @@ func (c *ApiController) Login() {
if authForm.Method == "signup" { if authForm.Method == "signup" {
user := &object.User{} user := &object.User{}
if provider.Category == "SAML" { 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -543,7 +567,12 @@ func (c *ApiController) Login() {
if user.IsForbidden { if user.IsForbidden {
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator")) c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
} }
// sync info from 3rd-party if possible
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
if err != nil {
c.ResponseError(err.Error())
return
}
resp = c.HandleLoggedIn(application, user, &authForm) resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
@@ -584,14 +613,16 @@ func (c *ApiController) Login() {
} }
// Handle username conflicts // Handle username conflicts
tmpUser, err := object.GetUser(util.GetId(application.Organization, userInfo.Username)) var tmpUser *object.User
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if tmpUser != nil { if tmpUser != nil {
uid, err := uuid.NewRandom() var uid uuid.UUID
uid, err = uuid.NewRandom()
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -602,14 +633,16 @@ func (c *ApiController) Login() {
} }
properties := map[string]string{} properties := map[string]string{}
count, err := object.GetUserCount(application.Organization, "", "", "") var count int64
count, err = object.GetUserCount(application.Organization, "", "", "")
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
properties["no"] = strconv.Itoa(int(count + 2)) properties["no"] = strconv.Itoa(int(count + 2))
initScore, err := organization.GetInitScore() var initScore int
initScore, err = organization.GetInitScore()
if err != nil { if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error()) c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return return
@@ -641,7 +674,8 @@ func (c *ApiController) Login() {
Properties: properties, Properties: properties,
} }
affected, err := object.AddUser(user) var affected bool
affected, err = object.AddUser(user)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -651,10 +685,19 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user))) c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user)))
return 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 // sync info from 3rd-party if possible
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo) _, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -679,6 +722,7 @@ func (c *ApiController) Login() {
record2.User = user.Name record2.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record2) }) util.SafeGoroutine(func() { object.AddRecord(record2) })
} else if provider.Category == "SAML" { } 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: "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} // resp = &Response{Status: "ok", Msg: "", Data: res}
@@ -689,7 +733,8 @@ func (c *ApiController) Login() {
return return
} }
oldUser, err := object.GetUserByField(application.Organization, provider.Type, userInfo.Id) var oldUser *object.User
oldUser, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -700,7 +745,8 @@ func (c *ApiController) Login() {
return return
} }
user, err := object.GetUser(userId) var user *object.User
user, err = object.GetUser(userId)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -713,7 +759,8 @@ func (c *ApiController) Login() {
return return
} }
isLinked, err := object.LinkUserAccount(user, provider.Type, userInfo.Id) var isLinked bool
isLinked, err = object.LinkUserAccount(user, provider.Type, userInfo.Id)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -726,7 +773,8 @@ func (c *ApiController) Login() {
} }
} }
} else if c.getMfaUserSession() != "" { } else if c.getMfaUserSession() != "" {
user, err := object.GetUser(c.getMfaUserSession()) var user *object.User
user, err = object.GetUser(c.getMfaUserSession())
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -759,7 +807,8 @@ func (c *ApiController) Login() {
return return
} }
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -780,7 +829,8 @@ func (c *ApiController) Login() {
} else { } else {
if c.GetSessionUsername() != "" { if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in // user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application)) var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
@@ -837,6 +887,7 @@ func (c *ApiController) HandleSamlLogin() {
// @Tag HandleOfficialAccountEvent API // @Tag HandleOfficialAccountEvent API
// @Title HandleOfficialAccountEvent // @Title HandleOfficialAccountEvent
// @router /api/webhook [POST] // @router /api/webhook [POST]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) HandleOfficialAccountEvent() { func (c *ApiController) HandleOfficialAccountEvent() {
respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body) respBytes, err := ioutil.ReadAll(c.Ctx.Request.Body)
if err != nil { if err != nil {
@@ -867,6 +918,7 @@ func (c *ApiController) HandleOfficialAccountEvent() {
// @Tag GetWebhookEventType API // @Tag GetWebhookEventType API
// @Title GetWebhookEventType // @Title GetWebhookEventType
// @router /api/get-webhook-event [GET] // @router /api/get-webhook-event [GET]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) GetWebhookEventType() { func (c *ApiController) GetWebhookEventType() {
lock.Lock() lock.Lock()
defer lock.Unlock() defer lock.Unlock()
@@ -896,8 +948,14 @@ func (c *ApiController) GetCaptchaStatus() {
return return
} }
failedSigninLimit, _, err := object.GetFailedSigninConfigByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
var captchaEnabled bool var captchaEnabled bool
if user != nil && user.SigninWrongTimes >= object.SigninWrongTimesLimit { if user != nil && user.SigninWrongTimes >= failedSigninLimit {
captchaEnabled = true captchaEnabled = true
} }
c.ResponseOk(captchaEnabled) c.ResponseOk(captchaEnabled)
@@ -908,6 +966,7 @@ func (c *ApiController) GetCaptchaStatus() {
// @Tag Callback API // @Tag Callback API
// @Description Get Login Error Counts // @Description Get Login Error Counts
// @router /api/Callback [post] // @router /api/Callback [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) Callback() { func (c *ApiController) Callback() {
code := c.GetString("code") code := c.GetString("code")
state := c.GetString("state") state := c.GetString("state")

View File

@@ -16,6 +16,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@@ -25,7 +26,7 @@ import (
// @Title Enforce // @Title Enforce
// @Tag Enforce API // @Tag Enforce API
// @Description Call Casbin 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 permissionId query string false "permission id"
// @Param modelId query string false "model id" // @Param modelId query string false "model id"
// @Param resourceId query string false "resource id" // @Param resourceId query string false "resource id"
@@ -42,7 +43,7 @@ func (c *ApiController) Enforce() {
return return
} }
var request object.CasbinRequest var request []string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request) err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
@@ -56,13 +57,22 @@ func (c *ApiController) Enforce() {
return return
} }
res, err := enforcer.Enforce(request...) res := []bool{}
keyRes := []string{}
// type transformation
interfaceRequest := util.StringToInterfaceArray(request)
enforceResult, err := enforcer.Enforce(interfaceRequest...)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
c.ResponseOk(res) res = append(res, enforceResult)
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
c.ResponseOk(res, keyRes)
return return
} }
@@ -72,22 +82,24 @@ func (c *ApiController) Enforce() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
res := []bool{}
if permission == nil { if permission == nil {
res = append(res, false) c.ResponseError(fmt.Sprintf("permission: %s doesn't exist", permissionId))
} else { return
enforceResult, err := object.Enforce(permission, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
} }
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 return
} }
@@ -111,32 +123,33 @@ func (c *ApiController) Enforce() {
} }
res := []bool{} res := []bool{}
keyRes := []string{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions) listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap { for key, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0]) firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...) enforceResult, err := object.Enforce(firstPermission, request, permissionIds...)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
res = append(res, enforceResult) res = append(res, enforceResult)
keyRes = append(keyRes, key)
} }
c.ResponseOk(res) c.ResponseOk(res, keyRes)
} }
// BatchEnforce // BatchEnforce
// @Title BatchEnforce // @Title BatchEnforce
// @Tag Enforce API // @Tag Enforce API
// @Description Call Casbin BatchEnforce 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 permissionId query string false "permission id"
// @Param modelId query string false "model id" // @Param modelId query string false "model id"
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
@@ -146,7 +159,7 @@ func (c *ApiController) BatchEnforce() {
modelId := c.Input().Get("modelId") modelId := c.Input().Get("modelId")
enforcerId := c.Input().Get("enforcerId") enforcerId := c.Input().Get("enforcerId")
var requests []object.CasbinRequest var requests [][]string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests) err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
@@ -160,13 +173,22 @@ func (c *ApiController) BatchEnforce() {
return return
} }
res, err := enforcer.BatchEnforce(requests) res := [][]bool{}
keyRes := []string{}
// type transformation
interfaceRequests := util.StringToInterfaceArray2d(requests)
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
c.ResponseOk(res) res = append(res, enforceResult)
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
c.ResponseOk(res, keyRes)
return return
} }
@@ -176,28 +198,24 @@ func (c *ApiController) BatchEnforce() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
res := [][]bool{}
if permission == nil { if permission == nil {
l := len(requests) c.ResponseError(fmt.Sprintf("permission: %s doesn't exist", permissionId))
resRequest := make([]bool, l) return
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.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 return
} }
@@ -215,7 +233,7 @@ func (c *ApiController) BatchEnforce() {
} }
res := [][]bool{} res := [][]bool{}
keyRes := []string{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions) listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap { for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0]) firstPermission, err := object.GetPermission(permissionIds[0])
@@ -224,16 +242,17 @@ func (c *ApiController) BatchEnforce() {
return return
} }
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...) enforceResult, err := object.BatchEnforce(firstPermission, requests, permissionIds...)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
res = append(res, enforceResult) res = append(res, enforceResult)
keyRes = append(keyRes, firstPermission.GetModelAndAdapter())
} }
c.ResponseOk(res) c.ResponseOk(res, keyRes)
} }
func (c *ApiController) GetAllObjects() { func (c *ApiController) GetAllObjects() {
@@ -243,7 +262,13 @@ func (c *ApiController) GetAllObjects() {
return return
} }
c.ResponseOk(object.GetAllObjects(userId)) objects, err := object.GetAllObjects(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(objects)
} }
func (c *ApiController) GetAllActions() { func (c *ApiController) GetAllActions() {
@@ -253,7 +278,13 @@ func (c *ApiController) GetAllActions() {
return return
} }
c.ResponseOk(object.GetAllActions(userId)) actions, err := object.GetAllActions(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(actions)
} }
func (c *ApiController) GetAllRoles() { func (c *ApiController) GetAllRoles() {
@@ -263,5 +294,11 @@ func (c *ApiController) GetAllRoles() {
return return
} }
c.ResponseOk(object.GetAllRoles(userId)) roles, err := object.GetAllRoles(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(roles)
} }

View File

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

View File

@@ -71,7 +71,7 @@ func (c *ApiController) GetEnforcers() {
// @Tag Enforcer API // @Tag Enforcer API
// @Description get enforcer // @Description get enforcer
// @Param id query string true "The id ( owner/name ) of 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] // @router /get-enforcer [get]
func (c *ApiController) GetEnforcer() { func (c *ApiController) GetEnforcer() {
id := c.Input().Get("id") id := c.Input().Get("id")
@@ -99,7 +99,7 @@ func (c *ApiController) GetEnforcer() {
// @Description update enforcer // @Description update enforcer
// @Param id query string true "The id ( owner/name ) of enforcer" // @Param id query string true "The id ( owner/name ) of enforcer"
// @Param enforcer body object true "The enforcer object" // @Param enforcer body object true "The enforcer object"
// @Success 200 {object} object // @Success 200 {object} object.Enforcer
// @router /update-enforcer [post] // @router /update-enforcer [post]
func (c *ApiController) UpdateEnforcer() { func (c *ApiController) UpdateEnforcer() {
id := c.Input().Get("id") id := c.Input().Get("id")
@@ -120,7 +120,7 @@ func (c *ApiController) UpdateEnforcer() {
// @Tag Enforcer API // @Tag Enforcer API
// @Description add enforcer // @Description add enforcer
// @Param enforcer body object true "The enforcer object" // @Param enforcer body object true "The enforcer object"
// @Success 200 {object} object // @Success 200 {object} object.Enforcer
// @router /add-enforcer [post] // @router /add-enforcer [post]
func (c *ApiController) AddEnforcer() { func (c *ApiController) AddEnforcer() {
enforcer := object.Enforcer{} enforcer := object.Enforcer{}
@@ -138,8 +138,8 @@ func (c *ApiController) AddEnforcer() {
// @Title DeleteEnforcer // @Title DeleteEnforcer
// @Tag Enforcer API // @Tag Enforcer API
// @Description delete enforcer // @Description delete enforcer
// @Param body body object.Enforce true "The enforcer object" // @Param body body object.Enforcer true "The enforcer object"
// @Success 200 {object} object // @Success 200 {object} object.Enforcer
// @router /delete-enforcer [post] // @router /delete-enforcer [post]
func (c *ApiController) DeleteEnforcer() { func (c *ApiController) DeleteEnforcer() {
var enforcer object.Enforcer var enforcer object.Enforcer

View File

@@ -42,7 +42,7 @@ type LdapSyncResp struct {
// @Tag Account API // @Tag Account API
// @Description get ldap users // @Description get ldap users
// Param id string true "id" // 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] // @router /get-ldap-users [get]
func (c *ApiController) GetLdapUsers() { func (c *ApiController) GetLdapUsers() {
id := c.Input().Get("id") id := c.Input().Get("id")
@@ -59,6 +59,7 @@ func (c *ApiController) GetLdapUsers() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
defer conn.Close()
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn) //groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
//if err != nil { //if err != nil {
@@ -249,7 +250,7 @@ func (c *ApiController) DeleteLdap() {
// @Tag Account API // @Tag Account API
// @Description sync ldap users // @Description sync ldap users
// @Param id query string true "id" // @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] // @router /sync-ldap-users [post]
func (c *ApiController) SyncLdapUsers() { func (c *ApiController) SyncLdapUsers() {
id := c.Input().Get("id") id := c.Input().Get("id")

View File

@@ -26,8 +26,10 @@ type LinkForm struct {
} }
// Unlink ... // Unlink ...
// @router /unlink [post]
// @Tag Login API // @Tag Login API
// @Title Unlink
// @router /unlink [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) Unlink() { func (c *ApiController) Unlink() {
user, ok := c.RequireSignedInUser() user, ok := c.RequireSignedInUser()
if !ok { if !ok {

View File

@@ -73,7 +73,7 @@ func (c *ApiController) MfaSetupInitiate() {
// @Description setup verify totp // @Description setup verify totp
// @param secret form string true "MFA secret" // @param secret form string true "MFA secret"
// @param passcode form string true "MFA passcode" // @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] // @router /mfa/setup/verify [post]
func (c *ApiController) MfaSetupVerify() { func (c *ApiController) MfaSetupVerify() {
mfaType := c.Ctx.Request.Form.Get("mfaType") mfaType := c.Ctx.Request.Form.Get("mfaType")
@@ -104,7 +104,7 @@ func (c *ApiController) MfaSetupVerify() {
// @param owner form string true "owner of user" // @param owner form string true "owner of user"
// @param name form string true "name of user" // @param name form string true "name of user"
// @param type form string true "MFA auth type" // @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] // @router /mfa/setup/enable [post]
func (c *ApiController) MfaSetupEnable() { func (c *ApiController) MfaSetupEnable() {
owner := c.Ctx.Request.Form.Get("owner") owner := c.Ctx.Request.Form.Get("owner")
@@ -143,7 +143,7 @@ func (c *ApiController) MfaSetupEnable() {
// @Description: Delete MFA // @Description: Delete MFA
// @param owner form string true "owner of user" // @param owner form string true "owner of user"
// @param name form string true "name 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] // @router /delete-mfa/ [post]
func (c *ApiController) DeleteMfa() { func (c *ApiController) DeleteMfa() {
owner := c.Ctx.Request.Form.Get("owner") owner := c.Ctx.Request.Form.Get("owner")
@@ -176,7 +176,7 @@ func (c *ApiController) DeleteMfa() {
// @param owner form string true "owner of user" // @param owner form string true "owner of user"
// @param name form string true "name of user" // @param name form string true "name of user"
// @param id form string true "id of user's MFA props" // @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] // @router /set-preferred-mfa [post]
func (c *ApiController) SetPreferredMfa() { func (c *ApiController) SetPreferredMfa() {
mfaType := c.Ctx.Request.Form.Get("mfaType") mfaType := c.Ctx.Request.Form.Get("mfaType")

View File

@@ -178,7 +178,7 @@ func (c *ApiController) DeleteOrganization() {
// @Tag Organization API // @Tag Organization API
// @Description get default application // @Description get default application
// @Param id query string true "organization id" // @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] // @router /get-default-application [get]
func (c *ApiController) GetDefaultApplication() { func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername() userId := c.GetSessionUsername()

View File

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

View File

@@ -33,7 +33,13 @@ func (c *ApiController) GetSamlMeta() {
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp)) c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
return 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.Data["xml"] = metadata
c.ServeXML() c.ServeXML()
} }

View File

@@ -51,9 +51,14 @@ type NotificationForm struct {
// @Param clientId query string true "The clientId of the application" // @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application" // @Param clientSecret query string true "The clientSecret of the application"
// @Param from body controllers.EmailForm true "Details of the email request" // @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] // @router /api/send-email [post]
func (c *ApiController) SendEmail() { func (c *ApiController) SendEmail() {
user, ok := c.RequireSignedInUser()
if !ok {
return
}
var emailForm EmailForm var emailForm EmailForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm) err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
@@ -108,8 +113,13 @@ func (c *ApiController) SendEmail() {
} }
code := "123456" code := "123456"
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes." // "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(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 { for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender) err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
if err != nil { if err != nil {
@@ -128,7 +138,7 @@ func (c *ApiController) SendEmail() {
// @Param clientId query string true "The clientId of the application" // @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application" // @Param clientSecret query string true "The clientSecret of the application"
// @Param from body controllers.SmsForm true "Details of the sms request" // @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] // @router /api/send-sms [post]
func (c *ApiController) SendSms() { func (c *ApiController) SendSms() {
provider, err := c.GetProviderFromContext("SMS") provider, err := c.GetProviderFromContext("SMS")
@@ -166,7 +176,7 @@ func (c *ApiController) SendSms() {
// @Tag Service API // @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs. // @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" // @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] // @router /api/send-notification [post]
func (c *ApiController) SendNotification() { func (c *ApiController) SendNotification() {
provider, err := c.GetProviderFromContext("Notification") provider, err := c.GetProviderFromContext("Notification")

View File

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

View File

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

View File

@@ -161,7 +161,6 @@ func (c *ApiController) GetUser() {
} }
var user *object.User var user *object.User
if id == "" && owner == "" { if id == "" && owner == "" {
switch { switch {
case email != "": case email != "":
@@ -176,11 +175,16 @@ func (c *ApiController) GetUser() {
owner = util.GetOwnerFromId(id) owner = util.GetOwnerFromId(id)
} }
organization, err := object.GetOrganization(util.GetId("admin", owner)) var organization *object.Organization
organization, err = object.GetOrganization(util.GetId("admin", owner))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if organization == nil {
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
return
}
if !organization.IsProfilePublic { if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
@@ -472,16 +476,16 @@ func (c *ApiController) SetPassword() {
isAdmin := c.IsAdmin() isAdmin := c.IsAdmin()
if isAdmin { if isAdmin {
if oldPassword != "" { if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage()) err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" { if err != nil {
c.ResponseError(msg) c.ResponseError(err.Error())
return return
} }
} }
} else { } else if code == "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage()) err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" { if err != nil {
c.ResponseError(msg) c.ResponseError(err.Error())
return return
} }
} }
@@ -506,6 +510,7 @@ func (c *ApiController) SetPassword() {
// @Title CheckUserPassword // @Title CheckUserPassword
// @router /check-user-password [post] // @router /check-user-password [post]
// @Tag User API // @Tag User API
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) CheckUserPassword() { func (c *ApiController) CheckUserPassword() {
var user object.User var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user) err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
@@ -514,11 +519,11 @@ func (c *ApiController) CheckUserPassword() {
return return
} }
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage()) _, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
if msg == "" { if err != nil {
c.ResponseOk() c.ResponseError(err.Error())
} else { } else {
c.ResponseError(msg) c.ResponseOk()
} }
} }
@@ -572,11 +577,12 @@ func (c *ApiController) GetUserCount() {
c.ResponseOk(count) c.ResponseOk(count)
} }
// AddUserkeys // AddUserKeys
// @Title AddUserkeys // @Title AddUserKeys
// @router /add-user-keys [post] // @router /add-user-keys [post]
// @Tag User API // @Tag User API
func (c *ApiController) AddUserkeys() { // @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) AddUserKeys() {
var user object.User var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user) err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil { if err != nil {
@@ -585,7 +591,7 @@ func (c *ApiController) AddUserkeys() {
} }
isAdmin := c.IsAdmin() isAdmin := c.IsAdmin()
affected, err := object.AddUserkeys(&user, isAdmin) affected, err := object.AddUserKeys(&user, isAdmin)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@@ -96,6 +96,13 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
return nil, false return nil, false
} }
if strings.HasPrefix(userId, "app/") {
tmpUserId := c.Input().Get("userId")
if tmpUserId != "" {
userId = tmpUserId
}
}
user, err := object.GetUser(userId) user, err := object.GetUser(userId)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())

View File

@@ -39,6 +39,7 @@ const (
// @Title SendVerificationCode // @Title SendVerificationCode
// @Tag Verification API // @Tag Verification API
// @router /send-verification-code [post] // @router /send-verification-code [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) SendVerificationCode() { func (c *ApiController) SendVerificationCode() {
var vform form.VerificationForm var vform form.VerificationForm
err := c.ParseForm(&vform) err := c.ParseForm(&vform)
@@ -53,17 +54,34 @@ func (c *ApiController) SendVerificationCode() {
return return
} }
if vform.CaptchaType != "none" { provider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil { if err != nil {
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType) c.ResponseError(err.Error())
return return
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil { }
c.ResponseError(err.Error())
return if provider != nil {
} else if !isHuman { if vform.CaptchaType != provider.Type {
c.ResponseError(c.T("verification:Turing test failed.")) c.ResponseError(c.T("verification:Turing test failed."))
return return
} }
if provider.Type != "Default" {
vform.ClientSecret = provider.ClientSecret
}
if vform.CaptchaType != "none" {
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
return
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, vform.ClientSecret); err != nil {
c.ResponseError(err.Error())
return
} else if !isHuman {
c.ResponseError(c.T("verification:Turing test failed."))
return
}
}
} }
application, err := object.GetApplication(vform.ApplicationId) application, err := object.GetApplication(vform.ApplicationId)
@@ -142,6 +160,10 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return 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) sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
case object.VerifyTypePhone: case object.VerifyTypePhone:
@@ -184,6 +206,10 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return 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 { 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)) 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 // @Title VerifyCaptcha
// @Tag Verification API // @Tag Verification API
// @router /verify-captcha [post] // @router /verify-captcha [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) VerifyCaptcha() { func (c *ApiController) VerifyCaptcha() {
var vform form.VerificationForm var vform form.VerificationForm
err := c.ParseForm(&vform) err := c.ParseForm(&vform)
@@ -217,6 +244,16 @@ func (c *ApiController) VerifyCaptcha() {
return return
} }
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
if captchaProvider.Type != "Default" {
vform.ClientSecret = captchaProvider.ClientSecret
}
provider := captcha.GetCaptchaProvider(vform.CaptchaType) provider := captcha.GetCaptchaProvider(vform.CaptchaType)
if provider == nil { if provider == nil {
c.ResponseError(c.T("verification:Invalid captcha provider.")) c.ResponseError(c.T("verification:Invalid captcha provider."))
@@ -236,6 +273,7 @@ func (c *ApiController) VerifyCaptcha() {
// @Tag Account API // @Tag Account API
// @Title ResetEmailOrPhone // @Title ResetEmailOrPhone
// @router /api/reset-email-or-phone [post] // @router /api/reset-email-or-phone [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) ResetEmailOrPhone() { func (c *ApiController) ResetEmailOrPhone() {
user, ok := c.RequireSignedInUser() user, ok := c.RequireSignedInUser()
if !ok { if !ok {
@@ -330,6 +368,7 @@ func (c *ApiController) ResetEmailOrPhone() {
// @Tag Verification API // @Tag Verification API
// @Title VerifyCode // @Title VerifyCode
// @router /api/verify-code [post] // @router /api/verify-code [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) VerifyCode() { func (c *ApiController) VerifyCode() {
var authForm form.AuthForm var authForm form.AuthForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm) err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)

View File

@@ -146,7 +146,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
} }
// WebAuthnSigninFinish // WebAuthnSigninFinish
// @Title WebAuthnSigninBegin // @Title WebAuthnSigninFinish
// @Tag Login API // @Tag Login API
// @Description WebAuthn Login Flow 2nd stage // @Description WebAuthn Login Flow 2nd stage
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response" // @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
@@ -154,6 +154,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
// @router /webauthn/signin/finish [post] // @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() { func (c *ApiController) WebAuthnSigninFinish() {
responseType := c.Input().Get("responseType") responseType := c.Input().Get("responseType")
clientId := c.Input().Get("clientId")
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host) webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
@@ -182,7 +183,13 @@ func (c *ApiController) WebAuthnSigninFinish() {
c.SetSessionUsername(userId) c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId) util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
application, err := object.GetApplicationByUser(user) var application *object.Application
if clientId != "" && (responseType == ResponseTypeCode) {
application, err = object.GetApplicationByClientId(clientId)
} else {
application, err = object.GetApplicationByUser(user)
}
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

82
email/custom_http.go Normal file
View 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
}

View File

@@ -18,9 +18,11 @@ type EmailProvider interface {
Send(fromAddress string, fromName, toAddress string, subject string, content string) error 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" { if typ == "Azure ACS" {
return NewAzureACSEmailProvider(clientSecret, host) return NewAzureACSEmailProvider(clientSecret, host)
} else if typ == "Custom HTTP Email" {
return NewHttpEmailProvider(endpoint, method)
} else { } else {
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl) return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
} }

29
go.mod
View File

@@ -4,18 +4,16 @@ go 1.16
require ( require (
github.com/Masterminds/squirrel v1.5.3 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/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
github.com/aws/aws-sdk-go v1.45.5 github.com/aws/aws-sdk-go v1.45.5
github.com/beego/beego v1.12.12 github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.77.2 github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.15.0 github.com/casdoor/go-sms-sender v0.19.0
github.com/casdoor/gomail/v2 v2.0.1 github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.44.0 github.com/casdoor/notify v0.45.0
github.com/casdoor/oss v1.3.0 github.com/casdoor/oss v1.4.1
github.com/casdoor/xorm-adapter/v3 v3.0.4 github.com/casdoor/xorm-adapter/v3 v3.1.0
github.com/casvisor/casvisor-go-sdk v1.0.3 github.com/casvisor/casvisor-go-sdk v1.0.3
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0 github.com/denisenkom/go-mssqldb v0.9.0
@@ -23,26 +21,26 @@ require (
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3 github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
github.com/fogleman/gg v1.3.0 github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0 github.com/forestmgy/ldapserver v1.1.0
github.com/go-asn1-ber/asn1-ber v1.5.5
github.com/go-git/go-git/v5 v5.6.0 github.com/go-git/go-git/v5 v5.6.0
github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-mysql-org/go-mysql v1.7.0 github.com/go-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/go-webauthn/webauthn v0.6.0 github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.3.1 github.com/google/uuid v1.4.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/json-iterator/go v1.1.12
github.com/lestrrat-go/jwx v1.2.21 github.com/lestrrat-go/jwx v1.2.21
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/markbates/goth v1.75.2 github.com/markbates/goth v1.75.2
github.com/mitchellh/mapstructure v1.5.0 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/nyaruka/phonenumbers v1.1.5
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1 github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.3.0 github.com/prometheus/client_model v0.4.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0 github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0 github.com/russellhaering/gosaml2 v0.9.0
@@ -61,11 +59,10 @@ require (
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6 github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.12.0 golang.org/x/crypto v0.14.0
golang.org/x/net v0.14.0 golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.11.0 golang.org/x/oauth2 v0.13.0
golang.org/x/text v0.13.0 // indirect google.golang.org/api v0.150.0
google.golang.org/api v0.138.0
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68

313
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung 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", "Unauthorized operation": "Nicht autorisierte Operation",
"Unknown authentication type (not password or provider), form = %s": "Unbekannter Authentifizierungstyp (nicht Passwort oder Anbieter), Formular = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein" "Service %s and %s do not match": "Service %s und %s stimmen nicht überein"
@@ -33,11 +34,12 @@
"Email is invalid": "E-Mail ist ungültig", "Email is invalid": "E-Mail ist ungültig",
"Empty username.": "Leerer Benutzername.", "Empty username.": "Leerer Benutzername.",
"FirstName cannot be blank": "Vorname darf nicht leer sein", "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", "LDAP user name or password incorrect": "Ldap Benutzername oder Passwort falsch",
"LastName cannot be blank": "Nachname darf nicht leer sein", "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", "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", "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 already exists": "Telefon existiert bereits",
"Phone cannot be empty": "Das Telefon darf nicht leer sein", "Phone cannot be empty": "Das Telefon darf nicht leer sein",
"Phone number is invalid": "Die Telefonnummer ist ungültig", "Phone number is invalid": "Die Telefonnummer ist ungültig",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "El proveedor: %s 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", "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", "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": { "cas": {
"Service %s and %s do not match": "Los servicios %s y %s no coinciden" "Service %s and %s do not match": "Los servicios %s y %s no coinciden"
@@ -33,11 +34,12 @@
"Email is invalid": "El correo electrónico no es válido", "Email is invalid": "El correo electrónico no es válido",
"Empty username.": "Nombre de usuario vacío.", "Empty username.": "Nombre de usuario vacío.",
"FirstName cannot be blank": "El nombre no puede estar en blanco", "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", "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", "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", "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", "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 already exists": "El teléfono ya existe",
"Phone cannot be empty": "Teléfono no puede estar vacío", "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", "Phone number is invalid": "El número de teléfono no es válido",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application", "The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
"Unauthorized operation": "Opération non autorisée", "Unauthorized operation": "Opération non autorisée",
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s", "Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags" "User's tag: %s is not listed in the application's tags": "Le tag de lutilisateur %s nest pas répertorié dans les tags de lapplication",
"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": { "cas": {
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas" "Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
@@ -33,17 +34,18 @@
"Email is invalid": "L'adresse e-mail est invalide", "Email is invalid": "L'adresse e-mail est invalide",
"Empty username.": "Nom d'utilisateur vide.", "Empty username.": "Nom d'utilisateur vide.",
"FirstName cannot be blank": "Le prénom ne peut pas être laissé 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", "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", "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", "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", "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 already exists": "Le téléphone existe déjà",
"Phone cannot be empty": "Le téléphone ne peut pas être vide", "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", "Phone number is invalid": "Le numéro de téléphone est invalide",
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau", "Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur", "The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server", "The user: %s doesn't exist in LDAP server": "L'utilisateur %s n'existe pas sur le serveur LDAP",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
"Username already exists": "Nom d'utilisateur existe déjà", "Username already exists": "Nom d'utilisateur existe déjà",
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail", "Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
@@ -53,7 +55,7 @@
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères", "Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer", "You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone", "Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
"password or code is incorrect": "password or code is incorrect", "password or code is incorrect": "mot de passe ou code invalide",
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances", "password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s" "unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
}, },
@@ -61,8 +63,8 @@
"Missing parameter": "Paramètre manquant", "Missing parameter": "Paramètre manquant",
"Please login first": "Veuillez d'abord vous connecter", "Please login first": "Veuillez d'abord vous connecter",
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas", "The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider", "don't support captchaProvider: ": "ne prend pas en charge captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode" "this operation is not allowed in demo mode": "cette opération nest pas autorisée en mode démo"
}, },
"ldap": { "ldap": {
"Ldap server exist": "Le serveur LDAP existe" "Ldap server exist": "Le serveur LDAP existe"

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini", "The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
"Unauthorized operation": "Operasi tidak sah", "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", "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": { "cas": {
"Service %s and %s do not match": "Layanan %s dan %s tidak cocok" "Service %s and %s do not match": "Layanan %s dan %s tidak cocok"
@@ -33,11 +34,12 @@
"Email is invalid": "Email tidak valid", "Email is invalid": "Email tidak valid",
"Empty username.": "Nama pengguna kosong.", "Empty username.": "Nama pengguna kosong.",
"FirstName cannot be blank": "Nama depan tidak boleh 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", "LDAP user name or password incorrect": "Nama pengguna atau kata sandi Ldap salah",
"LastName cannot be blank": "Nama belakang tidak boleh kosong", "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", "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", "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 already exists": "Telepon sudah ada",
"Phone cannot be empty": "Telepon tidak boleh kosong", "Phone cannot be empty": "Telepon tidak boleh kosong",
"Phone number is invalid": "Nomor telepon tidak valid", "Phone number is invalid": "Nomor telepon tidak valid",

View File

@@ -19,19 +19,12 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
}, },
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": { "check": {
"Affiliation cannot be blank": "Affiliation cannot be blank", "Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank", "DisplayName cannot be blank": "DisplayName cannot be blank",
@@ -41,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません", "The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません",
"Unauthorized operation": "不正操作", "Unauthorized operation": "不正操作",
"Unknown authentication type (not password or provider), form = %s": "不明な認証タイプ(パスワードまたはプロバイダーではない)フォーム=%s", "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": { "cas": {
"Service %s and %s do not match": "サービス%sと%sは一致しません" "Service %s and %s do not match": "サービス%sと%sは一致しません"
@@ -33,11 +34,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のユーザー名またはパスワードが間違っています", "LDAP user name or password incorrect": "Ldapのユーザー名またはパスワードが間違っています",
"LastName cannot be blank": "姓は空白にできません", "LastName cannot be blank": "姓は空白にできません",
"Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください", "Multiple accounts with same uid, please check your ldap server": "同じuidを持つ複数のアカウントがあります。あなたのLDAPサーバーを確認してください",
"Organization does not exist": "組織は存在しません", "Organization does not exist": "組織は存在しません",
"Password must have at least 6 characters": "パスワードは少なくとも6つの文字が必要です",
"Phone already exists": "電話はすでに存在しています", "Phone already exists": "電話はすでに存在しています",
"Phone cannot be empty": "電話は空っぽにできません", "Phone cannot be empty": "電話は空っぽにできません",
"Phone number is invalid": "電話番号が無効です", "Phone number is invalid": "電話番号が無効です",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다", "The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다",
"Unauthorized operation": "무단 조작", "Unauthorized operation": "무단 조작",
"Unknown authentication type (not password or provider), form = %s": "알 수 없는 인증 유형(암호 또는 공급자가 아님), 폼 = %s", "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": { "cas": {
"Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다" "Service %s and %s do not match": "서비스 %s와 %s는 일치하지 않습니다"
@@ -33,11 +34,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 사용자 이름 또는 암호가 잘못되었습니다", "LDAP user name or password incorrect": "LDAP 사용자 이름 또는 암호가 잘못되었습니다",
"LastName cannot be blank": "성은 비어 있을 수 없습니다", "LastName cannot be blank": "성은 비어 있을 수 없습니다",
"Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요", "Multiple accounts with same uid, please check your ldap server": "동일한 UID를 가진 여러 계정이 있습니다. LDAP 서버를 확인해주세요",
"Organization does not exist": "조직은 존재하지 않습니다", "Organization does not exist": "조직은 존재하지 않습니다",
"Password must have at least 6 characters": "암호는 적어도 6자 이상이어야 합니다",
"Phone already exists": "전화기는 이미 존재합니다", "Phone already exists": "전화기는 이미 존재합니다",
"Phone cannot be empty": "전화는 비워 둘 수 없습니다", "Phone cannot be empty": "전화는 비워 둘 수 없습니다",
"Phone number is invalid": "전화번호가 유효하지 않습니다", "Phone number is invalid": "전화번호가 유효하지 않습니다",

View File

@@ -19,19 +19,12 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
}, },
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": { "check": {
"Affiliation cannot be blank": "Affiliation cannot be blank", "Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank", "DisplayName cannot be blank": "DisplayName cannot be blank",
@@ -41,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения", "The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
"Unauthorized operation": "Несанкционированная операция", "Unauthorized operation": "Несанкционированная операция",
"Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %s", "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": { "cas": {
"Service %s and %s do not match": "Сервисы %s и %s не совпадают" "Service %s and %s do not match": "Сервисы %s и %s не совпадают"
@@ -33,11 +34,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", "LDAP user name or password incorrect": "Неправильное имя пользователя или пароль Ldap",
"LastName cannot be blank": "Фамилия не может быть пустой", "LastName cannot be blank": "Фамилия не может быть пустой",
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP", "Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
"Organization does not exist": "Организация не существует", "Organization does not exist": "Организация не существует",
"Password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
"Phone already exists": "Телефон уже существует", "Phone already exists": "Телефон уже существует",
"Phone cannot be empty": "Телефон не может быть пустым", "Phone cannot be empty": "Телефон не может быть пустым",
"Phone number is invalid": "Номер телефона является недействительным", "Phone number is invalid": "Номер телефона является недействительным",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,19 +19,12 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
}, },
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": { "check": {
"Affiliation cannot be blank": "Affiliation cannot be blank", "Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank", "DisplayName cannot be blank": "DisplayName cannot be blank",
@@ -41,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "The provider: %s 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", "Unauthorized operation": "Unauthorized operation",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s", "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": { "cas": {
"Service %s and %s do not match": "Service %s and %s do not match" "Service %s and %s do not match": "Service %s and %s do not match"
@@ -33,11 +34,12 @@
"Email is invalid": "Email is invalid", "Email is invalid": "Email is invalid",
"Empty username.": "Empty username.", "Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank", "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", "LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank", "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", "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", "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 already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty", "Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid", "Phone number is invalid": "Phone number is invalid",

View File

@@ -19,7 +19,8 @@
"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", "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", "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", "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": { "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" "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 +34,12 @@
"Email is invalid": "Địa chỉ email không hợp lệ", "Email is invalid": "Địa chỉ email không hợp lệ",
"Empty username.": "Tên đăng nhập trống.", "Empty username.": "Tên đăng nhập trống.",
"FirstName cannot be blank": "Tên không được để 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", "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", "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", "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", "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 already exists": "Điện thoại đã tồn tại",
"Phone cannot be empty": "Điện thoại không thể để trống", "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ệ", "Phone number is invalid": "Số điện thoại không hợp lệ",

View File

@@ -19,7 +19,8 @@
"The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用", "The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用",
"Unauthorized operation": "未授权的操作", "Unauthorized operation": "未授权的操作",
"Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s", "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": { "cas": {
"Service %s and %s do not match": "服务%s与%s不匹配" "Service %s and %s do not match": "服务%s与%s不匹配"
@@ -33,17 +34,18 @@
"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密码错误", "LDAP user name or password incorrect": "LDAP密码错误",
"LastName cannot be blank": "姓不可以为空", "LastName cannot be blank": "姓不可以为空",
"Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid请检查您的 LDAP 服务器", "Multiple accounts with same uid, please check your ldap server": "多个帐户具有相同的uid请检查您的 LDAP 服务器",
"Organization does not exist": "组织不存在", "Organization does not exist": "组织不存在",
"Password must have at least 6 characters": "新密码至少为6位",
"Phone already exists": "该手机号已存在", "Phone already exists": "该手机号已存在",
"Phone cannot be empty": "手机号不可为空", "Phone cannot be empty": "手机号不可为空",
"Phone number is invalid": "无效手机号", "Phone number is invalid": "无效手机号",
"Session outdated, please login again": "会话已过期,请重新登录", "Session outdated, please login again": "会话已过期,请重新登录",
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员", "The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server", "The user: %s doesn't exist in LDAP server": "用户: %s 在LDAP服务器中未找到",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾", "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
"Username already exists": "用户名已存在", "Username already exists": "用户名已存在",
"Username cannot be an email address": "用户名不可以是邮箱地址", "Username cannot be an email address": "用户名不可以是邮箱地址",
@@ -62,7 +64,7 @@
"Please login first": "请先登录", "Please login first": "请先登录",
"The user: %s doesn't exist": "用户: %s不存在", "The user: %s doesn't exist": "用户: %s不存在",
"don't support captchaProvider: ": "不支持验证码提供商: ", "don't support captchaProvider: ": "不支持验证码提供商: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode" "this operation is not allowed in demo mode": "demo模式下不允许该操作"
}, },
"ldap": { "ldap": {
"Ldap server exist": "LDAP服务器已存在" "Ldap server exist": "LDAP服务器已存在"

View File

@@ -19,7 +19,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
@@ -84,11 +83,14 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
payload.Set("code", code) payload.Set("code", code)
payload.Set("grant_type", "authorization_code") payload.Set("grant_type", "authorization_code")
payload.Set("client_id", idp.Config.ClientID) payload.Set("client_id", idp.Config.ClientID)
payload.Set("client_secret", idp.Config.ClientSecret)
payload.Set("redirect_uri", idp.Config.RedirectURL) payload.Set("redirect_uri", idp.Config.RedirectURL)
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload) resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -97,10 +99,10 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
pToken := &AdfsToken{} pToken := &AdfsToken{}
err = json.Unmarshal(data, pToken) err = json.Unmarshal(data, pToken)
if err != nil { if err != nil {
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error()) return nil, err
} }
if pToken.ErrMsg != "" { if pToken.ErrMsg != "" {
return nil, fmt.Errorf("pToken.Errmsg = %s", pToken.ErrMsg) return nil, fmt.Errorf(pToken.ErrMsg)
} }
token := &oauth2.Token{ token := &oauth2.Token{
@@ -118,11 +120,25 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
body, err := ioutil.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
keyset, err := jwk.ParseKey(body) var respKeys struct {
Keys []interface{} `json:"keys"`
}
if err := json.Unmarshal(body, &respKeys); err != nil {
return nil, err
}
respKey, err := json.Marshal(&(respKeys.Keys[0]))
if err != nil { if err != nil {
return nil, err return nil, err
} }
keyset, err := jwk.ParseKey(respKey)
if err != nil {
return nil, err
}
tokenSrc := []byte(token.AccessToken) tokenSrc := []byte(token.AccessToken)
publicKey, _ := keyset.PublicKey() publicKey, _ := keyset.PublicKey()
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey)) idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))

126
idp/azuread_b2c.go Normal file
View 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
}

View File

@@ -89,7 +89,7 @@ type GothIdProvider struct {
Session goth.Session Session goth.Session
} }
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string, hostUrl string) *GothIdProvider { func NewGothIdProvider(providerType string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, redirectUrl string, hostUrl string) (*GothIdProvider, error) {
var idp GothIdProvider var idp GothIdProvider
switch providerType { switch providerType {
case "Amazon": case "Amazon":
@@ -101,8 +101,24 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
if !strings.Contains(redirectUrl, "/api/callback") { if !strings.Contains(redirectUrl, "/api/callback") {
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1) redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
} }
iat := time.Now().Unix()
exp := iat + 60*60
sp := apple.SecretParams{
ClientId: clientId,
TeamId: clientSecret,
KeyId: clientId2,
PKCS8PrivateKey: clientSecret2,
Iat: int(iat),
Exp: int(exp),
}
secret, err := apple.MakeSecret(sp)
if err != nil {
return nil, err
}
idp = GothIdProvider{ idp = GothIdProvider{
Provider: apple.New(clientId, clientSecret, redirectUrl, nil), Provider: apple.New(clientId, *secret, redirectUrl, nil),
Session: &apple.Session{}, Session: &apple.Session{},
} }
case "AzureAD": case "AzureAD":
@@ -386,10 +402,10 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
Session: &zoom.Session{}, Session: &zoom.Session{},
} }
default: default:
return nil return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType)
} }
return &idp return &idp, nil
} }
// SetHttpClient // SetHttpClient

View File

@@ -15,6 +15,7 @@
package idp package idp
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
@@ -30,16 +31,19 @@ type UserInfo struct {
Phone string Phone string
CountryCode string CountryCode string
AvatarUrl string AvatarUrl string
Extra map[string]string
} }
type ProviderInfo struct { type ProviderInfo struct {
Type string Type string
SubType string SubType string
ClientId string ClientId string
ClientSecret string ClientSecret string
AppId string ClientId2 string
HostUrl string ClientSecret2 string
RedirectUrl string AppId string
HostUrl string
RedirectUrl string
TokenURL string TokenURL string
AuthURL string AuthURL string
@@ -53,71 +57,73 @@ type IdProvider interface {
GetUserInfo(token *oauth2.Token) (*UserInfo, error) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
} }
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider { func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) {
switch idpInfo.Type { switch idpInfo.Type {
case "GitHub": case "GitHub":
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Google": case "Google":
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "QQ": case "QQ":
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "WeChat": case "WeChat":
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Facebook": case "Facebook":
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "DingTalk": case "DingTalk":
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Weibo": case "Weibo":
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Gitee": case "Gitee":
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "LinkedIn": case "LinkedIn":
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "WeCom": case "WeCom":
if idpInfo.SubType == "Internal" { if idpInfo.SubType == "Internal" {
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
} else if idpInfo.SubType == "Third-party" { } else if idpInfo.SubType == "Third-party" {
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
} else { } else {
return nil return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
} }
case "Lark": case "Lark":
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "GitLab": case "GitLab":
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Adfs": case "ADFS":
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "AzureADB2C":
return NewAzureAdB2cProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl, idpInfo.AppId), nil
case "Baidu": case "Baidu":
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Alipay": case "Alipay":
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Custom": case "Custom":
return NewCustomIdProvider(idpInfo, redirectUrl) return NewCustomIdProvider(idpInfo, redirectUrl), nil
case "Infoflow": case "Infoflow":
if idpInfo.SubType == "Internal" { if idpInfo.SubType == "Internal" {
return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl) return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
} else if idpInfo.SubType == "Third-party" { } else if idpInfo.SubType == "Third-party" {
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl) return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
} else { } else {
return nil return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType)
} }
case "Casdoor": case "Casdoor":
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Okta": case "Okta":
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Douyin": case "Douyin":
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Bilibili": case "Bilibili":
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl) return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "MetaMask": case "MetaMask":
return NewMetaMaskIdProvider() return NewMetaMaskIdProvider(), nil
case "Web3Onboard": case "Web3Onboard":
return NewWeb3OnboardIdProvider() return NewWeb3OnboardIdProvider(), nil
default: default:
if isGothSupport(idpInfo.Type) { if isGothSupport(idpInfo.Type) {
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl) return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.ClientId2, idpInfo.ClientSecret2, redirectUrl, idpInfo.HostUrl)
} }
return nil return nil, fmt.Errorf("OAuth provider type: %s is not supported", idpInfo.Type)
} }
} }

View File

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

View File

@@ -15,6 +15,7 @@
"tags": [], "tags": [],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"], "languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
"masterPassword": "", "masterPassword": "",
"defaultPassword": "",
"initScore": 2000, "initScore": 2000,
"enableSoftDeletion": false, "enableSoftDeletion": false,
"isProfilePublic": true, "isProfilePublic": true,
@@ -103,7 +104,9 @@
} }
], ],
"redirectUris": [""], "redirectUris": [""],
"expireInHours": 168 "expireInHours": 168,
"failedSigninLimit": 5,
"failedSigninfrozenTime": 15
} }
], ],
"users": [ "users": [

View File

@@ -16,6 +16,7 @@ package ldap
import ( import (
"fmt" "fmt"
"hash/fnv"
"log" "log"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@@ -25,6 +26,11 @@ import (
) )
func StartLdapServer() { func StartLdapServer() {
ldapServerPort := conf.GetConfigString("ldapServerPort")
if ldapServerPort == "" || ldapServerPort == "0" {
return
}
server := ldap.NewServer() server := ldap.NewServer()
routes := ldap.NewRouteMux() routes := ldap.NewRouteMux()
@@ -32,7 +38,7 @@ func StartLdapServer() {
routes.Search(handleSearch).Label(" SEARCH****") routes.Search(handleSearch).Label(" SEARCH****")
server.Handle(routes) server.Handle(routes)
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort")) err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
if err != nil { if err != nil {
log.Printf("StartLdapServer() failed, err = %s", err.Error()) log.Printf("StartLdapServer() failed, err = %s", err.Error())
} }
@@ -44,20 +50,20 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
if r.AuthenticationChoice() == "simple" { if r.AuthenticationChoice() == "simple" {
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name())) bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
if err != "" { if err != nil {
log.Printf("Bind failed ,ErrMsg=%s", err) log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax) res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
res.SetDiagnosticMessage("bind failed ErrMsg: " + err) res.SetDiagnosticMessage(fmt.Sprintf("getNameAndOrgFromDN() error: %s", err.Error()))
w.Write(res) w.Write(res)
return return
} }
bindPassword := string(r.AuthenticationSimple()) bindPassword := string(r.AuthenticationSimple())
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en") bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
if err != "" { if err != nil {
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err) log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
res.SetResultCode(ldap.LDAPResultInvalidCredentials) res.SetResultCode(ldap.LDAPResultInvalidCredentials)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err) res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err.Error())
w.Write(res) w.Write(res)
return return
} }
@@ -73,7 +79,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
m.Client.OrgName = bindOrg m.Client.OrgName = bindOrg
} else { } else {
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported) res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication") res.SetDiagnosticMessage("Authentication method not supported, please use Simple Authentication")
} }
w.Write(res) w.Write(res)
} }
@@ -108,10 +114,22 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
} }
for _, user := range users { for _, user := range users {
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject())) dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
e := ldap.NewSearchResultEntry(dn) e := ldap.NewSearchResultEntry(dn)
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
for _, attr := range r.Attributes() { e.AddAttribute("uidNumber", message.AttributeValue(uidNumberStr))
e.AddAttribute("gidNumber", message.AttributeValue(uidNumberStr))
e.AddAttribute("homeDirectory", message.AttributeValue("/home/"+user.Name))
e.AddAttribute("cn", message.AttributeValue(user.Name))
e.AddAttribute("uid", message.AttributeValue(user.Id))
attrs := r.Attributes()
for _, attr := range attrs {
if string(attr) == "*" {
attrs = AdditionalLdapAttributes
break
}
}
for _, attr := range attrs {
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user)) e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
if string(attr) == "cn" { if string(attr) == "cn" {
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user)) e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
@@ -122,3 +140,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
} }
w.Write(res) w.Write(res)
} }
func hash(s string) uint32 {
h := fnv.New32a()
h.Write([]byte(s))
return h.Sum32()
}

View File

@@ -24,9 +24,73 @@ import (
"github.com/lor00x/goldap/message" "github.com/lor00x/goldap/message"
ldap "github.com/forestmgy/ldapserver" ldap "github.com/forestmgy/ldapserver"
"github.com/xorm-io/builder"
) )
func getNameAndOrgFromDN(DN string) (string, string, string) { type AttributeMapper func(user *object.User) message.AttributeValue
type FieldRelation struct {
userField string
notSearchable bool
hideOnStarOp bool
fieldMapper AttributeMapper
}
func (rel FieldRelation) GetField() (string, error) {
if rel.notSearchable {
return "", fmt.Errorf("attribute %s not supported", rel.userField)
}
return rel.userField, nil
}
func (rel FieldRelation) GetAttributeValue(user *object.User) message.AttributeValue {
return rel.fieldMapper(user)
}
var ldapAttributesMapping = map[string]FieldRelation{
"cn": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Name)
}},
"uid": {userField: "name", hideOnStarOp: true, fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Name)
}},
"displayname": {userField: "displayName", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.DisplayName)
}},
"email": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Email)
}},
"mail": {userField: "email", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Email)
}},
"mobile": {userField: "phone", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Phone)
}},
"title": {userField: "tag", fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(user.Tag)
}},
"userPassword": {
userField: "userPassword",
notSearchable: true,
fieldMapper: func(user *object.User) message.AttributeValue {
return message.AttributeValue(getUserPasswordWithType(user))
},
},
}
var AdditionalLdapAttributes []message.LDAPString
func init() {
for k, v := range ldapAttributesMapping {
if v.hideOnStarOp {
continue
}
AdditionalLdapAttributes = append(AdditionalLdapAttributes, message.LDAPString(k))
}
}
func getNameAndOrgFromDN(DN string) (string, string, error) {
DNFields := strings.Split(DN, ",") DNFields := strings.Split(DN, ",")
params := make(map[string]string, len(DNFields)) params := make(map[string]string, len(DNFields))
for _, field := range DNFields { for _, field := range DNFields {
@@ -37,12 +101,12 @@ func getNameAndOrgFromDN(DN string) (string, string, string) {
} }
if params["cn"] == "" { if params["cn"] == "" {
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com" return "", "", fmt.Errorf("please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com")
} }
if params["ou"] == "" { if params["ou"] == "" {
return params["cn"], object.CasdoorOrganization, "" return params["cn"], object.CasdoorOrganization, nil
} }
return params["cn"], params["ou"], "" return params["cn"], params["ou"], nil
} }
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) { func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
@@ -50,7 +114,11 @@ func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
return "", "", ldap.LDAPResultInvalidDNSyntax return "", "", ldap.LDAPResultInvalidDNSyntax
} }
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN) name, org, err := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
if err != nil {
panic(err)
}
return name, org, ldap.LDAPResultSuccess return name, org, ldap.LDAPResultSuccess
} }
@@ -83,6 +151,92 @@ func stringInSlice(value string, list []string) bool {
return false return false
} }
func buildUserFilterCondition(filter interface{}) (builder.Cond, error) {
switch f := filter.(type) {
case message.FilterAnd:
conditions := make([]builder.Cond, len(f))
for i, v := range f {
cond, err := buildUserFilterCondition(v)
if err != nil {
return nil, err
}
conditions[i] = cond
}
return builder.And(conditions...), nil
case message.FilterOr:
conditions := make([]builder.Cond, len(f))
for i, v := range f {
cond, err := buildUserFilterCondition(v)
if err != nil {
return nil, err
}
conditions[i] = cond
}
return builder.Or(conditions...), nil
case message.FilterNot:
cond, err := buildUserFilterCondition(f.Filter)
if err != nil {
return nil, err
}
return builder.Not{cond}, nil
case message.FilterEqualityMatch:
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
if err != nil {
return nil, err
}
return builder.Eq{field: string(f.AssertionValue())}, nil
case message.FilterPresent:
field, err := getUserFieldFromAttribute(string(f))
if err != nil {
return nil, err
}
return builder.NotNull{field}, nil
case message.FilterGreaterOrEqual:
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
if err != nil {
return nil, err
}
return builder.Gte{field: string(f.AssertionValue())}, nil
case message.FilterLessOrEqual:
field, err := getUserFieldFromAttribute(string(f.AttributeDesc()))
if err != nil {
return nil, err
}
return builder.Lte{field: string(f.AssertionValue())}, nil
case message.FilterSubstrings:
field, err := getUserFieldFromAttribute(string(f.Type_()))
if err != nil {
return nil, err
}
var expr string
for _, substring := range f.Substrings() {
switch s := substring.(type) {
case message.SubstringInitial:
expr += string(s) + "%"
continue
case message.SubstringAny:
expr += string(s) + "%"
continue
case message.SubstringFinal:
expr += string(s)
continue
}
}
return builder.Expr(field+" LIKE ?", expr), nil
default:
return nil, fmt.Errorf("LDAP filter operation %#v not supported", f)
}
}
func buildSafeCondition(filter interface{}) builder.Cond {
condition, err := buildUserFilterCondition(filter)
if err != nil {
log.Printf("err = %v", err.Error())
return nil
}
return condition
}
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) { func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
var err error var err error
r := m.GetSearchRequest() r := m.GetSearchRequest()
@@ -94,15 +248,14 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org' if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
if m.Client.IsGlobalAdmin && org == "*" { if m.Client.IsGlobalAdmin && org == "*" {
filteredUsers, err = object.GetGlobalUsersWithFilter(buildSafeCondition(r.Filter()))
filteredUsers, err = object.GetGlobalUsers()
if err != nil { if err != nil {
panic(err) panic(err)
} }
return filteredUsers, ldap.LDAPResultSuccess return filteredUsers, ldap.LDAPResultSuccess
} }
if m.Client.IsGlobalAdmin || org == m.Client.OrgName { if m.Client.IsGlobalAdmin || org == m.Client.OrgName {
filteredUsers, err = object.GetUsers(org) filteredUsers, err = object.GetUsersWithFilter(org, buildSafeCondition(r.Filter()))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -144,7 +297,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
return nil, ldap.LDAPResultNoSuchObject return nil, ldap.LDAPResultNoSuchObject
} }
users, err := object.GetUsersByTag(org, name) users, err := object.GetUsersByTagWithFilter(org, name, buildSafeCondition(r.Filter()))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -178,24 +331,17 @@ func getUserPasswordWithType(user *object.User) string {
} }
func getAttribute(attributeName string, user *object.User) message.AttributeValue { func getAttribute(attributeName string, user *object.User) message.AttributeValue {
switch attributeName { v, ok := ldapAttributesMapping[attributeName]
case "cn": if !ok {
return message.AttributeValue(user.Name)
case "uid":
return message.AttributeValue(user.Name)
case "displayname":
return message.AttributeValue(user.DisplayName)
case "email":
return message.AttributeValue(user.Email)
case "mail":
return message.AttributeValue(user.Email)
case "mobile":
return message.AttributeValue(user.Phone)
case "title":
return message.AttributeValue(user.Tag)
case "userPassword":
return message.AttributeValue(getUserPasswordWithType(user))
default:
return "" return ""
} }
return v.GetAttributeValue(user)
}
func getUserFieldFromAttribute(attributeName string) (string, error) {
v, ok := ldapAttributesMapping[attributeName]
if !ok {
return "", fmt.Errorf("attribute %s not supported", attributeName)
}
return v.GetField()
} }

87
ldap/util_test.go Normal file
View File

@@ -0,0 +1,87 @@
package ldap
import (
"testing"
"github.com/stretchr/testify/assert"
ber "github.com/go-asn1-ber/asn1-ber"
goldap "github.com/go-ldap/ldap/v3"
"github.com/lor00x/goldap/message"
"github.com/xorm-io/builder"
)
func args(exp ...interface{}) []interface{} {
return exp
}
func TestLdapFilterAsQuery(t *testing.T) {
scenarios := []struct {
description string
input string
expectedExpr string
expectedArgs []interface{}
}{
{"Should be SQL for FilterAnd", "(&(mail=2)(email=1))", "email=? AND email=?", args("2", "1")},
{"Should be SQL for FilterOr", "(|(mail=2)(email=1))", "email=? OR email=?", args("2", "1")},
{"Should be SQL for FilterNot", "(!(mail=2))", "NOT email=?", args("2")},
{"Should be SQL for FilterEqualityMatch", "(mail=2)", "email=?", args("2")},
{"Should be SQL for FilterPresent", "(mail=*)", "email IS NOT NULL", nil},
{"Should be SQL for FilterGreaterOrEqual", "(mail>=admin)", "email>=?", args("admin")},
{"Should be SQL for FilterLessOrEqual", "(mail<=admin)", "email<=?", args("admin")},
{"Should be SQL for FilterSubstrings", "(mail=admin*ex*c*m)", "email LIKE ?", args("admin%ex%c%m")},
}
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
searchRequest, err := buildLdapSearchRequest(scenery.input)
if err != nil {
assert.FailNow(t, "Unable to create searchRequest", err)
}
m, err := message.ReadLDAPMessage(message.NewBytes(0, searchRequest.Bytes()))
if err != nil {
assert.FailNow(t, "Unable to create searchRequest", err)
}
req := m.ProtocolOp().(message.SearchRequest)
cond, err := buildUserFilterCondition(req.Filter())
if err != nil {
assert.FailNow(t, "Unable to build condition", err)
}
expr, args, err := builder.ToSQL(cond)
if err != nil {
assert.FailNow(t, "Unable to build sql", err)
}
assert.Equal(t, scenery.expectedExpr, expr)
assert.Equal(t, scenery.expectedArgs, args)
})
}
}
func buildLdapSearchRequest(filter string) (*ber.Packet, error) {
packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request")
packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 1, "MessageID"))
pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, goldap.ApplicationSearchRequest, nil, "Search Request")
pkt.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "", "Base DN"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, 0, "Scope"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, 0, "Deref Aliases"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 0, "Size Limit"))
pkt.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 0, "Time Limit"))
pkt.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, false, "Types Only"))
// compile and encode filter
filterPacket, err := goldap.CompileFilter(filter)
if err != nil {
return nil, err
}
pkt.AppendChild(filterPacket)
// encode attributes
attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes")
attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, "*", "Attribute"))
pkt.AppendChild(attributesPacket)
packet.AppendChild(pkt)
return packet, nil
}

View File

@@ -34,7 +34,6 @@ func main() {
object.InitFlag() object.InitFlag()
object.InitAdapter() object.InitAdapter()
object.CreateTables() object.CreateTables()
object.DoMigration()
object.InitDb() object.InitDb()
object.InitFromFile() object.InitFromFile()

View File

@@ -1,5 +1,5 @@
apiVersion: v2 apiVersion: v2
name: casdoor name: casdoor-helm-charts
description: A Helm chart for Kubernetes description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart. # 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 # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0 version: 0.3.0
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "1.16.0" appVersion: "1.18.0"

View File

@@ -59,6 +59,9 @@ spec:
volumeMounts: volumeMounts:
- name: config-volume - name: config-volume
mountPath: /conf mountPath: /conf
{{ if .Values.extraContainersEnabled }}
{{- .Values.extraContainers | nindent 8 }}
{{- end }}
volumes: volumes:
- name: config-volume - name: config-volume
projected: projected:

View File

@@ -22,14 +22,15 @@ config: |
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1" dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
dbName = casdoor dbName = casdoor
redisEndpoint = redisEndpoint =
defaultStorageProvider = defaultStorageProvider =
isCloudIntranet = false isCloudIntranet = false
authState = "casdoor" authState = "casdoor"
socks5Proxy = "" socks5Proxy = ""
verificationCodeTimeout = 10 verificationCodeTimeout = 10
initScore = 2000 initScore = 0
logPostOnly = true logPostOnly = true
origin = "https://door.casbin.com" origin =
enableGzip = true
imagePullSecrets: [] imagePullSecrets: []
nameOverride: "" nameOverride: ""
@@ -107,3 +108,10 @@ nodeSelector: {}
tolerations: [] tolerations: []
affinity: {} affinity: {}
# -- Optionally add extra sidecar containers.
extraContainersEnabled: false
extraContainers: ""
# extraContainers: |
# - name: ...
# image: ...

View File

@@ -15,10 +15,11 @@
package notification package notification
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings"
"github.com/casdoor/casdoor/proxy" "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 { func (c *HttpNotificationClient) Send(ctx context.Context, subject string, content string) error {
var req *http.Request
var err error 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" { if c.method == "POST" {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") formValues := url.Values{}
req.PostForm = map[string][]string{ formValues.Set(c.paramName, content)
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" { } else if c.method == "GET" {
req, err = http.NewRequest(c.method, c.endpoint, nil)
if err != nil {
return err
}
q := req.URL.Query() q := req.URL.Query()
q.Add(c.paramName, content) q.Add(c.paramName, content)
req.URL.RawQuery = q.Encode() req.URL.RawQuery = q.Encode()
} }
httpClient := proxy.DefaultHttpClient
resp, err := httpClient.Do(req) resp, err := httpClient.Do(req)
if err != nil { if err != nil {
return err return err
@@ -66,7 +70,7 @@ func (c *HttpNotificationClient) Send(ctx context.Context, subject string, conte
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { 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 return err

View File

@@ -25,11 +25,19 @@ import (
) )
type SignupItem struct { type SignupItem struct {
Name string `json:"name"` Name string `json:"name"`
Visible bool `json:"visible"` Visible bool `json:"visible"`
Required bool `json:"required"` Required bool `json:"required"`
Prompted bool `json:"prompted"` Prompted bool `json:"prompted"`
Rule string `json:"rule"` 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 { type Application struct {
@@ -49,17 +57,19 @@ type Application struct {
EnableAutoSignin bool `json:"enableAutoSignin"` EnableAutoSignin bool `json:"enableAutoSignin"`
EnableCodeSignin bool `json:"enableCodeSignin"` EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"` EnableSamlCompress bool `json:"enableSamlCompress"`
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
EnableWebAuthn bool `json:"enableWebAuthn"` EnableWebAuthn bool `json:"enableWebAuthn"`
EnableLinkWithEmail bool `json:"enableLinkWithEmail"` EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
OrgChoiceMode string `json:"orgChoiceMode"` OrgChoiceMode string `json:"orgChoiceMode"`
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"` SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"` Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"` SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"` GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"` OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
CertPublicKey string `xorm:"-" json:"certPublicKey"` CertPublicKey string `xorm:"-" json:"certPublicKey"`
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"` InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
@@ -80,6 +90,9 @@ type Application struct {
FormOffset int `json:"formOffset"` FormOffset int `json:"formOffset"`
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"` FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"` FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
FailedSigninLimit int `json:"failedSigninLimit"`
FailedSigninfrozenTime int `json:"failedSigninfrozenTime"`
} }
func GetApplicationCount(owner, field, value string) (int64, error) { func GetApplicationCount(owner, field, value string) (int64, error) {
@@ -306,6 +319,12 @@ func GetMaskedApplication(application *Application, userId string) *Application
if application.OrganizationObj.MasterPassword != "" { if application.OrganizationObj.MasterPassword != "" {
application.OrganizationObj.MasterPassword = "***" application.OrganizationObj.MasterPassword = "***"
} }
if application.OrganizationObj.DefaultPassword != "" {
application.OrganizationObj.DefaultPassword = "***"
}
if application.OrganizationObj.MasterVerificationCode != "" {
application.OrganizationObj.MasterVerificationCode = "***"
}
if application.OrganizationObj.PasswordType != "" { if application.OrganizationObj.PasswordType != "" {
application.OrganizationObj.PasswordType = "***" application.OrganizationObj.PasswordType = "***"
} }
@@ -332,6 +351,34 @@ func GetMaskedApplications(applications []*Application, userId string) []*Applic
return applications return applications
} }
func GetAllowedApplications(applications []*Application, userId string) ([]*Application, error) {
if userId == "" || isUserIdGlobalAdmin(userId) {
return applications, nil
}
user, err := GetUser(userId)
if err != nil {
return nil, err
}
if user != nil && user.IsAdmin {
return applications, nil
}
res := []*Application{}
for _, application := range applications {
var allowed bool
allowed, err = CheckLoginPermission(userId, application)
if err != nil {
return nil, err
}
if allowed {
res = append(res, application)
}
}
return res, nil
}
func UpdateApplication(id string, application *Application) (bool, error) { func UpdateApplication(id string, application *Application) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
oldApplication, err := getApplication(owner, name) oldApplication, err := getApplication(owner, name)

View File

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

View File

@@ -28,8 +28,9 @@ import (
) )
const ( const (
SigninWrongTimesLimit = 5 DefaultFailedSigninLimit = 5
LastSignWrongTimeDuration = time.Minute * 15 // DefaultFailedSigninfrozenTime The unit of frozen time is minutes
DefaultFailedSigninfrozenTime = 15
) )
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string { func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
@@ -142,45 +143,52 @@ func CheckUserSignup(application *Application, organization *Organization, form
return "" return ""
} }
func checkSigninErrorTimes(user *User, lang string) string { func checkSigninErrorTimes(user *User, lang string) error {
if user.SigninWrongTimes >= SigninWrongTimesLimit { failedSigninLimit, failedSigninfrozenTime, err := GetFailedSigninConfigByUser(user)
if err != nil {
return err
}
if user.SigninWrongTimes >= failedSigninLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime) lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime) passedTime := time.Now().UTC().Sub(lastSignWrongTime)
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 // deny the login if the error times is greater than the limit and the last login time is less than the duration
if minutes > 0 { if minutes > 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes) return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
} }
// reset the error times // reset the error times
user.SigninWrongTimes = 0 user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false) _, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
return err
} }
return "" return nil
} }
func CheckPassword(user *User, password string, lang string, options ...bool) string { func CheckPassword(user *User, password string, lang string, options ...bool) error {
enableCaptcha := false enableCaptcha := false
if len(options) > 0 { if len(options) > 0 {
enableCaptcha = options[0] enableCaptcha = options[0]
} }
// check the login error times // check the login error times
if !enableCaptcha { if !enableCaptcha {
if msg := checkSigninErrorTimes(user, lang); msg != "" { err := checkSigninErrorTimes(user, lang)
return msg if err != nil {
return err
} }
} }
organization, err := GetOrganizationByUser(user) organization, err := GetOrganizationByUser(user)
if err != nil { if err != nil {
panic(err) return err
} }
if organization == nil { if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist") return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
} }
passwordType := user.PasswordType passwordType := user.PasswordType
@@ -191,19 +199,17 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
if credManager != nil { if credManager != nil {
if organization.MasterPassword != "" { if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) { if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
resetUserSigninErrorTimes(user) return resetUserSigninErrorTimes(user)
return ""
} }
} }
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) { if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
resetUserSigninErrorTimes(user) return resetUserSigninErrorTimes(user)
return ""
} }
return recordSigninErrorInfo(user, lang, enableCaptcha) return recordSigninErrorInfo(user, lang, enableCaptcha)
} else { } else {
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType) return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
} }
} }
@@ -217,10 +223,10 @@ func CheckPasswordComplexity(user *User, password string) string {
return CheckPasswordComplexityByOrg(organization, password) return CheckPasswordComplexityByOrg(organization, password)
} }
func checkLdapUserPassword(user *User, password string, lang string) string { func checkLdapUserPassword(user *User, password string, lang string) error {
ldaps, err := GetLdaps(user.Owner) ldaps, err := GetLdaps(user.Owner)
if err != nil { if err != nil {
return err.Error() return err
} }
ldapLoginSuccess := false ldapLoginSuccess := false
@@ -237,65 +243,73 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
searchResult, err := conn.Conn.Search(searchReq) searchResult, err := conn.Conn.Search(searchReq)
if err != nil { if err != nil {
return err.Error() conn.Close()
return err
} }
if len(searchResult.Entries) == 0 { if len(searchResult.Entries) == 0 {
conn.Close()
continue continue
} }
if len(searchResult.Entries) > 1 { if len(searchResult.Entries) > 1 {
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server") conn.Close()
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
} }
hit = true hit = true
dn := searchResult.Entries[0].DN dn := searchResult.Entries[0].DN
if err := conn.Conn.Bind(dn, password); err == nil { if err = conn.Conn.Bind(dn, password); err == nil {
ldapLoginSuccess = true ldapLoginSuccess = true
conn.Close()
break break
} }
conn.Close()
} }
if !ldapLoginSuccess { if !ldapLoginSuccess {
if !hit { if !hit {
return "user not exist" return fmt.Errorf("user not exist")
} }
return i18n.Translate(lang, "check:LDAP user name or password incorrect") return fmt.Errorf(i18n.Translate(lang, "check:LDAP user name or password incorrect"))
} }
return "" return nil
} }
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) { func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
enableCaptcha := false enableCaptcha := false
if len(options) > 0 { if len(options) > 0 {
enableCaptcha = options[0] enableCaptcha = options[0]
} }
user, err := GetUserByFields(organization, username) user, err := GetUserByFields(organization, username)
if err != nil { if err != nil {
panic(err) return nil, err
} }
if user == nil || user.IsDeleted { if user == nil || user.IsDeleted {
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username)) return nil, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
} }
if user.IsForbidden { if user.IsForbidden {
return nil, i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator") return nil, fmt.Errorf(i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator"))
} }
if user.Ldap != "" { if user.Ldap != "" {
// ONLY for ldap users // only for LDAP users
if msg := checkLdapUserPassword(user, password, lang); msg != "" { err = checkLdapUserPassword(user, password, lang)
if msg == "user not exist" { if err != nil {
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username) if err.Error() == "user not exist" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
} }
return nil, msg return nil, err
} }
} else { } else {
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" { err = CheckPassword(user, password, lang, enableCaptcha)
return nil, msg if err != nil {
return nil, err
} }
} }
return user, "" return user, nil
} }
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) { func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
@@ -308,7 +322,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
if userId != "" { if userId != "" {
targetUser, err := GetUser(userId) targetUser, err := GetUser(userId)
if err != nil { if err != nil {
panic(err) return false, err
} }
if targetUser == nil { if targetUser == nil {
@@ -351,8 +365,8 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
} }
func CheckLoginPermission(userId string, application *Application) (bool, error) { func CheckLoginPermission(userId string, application *Application) (bool, error) {
var err error owner, _ := util.GetOwnerAndNameFromId(userId)
if userId == "built-in/admin" { if owner == "built-in" {
return true, nil return true, nil
} }
@@ -366,11 +380,11 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
allowCount := 0 allowCount := 0
denyCount := 0 denyCount := 0
for _, permission := range permissions { for _, permission := range permissions {
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) { if !permission.IsEnabled || permission.State != "Approved" || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
continue continue
} }
if !permission.isUserHit(userId) { if !permission.isUserHit(userId) && !permission.isRoleHit(userId) {
if permission.Effect == "Allow" { if permission.Effect == "Allow" {
allowPermissionCount += 1 allowPermissionCount += 1
} else { } else {
@@ -379,7 +393,10 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
continue continue
} }
enforcer := getPermissionEnforcer(permission) enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return false, err
}
var isAllowed bool var isAllowed bool
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read") isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
@@ -468,7 +485,14 @@ func CheckToEnableCaptcha(application *Application, organization, username strin
if err != nil { if err != nil {
return false, err 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 return providerItem.Rule == "Always", nil
} }

View File

@@ -36,38 +36,70 @@ func isValidRealName(s string) bool {
return reRealName.MatchString(s) return reRealName.MatchString(s)
} }
func resetUserSigninErrorTimes(user *User) { func resetUserSigninErrorTimes(user *User) error {
// if the password is correct and wrong times is not zero, reset the error times // if the password is correct and wrong times is not zero, reset the error times
if user.SigninWrongTimes == 0 { if user.SigninWrongTimes == 0 {
return return nil
} }
user.SigninWrongTimes = 0 user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false) _, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
return err
} }
func recordSigninErrorInfo(user *User, lang string, options ...bool) string { func 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 enableCaptcha := false
if len(options) > 0 { if len(options) > 0 {
enableCaptcha = options[0] enableCaptcha = options[0]
} }
failedSigninLimit, failedSigninfrozenTime, errSignin := GetFailedSigninConfigByUser(user)
if errSignin != nil {
return errSignin
}
// increase failed login count // increase failed login count
if user.SigninWrongTimes < SigninWrongTimesLimit { if user.SigninWrongTimes < failedSigninLimit {
user.SigninWrongTimes++ user.SigninWrongTimes++
} }
if user.SigninWrongTimes >= SigninWrongTimesLimit { if user.SigninWrongTimes >= failedSigninLimit {
// record the latest failed login time // record the latest failed login time
user.LastSigninWrongTime = time.Now().UTC().Format(time.RFC3339) user.LastSigninWrongTime = time.Now().UTC().Format(time.RFC3339)
} }
// update user // update user
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false) _, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes if err != nil {
if leftChances == 0 && enableCaptcha { return err
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)
} }
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 // 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)
} }

View File

@@ -36,7 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
} }
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error { 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 fromAddress := provider.ClientId2
if fromAddress == "" { if fromAddress == "" {

View File

@@ -125,6 +125,10 @@ func (enforcer *Enforcer) GetId() string {
return fmt.Sprintf("%s/%s", enforcer.Owner, enforcer.Name) 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 { func (enforcer *Enforcer) InitEnforcer() error {
if enforcer.Enforcer != nil { if enforcer.Enforcer != nil {
return nil return nil

View File

@@ -271,7 +271,9 @@ func GetGroupUsers(groupId string) ([]*User, error) {
users := []*User{} users := []*User{}
owner, _ := util.GetOwnerAndNameFromId(groupId) owner, _ := util.GetOwnerAndNameFromId(groupId)
names, err := userEnforcer.GetUserNamesByGroupName(groupId) names, err := userEnforcer.GetUserNamesByGroupName(groupId)
if err != nil {
return nil, err
}
err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users) err = ormer.Engine.Where("owner = ?", owner).In("name", names).Find(&users)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -303,6 +305,9 @@ func GroupChangeTrigger(oldName, newName string) error {
groups := []*Group{} groups := []*Group{}
err = session.Where("parent_id = ?", oldName).Find(&groups) err = session.Where("parent_id = ?", oldName).Find(&groups)
if err != nil {
return err
}
for _, group := range groups { for _, group := range groups {
group.ParentId = newName group.ParentId = newName
_, err := session.ID(core.PK{group.Owner, group.Name}).Cols("parent_id").Update(group) _, err := session.ID(core.PK{group.Owner, group.Name}).Cols("parent_id").Update(group)

View File

@@ -178,7 +178,7 @@ func initBuiltInApplication() {
EnablePassword: true, EnablePassword: true,
EnableSignUp: true, EnableSignUp: true,
Providers: []*ProviderItem{ 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},
}, },
SignupItems: []*SignupItem{ SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"}, {Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
@@ -396,15 +396,22 @@ func initBuiltInPermission() {
Name: "permission-built-in", Name: "permission-built-in",
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Permission", DisplayName: "Built-in Permission",
Description: "Built-in Permission",
Users: []string{"built-in/*"}, Users: []string{"built-in/*"},
Groups: []string{},
Roles: []string{}, Roles: []string{},
Domains: []string{}, Domains: []string{},
Model: "model-built-in", Model: "model-built-in",
Adapter: "",
ResourceType: "Application", ResourceType: "Application",
Resources: []string{"app-built-in"}, Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"}, Actions: []string{"Read", "Write", "Admin"},
Effect: "Allow", Effect: "Allow",
IsEnabled: true, IsEnabled: true,
Submitter: "admin",
Approver: "admin",
ApproveTime: util.GetCurrentTime(),
State: "Approved",
} }
_, err = AddPermission(permission) _, err = AddPermission(permission)
if err != nil { if err != nil {

121
object/init_data_dump.go Normal file
View 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
}

View 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)
}
}

View File

@@ -100,6 +100,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
users, err := conn.GetLdapUsers(ldap) users, err := conn.GetLdapUsers(ldap)
if err != nil { if err != nil {
conn.Close()
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err)) logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue continue
} }
@@ -111,6 +112,8 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) e
} else { } else {
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed))) logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
} }
conn.Close()
} }
} }

View File

@@ -81,6 +81,17 @@ func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
return &LdapConn{Conn: conn, IsAD: isAD}, nil return &LdapConn{Conn: conn, IsAD: isAD}, nil
} }
func (l *LdapConn) Close() {
if l.Conn == nil {
return
}
err := l.Conn.Unbind()
if err != nil {
panic(err)
}
}
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) { func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
SearchFilter := "(objectClass=*)" SearchFilter := "(objectClass=*)"
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"} SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
@@ -305,7 +316,7 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
return nil, nil, err return nil, nil, err
} }
name, err := syncUser.buildLdapUserName() name, err := syncUser.buildLdapUserName(owner)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -354,10 +365,10 @@ func GetExistUuids(owner string, uuids []string) ([]string, error) {
return existUuids, nil return existUuids, nil
} }
func (ldapUser *LdapUser) buildLdapUserName() (string, error) { func (ldapUser *LdapUser) buildLdapUserName(owner string) (string, error) {
user := User{} user := User{}
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber) uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
has, err := ormer.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user) has, err := ormer.Engine.Where("owner = ? and (name = ? or name = ?)", owner, ldapUser.Uid, uidWithNumber).Get(&user)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -59,7 +59,7 @@ func isIpAddress(host string) bool {
return ip != nil return ip != nil
} }
func getOriginFromHost(host string) (string, string) { func getOriginFromHostInternal(host string) (string, string) {
origin := conf.GetConfigString("origin") origin := conf.GetConfigString("origin")
if origin != "" { if origin != "" {
return origin, 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 { func GetOidcDiscovery(host string) OidcDiscovery {
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)

View File

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

View File

@@ -64,7 +64,6 @@ func InitConfig() {
InitAdapter() InitAdapter()
CreateTables() CreateTables()
DoMigration()
} }
func InitAdapter() { func InitAdapter() {
@@ -330,11 +329,6 @@ func (a *Ormer) createTable() {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(PermissionRule))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(xormadapter.CasbinRule)) err = a.Engine.Sync2(new(xormadapter.CasbinRule))
if err != nil { if err != nil {
panic(err) panic(err)

View File

@@ -54,7 +54,7 @@ type Payment struct {
// Order Info // Order Info
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"` OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
PayUrl string `xorm:"varchar(2000)" json:"payUrl"` PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl""` // `successUrl` is redirected from `payUrl` after pay success SuccessUrl string `xorm:"varchar(2000)" json:"successUrl"` // `successUrl` is redirected from `payUrl` after pay success
State pp.PaymentState `xorm:"varchar(100)" json:"state"` State pp.PaymentState `xorm:"varchar(100)" json:"state"`
Message string `xorm:"varchar(2000)" json:"message"` Message string `xorm:"varchar(2000)" json:"message"`
} }

View File

@@ -49,17 +49,6 @@ type Permission struct {
State string `xorm:"varchar(100)" json:"state"` State string `xorm:"varchar(100)" json:"state"`
} }
type PermissionRule struct {
Ptype string `xorm:"varchar(100) index not null default ''" json:"ptype"`
V0 string `xorm:"varchar(100) index not null default ''" json:"v0"`
V1 string `xorm:"varchar(100) index not null default ''" json:"v1"`
V2 string `xorm:"varchar(100) index not null default ''" json:"v2"`
V3 string `xorm:"varchar(100) index not null default ''" json:"v3"`
V4 string `xorm:"varchar(100) index not null default ''" json:"v4"`
V5 string `xorm:"varchar(100) index not null default ''" json:"v5"`
Id string `xorm:"varchar(100) index not null default ''" json:"id"`
}
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
func GetPermissionCount(owner, field, value string) (int64, error) { func GetPermissionCount(owner, field, value string) (int64, error) {
@@ -113,11 +102,15 @@ func GetPermission(id string) (*Permission, error) {
// checkPermissionValid verifies if the permission is valid // checkPermissionValid verifies if the permission is valid
func checkPermissionValid(permission *Permission) error { func checkPermissionValid(permission *Permission) error {
enforcer := getPermissionEnforcer(permission) enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
enforcer.EnableAutoSave(false) enforcer.EnableAutoSave(false)
policies := getPolicies(permission) policies := getPolicies(permission)
_, err := enforcer.AddPolicies(policies) _, err = enforcer.AddPolicies(policies)
if err != nil { if err != nil {
return err return err
} }
@@ -127,9 +120,13 @@ func checkPermissionValid(permission *Permission) error {
return nil return nil
} }
groupingPolicies := getGroupingPolicies(permission) groupingPolicies, err := getGroupingPolicies(permission)
if err != nil {
return err
}
if len(groupingPolicies) > 0 { if len(groupingPolicies) > 0 {
_, err := enforcer.AddGroupingPolicies(groupingPolicies) _, err = enforcer.AddGroupingPolicies(groupingPolicies)
if err != nil { if err != nil {
return err return err
} }
@@ -150,7 +147,7 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
return false, nil return false, nil
} }
if permission.ResourceType == "Application" { if permission.ResourceType == "Application" && permission.Model != "" {
model, err := GetModelEx(util.GetId(owner, permission.Model)) model, err := GetModelEx(util.GetId(owner, permission.Model))
if err != nil { if err != nil {
return false, err return false, err
@@ -174,8 +171,16 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
} }
if affected != 0 { if affected != 0 {
removeGroupingPolicies(oldPermission) err = removeGroupingPolicies(oldPermission)
removePolicies(oldPermission) if err != nil {
return false, err
}
err = removePolicies(oldPermission)
if err != nil {
return false, err
}
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter { if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter) isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
if isEmpty { if isEmpty {
@@ -185,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 return affected != 0, nil
@@ -199,40 +212,54 @@ func AddPermission(permission *Permission) (bool, error) {
} }
if affected != 0 { if affected != 0 {
addGroupingPolicies(permission) err = addGroupingPolicies(permission)
addPolicies(permission) if err != nil {
return false, err
}
err = addPolicies(permission)
if err != nil {
return false, err
}
} }
return affected != 0, nil return affected != 0, nil
} }
func AddPermissions(permissions []*Permission) bool { func AddPermissions(permissions []*Permission) (bool, error) {
if len(permissions) == 0 { if len(permissions) == 0 {
return false return false, nil
} }
affected, err := ormer.Engine.Insert(permissions) affected, err := ormer.Engine.Insert(permissions)
if err != nil { if err != nil {
if !strings.Contains(err.Error(), "Duplicate entry") { if !strings.Contains(err.Error(), "Duplicate entry") {
panic(err) return false, err
} }
} }
for _, permission := range permissions { for _, permission := range permissions {
// add using for loop // add using for loop
if affected != 0 { if affected != 0 {
addGroupingPolicies(permission) err = addGroupingPolicies(permission)
addPolicies(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() batchSize := conf.GetConfigBatchSize()
if len(permissions) == 0 { if len(permissions) == 0 {
return false return false, nil
} }
affected := false affected := false
@@ -245,12 +272,18 @@ func AddPermissionsInBatch(permissions []*Permission) bool {
tmp := permissions[start:end] tmp := permissions[start:end]
fmt.Printf("The syncer adds permissions: [%d - %d]\n", 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 affected = true
} }
} }
return affected return affected, nil
} }
func DeletePermission(permission *Permission) (bool, error) { func DeletePermission(permission *Permission) (bool, error) {
@@ -260,8 +293,16 @@ func DeletePermission(permission *Permission) (bool, error) {
} }
if affected != 0 { if affected != 0 {
removeGroupingPolicies(permission) err = removeGroupingPolicies(permission)
removePolicies(permission) if err != nil {
return false, err
}
err = removePolicies(permission)
if err != nil {
return false, err
}
if permission.Adapter != "" && permission.Adapter != "permission_rule" { if permission.Adapter != "" && permission.Adapter != "permission_rule" {
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter) isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
if isEmpty { if isEmpty {
@@ -405,9 +446,8 @@ func GetMaskedPermissions(permissions []*Permission) []*Permission {
// as the policyFilter when the enforcer load policy). // as the policyFilter when the enforcer load policy).
func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]string { func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]string {
m := make(map[string][]string) m := make(map[string][]string)
for _, permission := range permissions { for _, permission := range permissions {
key := permission.Model + permission.Adapter key := permission.GetModelAndAdapter()
permissionIds, ok := m[key] permissionIds, ok := m[key]
if !ok { if !ok {
m[key] = []string{permission.GetId()} m[key] = []string{permission.GetId()}
@@ -423,9 +463,17 @@ func (p *Permission) GetId() string {
return util.GetId(p.Owner, p.Name) 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 { func (p *Permission) isUserHit(name string) bool {
targetOrg, targetName := util.GetOwnerAndNameFromId(name) targetOrg, targetName := util.GetOwnerAndNameFromId(name)
for _, user := range p.Users { for _, user := range p.Users {
if user == "*" {
return true
}
userOrg, userName := util.GetOwnerAndNameFromId(user) userOrg, userName := util.GetOwnerAndNameFromId(user)
if userOrg == targetOrg && (userName == "*" || userName == targetName) { if userOrg == targetOrg && (userName == "*" || userName == targetName) {
return true return true
@@ -434,6 +482,26 @@ func (p *Permission) isUserHit(name string) bool {
return false 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 { func (p *Permission) isResourceHit(name string) bool {
for _, resource := range p.Resources { for _, resource := range p.Resources {
if resource == "*" || resource == name { if resource == "*" || resource == name {

View File

@@ -23,26 +23,27 @@ import (
"github.com/casbin/casbin/v2/log" "github.com/casbin/casbin/v2/log"
"github.com/casbin/casbin/v2/model" "github.com/casbin/casbin/v2/model"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3" xormadapter "github.com/casdoor/xorm-adapter/v3"
) )
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. // Init an enforcer instance without specifying a model or adapter.
// If you specify an adapter, it will load all policies, which is a // If you specify an adapter, it will load all policies, which is a
// heavy process that can slow down the application. // heavy process that can slow down the application.
enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false) enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false)
if err != nil { if err != nil {
panic(err) return nil, err
} }
err = p.setEnforcerModel(enforcer) err = p.setEnforcerModel(enforcer)
if err != nil { if err != nil {
panic(err) return nil, err
} }
err = p.setEnforcerAdapter(enforcer) err = p.setEnforcerAdapter(enforcer)
if err != nil { if err != nil {
panic(err) return nil, err
} }
policyFilterV5 := []string{p.GetId()} policyFilterV5 := []string{p.GetId()}
@@ -60,10 +61,10 @@ func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enfor
err = enforcer.LoadFilteredPolicy(policyFilter) err = enforcer.LoadFilteredPolicy(policyFilter)
if err != nil { if err != nil {
panic(err) return nil, err
} }
return enforcer return enforcer, nil
} }
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error { 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) { func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error) {
roleOwner, roleName := util.GetOwnerAndNameFromId(roleId)
if roleName == "*" {
roles, err := GetRoles(roleOwner)
if err != nil {
return []*Role{}, err
}
return roles, nil
}
role, err := GetRole(roleId) role, err := GetRole(roleId)
if err != nil { if err != nil {
return []*Role{}, err return []*Role{}, err
@@ -162,7 +173,7 @@ func getRolesInRole(roleId string, visited map[string]struct{}) ([]*Role, error)
return roles, nil return roles, nil
} }
func getGroupingPolicies(permission *Permission) [][]string { func getGroupingPolicies(permission *Permission) ([][]string, error) {
var groupingPolicies [][]string var groupingPolicies [][]string
domainExist := len(permission.Domains) > 0 domainExist := len(permission.Domains) > 0
@@ -170,12 +181,18 @@ func getGroupingPolicies(permission *Permission) [][]string {
for _, roleId := range permission.Roles { for _, roleId := range permission.Roles {
visited := map[string]struct{}{} visited := map[string]struct{}{}
if roleId == "*" {
roleId = util.GetId(permission.Owner, "*")
}
rolesInRole, err := getRolesInRole(roleId, visited) rolesInRole, err := getRolesInRole(roleId, visited)
if err != nil { if err != nil {
panic(err) return nil, err
} }
for _, role := range rolesInRole { for _, role := range rolesInRole {
roleId := role.GetId() roleId = role.GetId()
for _, subUser := range role.Users { for _, subUser := range role.Users {
if domainExist { if domainExist {
for _, domain := range permission.Domains { for _, domain := range permission.Domains {
@@ -198,75 +215,114 @@ func getGroupingPolicies(permission *Permission) [][]string {
} }
} }
return groupingPolicies return groupingPolicies, nil
} }
func addPolicies(permission *Permission) { func addPolicies(permission *Permission) error {
enforcer := getPermissionEnforcer(permission) enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return err
}
policies := getPolicies(permission) 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 { 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) 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 { 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) { groupingPolicies, err := getGroupingPolicies(permission)
enforcer := getPermissionEnforcer(permission, permissionIds...) if err != nil {
return enforcer.Enforce(*request...) 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) { func Enforce(permission *Permission, request []string, permissionIds ...string) (bool, error) {
enforcer := getPermissionEnforcer(permission, permissionIds...) enforcer, err := getPermissionEnforcer(permission, permissionIds...)
return enforcer.BatchEnforce(*requests) 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) permissions, _, err := getPermissionsAndRolesByUser(userId)
if err != nil { 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) permissionsByRole, err := GetPermissionsByRole(role)
if err != nil { if err != nil {
panic(err) return nil, err
} }
permissions = append(permissions, permissionsByRole...) permissions = append(permissions, permissionsByRole...)
@@ -274,35 +330,40 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []
var values []string var values []string
for _, permission := range permissions { for _, permission := range permissions {
enforcer := getPermissionEnforcer(permission) enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return nil, err
}
values = append(values, fn(enforcer)...) 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 getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllObjects() return enforcer.GetAllObjects()
}) })
} }
func GetAllActions(userId string) []string { func GetAllActions(userId string) ([]string, error) {
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string { return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllActions() return enforcer.GetAllActions()
}) })
} }
func GetAllRoles(userId string) []string { func GetAllRoles(userId string) ([]string, error) {
roles, err := getRolesByUser(userId) roles, err := getRolesByUser(userId)
if err != nil { if err != nil {
panic(err) return nil, err
} }
var res []string res := []string{}
for _, role := range roles { for _, role := range roles {
res = append(res, role.Name) res = append(res, role.Name)
} }
return res return res, nil
} }
func GetBuiltInModel(modelText string) (model.Model, error) { func GetBuiltInModel(modelText string) (model.Model, error) {
@@ -330,17 +391,23 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
// load [policy_definition] // load [policy_definition]
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",") policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
fieldsNum := len(policyDefinition) fieldsNum := len(policyDefinition)
if fieldsNum > builtInAvailableField { 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" // filled empty field with "" and V5 with "permissionId"
for i := builtInAvailableField - fieldsNum; i > 0; i-- { for i := builtInAvailableField - fieldsNum; i > 0; i-- {
policyDefinition = append(policyDefinition, "") policyDefinition = append(policyDefinition, "")
} }
policyDefinition = append(policyDefinition, "permissionId") 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, ",")) m.AddDef("p", "p", strings.Join(policyDefinition, ","))
return m, err return m, err

View File

@@ -83,5 +83,10 @@ func UploadPermissions(owner string, path string) (bool, error) {
return false, nil return false, nil
} }
return AddPermissionsInBatch(newPermissions), nil affected, err := AddPermissionsInBatch(newPermissions)
if err != nil {
return false, err
}
return affected, nil
} }

View File

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

View File

@@ -37,9 +37,9 @@ type Provider struct {
SubType string `xorm:"varchar(100)" json:"subType"` SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"` Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(200)" json:"clientId"` ClientId string `xorm:"varchar(200)" json:"clientId"`
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"` ClientSecret string `xorm:"varchar(3000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"` ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` ClientSecret2 string `xorm:"varchar(500)" json:"clientSecret2"`
Cert string `xorm:"varchar(100)" json:"cert"` Cert string `xorm:"varchar(100)" json:"cert"`
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"` CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"` CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
@@ -52,7 +52,7 @@ type Provider struct {
Port int `json:"port"` Port int `json:"port"`
DisableSsl bool `json:"disableSsl"` // If the provider type is WeChat, DisableSsl means EnableQRCode DisableSsl bool `json:"disableSsl"` // If the provider type is WeChat, DisableSsl means EnableQRCode
Title string `xorm:"varchar(100)" json:"title"` 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"` Receiver string `xorm:"varchar(100)" json:"receiver"`
RegionId string `xorm:"varchar(100)" json:"regionId"` 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 { func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo {
providerInfo := &idp.ProviderInfo{ providerInfo := &idp.ProviderInfo{
Type: provider.Type, Type: provider.Type,
SubType: provider.SubType, SubType: provider.SubType,
ClientId: provider.ClientId, ClientId: provider.ClientId,
ClientSecret: provider.ClientSecret, ClientSecret: provider.ClientSecret,
AppId: provider.AppId, ClientId2: provider.ClientId2,
HostUrl: provider.Host, ClientSecret2: provider.ClientSecret2,
TokenURL: provider.CustomTokenUrl, AppId: provider.AppId,
AuthURL: provider.CustomAuthUrl, HostUrl: provider.Host,
UserInfoURL: provider.CustomUserInfoUrl, TokenURL: provider.CustomTokenUrl,
UserMapping: provider.UserMapping, AuthURL: provider.CustomAuthUrl,
UserInfoURL: provider.CustomUserInfoUrl,
UserMapping: provider.UserMapping,
} }
if provider.Type == "WeChat" { if provider.Type == "WeChat" {
@@ -415,7 +417,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
providerInfo.ClientId = provider.ClientId2 providerInfo.ClientId = provider.ClientId2
providerInfo.ClientSecret = provider.ClientSecret2 providerInfo.ClientSecret = provider.ClientSecret2
} }
} else if provider.Type == "AzureAD" { } else if provider.Type == "AzureAD" || provider.Type == "AzureADB2C" || provider.Type == "ADFS" || provider.Type == "Okta" {
providerInfo.HostUrl = provider.Domain providerInfo.HostUrl = provider.Domain
} }

View File

@@ -18,13 +18,13 @@ type ProviderItem struct {
Owner string `json:"owner"` Owner string `json:"owner"`
Name string `json:"name"` Name string `json:"name"`
CanSignUp bool `json:"canSignUp"` CanSignUp bool `json:"canSignUp"`
CanSignIn bool `json:"canSignIn"` CanSignIn bool `json:"canSignIn"`
CanUnlink bool `json:"canUnlink"` CanUnlink bool `json:"canUnlink"`
Prompted bool `json:"prompted"` Prompted bool `json:"prompted"`
AlertType string `json:"alertType"` SignupGroup string `json:"signupGroup"`
Rule string `json:"rule"` Rule string `json:"rule"`
Provider *Provider `json:"provider"` Provider *Provider `json:"provider"`
} }
func (application *Application) GetProviderItem(providerName string) *ProviderItem { func (application *Application) GetProviderItem(providerName string) *ProviderItem {

View File

@@ -9,7 +9,6 @@ import (
) )
// https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html // https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a
type RadiusAccounting struct { type RadiusAccounting struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`

View File

@@ -151,8 +151,16 @@ func UpdateRole(id string, role *Role) (bool, error) {
} }
for _, permission := range permissions { for _, permission := range permissions {
addGroupingPolicies(permission) err = addGroupingPolicies(permission)
addPolicies(permission) if err != nil {
return false, err
}
err = addPolicies(permission)
if err != nil {
return false, err
}
visited[permission.GetId()] = struct{}{} visited[permission.GetId()] = struct{}{}
} }
@@ -166,10 +174,15 @@ func UpdateRole(id string, role *Role) (bool, error) {
if err != nil { if err != nil {
return false, err return false, err
} }
for _, permission := range permissions { for _, permission := range permissions {
permissionId := permission.GetId() permissionId := permission.GetId()
if _, ok := visited[permissionId]; !ok { if _, ok := visited[permissionId]; !ok {
addGroupingPolicies(permission) err = addGroupingPolicies(permission)
if err != nil {
return false, err
}
visited[permissionId] = struct{}{} visited[permissionId] = struct{}{}
} }
} }
@@ -254,14 +267,27 @@ func (role *Role) GetId() string {
func getRolesByUserInternal(userId string) ([]*Role, error) { func getRolesByUserInternal(userId string) ([]*Role, error) {
roles := []*Role{} roles := []*Role{}
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&roles) user, err := GetUser(userId)
if err != nil {
return roles, err
}
if user == nil {
return nil, fmt.Errorf("The user: %s doesn't exist", userId)
}
query := ormer.Engine.Alias("r").Where("r.users like ?", fmt.Sprintf("%%%s%%", userId))
for _, group := range user.Groups {
query = query.Or("r.groups like ?", fmt.Sprintf("%%%s%%", group))
}
err = query.Find(&roles)
if err != nil { if err != nil {
return roles, err return roles, err
} }
res := []*Role{} res := []*Role{}
for _, role := range roles { for _, role := range roles {
if util.InSlice(role.Users, userId) { if util.InSlice(role.Users, userId) || util.HaveIntersection(role.Groups, user.Groups) {
res = append(res, role) res = append(res, role)
} }
} }

View File

@@ -28,16 +28,16 @@ import (
"io" "io"
"time" "time"
"github.com/RobotsAndPencils/go-saml"
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/google/uuid" "github.com/google/uuid"
saml "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig" dsig "github.com/russellhaering/goxmldsig"
) )
// NewSamlResponse // NewSamlResponse
// returns a saml2 response // returns a saml2 response
func NewSamlResponse(user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) { func NewSamlResponse(application *Application, user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
samlResponse := &etree.Element{ samlResponse := &etree.Element{
Space: "samlp", Space: "samlp",
Tag: "Response", Tag: "Response",
@@ -103,6 +103,13 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic") displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName) displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
for _, item := range application.SamlAttributes {
role := attributes.CreateElement("saml:Attribute")
role.CreateAttr("Name", item.Name)
role.CreateAttr("NameFormat", item.NameFormat)
role.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(item.Value)
}
roles := attributes.CreateElement("saml:Attribute") roles := attributes.CreateElement("saml:Attribute")
roles.CreateAttr("Name", "Roles") roles.CreateAttr("Name", "Roles")
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic") roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
@@ -184,10 +191,11 @@ type SingleSignOnService struct {
type Attribute struct { type Attribute struct {
XMLName xml.Name XMLName xml.Name
Name string `xml:"Name,attr"` Name string `xml:"Name,attr"`
NameFormat string `xml:"NameFormat,attr"` NameFormat string `xml:"NameFormat,attr"`
FriendlyName string `xml:"FriendlyName,attr"` FriendlyName string `xml:"FriendlyName,attr"`
Xmlns string `xml:"xmlns,attr"` Xmlns string `xml:"xmlns,attr"`
Values []string `xml:"AttributeValue"`
} }
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) { func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
@@ -275,15 +283,15 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
} }
} }
var authnRequest saml.AuthnRequest var authnRequest saml.AuthNRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest) err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil { if err != nil {
return "", "", method, fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request. %s", err.Error()) return "", "", method, fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request. %s", err.Error())
} }
// verify samlRequest // verify samlRequest
if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid { if isValid := application.IsRedirectUriValid(authnRequest.Issuer); !isValid {
return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url) return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer)
} }
// get certificate string // get certificate string
@@ -309,13 +317,18 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
// build signedResponse // build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris) samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{ randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey, PrivateKey: cert.PrivateKey,
X509Certificate: certificate, X509Certificate: certificate,
} }
ctx := dsig.NewDefaultSigningContext(randomKeyStore) ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1 ctx.Hash = crypto.SHA1
if application.EnableSamlC14n10 {
ctx.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
}
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse) //signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
//if err != nil { //if err != nil {
// return "", "", fmt.Errorf("err: %s", err.Error()) // return "", "", fmt.Errorf("err: %s", err.Error())

View File

@@ -23,23 +23,49 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/casdoor/casdoor/idp"
"github.com/mitchellh/mapstructure"
"github.com/casdoor/casdoor/i18n" "github.com/casdoor/casdoor/i18n"
saml2 "github.com/russellhaering/gosaml2" saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig" dsig "github.com/russellhaering/goxmldsig"
) )
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) { func ParseSamlResponse(samlResponse string, provider *Provider, host string) (*idp.UserInfo, error) {
samlResponse, _ = url.QueryUnescape(samlResponse) samlResponse, _ = url.QueryUnescape(samlResponse)
sp, err := buildSp(provider, samlResponse, host) sp, err := buildSp(provider, samlResponse, host)
if err != nil { if err != nil {
return "", err return nil, err
} }
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse) assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
if err != nil { if err != nil {
return "", err return nil, err
} }
return assertionInfo.NameID, err
userInfoMap := make(map[string]string)
for spAttr, idpAttr := range provider.UserMapping {
for _, attr := range assertionInfo.Values {
if attr.Name == idpAttr {
userInfoMap[spAttr] = attr.Values[0].Value
}
}
}
userInfoMap["id"] = assertionInfo.NameID
customUserInfo := &idp.CustomUserInfo{}
err = mapstructure.Decode(userInfoMap, customUserInfo)
if err != nil {
return nil, err
}
userInfo := &idp.UserInfo{
Id: customUserInfo.Id,
Username: customUserInfo.Username,
DisplayName: customUserInfo.DisplayName,
Email: customUserInfo.Email,
AvatarUrl: customUserInfo.AvatarUrl,
}
return userInfo, err
} }
func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) { func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
@@ -146,14 +172,24 @@ func getCertificateFromSamlResponse(samlResponse string, providerType string) (s
if err != nil { if err != nil {
return "", err return "", err
} }
var (
deStr := strings.Replace(string(de), "\n", "", -1) expression string
tagMap := map[string]string{ deStr = strings.Replace(string(de), "\n", "", -1)
"Aliyun IDaaS": "ds", tagMap = map[string]string{
"Keycloak": "dsig", "Aliyun IDaaS": "ds",
} "Keycloak": "dsig",
}
)
tag := tagMap[providerType] tag := tagMap[providerType]
expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag) if tag == "" {
// <ds:X509Certificate>...</ds:X509Certificate>
// <dsig:X509Certificate>...</dsig:X509Certificate>
// <X509Certificate>...</X509Certificate>
// ...
expression = "<[^>]*:?X509Certificate>([\\s\\S]*?)<[^>]*:?X509Certificate>"
} else {
expression = fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
}
res := regexp.MustCompile(expression).FindStringSubmatch(deStr) res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
return res[1], nil return res[1], nil
} }

View File

@@ -26,6 +26,8 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS { if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId) client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
} else if provider.Type == "Custom HTTP SMS" {
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.Title)
} else { } else {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId) client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
} }

83
object/sms_custom_http.go Normal file
View File

@@ -0,0 +1,83 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/casdoor/casdoor/proxy"
)
type HttpSmsClient struct {
endpoint string
method string
paramName string
}
func newHttpSmsClient(endpoint string, method string, paramName string) (*HttpSmsClient, error) {
client := &HttpSmsClient{
endpoint: endpoint,
method: method,
paramName: paramName,
}
return client, nil
}
func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error {
phoneNumber := targetPhoneNumber[0]
content := param["code"]
var req *http.Request
var err error
if c.method == "POST" {
formValues := url.Values{}
formValues.Set("phoneNumber", phoneNumber)
formValues.Set(c.paramName, content)
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
} else if c.method == "GET" {
req, err = http.NewRequest(c.method, c.endpoint, nil)
if err != nil {
return err
}
q := req.URL.Query()
q.Add("phoneNumber", phoneNumber)
q.Add(c.paramName, content)
req.URL.RawQuery = q.Encode()
} else {
return fmt.Errorf("HttpSmsClient's SendMessage() error, unsupported method: %s", c.method)
}
httpClient := proxy.DefaultHttpClient
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HttpSmsClient's SendMessage() error, custom HTTP SMS request failed with status: %s", resp.Status)
}
return err
}

Some files were not shown because too many files have changed in this diff Show More