Compare commits

...

261 Commits

Author SHA1 Message Date
c1f553440e feat: add wecom internal support (#452)
Signed-off-by: 0x2a <stevesough@gmail.com>
2022-01-28 12:44:45 +08:00
7dcae2d183 fix: add k8s deployments example (#446) 2022-01-28 09:25:25 +08:00
5ec0c7a890 fix: fix the SQL injection vulnerability in field filter (#442)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-01-26 19:36:36 +08:00
051752340d feat: add userinfo endpoint (#447)
* feat: add userinfo endpoint

Signed-off-by: 0x2a <stevesough@gmail.com>

* feat: add scope support

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: modify the endpoint of discovery

Signed-off-by: 0x2a <stevesough@gmail.com>
2022-01-26 11:56:01 +08:00
c87c001da3 fix: fix the permission page can not open when initial a new project (#449) 2022-01-25 19:39:04 +08:00
12bc419659 fix: baidu's display name error (#440)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-01-23 20:32:44 +08:00
d5f18f2d64 Support SilentSignin. 2022-01-23 13:02:55 +08:00
02c06bc93c feat: add baidu support as idp (#438)
* feat: add baidu support as idp

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: add license

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-01-22 19:36:44 +08:00
40aa9a4693 fix: remove wait-for-it (#436) 2022-01-22 15:50:48 +08:00
630b84f534 feat: add PKCE support (#434)
* feat: add PKCE support

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: error output when challenge is empty

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-01-21 09:29:19 +08:00
339a85e4b0 Support tableNamePrefix in authz table. 2022-01-20 14:20:37 +08:00
c22ab44894 Update import path. 2022-01-20 14:11:46 +08:00
c3fb48f473 fix: Add a configuration that can set the table prefix. There is no prefix by default (#432)
* fix: Add a configuration that can set the table prefix. There is no prefix by default

* fix: Add a configuration that can set the table prefix. There is no prefix by default
2022-01-20 13:50:20 +08:00
a111fd672c fix: Add the configuration of whether to print SQL. The default value is false (#429) 2022-01-19 16:58:45 +08:00
9fd175eefd Add ErrorText to syncer. 2022-01-17 21:17:42 +08:00
d9bcce9485 Start syncer dynamically. 2022-01-17 20:09:29 +08:00
d183b9eca9 Change syncer.SyncInterval to second-level. 2022-01-17 19:27:52 +08:00
f24d9ae251 Don't update password in AddUsers(). 2022-01-17 13:26:30 +08:00
030c1caa50 Fix bug in IsGlobalAdmin(). 2022-01-15 23:23:14 +08:00
cee2c608a2 Disable PasswordModal when needed in user edit page. 2022-01-15 21:34:37 +08:00
82d0e895e0 Update users and roles when org is changed in permission edit page. 2022-01-15 21:11:47 +08:00
dee9bac110 Show signupApplication in user edit page. 2022-01-15 18:29:10 +08:00
e7a6986b62 Add index to User.Id 2022-01-14 17:42:11 +08:00
b91b4aec91 Allow global admin to modify username. 2022-01-13 23:20:10 +08:00
fe48c38bc6 feat: support minio (#418)
Signed-off-by: abingcbc <abingcbc626@gmail.com>
2022-01-13 21:48:00 +08:00
1be777c08f Fix GetUserByField()'s bug for idCard. 2022-01-13 12:48:15 +08:00
8d54bfad8a feat: support create database via cmd line (#417) 2022-01-13 11:35:13 +08:00
728fe11a3c Refactor CountDownInput. 2022-01-07 20:34:27 +08:00
69e0f4e40d Add idCard in GetUserByFields(). 2022-01-04 19:52:29 +08:00
ba32a45693 Add ClaimsShort to fix the JWT user's owner and name empty bug. 2022-01-03 22:54:27 +08:00
a4d83af768 refactor: New Crowdin translations by Github Action (#412)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-01-02 23:55:06 +08:00
5b8f6415d9 Add Gitter badge. 2022-01-02 23:34:24 +08:00
5389cb435c Fix Crowdin sync. 2022-01-02 23:16:08 +08:00
9b6131890c Add permission page. 2022-01-01 16:28:33 +08:00
ffc0a0e0d5 fix: refresh_token endpoint does not work (#410)
Signed-off-by: 0x2a <stevesough@gmail.com>
2022-01-01 15:20:49 +08:00
ff22bf507f Add role page. 2022-01-01 15:11:16 +08:00
2d4103d751 Add isUserExtended to webhook. 2022-01-01 11:16:37 +08:00
4611b59b08 Add webhook edit page's preview. 2022-01-01 10:58:39 +08:00
445d3c9d0e feat: support spring security oauth2 (#408)
Signed-off-by: abingcbc <abingcbc626@gmail.com>
2021-12-31 19:55:34 +08:00
dbebd1846f Fix code sign-in link hiding. 2021-12-31 13:36:10 +08:00
2fcc8f5bfe Support app user in SetPassword(). 2021-12-31 13:32:18 +08:00
4b65320a96 Support user uploading via xlsx. 2021-12-31 13:00:35 +08:00
5e8897e41b Make cert work. 2021-12-31 10:02:06 +08:00
ba1646a0c3 Add cert pages. 2021-12-31 00:36:36 +08:00
c1cd187558 Improve UI. 2021-12-29 20:50:49 +08:00
519fd655cf Add GetMaskedApplication() and GetMaskedApplications(). 2021-12-29 20:04:39 +08:00
377ac05928 Don't clear session in SetPassword(). 2021-12-28 23:07:09 +08:00
4f124ff140 fix: refresh token does not return (#401)
Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-28 19:44:17 +08:00
d5f802ec7d Support IdCard in signup page. 2021-12-28 17:48:24 +08:00
64d3b7e87f Add EnableSigninSession to app. 2021-12-28 17:15:47 +08:00
dfce1bd74c Remove adapter.createDatabase(). 2021-12-27 22:49:54 +08:00
067ae5448f fix: idp using goth shows wrong display name (#398)
* fix: adjust the accessToken field

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: missing name and owner

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: get wrong display name

Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-27 18:55:25 +08:00
9943e3c316 Add isEnabled to webhook. 2021-12-26 21:03:12 +08:00
0c665edcbc Add headers to webhook. 2021-12-26 20:43:32 +08:00
5015bf1c7d Add method to webhook. 2021-12-26 19:56:02 +08:00
2ec947d488 Update project_id. 2021-12-26 09:49:00 +08:00
10a85f2386 feat: add server-side search, filter and sorter for all pages (#388)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2021-12-25 10:55:10 +08:00
0d13512eb1 docs: add docs for all-in-one image (#396) 2021-12-25 00:33:35 +08:00
b60856be5e Fix bug in updateUserForOriginalFields(). 2021-12-25 00:19:17 +08:00
4b4c9be71b Fix other bugs in syncer. 2021-12-25 00:05:54 +08:00
e79e3c36d0 Support more DBs in syncer. 2021-12-24 23:23:06 +08:00
cc8c9b32ef Fix missing options in renderOptions(). 2021-12-24 14:09:12 +08:00
efdcb3279d Fix null address bug in getOriginalUsersFromMap(). 2021-12-24 01:03:50 +08:00
3818492065 Fix updating old DB code. 2021-12-24 00:36:53 +08:00
f4890a6a22 Improve syncer.initAdapter(). 2021-12-23 21:28:40 +08:00
fa45d3083a fix: Add loading and countdown status to the verification code sending button (#384)
Add loading and countdown status to the verification code sending button

Revert "Add bcrypt encrypted password type"

This reverts commit ae995b5805.

fix:indentation
2021-12-23 19:41:26 +08:00
caa1ffe3c8 feat: add default password for mysql (#392) 2021-12-23 12:43:04 +08:00
c88edc4d3e Fix app side bug in org renaming. 2021-12-23 01:01:23 +08:00
f5bc76016d Add restriction to built-in org and app modification. 2021-12-23 00:52:32 +08:00
8345295d0c feat: fix github-ci (#390) 2021-12-22 23:20:33 +08:00
3bdc0e0d1b feat: add all-in-one version (#389)
Signed-off-by: Товарищ программист <2962928213@qq.com>
2021-12-22 21:58:25 +08:00
8dcb56ea71 Fix affiliationMap null bug. 2021-12-22 21:09:13 +08:00
b95f107a60 Support cred manager for organization.MasterPassword 2021-12-22 20:56:22 +08:00
d6c2d0f3e8 feat: Add bcrypt encrypted password type (#386)
* Add loading and countdown status to the verification code sending button

* Add bcrypt encrypted password type

* Revert "Add loading and countdown status to the verification code sending button"

This reverts commit 782b9e229a.

* Update bcrypt.go

* Update go.sum
2021-12-22 20:26:19 +08:00
7cfece3019 Add GetMaskedProvider() and GetMaskedProviders(). 2021-12-21 00:20:12 +08:00
3efbcc739d Support WeChat MP at the same time. 2021-12-20 23:36:28 +08:00
99ef329325 feat: support mobile login with WeChat Official Accounts (#383)
* fix: adjust the accessToken field

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: missing name and owner

Signed-off-by: 0x2a <stevesough@gmail.com>

* feat: support mobile login with WeChat

Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-20 20:21:12 +08:00
8f0995ca34 Fix bug in showing OAuthWidget. 2021-12-20 01:11:42 +08:00
e64f181e28 Add getCasdoorColumns(). 2021-12-20 00:26:46 +08:00
db56f54b8c Add TablePrimaryKey to syncer. 2021-12-19 23:33:22 +08:00
bf642b35d4 Add isHashed to syncer's tableColumn. 2021-12-19 23:32:42 +08:00
5ee5299a68 Improve syncer code. 2021-12-19 22:30:54 +08:00
e7f395cfd4 Add tableColumns to syncer. 2021-12-19 10:21:05 +08:00
29d512d316 Fix translation. 2021-12-19 01:13:11 +08:00
5814ae6baf Fix translation. 2021-12-19 01:08:59 +08:00
52145abdc8 Improve translation. 2021-12-19 00:34:37 +08:00
157e515310 refactor: New Crowdin translations by Github Action (#380)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2021-12-18 23:34:17 +08:00
47ed2e903c feat: set to push image with tag latest as well (#381)
Signed-off-by: Товарищ <2962928213@qq.com>
2021-12-18 23:32:28 +08:00
822ad14ea9 feat: add more instructions in readme (#379)
Signed-off-by: Товарищ <2962928213@qq.com>
2021-12-18 22:17:41 +08:00
3355f8644e fix: modify image push policy (#377)
Signed-off-by: Товарищ <2962928213@qq.com>
2021-12-18 20:44:27 +08:00
00f06930ba fix: adjust the accessToken field (#378)
* fix: adjust the accessToken field

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: missing name and owner

Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-18 20:19:38 +08:00
755d912f61 feat: add refresh token mechanism for server side (#336)
* feat: add refresh token mechanism for server side

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* feat: add refresh token expire configuration UI

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2021-12-18 18:49:38 +08:00
95f2a3b311 Add TokenFormat to application. 2021-12-18 16:16:34 +08:00
cb625b3fa2 Fix TestGetUsers(). 2021-12-18 12:48:53 +08:00
16cc64f08d Fix translation. 2021-12-18 11:42:56 +08:00
318cf52b33 Refactor the original db code. 2021-12-18 01:08:03 +08:00
07f9a9ee96 Add more fields to syncer. 2021-12-17 20:33:03 +08:00
c2110ef59d Add sync pages. 2021-12-17 16:35:45 +08:00
2f70e77e53 fix: wrong sub return and docker-compose boolean value error. (#375)
* fix: wrong sub return

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: yaml bool value must be enclosed in quotes

Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-16 11:10:25 +08:00
98f6cc0085 feat: add OIDC feature support. (#373)
1. add nonce parameter.
2. add sub in userinfo endpoint.

Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-15 21:42:16 +08:00
370e835499 feat: support AuthnRequest in SAML (#372)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2021-12-15 21:38:00 +08:00
f43d01c5c2 feat: implement automatic synchronization for ldap users (#371)
Signed-off-by: Товарищ программист <2962928213@qq.com>
2021-12-15 17:45:11 +08:00
4ca5f4b196 feat: add Keycloak idp support (#356)
* feat: add Keycloak idp support

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: fix the profile UI

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2021-12-13 19:49:30 +08:00
cf9e628a3e Improve authConfig. 2021-12-13 16:42:54 +08:00
726e4e3dc1 refactor: move from io/ioutil to io and os packages (#366)
* chore: format code by running `go fmt ./...`

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>

* refactor: move from io/ioutil to io and os packages

The io/ioutil package has been deprecated as of Go 1.16, see
https://golang.org/doc/go1.16#ioutil. This commit replaces the existing
io/ioutil functions with their new definitions in io and os packages.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2021-12-13 09:49:43 +08:00
0adb9b0047 Improve parseBearerToken(). 2021-12-13 00:37:13 +08:00
00ab156453 fix: support using bearer token to access protected resources (#364)
* fix: require signed in by bearer token.

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: utilize existing code refactoring functions

Signed-off-by: 0x2a <stevesough@gmail.com>

* fix: improve the bearer parese function

Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-13 00:25:44 +08:00
589c0404d2 Fix dataSourceName config to use correct DB host in Docker. 2021-12-12 23:51:53 +08:00
b2f2674d3e Revert: Make user.Ranking auto-increase. 2021-12-12 22:50:57 +08:00
a555d27dd2 Update Dockerfile versions. 2021-12-12 22:28:14 +08:00
8d8c662e58 Fix missing ranking in initBuiltInUser(). 2021-12-12 21:06:56 +08:00
94c78593fc Fix bug in Login(). 2021-12-12 20:07:51 +08:00
f4265d015a Improve user error handling. 2021-12-12 19:59:55 +08:00
96e2f286ee Merge into one origin config. 2021-12-12 19:26:06 +08:00
29807b82e1 Admin can reset password without old password. 2021-12-12 19:15:24 +08:00
e0b7286882 Put application's providers to 2 columns. 2021-12-12 19:06:40 +08:00
1762d19787 Improve creation UI for pages. 2021-12-12 18:51:12 +08:00
2f71d9743b Improve org creation UI. 2021-12-12 17:12:15 +08:00
6ba658ac60 Support columns arg in UpdateUser(). 2021-12-11 14:45:08 +08:00
cc47f3b65d Improve UpdateUserToOriginalDatabase(). 2021-12-11 13:45:26 +08:00
eca1d23e35 Make user.Ranking auto-increase. 2021-12-11 12:16:09 +08:00
6947ebd152 feat: support checking password through ldap server (#354)
Signed-off-by: Товарищ программист <2962928213@qq.com>
2021-12-10 22:45:01 +08:00
967113689d feat: add three idp support by goth. (#351)
1. add 3 providers: apple, azuread(v1) and slack.
2. support importing providers from goth.

Signed-off-by: 0x2a <stevesough@gmail.com>
2021-12-10 00:55:27 +08:00
b73b9a65b6 feat: support third-party application to login with SAML rather than only Casdoor itself (#350)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2021-12-10 00:23:04 +08:00
70a550d8bc feat: login by code (#344)
Signed-off-by: abingcbc <abingcbc626@gmail.com>
2021-12-07 00:05:53 +08:00
113398c36b feat: support SAML and test with aliyun IDaaS (#346)
* feat: support SAML and test with aliyun IDaaS

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* refactor: refactor saml.go and router

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: add param to getSamlLogin()

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* feat: add inputs to parse metadata automatically and show sp-acs-url, sp-entity-id

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2021-12-06 21:46:50 +08:00
667158f585 Show message for signing up with at least 6 chars' password. 2021-12-06 09:38:22 +08:00
dc9d2389a5 Add IntranetEndpoint to provider. 2021-12-04 16:38:34 +08:00
c8b8488797 Improve Redirect URI error message. 2021-12-04 00:40:21 +08:00
07fa438348 feat: update swagger api json with tags (#347)
Signed-off-by: Товарищ программист <2962928213@qq.com>
2021-12-03 20:42:36 +08:00
d2565e03c8 Add tencent_cloud_cos.go 2021-12-03 15:12:16 +08:00
cc2797ed27 Rename to AddUsersInBatch(). 2021-12-02 10:55:53 +08:00
746967e18a Change pageSize to 20 for record page. 2021-12-01 21:28:45 +08:00
14c4f60a40 Add IsDefaultAvatar to user. 2021-12-01 21:26:28 +08:00
79fd6ff5d3 Make Application's fields smaller. 2021-12-01 21:26:03 +08:00
04bc8628a8 Change Application.Providers to mediumtext. 2021-11-30 00:48:09 +08:00
d6c9ee508c Add code sign in UI. 2021-11-28 21:15:58 +08:00
d224e728a3 Reduce CountDownInput args. 2021-11-28 20:57:14 +08:00
36b7993994 Improve UI. 2021-11-28 20:18:03 +08:00
c10ccd8106 Improve provider table in app edit page. 2021-11-28 18:56:56 +08:00
a04702a8d0 Add Setting.getNewRowNameForTable(). 2021-11-28 18:56:56 +08:00
e888ff8475 fix: add id_token and support auth header (#338) 2021-11-28 18:54:58 +08:00
7923bffa6d Improve forget page CSS. 2021-11-28 14:23:00 +08:00
bfd5d0172a Remove regionId for SMS providers. 2021-11-28 13:42:30 +08:00
4e92a8273c Improve forget page logic. 2021-11-28 11:10:07 +08:00
c46925dbe8 Don't panic for sendWebhook() error. 2021-11-27 21:12:13 +08:00
7f39aee9c4 Make application's fields longer. 2021-11-25 16:44:49 +08:00
b86087b2af Detect as Go. 2021-11-25 09:54:06 +08:00
bddd57cda8 feat: implement jwks_uri handler in oidc discovery (#334)
Signed-off-by: Товарищ <2962928213@qq.com>
2021-11-22 17:47:44 +08:00
44b59d866a Add more args to UploadResource(). 2021-11-21 16:21:35 +08:00
95600414d9 Add UploadFileSafe(). 2021-11-20 17:26:58 +08:00
9eb09b7db0 Add GetUserNoCheck(). 2021-11-20 15:46:54 +08:00
91c0282040 Support username arg in UploadResource(). 2021-11-20 15:35:33 +08:00
2b6f397bb9 Add some new fields to User. 2021-11-19 21:37:13 +08:00
db7f4a4af9 Show "canSignUp" column when password is ON. 2021-11-19 21:27:22 +08:00
825de2bdaa fix: fix incorrect issuer in id token of oidc (#333)
Signed-off-by: Товарищ <2962928213@qq.com>
2021-11-19 16:32:05 +08:00
0b3742b0b1 Add GetSortedUsers() and GetUserCount() APIs. 2021-11-19 10:51:06 +08:00
1394dce306 fix: preinstall scripts bug (#331)
* feat: ban npm to solve potential bugs

Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>

* feat: fix preinstall scripts bug

Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-11-17 21:13:20 +08:00
64fc810359 fix: panic while insert record when request uri too long (#325)
Signed-off-by: Lex Lim <hyperzlink@outlook.com>
2021-11-12 00:01:37 +08:00
abba56a6f3 fix: display "Continue with (User)" button of another organization (#327)
Signed-off-by: Lex Lim <hyperzlink@outlook.com>
2021-11-11 20:43:26 +08:00
cc933cf5f3 fix: save country code (#324)
Signed-off-by: “seriouszyx” <seriouszyx@foxmail.com>
2021-11-11 14:38:35 +08:00
37829062ad Run sync user job if configured. 2021-11-11 00:50:08 +08:00
f5fdf0af6a feat: translate the region select box (#321)
Signed-off-by: “seriouszyx” <seriouszyx@foxmail.com>
2021-11-10 22:27:58 +08:00
21f433d278 Add logPostOnly. 2021-11-09 23:32:53 +08:00
102e22f2c7 Add method to record. 2021-11-09 23:24:13 +08:00
8d6756fe9a Improve some translations. 2021-11-09 20:49:08 +08:00
63f33d0ad9 Improve email and phone checking in Signup(). 2021-11-09 20:29:38 +08:00
1c2e9064fe docs: Upgrade to swagger UI 4.1.0 (#319)
* docs: update swagger docs

Signed-off-by: “seriouszyx” <seriouszyx@foxmail.com>

* docs: upgrade to Swagger UI 4.1.0

Signed-off-by: “seriouszyx” <seriouszyx@foxmail.com>
2021-11-09 19:00:02 +08:00
54ef2ec09f Expose GetOAuthCode() as API. 2021-11-08 23:28:41 +08:00
d84ddda607 fix: Wrong token endpoint value #314 (#315)
Signed-off-by: leo <leo@himysql.com>

Co-authored-by: leo <leo@himysql.com>
2021-11-08 22:38:23 +08:00
30a2fdef37 Make webhook work. 2021-11-07 23:53:17 +08:00
87e6fb63e1 Add isTriggered to record. 2021-11-07 17:56:45 +08:00
e9e0721b34 Add missing fields for record. 2021-11-07 17:36:52 +08:00
5ec678fa28 Improve record's User field. 2021-11-07 17:20:15 +08:00
77fffcacac Refactor the record code. 2021-11-07 16:51:16 +08:00
0e71e603ac Add webhook pages. 2021-11-07 16:24:13 +08:00
cbf973882d Show CustomGithubCorner in other pages. 2021-11-06 22:04:20 +08:00
467d709b8e Add MasterPassword to organization. 2021-11-06 21:14:53 +08:00
4d71725bf5 Add i18n util code and update translation files. 2021-11-06 20:18:25 +08:00
9e920181d2 Add user soft deletion. 2021-11-06 15:52:03 +08:00
db892333fe Set default value of page and pageSize for get batch APIs. 2021-11-06 15:17:18 +08:00
acaee2e892 Fix "Phone prefix" text typo. 2021-11-06 15:01:51 +08:00
5fd681e971 Fix CheckPassword(). 2021-11-06 14:31:41 +08:00
b1db47bad1 feat: add server-side pagination (#312)
Signed-off-by: “seriouszyx” <seriouszyx@foxmail.com>
2021-11-06 11:32:22 +08:00
7520b71198 Add Md5UserSaltCredManager. 2021-11-04 21:32:54 +08:00
7792f4589d Add CredManager. 2021-11-04 21:30:48 +08:00
609e9785e4 fix: some bugs about SMS API (#310)
Signed-off-by: “seriouszyx” <674965440@qq.com>
2021-10-31 08:49:39 +08:00
b7f2f9056f docs: revise 'quick start' part of readme (#311)
Signed-off-by: Товарищ <2962928213@qq.com>
2021-10-30 14:19:24 +08:00
ebc5fe454c Fix docker's mysql password. 2021-10-30 14:19:24 +08:00
2e1b51910f Support custom HTML for signup and signin pages. 2021-10-30 14:18:58 +08:00
5526286ad8 Use RS256 to sign JWT token. 2021-10-30 14:18:48 +08:00
795240687d Improve token var name. 2021-10-30 14:18:38 +08:00
6c30ccfb14 Update github.com/golang-jwt/jwt to v4. 2021-10-10 11:51:19 +08:00
802df55009 Switch to github.com/golang-jwt/jwt 2021-10-10 11:06:54 +08:00
353bf46daf Improve non-oauth logos. 2021-10-10 00:04:25 +08:00
122970bb54 Improve provider logos. 2021-10-09 22:33:39 +08:00
fa3681ed75 Add authInfo to js. 2021-10-09 22:15:43 +08:00
08e59796c8 Improve css code format. 2021-10-09 21:17:30 +08:00
568372d077 Support quick sign-in. 2021-10-09 21:17:03 +08:00
4f2668cd90 Remove password in JWT token payload. 2021-10-03 22:08:40 +08:00
8cb1291f6f Accept audio and pdf as resource. 2021-10-03 22:08:18 +08:00
15786070bb Mask application for /api/get-app-login 2021-09-28 23:41:27 +08:00
ba0d089589 Fix wrong links in oidcDiscovery data. 2021-09-26 00:08:35 +08:00
9a71bb02e3 Add data to oidcDiscovery. 2021-09-25 15:17:35 +08:00
a1b5282da9 Add /.well-known/openid-configuration route. 2021-09-25 14:54:13 +08:00
80d2738863 Increase Resource.Url length to 1000. 2021-09-22 22:21:16 +08:00
964b60da29 Support silent login from HTTP basic authentication. 2021-09-21 22:57:37 +08:00
ea8017dd4b Change default db password to 6 digits. 2021-09-21 20:07:34 +08:00
391bc3ebc9 Leave ForceLanguage empty. 2021-09-21 18:19:09 +08:00
2326d55f73 Don't crash when proxy is unavailable. 2021-09-21 18:14:00 +08:00
890030b3ef Display resources and swagger menu for normal users (#300) 2021-09-21 17:35:59 +08:00
725ee3393e fix: swagger path dump (#299) 2021-09-21 16:21:33 +08:00
e61e46f1e5 fix: signup logic when email and phone not enabled (#298)
Signed-off-by: chinggg <24590067+chinggg@users.noreply.github.com>
2021-09-21 14:11:05 +08:00
63c720985b Fix email must input bug in signup page. 2021-09-21 14:04:33 +08:00
7582ba0b6f fix: log file permission (#297)
Signed-off-by: chinggg <24590067+chinggg@users.noreply.github.com>
2021-09-21 11:08:08 +08:00
a04f669580 Fix alpine bug in Dockerfile. 2021-09-21 01:06:57 +08:00
8cb96142db Use yarn in Dockerfile. 2021-09-21 00:00:33 +08:00
d62a5fd7de Don't auto-redirect when enablePassword is false in login page. 2021-09-20 22:55:17 +08:00
c7a3ad901b Fix PostgreSQL error in getVerificationRecord(). 2021-09-20 22:38:04 +08:00
0ef87632f4 Init signup items for "app-built-in". 2021-09-20 22:17:52 +08:00
331862a94c Fix session cookie time doesn't work bug. 2021-09-19 21:11:29 +08:00
08eaf298c1 feat: ban npm to solve potential bugs (#296)
Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-09-17 17:07:31 +08:00
69794fe29d Improve language code. 2021-09-15 01:02:17 +08:00
09f430266b Improve menu key. 2021-09-13 23:56:25 +08:00
52d9017611 fix: swagger bug in dev mode (#291)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-12 12:09:44 +08:00
c70c62f52e docs: Update README.md for backend port caution (#293) 2021-09-08 22:02:58 +08:00
355b0b35d0 Fix gitee login link. 2021-09-07 21:46:44 +08:00
e4846807cd Show resource list page to users. 2021-09-06 00:49:10 +08:00
f4a59de3a5 Improve resource list page. 2021-09-06 00:08:16 +08:00
a1b16f88d1 Add user to Resource. 2021-09-05 23:46:56 +08:00
90ec8ec787 Add GetOwnerAndNameFromIdNoCheck() to fix bug. 2021-09-05 23:46:55 +08:00
ea8971ff29 refactor: New Crowdin translations by Github Action (#276)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2021-09-05 22:49:59 +08:00
bd41425039 Improve format. 2021-09-05 22:09:54 +08:00
9d9a1da07f fix: remove routers/util (#287)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-05 22:02:32 +08:00
465d25a272 Improve router base.go 2021-09-05 14:44:27 +08:00
ef1195960e Improve SendSms() API. 2021-09-05 13:15:38 +08:00
089f4ff480 Handle error in go-sms-sender. 2021-09-05 10:56:11 +08:00
88aa444ad1 Improve SendEmail() and SendSms() APIs. 2021-09-05 10:30:51 +08:00
1c5ce46bd5 Refactor GetProviderFromContext(). 2021-09-05 09:44:15 +08:00
14d09cad2c Support server-side upload-resource call. 2021-09-05 01:03:29 +08:00
06006c87b8 Improve filter code. 2021-09-05 00:22:08 +08:00
a4edf47dc4 fix: improvde code logic (#285)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-04 22:20:47 +08:00
e68b0198f1 fix: go proxy of dockerfile (#283)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-04 22:10:16 +08:00
015961bc3c Add application to Resource. 2021-09-04 16:50:26 +08:00
5d98cc6ac5 Use objectKey as resource name. 2021-09-04 15:02:11 +08:00
b3eec024b8 Add getInitScore(). 2021-08-30 01:06:05 +08:00
eefcfd8440 Fix address null bug. 2021-08-27 23:43:43 +08:00
c6b2106c94 Add bio to user. 2021-08-25 08:07:08 +08:00
edf621f4d5 feat: support web-auth way for wecom (#275)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-23 23:12:53 +08:00
e50c6cd4b5 Add PermanentAvatar to user. 2021-08-21 23:17:33 +08:00
9c3117beb0 Rename UpdateUser functions. 2021-08-21 22:54:53 +08:00
4ca307564c Add proxy pkg. 2021-08-21 22:16:25 +08:00
15a6f64fdc Add 5 new user properties. 2021-08-21 10:58:34 +08:00
75e917a070 feat: add gitlab provider (#273)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-19 22:13:40 +08:00
e1182bb635 refactor: New Crowdin translations by Github Action (#265)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2021-08-18 21:05:31 +08:00
2b70698c2a docs: updated README.md for npm RAM caution (#272)
Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com>
2021-08-18 20:07:39 +08:00
2def51ad99 fix: remove forked repo workflow (#267)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-16 21:04:23 +08:00
398ba19fa5 Add CheckUserPassword() API. 2021-08-15 21:57:36 +08:00
8674b4853a Add provider to file API. 2021-08-15 01:14:21 +08:00
518c3f9f69 Add DeleteFile(). 2021-08-15 00:41:51 +08:00
495b64995f Add resource list page. 2021-08-15 00:25:46 +08:00
222 changed files with 18399 additions and 4799 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
*.go linguist-detectable=true
*.js linguist-detectable=false

View File

@ -14,6 +14,7 @@ jobs:
- run: yarn install && CI=false yarn run build
working-directory: ./web
backend:
name: Back-end
runs-on: ubuntu-latest
@ -28,9 +29,10 @@ jobs:
go build -race -ldflags "-extldflags '-static'"
working-directory: ./
release:
name: Release
release-and-push:
name: Release And Push
runs-on: ubuntu-latest
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
needs: [ frontend, backend ]
steps:
- name: Checkout
@ -41,26 +43,60 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 12
- name: Fetch Previous version
id: get-previous-tag
uses: actions-ecosystem/action-get-latest-tag@v1
- name: Release
run: yarn global add semantic-release@17.4.4 && semantic-release
env:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
publish:
name: Publish
runs-on: ubuntu-latest
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
needs: release
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Fetch Current version
id: get-current-tag
uses: actions-ecosystem/action-get-latest-tag@v1
- name: Decide Should_Push Or Not
id: should_push
run: |
old_version=${{steps.get-previous-tag.outputs.tag}}
new_version=${{steps.get-current-tag.outputs.tag }}
old_array=(${old_version//\./ })
new_array=(${new_version//\./ })
if [ ${old_array[0]} != ${new_array[0]} ]
then
echo ::set-output name=push::'true'
elif [ ${old_array[1]} != ${new_array[1]} ]
then
echo ::set-output name=push::'true'
else
echo ::set-output name=push::'false'
fi
- name: Log in to Docker Hub
uses: docker/login-action@v1
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push to Docker Hub
uses: docker/build-push-action@v2
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
push: true
tags: casbin/casdoor:latest
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
- name: Push All In One Version to Docker Hub
uses: docker/build-push-action@v2
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
target: ALLINONE
push: true
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest

View File

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

1
.gitignore vendored
View File

@ -17,6 +17,7 @@
.idea/
*.iml
.vscode/
tmp/
tmpFiles/

View File

@ -1,19 +1,36 @@
FROM golang:1.16 AS BACK
FROM golang:1.17.5 AS BACK
WORKDIR /go/src/casdoor
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server . \
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . \
&& apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
FROM node:14.17.4 AS FRONT
FROM node:16.13.0 AS FRONT
WORKDIR /web
COPY ./web .
RUN npm install && npm run build
RUN yarn config set registry https://registry.npm.taobao.org
RUN yarn install && yarn run build
FROM debian:latest AS ALLINONE
RUN apt update && apt install -y mariadb-server mariadb-client&&mkdir -p web/build && chmod 777 /tmp
LABEL MAINTAINER="https://casdoor.org/"
COPY --from=BACK /go/src/casdoor/ ./
COPY --from=BACK /usr/bin/wait-for-it ./
COPY --from=FRONT /web/build /web/build
CMD chmod 777 /tmp && service mariadb start&&\
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
./wait-for-it localhost:3306 -- ./server --createDatabase=true
FROM alpine:latest
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl
LABEL MAINTAINER="https://casdoor.org/"
COPY --from=BACK /go/src/casdoor/ ./
COPY --from=BACK /usr/bin/wait-for-it ./
RUN mkdir -p web/build && apk add --no-cache bash coreutils
COPY --from=FRONT /web/build /web/build
CMD ./wait-for-it db:3306 -- ./server
CMD ./server

View File

@ -7,10 +7,10 @@
<a href="https://hub.docker.com/r/casbin/casdoor">
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
</a>
<a href="https://github.com/casbin/casdoor/actions/workflows/build.yml">
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/workflows/build/badge.svg?style=flat-square">
</a>
<a href="https://github.com/casbin/casdoor/releases/latest">
<a href="https://github.com/casdoor/casdoor/releases/latest">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
</a>
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
@ -19,21 +19,27 @@
</p>
<p align="center">
<a href="https://goreportcard.com/report/github.com/casbin/casdoor">
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casbin/casdoor?style=flat-square">
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
</a>
<a href="https://github.com/casbin/casdoor/blob/master/LICENSE">
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license">
</a>
<a href="https://github.com/casbin/casdoor/issues">
<a href="https://github.com/casdoor/casdoor/issues">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
</a>
<a href="#">
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
</a>
<a href="https://github.com/casbin/casdoor/network">
<a href="https://github.com/casdoor/casdoor/network">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
</a>
<a href="https://crowdin.com/project/casdoor-site">
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
</a>
<a href="https://gitter.im/casbin/casdoor">
<img alt="Gitter" src="https://badges.gitter.im/casbin/casdoor.svg">
</a>
</p>
## Online demo
@ -41,21 +47,20 @@
Deployed site: https://door.casbin.com/
## Quick Start
Run your own casdoor program in a few minutes:smiley:
Run your own casdoor program in a few minutes.
### Download
There are two methods, get code via go subcommand `get`:
```shell
go get github.com/casbin/casdoor
go get github.com/casdoor/casdoor
```
or `git`:
```bash
git clone https://github.com/casbin/casdoor
git clone https://github.com/casdoor/casdoor
```
Finally, change directory:
@ -69,6 +74,7 @@ We provide two start up methods for all kinds of users.
### Manual
#### Simple configuration
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
@ -85,8 +91,9 @@ Casdoor provides two run modes, the difference is binary size and user prompt.
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
```bash
cd web/ && npm install && npm run start
cd web/ && yarn && yarn run start
```
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
Then build back-end binary file, change directory to root(Relative to casdoor):
@ -94,14 +101,15 @@ Then build back-end binary file, change directory to root(Relative to casdoor):
go run main.go
```
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
**But make sure you always request the backend port 8000 when you are using SDKs.**
##### Production Mode
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
```bash
cd web/ && npm install && npm run build
cd web/ && yarn && yarn run build
```
Then build back-end binary file, change directory to root(Relative to casdoor):
@ -114,14 +122,32 @@ go build main.go && sudo ./main
### Docker
Casdoor provide 2 kinds of image:
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
#### Simple configuration
### Start casdoor with casbin/casdoor-all-in-one
if the image is not pulled, pull it from dockerhub
```shell
docker pull casbin/casdoor-all-in-one
```
Start it with
```shell
docker run -p 8000:8000 casbin/casdoor-all-in-one
```
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
Edit `conf/app.conf`, modify `dataSourceName` to the fixed content:
### Start casdoor with casbin/casdoor
#### modify the configurations
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
```bash
dataSourceName = root:123@tcp(db:3306)/
dataSourceName = root:123456@tcp(db:3306)/
```
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
@ -134,14 +160,6 @@ docker-compose up
That's it! Try to visit http://localhost:8000/. :small_airplane:
### Docker Hub
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
```bash
docker pull casbin/casdoor
```
## Detailed documentation
We also provide a complete [document](https://casdoor.org/) as a reference.
@ -156,7 +174,7 @@ These all use casdoor as a centralized authentication platform.
## Contribute
For casdoor, if you have any questions, you can give Issues, and you can also directly Pull Requests(but we recommend give issues first to communicate with the community).
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
### I18n notice
@ -164,5 +182,5 @@ If you are contributing to casdoor, please note that we use [Crowdin](https://cr
## License
[Apache-2.0](https://github.com/casbin/casdoor/blob/master/LICENSE)
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)

View File

@ -19,6 +19,7 @@ import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
"github.com/casdoor/casdoor/conf"
stringadapter "github.com/qiangmzsx/string-adapter/v2"
)
@ -27,7 +28,8 @@ var Enforcer *casbin.Enforcer
func InitAuthz() {
var err error
a, err := xormadapter.NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName")+beego.AppConfig.String("dbName"), true)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}
@ -71,25 +73,34 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
if true {
ruleText := `
p, built-in, *, *, *, *, *
p, app, *, *, *, *, *
p, *, *, POST, /api/signup, *, *
p, *, *, POST, /api/get-email-and-phone, *, *
p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, POST, /api/login/oauth/access_token, *, *
p, *, *, POST, /api/login/oauth/refresh_token, *, *
p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-users, *, *
p, *, *, GET, /api/get-user, *, *
p, *, *, GET, /api/get-organizations, *, *
p, *, *, GET, /api/get-user-application, *, *
p, *, *, GET, /api/get-default-providers, *, *
p, *, *, GET, /api/get-resources, *, *
p, *, *, POST, /api/upload-avatar, *, *
p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *
p, *, *, GET, /api/get-human-check, *, *
p, *, *, POST, /api/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, *
p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, *, /api/certs, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
`
sa := stringadapter.NewAdapter(ruleText)

View File

@ -4,10 +4,16 @@ runmode = dev
SessionOn = true
copyrequestbody = true
driverName = mysql
dataSourceName = root:123@tcp(localhost:3306)/
dataSourceName = root:123456@tcp(localhost:3306)/
dbName = casdoor
tableNamePrefix =
showSql = false
redisEndpoint =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
httpProxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
initScore = 2000
logPostOnly = true
origin = "https://door.casbin.com"

33
conf/conf.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2021 The casbin 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 conf
import (
"os"
"strings"
"github.com/astaxie/beego"
)
func GetBeegoConfDataSourceName() string {
dataSourceName := beego.AppConfig.String("dataSourceName")
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" {
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
}
return dataSourceName
}

View File

@ -15,15 +15,14 @@
package controllers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/original"
"github.com/casbin/casdoor/util"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
const (
@ -41,6 +40,7 @@ type RequestForm struct {
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
IdCard string `json:"idCard"`
Region string `json:"region"`
Application string `json:"application"`
@ -55,15 +55,32 @@ type RequestForm struct {
PhonePrefix string `json:"phonePrefix"`
AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"`
SamlResponse string `json:"samlResponse"`
}
type Response struct {
Status string `json:"status"`
Msg string `json:"msg"`
Sub string `json:"sub"`
Name string `json:"name"`
Data interface{} `json:"data"`
Data2 interface{} `json:"data2"`
}
type Userinfo struct {
Sub string `json:"sub"`
Iss string `json:"iss"`
Aud string `json:"aud"`
Name string `json:"name,omitempty"`
DisplayName string `json:"preferred_username,omitempty"`
Email string `json:"email,omitempty"`
Avatar string `json:"picture,omitempty"`
Address string `json:"address,omitempty"`
Phone string `json:"phone,omitempty"`
}
type HumanCheck struct {
Type string `json:"type"`
AppKey string `json:"appKey"`
@ -73,6 +90,7 @@ type HumanCheck struct {
}
// Signup
// @Tag Login API
// @Title Signup
// @Description sign up a new user
// @Param username formData string true "The username to sign up"
@ -97,26 +115,6 @@ func (c *ApiController) Signup() {
return
}
if application.IsSignupItemEnabled("Email") {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email%s", checkResult))
return
}
}
var checkPhone string
if application.IsSignupItemEnabled("Phone") {
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Phone%s", checkResult))
return
}
}
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.Email, form.Phone, form.Affiliation)
if msg != "" {
@ -124,6 +122,26 @@ func (c *ApiController) Signup() {
return
}
if application.IsSignupItemVisible("Email") && form.Email != "" {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
return
}
}
var checkPhone string
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Phone: %s", checkResult))
return
}
}
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization)
@ -149,19 +167,25 @@ func (c *ApiController) Signup() {
Phone: form.Phone,
Address: []string{},
Affiliation: form.Affiliation,
IdCard: form.IdCard,
Region: form.Region,
Score: getInitScore(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
SignupApplication: application.Name,
Properties: map[string]string{},
}
affected := object.AddUser(user)
if affected {
original.AddUserToOriginalDatabase(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))
return
}
object.AddUserToOriginalDatabase(user)
if application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId())
@ -177,6 +201,7 @@ func (c *ApiController) Signup() {
// Logout
// @Title Logout
// @Tag Login API
// @Description logout the current user
// @Success 200 {object} controllers.Response The Response object
// @router /logout [post]
@ -192,6 +217,7 @@ func (c *ApiController) Logout() {
// GetAccount
// @Title GetAccount
// @Tag Account API
// @Description get the details of the current account
// @Success 200 {object} controllers.Response The Response object
// @router /get-account [get]
@ -207,73 +233,63 @@ func (c *ApiController) GetAccount() {
return
}
organization := object.GetOrganizationByUser(user)
c.ResponseOk(user, organization)
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
resp := Response{
Status: "ok",
Sub: user.Id,
Name: user.Name,
Data: user,
Data2: organization,
}
c.Data["json"] = resp
c.ServeJSON()
}
// UploadFile
// @Title UploadFile
// @Description upload file
// @Param owner query string true "The owner"
// @Param tag query string true "The tag"
// @Param fullFilePath query string true "The full file path"
// @Param file query string true "The file"
// @Success 200 {object} controllers.Response The Response object
// @router /upload-file [post]
func (c *ApiController) UploadFile() {
// UserInfo
// @Title UserInfo
// @Tag Account API
// @Description return user information according to OIDC standards
// @Success 200 {object} controllers.Userinfo The Response object
// @router /userinfo [get]
func (c *ApiController) GetUserinfo() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
//owner := c.Input().Get("owner")
tag := c.Input().Get("tag")
parent := c.Input().Get("parent")
fullFilePath := c.Input().Get("fullFilePath")
file, _, err := c.GetFile("file")
defer file.Close()
if err != nil {
c.ResponseError(err.Error())
return
}
fileBuffer := bytes.NewBuffer(nil)
if _, err = io.Copy(fileBuffer, file); err != nil {
c.ResponseError(err.Error())
return
}
user := object.GetUser(userId)
application := object.GetApplicationByUser(user)
provider := application.GetStorageProvider()
if provider == nil {
c.ResponseError("No storage provider is found")
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
fileUrl, err := object.UploadFile(provider, fullFilePath, fileBuffer)
if err != nil {
c.ResponseError(err.Error())
return
scope, aud := c.GetSessionOidc()
iss := beego.AppConfig.String("origin")
resp := Userinfo{
Sub: user.Id,
Iss: iss,
Aud: aud,
}
switch tag {
case "avatar":
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user)
case "termsOfUse":
applicationId := fmt.Sprintf("admin/%s", parent)
app := object.GetApplication(applicationId)
app.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, app)
if strings.Contains(scope, "profile") {
resp.Name = user.Name
resp.DisplayName = user.DisplayName
resp.Avatar = user.Avatar
}
c.ResponseOk(fileUrl)
if strings.Contains(scope, "email") {
resp.Email = user.Email
}
if strings.Contains(scope, "address") {
resp.Address = user.Location
}
if strings.Contains(scope, "phone") {
resp.Phone = user.Phone
}
c.Data["json"] = resp
c.ServeJSON()
}
// GetHumanCheck ...
// @Tag Login API
// @Title GetHumancheck
// @router /api/get-human-check [get]
func (c *ApiController) GetHumanCheck() {
c.Data["json"] = HumanCheck{Type: "none"}

View File

@ -16,42 +16,72 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetApplications
// @Title GetApplications
// @Tag Application API
// @Description get all applications
// @Param owner query string true "The owner of applications."
// @Success 200 {array} object.Application The Response object
// @router /get-applications [get]
func (c *ApiController) GetApplications() {
userId := c.GetSessionUsername()
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
c.Data["json"] = object.GetApplications(owner)
c.ServeJSON()
if limit == "" || page == "" {
var applications []*object.Application
if organization == "" {
applications = object.GetApplications(owner)
} else {
applications = object.GetApplicationsByOrganizationName(owner, organization)
}
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
applications := object.GetMaskedApplications(object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
c.ResponseOk(applications, paginator.Nums())
}
}
// GetApplication
// @Title GetApplication
// @Tag Application API
// @Description get the detail of an application
// @Param id query string true "The id of the application."
// @Success 200 {object} object.Application The Response object
// @router /get-application [get]
func (c *ApiController) GetApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
c.Data["json"] = object.GetApplication(id)
c.Data["json"] = object.GetMaskedApplication(object.GetApplication(id), userId)
c.ServeJSON()
}
// GetUserApplication
// @Title GetUserApplication
// @Tag Application API
// @Description get the detail of the user's application
// @Param id query string true "The id of the user"
// @Success 200 {object} object.Application The Response object
// @router /get-user-application [get]
func (c *ApiController) GetUserApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
user := object.GetUser(id)
if user == nil {
@ -59,12 +89,13 @@ func (c *ApiController) GetUserApplication() {
return
}
c.Data["json"] = object.GetApplicationByUser(user)
c.Data["json"] = object.GetMaskedApplication(object.GetApplicationByUser(user), userId)
c.ServeJSON()
}
// UpdateApplication
// @Title UpdateApplication
// @Tag Application API
// @Description update an application
// @Param id query string true "The id of the application"
// @Param body body object.Application true "The details of the application"
@ -85,6 +116,7 @@ func (c *ApiController) UpdateApplication() {
// AddApplication
// @Title AddApplication
// @Tag Application API
// @Description add an application
// @Param body body object.Application true "The details of the application"
// @Success 200 {object} controllers.Response The Response object
@ -102,6 +134,7 @@ func (c *ApiController) AddApplication() {
// DeleteApplication
// @Title DeleteApplication
// @Tag Application API
// @Description delete an application
// @Param body body object.Application true "The details of the application"
// @Success 200 {object} controllers.Response The Response object

View File

@ -15,16 +15,19 @@
package controllers
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/idp"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
)
func codeToResponse(code *object.Code) *Response {
@ -48,11 +51,18 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
redirectUri := c.Input().Get("redirectUri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
nonce := c.Input().Get("nonce")
challengeMethod := c.Input().Get("code_challenge_method")
codeChallenge := c.Input().Get("code_challenge")
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state)
if challengeMethod != "S256" && challengeMethod != "null" {
c.ResponseError("Challenge method should be S256")
return
}
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
resp = codeToResponse(code)
if application.HasPromptPage() {
if application.EnableSigninSession || application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
@ -74,6 +84,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// GetApplicationLogin ...
// @Title GetApplicationLogin
// @Tag Login API
// @Description get application login
// @Param clientId query string true "client id"
// @Param responseType query string true "response type"
@ -99,14 +110,15 @@ func (c *ApiController) GetApplicationLogin() {
func setHttpClient(idProvider idp.IdProvider, providerType string) {
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" {
idProvider.SetHttpClient(proxyHttpClient)
idProvider.SetHttpClient(proxy.ProxyHttpClient)
} else {
idProvider.SetHttpClient(defaultHttpClient)
idProvider.SetHttpClient(proxy.DefaultHttpClient)
}
}
// Login ...
// @Title Login
// @Tag Login API
// @Description login
// @Param oAuthParams query string true "oAuth parameters"
// @Param body body RequestForm true "Login information"
@ -135,57 +147,39 @@ func (c *ApiController) Login() {
if form.Password == "" {
var verificationCodeType string
var checkResult string
// check result through Email or Phone
if strings.Contains(form.Email, "@") {
if strings.Contains(form.Username, "@") {
verificationCodeType = "email"
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
responseText := fmt.Sprintf("Email%s", checkResult)
c.ResponseError(responseText)
return
}
checkResult = object.CheckVerificationCode(form.Username, form.Code)
} else {
verificationCodeType = "phone"
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Email)
checkResult := object.CheckVerificationCode(checkPhone, form.EmailCode)
if len(checkResult) != 0 {
responseText := fmt.Sprintf("Phone%s", checkResult)
if len(form.PhonePrefix) == 0 {
responseText := fmt.Sprintf("%s%s", verificationCodeType, "No phone prefix")
c.ResponseError(responseText)
return
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
}
// get user
var userId string
if form.Username == "" {
userId, _ = c.RequireSignedIn()
} else {
userId = fmt.Sprintf("%s/%s", form.Organization, form.Username)
}
user = object.GetUser(userId)
if user == nil {
c.ResponseError("No such user.")
if len(checkResult) != 0 {
responseText := fmt.Sprintf("%s%s", verificationCodeType, checkResult)
c.ResponseError(responseText)
return
}
// disable the verification code
switch verificationCodeType {
case "email":
if user.Email != form.Email {
c.ResponseError("wrong email!")
}
object.DisableVerificationCode(form.Email)
case "phone":
if user.Phone != form.Email {
c.ResponseError("wrong phone!")
}
object.DisableVerificationCode(form.Email)
object.DisableVerificationCode(form.Username)
user = object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError("No such user.")
return
}
} else {
password := form.Password
user, msg = object.CheckUserLogin(form.Organization, form.Username, password)
user, msg = object.CheckUserPassword(form.Organization, form.Username, password)
}
if msg != "" {
@ -194,11 +188,10 @@ func (c *ApiController) Login() {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
record.User = user.Name
go object.AddRecord(record)
}
} else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
@ -210,47 +203,71 @@ func (c *ApiController) Login() {
return
}
idProvider := idp.GetIdProvider(provider.Type, provider.ClientId, provider.ClientSecret, form.RedirectUri)
if idProvider == nil {
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
return
}
userInfo := &idp.UserInfo{}
if provider.Category == "SAML" {
// SAML
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider.Type)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if provider.Category == "OAuth" {
// OAuth
setHttpClient(idProvider, provider.Type)
clientId := provider.ClientId
clientSecret := provider.ClientSecret
if provider.Type == "WeChat" && strings.Contains(c.Ctx.Request.UserAgent(), "MicroMessenger") {
clientId = provider.ClientId2
clientSecret = provider.ClientSecret2
}
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
return
}
idProvider := idp.GetIdProvider(provider.Type, clientId, clientSecret, form.RedirectUri)
if idProvider == nil {
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
return
}
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
token, err := idProvider.GetToken(form.Code)
if err != nil {
c.ResponseError(err.Error())
return
}
setHttpClient(idProvider, provider.Type)
if !token.Valid() {
c.ResponseError("Invalid token")
return
}
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
return
}
userInfo, err := idProvider.GetUserInfo(token)
if err != nil {
c.ResponseError(fmt.Sprintf("Failed to login in: %s", err.Error()))
return
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
token, err := idProvider.GetToken(form.Code)
if err != nil {
c.ResponseError(err.Error())
return
}
if !token.Valid() {
c.ResponseError("Invalid token")
return
}
userInfo, err = idProvider.GetUserInfo(token)
if err != nil {
c.ResponseError(fmt.Sprintf("Failed to login in: %s", err.Error()))
return
}
}
if form.Method == "signup" {
user := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if user == nil {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if user == nil {
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
user := &object.User{}
if provider.Category == "SAML" {
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
} else if provider.Category == "OAuth" {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if user == nil {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if user == nil {
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
}
}
if user != nil {
if user != nil && user.IsDeleted == false {
// Sign in via OAuth (want to sign up but already have account)
if user.IsForbidden {
@ -259,12 +276,11 @@ func (c *ApiController) Login() {
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
} else {
record.User = user.Name
go object.AddRecord(record)
} else if provider.Category == "OAuth" {
// Sign up via OAuth
if !application.EnableSignUp {
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", provider.Type, userInfo.Username, userInfo.DisplayName))
@ -276,15 +292,9 @@ func (c *ApiController) Login() {
return
}
var score int
score, err = strconv.Atoi(beego.AppConfig.String("initScore"))
if err != nil {
panic(err)
}
properties := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
user := &object.User{
user = &object.User{
Owner: application.Organization,
Name: userInfo.Username,
CreatedTime: util.GetCurrentTime(),
@ -292,28 +302,35 @@ func (c *ApiController) Login() {
Type: "normal-user",
DisplayName: userInfo.DisplayName,
Avatar: userInfo.AvatarUrl,
Address: []string{},
Email: userInfo.Email,
Score: score,
Score: getInitScore(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
SignupApplication: application.Name,
Properties: properties,
}
object.AddUser(user)
// sync info from 3rd-party if possible
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
affected := object.AddUser(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))
return
}
object.LinkUserAccount(user, provider.Type, userInfo.Id)
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
record.User = user.Name
go object.AddRecord(record)
} else if provider.Category == "SAML" {
resp = &Response{Status: "error", Msg: "The account does not exist"}
}
//resp = &Response{Status: "ok", Msg: "", Data: res}
} else { // form.Method != "signup"
@ -345,10 +362,42 @@ func (c *ApiController) Login() {
}
}
} else {
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
return
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
user := c.getCurrentUser()
resp = c.HandleLoggedIn(application, user, &form)
} else {
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
return
}
}
c.Data["json"] = resp
c.ServeJSON()
}
func (c *ApiController) GetSamlLogin() {
providerId := c.Input().Get("id")
relayState := c.Input().Get("relayState")
authURL, method, err := object.GenerateSamlLoginUrl(providerId, relayState)
if err != nil {
c.ResponseError(err.Error())
}
c.ResponseOk(authURL, method)
}
func (c *ApiController) HandleSamlLogin() {
relayState := c.Input().Get("RelayState")
samlResponse := c.Input().Get("SAMLResponse")
decode, err := base64.StdEncoding.DecodeString(relayState)
if err != nil {
c.ResponseError(err.Error())
}
slice := strings.Split(string(decode), "&")
relayState = url.QueryEscape(relayState)
samlResponse = url.QueryEscape(samlResponse)
targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
slice[4], relayState, samlResponse)
c.Redirect(targetUrl, 303)
}

View File

@ -15,20 +15,43 @@
package controllers
import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// controller for handlers under /api uri
type ApiController struct {
beego.Controller
}
// controller for handlers directly under / (root)
type RootController struct {
ApiController
}
type SessionData struct {
ExpireTime int64
}
func (c *ApiController) IsGlobalAdmin() bool {
username := c.GetSessionUsername()
if strings.HasPrefix(username, "app/") {
// e.g., "app/app-casnode"
return true
}
user := object.GetUser(username)
if user == nil {
return false
}
return user.Owner == "built-in" || user.IsGlobalAdmin
}
// GetSessionUsername ...
func (c *ApiController) GetSessionUsername() string {
// check if user session expired
@ -49,6 +72,28 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData()
if sessionData != nil &&
sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("")
c.SetSessionData(nil)
return "", ""
}
scopeValue := c.GetSession("scope")
audValue := c.GetSession("aud")
var scope, aud string
var ok bool
if scope, ok = scopeValue.(string); !ok {
scope = ""
}
if aud, ok = audValue.(string); !ok {
aud = ""
}
return scope, aud
}
// SetSessionUsername ...
func (c *ApiController) SetSessionUsername(user string) {
c.SetSession("username", user)

116
controllers/cert.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetCerts
// @Title GetCerts
// @Tag Cert API
// @Description get certs
// @Param owner query string true "The owner of certs"
// @Success 200 {array} object.Cert The Response object
// @router /get-certs [get]
func (c *ApiController) GetCerts() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedCerts(object.GetCerts(owner))
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCertCount(owner, field, value)))
certs := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
c.ResponseOk(certs, paginator.Nums())
}
}
// @Title GetCert
// @Tag Cert API
// @Description get cert
// @Param id query string true "The id of the cert"
// @Success 200 {object} object.Cert The Response object
// @router /get-cert [get]
func (c *ApiController) GetCert() {
id := c.Input().Get("id")
c.Data["json"] = object.GetMaskedCert(object.GetCert(id))
c.ServeJSON()
}
// @Title UpdateCert
// @Tag Cert API
// @Description update cert
// @Param id query string true "The id of the cert"
// @Param body body object.Cert true "The details of the cert"
// @Success 200 {object} controllers.Response The Response object
// @router /update-cert [post]
func (c *ApiController) UpdateCert() {
id := c.Input().Get("id")
var cert object.Cert
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateCert(id, &cert))
c.ServeJSON()
}
// @Title AddCert
// @Tag Cert API
// @Description add cert
// @Param body body object.Cert true "The details of the cert"
// @Success 200 {object} controllers.Response The Response object
// @router /add-cert [post]
func (c *ApiController) AddCert() {
var cert object.Cert
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddCert(&cert))
c.ServeJSON()
}
// @Title DeleteCert
// @Tag Cert API
// @Description delete cert
// @Param body body object.Cert true "The details of the cert"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-cert [post]
func (c *ApiController) DeleteCert() {
var cert object.Cert
err := json.Unmarshal(c.Ctx.Input.RequestBody, &cert)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteCert(&cert))
c.ServeJSON()
}

View File

@ -16,8 +16,9 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
type LdapServer struct {
@ -43,6 +44,9 @@ type LdapSyncResp struct {
Failed []object.LdapRespUser `json:"failed"`
}
// @Tag Account API
// @Title GetLdapser
// @router /get-ldap-user [post]
func (c *ApiController) GetLdapUser() {
ldapServer := LdapServer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
@ -96,6 +100,9 @@ func (c *ApiController) GetLdapUser() {
c.ServeJSON()
}
// @Tag Account API
// @Title GetLdaps
// @router /get-ldaps [post]
func (c *ApiController) GetLdaps() {
owner := c.Input().Get("owner")
@ -103,6 +110,9 @@ func (c *ApiController) GetLdaps() {
c.ServeJSON()
}
// @Tag Account API
// @Title GetLdap
// @router /get-ldap [post]
func (c *ApiController) GetLdap() {
id := c.Input().Get("id")
@ -115,6 +125,9 @@ func (c *ApiController) GetLdap() {
c.ServeJSON()
}
// @Tag Account API
// @Title AddLdap
// @router /add-ldap [post]
func (c *ApiController) AddLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
@ -138,11 +151,17 @@ func (c *ApiController) AddLdap() {
if affected {
resp.Data2 = ldap
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
}
c.Data["json"] = resp
c.ServeJSON()
}
// @Tag Account API
// @Title UpdateLdap
// @router /update-ldap [post]
func (c *ApiController) UpdateLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
@ -156,11 +175,17 @@ func (c *ApiController) UpdateLdap() {
if affected {
resp.Data2 = ldap
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
}
c.Data["json"] = resp
c.ServeJSON()
}
// @Tag Account API
// @Title DeleteLdap
// @router /delete-ldap [post]
func (c *ApiController) DeleteLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
@ -168,10 +193,14 @@ func (c *ApiController) DeleteLdap() {
panic(err)
}
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
c.ServeJSON()
}
// @Tag Account API
// @Title SyncLdapUsers
// @router /sync-ldap-users [post]
func (c *ApiController) SyncLdapUsers() {
owner := c.Input().Get("owner")
ldapId := c.Input().Get("ldapId")
@ -191,6 +220,9 @@ func (c *ApiController) SyncLdapUsers() {
c.ServeJSON()
}
// @Tag Account API
// @Title CheckLdapUserExist
// @router /check-ldap-users-exist [post]
func (c *ApiController) CheckLdapUsersExist() {
owner := c.Input().Get("owner")
var uuids []string

View File

@ -17,7 +17,7 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/casdoor/casdoor/object"
)
type LinkForm struct {
@ -25,6 +25,8 @@ type LinkForm struct {
}
// Unlink ...
// @router /unlink [post]
// @Tag Login API
func (c *ApiController) Unlink() {
userId, ok := c.RequireSignedIn()
if !ok {

View File

@ -0,0 +1,38 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import "github.com/casdoor/casdoor/object"
// @Title GetOidcDiscovery
// @Tag OIDC API
// @router /.well-known/openid-configuration [get]
func (c *RootController) GetOidcDiscovery() {
c.Data["json"] = object.GetOidcDiscovery()
c.ServeJSON()
}
// @Title GetOidcCert
// @Tag OIDC API
// @router /api/certs [get]
func (c *RootController) GetOidcCert() {
jwks, err := object.GetJsonWebKeySet()
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = jwks
c.ServeJSON()
}

View File

@ -17,24 +17,40 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetOrganizations ...
// @Title GetOrganizations
// @Tag Organization API
// @Description get organizations
// @Param owner query string true "owner"
// @Success 200 {array} object.Organization The Response object
// @router /get-organizations [get]
func (c *ApiController) GetOrganizations() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetOrganizations(owner)
c.ServeJSON()
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedOrganizations(object.GetOrganizations(owner))
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner, field, value)))
organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
c.ResponseOk(organizations, paginator.Nums())
}
}
// GetOrganization ...
// @Title GetOrganization
// @Tag Organization API
// @Description get organization
// @Param id query string true "organization id"
// @Success 200 {object} object.Organization The Response object
@ -42,12 +58,13 @@ func (c *ApiController) GetOrganizations() {
func (c *ApiController) GetOrganization() {
id := c.Input().Get("id")
c.Data["json"] = object.GetOrganization(id)
c.Data["json"] = object.GetMaskedOrganization(object.GetOrganization(id))
c.ServeJSON()
}
// UpdateOrganization ...
// @Title UpdateOrganization
// @Tag Organization API
// @Description update organization
// @Param id query string true "The id of the organization"
// @Param body body object.Organization true "The details of the organization"
@ -68,6 +85,7 @@ func (c *ApiController) UpdateOrganization() {
// AddOrganization ...
// @Title AddOrganization
// @Tag Organization API
// @Description add organization
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object
@ -85,6 +103,7 @@ func (c *ApiController) AddOrganization() {
// DeleteOrganization ...
// @Title DeleteOrganization
// @Tag Organization API
// @Description delete organization
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object

116
controllers/permission.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetPermissions
// @Title GetPermissions
// @Tag Permission API
// @Description get permissions
// @Param owner query string true "The owner of permissions"
// @Success 200 {array} object.Permission The Response object
// @router /get-permissions [get]
func (c *ApiController) GetPermissions() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetPermissions(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPermissionCount(owner, field, value)))
permissions := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(permissions, paginator.Nums())
}
}
// @Title GetPermission
// @Tag Permission API
// @Description get permission
// @Param id query string true "The id of the permission"
// @Success 200 {object} object.Permission The Response object
// @router /get-permission [get]
func (c *ApiController) GetPermission() {
id := c.Input().Get("id")
c.Data["json"] = object.GetPermission(id)
c.ServeJSON()
}
// @Title UpdatePermission
// @Tag Permission API
// @Description update permission
// @Param id query string true "The id of the permission"
// @Param body body object.Permission true "The details of the permission"
// @Success 200 {object} controllers.Response The Response object
// @router /update-permission [post]
func (c *ApiController) UpdatePermission() {
id := c.Input().Get("id")
var permission object.Permission
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdatePermission(id, &permission))
c.ServeJSON()
}
// @Title AddPermission
// @Tag Permission API
// @Description add permission
// @Param body body object.Permission true "The details of the permission"
// @Success 200 {object} controllers.Response The Response object
// @router /add-permission [post]
func (c *ApiController) AddPermission() {
var permission object.Permission
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddPermission(&permission))
c.ServeJSON()
}
// @Title DeletePermission
// @Tag Permission API
// @Description delete permission
// @Param body body object.Permission true "The details of the permission"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-permission [post]
func (c *ApiController) DeletePermission() {
var permission object.Permission
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permission)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeletePermission(&permission))
c.ServeJSON()
}

View File

@ -16,24 +16,39 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetProviders
// @Title GetProviders
// @Tag Provider API
// @Description get providers
// @Param owner query string true "The owner of providers"
// @Success 200 {array} object.Provider The Response object
// @router /get-providers [get]
func (c *ApiController) GetProviders() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetProviders(owner)
c.ServeJSON()
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner))
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
c.ResponseOk(providers, paginator.Nums())
}
}
// @Title GetProvider
// @Tag Provider API
// @Description get provider
// @Param id query string true "The id of the provider"
// @Success 200 {object} object.Provider The Response object
@ -41,11 +56,12 @@ func (c *ApiController) GetProviders() {
func (c *ApiController) GetProvider() {
id := c.Input().Get("id")
c.Data["json"] = object.GetProvider(id)
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
c.ServeJSON()
}
// @Title UpdateProvider
// @Tag Provider API
// @Description update provider
// @Param id query string true "The id of the provider"
// @Param body body object.Provider true "The details of the provider"
@ -65,6 +81,7 @@ func (c *ApiController) UpdateProvider() {
}
// @Title AddProvider
// @Tag Provider API
// @Description add provider
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object
@ -81,6 +98,7 @@ func (c *ApiController) AddProvider() {
}
// @Title DeleteProvider
// @Tag Provider API
// @Description delete provider
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object

View File

@ -15,34 +15,53 @@
package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetRecords
// @Title GetRecords
// @Tag Record API
// @Description get all records
// @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the page"
// @Success 200 {array} object.Records The Response object
// @router /get-records [get]
func (c *ApiController) GetRecords() {
c.Data["json"] = object.GetRecords()
c.ServeJSON()
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetRecords()
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value)))
records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(records, paginator.Nums())
}
}
// GetRecordsByFilter
// @Tag Record API
// @Title GetRecordsByFilter
// @Description get records by filter
// @Param body body object.Records true "filter Record message"
// @Success 200 {array} object.Records The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
var record object.Records
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
body := string(c.Ctx.Input.RequestBody)
record := &object.Record{}
err := util.JsonToStruct(body, record)
if err != nil {
panic(err)
}
c.Data["json"] = object.GetRecordsByField(&record)
c.Data["json"] = object.GetRecordsByField(record)
c.ServeJSON()
}

214
controllers/resource.go Normal file
View File

@ -0,0 +1,214 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime"
"path/filepath"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// @router /get-resources [get]
// @Tag Resource API
// @Title GetResources
func (c *ApiController) GetResources() {
owner := c.Input().Get("owner")
user := c.Input().Get("user")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetResources(owner, user)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user, field, value)))
resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(resources, paginator.Nums())
}
}
// @Tag Resource API
// @Title GetResource
// @router /get-resource [get]
func (c *ApiController) GetResource() {
id := c.Input().Get("id")
c.Data["json"] = object.GetResource(id)
c.ServeJSON()
}
// @Tag Resource API
// @Title UpdateResource
// @router /update-resource [post]
func (c *ApiController) UpdateResource() {
id := c.Input().Get("id")
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateResource(id, &resource))
c.ServeJSON()
}
// @Tag Resource API
// @Title AddResource
// @router /add-resource [post]
func (c *ApiController) AddResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddResource(&resource))
c.ServeJSON()
}
// @Tag Resource API
// @Title DeleteResource
// @router /delete-resource [post]
func (c *ApiController) DeleteResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
provider, _, ok := c.GetProviderFromContext("Storage")
if !ok {
return
}
err = object.DeleteFile(provider, resource.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteResource(&resource))
c.ServeJSON()
}
// @Tag Resource API
// @Title UploadResource
// @router /upload-resource [post]
func (c *ApiController) UploadResource() {
owner := c.Input().Get("owner")
username := c.Input().Get("user")
application := c.Input().Get("application")
tag := c.Input().Get("tag")
parent := c.Input().Get("parent")
fullFilePath := c.Input().Get("fullFilePath")
createdTime := c.Input().Get("createdTime")
description := c.Input().Get("description")
file, header, err := c.GetFile("file")
if err != nil {
c.ResponseError(err.Error())
return
}
defer file.Close()
if username == "" || fullFilePath == "" {
c.ResponseError(fmt.Sprintf("username or fullFilePath is empty: username = %s, fullFilePath = %s", username, fullFilePath))
return
}
filename := filepath.Base(fullFilePath)
fileBuffer := bytes.NewBuffer(nil)
if _, err = io.Copy(fileBuffer, file); err != nil {
c.ResponseError(err.Error())
return
}
provider, user, ok := c.GetProviderFromContext("Storage")
if !ok {
return
}
fileType := "unknown"
contentType := header.Header.Get("Content-Type")
fileType, _ = util.GetOwnerAndNameFromId(contentType)
if fileType != "image" && fileType != "video" {
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
}
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer)
if err != nil {
c.ResponseError(err.Error())
return
}
if createdTime == "" {
createdTime = util.GetCurrentTime()
}
fileFormat := filepath.Ext(fullFilePath)
fileSize := int(header.Size)
resource := &object.Resource{
Owner: owner,
Name: objectKey,
CreatedTime: createdTime,
User: username,
Provider: provider.Name,
Application: application,
Tag: tag,
Parent: parent,
FileName: filename,
FileType: fileType,
FileFormat: fileFormat,
FileSize: fileSize,
Url: fileUrl,
Description: description,
}
object.AddOrUpdateResource(resource)
switch tag {
case "avatar":
if user == nil {
user = object.GetUserNoCheck(username)
if user == nil {
c.ResponseError("user is nil for tag: \"avatar\"")
return
}
}
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
case "termsOfUse":
applicationId := fmt.Sprintf("admin/%s", parent)
app := object.GetApplication(applicationId)
app.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, app)
}
c.ResponseOk(fileUrl, objectKey)
}

116
controllers/role.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetRoles
// @Title GetRoles
// @Tag Role API
// @Description get roles
// @Param owner query string true "The owner of roles"
// @Success 200 {array} object.Role The Response object
// @router /get-roles [get]
func (c *ApiController) GetRoles() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetRoles(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRoleCount(owner, field, value)))
roles := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(roles, paginator.Nums())
}
}
// @Title GetRole
// @Tag Role API
// @Description get role
// @Param id query string true "The id of the role"
// @Success 200 {object} object.Role The Response object
// @router /get-role [get]
func (c *ApiController) GetRole() {
id := c.Input().Get("id")
c.Data["json"] = object.GetRole(id)
c.ServeJSON()
}
// @Title UpdateRole
// @Tag Role API
// @Description update role
// @Param id query string true "The id of the role"
// @Param body body object.Role true "The details of the role"
// @Success 200 {object} controllers.Response The Response object
// @router /update-role [post]
func (c *ApiController) UpdateRole() {
id := c.Input().Get("id")
var role object.Role
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateRole(id, &role))
c.ServeJSON()
}
// @Title AddRole
// @Tag Role API
// @Description add role
// @Param body body object.Role true "The details of the role"
// @Success 200 {object} controllers.Response The Response object
// @router /add-role [post]
func (c *ApiController) AddRole() {
var role object.Role
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddRole(&role))
c.ServeJSON()
}
// @Title DeleteRole
// @Tag Role API
// @Description delete role
// @Param body body object.Role true "The details of the role"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-role [post]
func (c *ApiController) DeleteRole() {
var role object.Role
err := json.Unmarshal(c.Ctx.Input.RequestBody, &role)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteRole(&role))
c.ServeJSON()
}

View File

@ -19,142 +19,113 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
sender "github.com/casdoor/go-sms-sender"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// SendEmail
// @Title SendEmail
// @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param body body emailForm true "Details of the email request"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param body body emailForm true "Details of the email request"
// @Success 200 {object} Response object
// @router /api/send-email [post]
func (c *ApiController) SendEmail() {
clientId := c.Input().Get("clientId")
clientSecret := c.Input().Get("clientSecret")
app := object.GetApplicationByClientIdAndSecret(clientId, clientSecret)
if app == nil {
c.ResponseError("Invalid clientId or clientSecret.")
return
}
provider := app.GetEmailProvider()
if provider == nil {
c.ResponseError("No Email provider is found")
provider, _, ok := c.GetProviderFromContext("Email")
if !ok {
return
}
var emailForm struct {
Title string `json:"title"`
Content string `json:"content"`
Receivers []string `json:"receivers"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
if err != nil {
c.ResponseError("Request body error.")
c.ResponseError(err.Error())
return
}
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
c.ResponseError("Missing parameters.")
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
return
}
var invalidEmails []string
invalidReceivers := []string{}
for _, receiver := range emailForm.Receivers {
if !util.IsEmailValid(receiver) {
invalidEmails = append(invalidEmails, receiver)
}
}
if len(invalidEmails) != 0 {
c.ResponseError("Invalid Email addresses", invalidEmails)
return
}
ok := 0
for _, receiver := range emailForm.Receivers {
if msg := object.SendEmail(
provider,
emailForm.Title,
emailForm.Content,
receiver,
emailForm.Sender); len(msg) == 0 {
ok++
}
}
c.Data["json"] = Response{Status: "ok", Data: ok}
c.ServeJSON()
}
// SendSms
// @Title SendSms
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param body body smsForm true "Details of the sms request"
// @Success 200 {object} Response object
// @router /api/send-sms [post]
func (c *ApiController) SendSms() {
clientId := c.Input().Get("clientId")
clientSecret := c.Input().Get("clientSecret")
app := object.GetApplicationByClientIdAndSecret(clientId, clientSecret)
if app == nil {
c.ResponseError("Invalid clientId or clientSecret.")
return
}
provider := app.GetSmsProvider()
if provider == nil {
c.ResponseError("No SMS provider is found")
return
}
client := sender.NewSmsClient(
provider.Type,
provider.ClientId,
provider.ClientSecret,
provider.SignName,
provider.RegionId,
provider.TemplateCode,
provider.AppId,
)
if client == nil {
c.ResponseError("Invalid provider info.")
return
}
var smsForm struct {
Receivers []string `json:"receivers"`
Parameters map[string]string `json:"parameters"`
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil {
c.ResponseError("Request body error.")
return
}
var invalidReceivers []string
for _, receiver := range smsForm.Receivers {
if !util.IsPhoneCnValid(receiver) {
invalidReceivers = append(invalidReceivers, receiver)
}
}
if len(invalidReceivers) != 0 {
c.ResponseError("Invalid phone numbers", invalidReceivers)
c.ResponseError(fmt.Sprintf("Invalid Email receivers: %s", invalidReceivers))
return
}
client.SendMessage(smsForm.Parameters, smsForm.Receivers...)
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.ResponseOk()
}
// SendSms
// @Title SendSms
// @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param body body smsForm true "Details of the sms request"
// @Success 200 {object} Response object
// @router /api/send-sms [post]
func (c *ApiController) SendSms() {
provider, _, ok := c.GetProviderFromContext("SMS")
if !ok {
return
}
var smsForm struct {
Content string `json:"content"`
Receivers []string `json:"receivers"`
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil {
c.ResponseError(err.Error())
return
}
org := object.GetOrganization(smsForm.OrgId)
var invalidReceivers []string
for idx, receiver := range smsForm.Receivers {
if !util.IsPhoneCnValid(receiver) {
invalidReceivers = append(invalidReceivers, receiver)
} else {
smsForm.Receivers[idx] = fmt.Sprintf("+%s%s", org.PhonePrefix, receiver)
}
}
if len(invalidReceivers) != 0 {
c.ResponseError(fmt.Sprintf("Invalid phone receivers: %s", invalidReceivers))
return
}
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}

116
controllers/syncer.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetSyncers
// @Title GetSyncers
// @Tag Syncer API
// @Description get syncers
// @Param owner query string true "The owner of syncers"
// @Success 200 {array} object.Syncer The Response object
// @router /get-syncers [get]
func (c *ApiController) GetSyncers() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetSyncers(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value)))
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(syncers, paginator.Nums())
}
}
// @Title GetSyncer
// @Tag Syncer API
// @Description get syncer
// @Param id query string true "The id of the syncer"
// @Success 200 {object} object.Syncer The Response object
// @router /get-syncer [get]
func (c *ApiController) GetSyncer() {
id := c.Input().Get("id")
c.Data["json"] = object.GetSyncer(id)
c.ServeJSON()
}
// @Title UpdateSyncer
// @Tag Syncer API
// @Description update syncer
// @Param id query string true "The id of the syncer"
// @Param body body object.Syncer true "The details of the syncer"
// @Success 200 {object} controllers.Response The Response object
// @router /update-syncer [post]
func (c *ApiController) UpdateSyncer() {
id := c.Input().Get("id")
var syncer object.Syncer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateSyncer(id, &syncer))
c.ServeJSON()
}
// @Title AddSyncer
// @Tag Syncer API
// @Description add syncer
// @Param body body object.Syncer true "The details of the syncer"
// @Success 200 {object} controllers.Response The Response object
// @router /add-syncer [post]
func (c *ApiController) AddSyncer() {
var syncer object.Syncer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddSyncer(&syncer))
c.ServeJSON()
}
// @Title DeleteSyncer
// @Tag Syncer API
// @Description delete syncer
// @Param body body object.Syncer true "The details of the syncer"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-syncer [post]
func (c *ApiController) DeleteSyncer() {
var syncer object.Syncer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
c.ServeJSON()
}

View File

@ -17,24 +17,42 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetTokens
// @Title GetTokens
// @Tag Token API
// @Description get tokens
// @Param owner query string true "The owner of tokens"
// @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the page"
// @Success 200 {array} object.Token The Response object
// @router /get-tokens [get]
func (c *ApiController) GetTokens() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetTokens(owner)
c.ServeJSON()
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetTokens(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value)))
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(tokens, paginator.Nums())
}
}
// GetToken
// @Title GetToken
// @Tag Token API
// @Description get token
// @Param id query string true "The id of token"
// @Success 200 {object} object.Token The Response object
@ -48,6 +66,7 @@ func (c *ApiController) GetToken() {
// UpdateToken
// @Title UpdateToken
// @Tag Token API
// @Description update token
// @Param id query string true "The id of token"
// @Param body body object.Token true "Details of the token"
@ -68,6 +87,7 @@ func (c *ApiController) UpdateToken() {
// AddToken
// @Title AddToken
// @Tag Token API
// @Description add token
// @Param body body object.Token true "Details of the token"
// @Success 200 {object} controllers.Response The Response object
@ -84,6 +104,7 @@ func (c *ApiController) AddToken() {
}
// DeleteToken
// @Tag Token API
// @Title DeleteToken
// @Description delete token
// @Param body body object.Token true "Details of the token"
@ -100,13 +121,47 @@ func (c *ApiController) DeleteToken() {
c.ServeJSON()
}
// GetOAuthCode
// @Title GetOAuthCode
// @Tag Token API
// @Description get OAuth code
// @Param user_id query string true "The id of user"
// @Param client_id query string true "OAuth client id"
// @Param response_type query string true "OAuth response type"
// @Param redirect_uri query string true "OAuth redirect URI"
// @Param scope query string true "OAuth scope"
// @Param state query string true "OAuth state"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/code [post]
func (c *ApiController) GetOAuthCode() {
userId := c.Input().Get("user_id")
clientId := c.Input().Get("client_id")
responseType := c.Input().Get("response_type")
redirectUri := c.Input().Get("redirect_uri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
nonce := c.Input().Get("nonce")
challengeMethod := c.Input().Get("code_challenge_method")
codeChallenge := c.Input().Get("code_challenge")
if challengeMethod != "S256" && challengeMethod != "null" {
c.ResponseError("Challenge method should be S256")
return
}
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
c.ServeJSON()
}
// GetOAuthToken
// @Title GetOAuthToken
// @Description get oAuth token
// @Param grant_type query string true "oAuth grant type"
// @Param client_id query string true "oAuth client id"
// @Param client_secret query string true "oAuth client secret"
// @Param code query string true "oAuth code"
// @Tag Token API
// @Description get OAuth access token
// @Param grant_type query string true "OAuth grant type"
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string true "OAuth client secret"
// @Param code query string true "OAuth code"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
@ -114,7 +169,33 @@ func (c *ApiController) GetOAuthToken() {
clientId := c.Input().Get("client_id")
clientSecret := c.Input().Get("client_secret")
code := c.Input().Get("code")
verifier := c.Input().Get("code_verifier")
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code)
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier)
c.ServeJSON()
}
// RefreshToken
// @Title RefreshToken
// @Description refresh OAuth access token
// @Param grant_type query string true "OAuth grant type"
// @Param refresh_token query string true "OAuth refresh token"
// @Param scope query string true "OAuth scope"
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string true "OAuth client secret"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() {
grantType := c.Input().Get("grant_type")
refreshToken := c.Input().Get("refresh_token")
scope := c.Input().Get("scope")
clientId := c.Input().Get("client_id")
clientSecret := c.Input().Get("client_secret")
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret)
c.ServeJSON()
}

View File

@ -19,48 +19,87 @@ import (
"fmt"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/original"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetGlobalUsers
// @Title GetGlobalUsers
// @Tag User API
// @Description get global users
// @Success 200 {array} object.User The Response object
// @router /get-global-users [get]
func (c *ApiController) GetGlobalUsers() {
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
c.ServeJSON()
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(users, paginator.Nums())
}
}
// GetUsers
// @Title GetUsers
// @Tag User API
// @Description
// @Param owner query string true "The owner of users"
// @Success 200 {array} object.User The Response object
// @router /get-users [get]
func (c *ApiController) GetUsers() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
c.ServeJSON()
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(users, paginator.Nums())
}
}
// GetUser
// @Title GetUser
// @Tag User API
// @Description get user
// @Param id query string true "The id of the user"
// @Success 200 {object} object.User The Response object
// @router /get-user [get]
func (c *ApiController) GetUser() {
id := c.Input().Get("id")
owner := c.Input().Get("owner")
email := c.Input().Get("email")
c.Data["json"] = object.GetMaskedUser(object.GetUser(id))
var user *object.User
if email == "" {
user = object.GetUser(id)
} else {
user = object.GetUserByEmail(owner, email)
}
c.Data["json"] = object.GetMaskedUser(user)
c.ServeJSON()
}
// UpdateUser
// @Title UpdateUser
// @Tag User API
// @Description update user
// @Param id query string true "The id of the user"
// @Param body body object.User true "The details of the user"
@ -68,6 +107,7 @@ func (c *ApiController) GetUser() {
// @router /update-user [post]
func (c *ApiController) UpdateUser() {
id := c.Input().Get("id")
columnsStr := c.Input().Get("columns")
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
@ -80,10 +120,15 @@ func (c *ApiController) UpdateUser() {
return
}
affected := object.UpdateUser(id, &user)
columns := []string{}
if columnsStr != "" {
columns = strings.Split(columnsStr, ",")
}
isGlobalAdmin := c.IsGlobalAdmin()
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
if affected {
newUser := object.GetUser(user.GetId())
original.UpdateUserToOriginalDatabase(newUser)
object.UpdateUserToOriginalDatabase(&user)
}
c.Data["json"] = wrapActionResponse(affected)
@ -92,6 +137,7 @@ func (c *ApiController) UpdateUser() {
// AddUser
// @Title AddUser
// @Tag User API
// @Description add user
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
@ -109,6 +155,7 @@ func (c *ApiController) AddUser() {
// DeleteUser
// @Title DeleteUser
// @Tag User API
// @Description delete user
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
@ -126,6 +173,7 @@ func (c *ApiController) DeleteUser() {
// GetEmailAndPhone
// @Title GetEmailAndPhone
// @Tag User API
// @Description get email and phone by username
// @Param username formData string true "The username of the user"
// @Param organization formData string true "The organization of the user"
@ -160,6 +208,7 @@ func (c *ApiController) GetEmailAndPhone() {
// SetPassword
// @Title SetPassword
// @Tag Account API
// @Description set password
// @Param userOwner formData string true "The owner of the user"
// @Param userName formData string true "The name of the user"
@ -178,29 +227,31 @@ func (c *ApiController) SetPassword() {
c.ResponseError("Please login first.")
return
}
requestUser := object.GetUser(requestUserId)
if requestUser == nil {
c.ResponseError("Session outdated. Please login again.")
return
}
userId := fmt.Sprintf("%s/%s", userOwner, userName)
targetUser := object.GetUser(userId)
if targetUser == nil {
c.ResponseError("Invalid user id.")
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
hasPermission := false
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := object.GetUser(requestUserId)
if requestUser == nil {
c.ResponseError("Session outdated. Please login again.")
return
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
hasPermission = true
}
}
if !hasPermission {
c.ResponseError("You don't have the permission to do this.")
return
@ -224,10 +275,67 @@ func (c *ApiController) SetPassword() {
return
}
c.SetSessionUsername("")
targetUser.Password = newPassword
object.SetUserField(targetUser, "password", targetUser.Password)
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
}
// @Title CheckUserPassword
// @router /check-user-password [post]
// @Tag User API
func (c *ApiController) CheckUserPassword() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
}
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password)
if msg == "" {
c.ResponseOk()
} else {
c.ResponseError(msg)
}
}
// GetSortedUsers
// @Title GetSortedUsers
// @Tag User API
// @Description
// @Param owner query string true "The owner of users"
// @Param sorter query string true "The DB column name to sort by, e.g., created_time"
// @Param limit query string true "The count of users to return, e.g., 25"
// @Success 200 {array} object.User The Response object
// @router /get-sorted-users [get]
func (c *ApiController) GetSortedUsers() {
owner := c.Input().Get("owner")
sorter := c.Input().Get("sorter")
limit := util.ParseInt(c.Input().Get("limit"))
c.Data["json"] = object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
c.ServeJSON()
}
// GetUserCount
// @Title GetUserCount
// @Tag User API
// @Description
// @Param owner query string true "The owner of users"
// @Param isOnline query string true "The filter for query, 1 for online, 0 for offline, empty string for all users"
// @Success 200 {int} int The count of filtered users for an organization
// @router /get-user-count [get]
func (c *ApiController) GetUserCount() {
owner := c.Input().Get("owner")
isOnline := c.Input().Get("isOnline")
count := 0
if isOnline == "" {
count = object.GetUserCount(owner, "", "")
} else {
count = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
}
c.Data["json"] = count
c.ServeJSON()
}

View File

@ -0,0 +1,60 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"fmt"
"io"
"mime/multipart"
"os"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func saveFile(path string, file *multipart.File) {
f, err := os.Create(path)
if err != nil {
panic(err)
}
defer f.Close()
_, err = io.Copy(f, *file)
if err != nil {
panic(err)
}
}
func (c *ApiController) UploadUsers() {
userId := c.GetSessionUsername()
owner, user := util.GetOwnerAndNameFromId(userId)
file, header, err := c.Ctx.Request.FormFile("file")
if err != nil {
panic(err)
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
util.EnsureFileFolderExists(path)
saveFile(path, &file)
affected := object.UploadUsers(owner, fileId)
if affected {
c.ResponseOk()
} else {
c.ResponseError("Failed to import users")
}
}

View File

@ -15,45 +15,14 @@
package controllers
import (
"net/http"
"fmt"
"strconv"
"github.com/astaxie/beego"
"golang.org/x/net/proxy"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
var defaultHttpClient *http.Client
var proxyHttpClient *http.Client
func InitHttpClient() {
// not use proxy
defaultHttpClient = http.DefaultClient
// use proxy
httpProxy := beego.AppConfig.String("httpProxy")
if httpProxy == "" {
proxyHttpClient = &http.Client{}
return
}
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
if err != nil {
panic(err)
}
tr := &http.Transport{Dial: dialer.Dial}
proxyHttpClient = &http.Client{
Transport: tr,
}
//resp, err2 := proxyHttpClient.Get("https://google.com")
//if err2 != nil {
// panic(err2)
//}
//defer resp.Body.Close()
//println("Response status: %s", resp.Status)
}
// ResponseOk ...
func (c *ApiController) ResponseOk(data ...interface{}) {
resp := Response{Status: "ok"}
@ -91,3 +60,43 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
}
return userId, true
}
func getInitScore() int {
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
if err != nil {
panic(err)
}
return score
}
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
providerName := c.Input().Get("provider")
if providerName != "" {
provider := object.GetProvider(util.GetId(providerName))
if provider == nil {
c.ResponseError(fmt.Sprintf("The provider: %s is not found", providerName))
return nil, nil, false
}
return provider, nil, true
}
userId, ok := c.RequireSignedIn()
if !ok {
return nil, nil, false
}
application, user := object.GetApplicationByUserId(userId)
if application == nil {
c.ResponseError(fmt.Sprintf("No application is found for userId: \"%s\"", userId))
return nil, nil, false
}
provider := application.GetProviderByCategory(category)
if provider == nil {
c.ResponseError(fmt.Sprintf("No provider for category: \"%s\" is found for application: %s", category, application.Name))
return nil, nil, false
}
return provider, user, true
}

View File

@ -15,11 +15,12 @@
package controllers
import (
"errors"
"fmt"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) getCurrentUser() *object.User {
@ -34,6 +35,9 @@ func (c *ApiController) getCurrentUser() *object.User {
}
// SendVerificationCode ...
// @Title SendVerificationCode
// @Tag Verification API
// @router /send-verification-code [post]
func (c *ApiController) SendVerificationCode() {
destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest")
@ -41,6 +45,7 @@ func (c *ApiController) SendVerificationCode() {
checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey")
checkUser := c.Ctx.Request.Form.Get("checkUser")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
@ -63,7 +68,13 @@ func (c *ApiController) SendVerificationCode() {
organization := object.GetOrganization(orgId)
application := object.GetApplicationByOrganizationName(organization.Name)
msg := "Invalid dest type."
if checkUser == "true" && user == nil &&
object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("No such user.")
return
}
sendResp := errors.New("Invalid dest type.")
switch destType {
case "email":
if !util.IsEmailValid(dest) {
@ -72,7 +83,7 @@ func (c *ApiController) SendVerificationCode() {
}
provider := application.GetEmailProvider()
msg = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone":
if !util.IsPhoneCnValid(dest) {
c.ResponseError("Invalid phone number")
@ -86,19 +97,22 @@ func (c *ApiController) SendVerificationCode() {
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
provider := application.GetSmsProvider()
msg = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
}
status := "ok"
if msg != "" {
status = "error"
if sendResp != nil {
c.Data["json"] = Response{Status: "error", Msg: sendResp.Error()}
} else {
c.Data["json"] = Response{Status: "ok"}
}
c.Data["json"] = Response{Status: status, Msg: msg}
c.ServeJSON()
}
// ResetEmailOrPhone ...
// @Tag Account API
// @Title ResetEmailOrPhone
// @router /api/reset-email-or-phone [post]
func (c *ApiController) ResetEmailOrPhone() {
userId, ok := c.RequireSignedIn()
if !ok {

116
controllers/webhook.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetWebhooks
// @Title GetWebhooks
// @Tag Webhook API
// @Description get webhooks
// @Param owner query string true "The owner of webhooks"
// @Success 200 {array} object.Webhook The Response object
// @router /get-webhooks [get]
func (c *ApiController) GetWebhooks() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetWebhooks(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value)))
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(webhooks, paginator.Nums())
}
}
// @Title GetWebhook
// @Tag Webhook API
// @Description get webhook
// @Param id query string true "The id of the webhook"
// @Success 200 {object} object.Webhook The Response object
// @router /get-webhook [get]
func (c *ApiController) GetWebhook() {
id := c.Input().Get("id")
c.Data["json"] = object.GetWebhook(id)
c.ServeJSON()
}
// @Title UpdateWebhook
// @Tag Webhook API
// @Description update webhook
// @Param id query string true "The id of the webhook"
// @Param body body object.Webhook true "The details of the webhook"
// @Success 200 {object} controllers.Response The Response object
// @router /update-webhook [post]
func (c *ApiController) UpdateWebhook() {
id := c.Input().Get("id")
var webhook object.Webhook
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateWebhook(id, &webhook))
c.ServeJSON()
}
// @Title AddWebhook
// @Tag Webhook API
// @Description add webhook
// @Param body body object.Webhook true "The details of the webhook"
// @Success 200 {object} controllers.Response The Response object
// @router /add-webhook [post]
func (c *ApiController) AddWebhook() {
var webhook object.Webhook
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddWebhook(&webhook))
c.ServeJSON()
}
// @Title DeleteWebhook
// @Tag Webhook API
// @Description delete webhook
// @Param body body object.Webhook true "The details of the webhook"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-webhook [post]
func (c *ApiController) DeleteWebhook() {
var webhook object.Webhook
err := json.Unmarshal(c.Ctx.Input.RequestBody, &webhook)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteWebhook(&webhook))
c.ServeJSON()
}

23
cred/bcrypt.go Normal file
View File

@ -0,0 +1,23 @@
package cred
import "golang.org/x/crypto/bcrypt"
type BcryptCredManager struct{}
func NewBcryptCredManager() *BcryptCredManager {
cm := &BcryptCredManager{}
return cm
}
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return ""
}
return string(bytes)
}
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
return err == nil
}

33
cred/manager.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2021 The casbin 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 cred
type CredManager interface {
GetHashedPassword(password string, userSalt string, organizationSalt string) string
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool
}
func GetCredManager(passwordType string) CredManager {
if passwordType == "plain" {
return NewPlainCredManager()
} else if passwordType == "salt" {
return NewSha256SaltCredManager()
} else if passwordType == "md5-salt" {
return NewMd5UserSaltCredManager()
} else if passwordType == "bcrypt" {
return NewBcryptCredManager()
}
return nil
}

48
cred/md5-user-salt.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2021 The casbin 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 cred
import (
"crypto/md5"
"encoding/hex"
)
type Md5UserSaltCredManager struct{}
func getMd5(data []byte) []byte {
hash := md5.Sum(data)
return hash[:]
}
func getMd5HexDigest(s string) string {
b := getMd5([]byte(s))
res := hex.EncodeToString(b)
return res
}
func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
cm := &Sha256SaltCredManager{}
return cm
}
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
hash := getMd5HexDigest(password)
res := getMd5HexDigest(hash + userSalt)
return res
}
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
}

30
cred/plain.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2021 The casbin 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 cred
type PlainCredManager struct{}
func NewPlainCredManager() *PlainCredManager {
cm := &PlainCredManager{}
return cm
}
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
return password
}
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == plainPwd
}

48
cred/sha256-salt.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2021 The casbin 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 cred
import (
"crypto/sha256"
"encoding/hex"
)
type Sha256SaltCredManager struct{}
func getSha256(data []byte) []byte {
hash := sha256.Sum256(data)
return hash[:]
}
func getSha256HexDigest(s string) string {
b := getSha256([]byte(s))
res := hex.EncodeToString(b)
return res
}
func NewSha256SaltCredManager() *Sha256SaltCredManager {
cm := &Sha256SaltCredManager{}
return cm
}
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
hash := getSha256HexDigest(password)
res := getSha256HexDigest(hash + organizationSalt)
return res
}
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
}

View File

@ -12,23 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package routers
package cred
import (
"net/url"
"strings"
"fmt"
"testing"
)
func parseQuery(query string, key string) string {
queryMap, err := url.ParseQuery(query)
if err != nil {
panic(err)
}
return queryMap.Get(key)
}
func parseSlash(s string) (string, string) {
tokens := strings.Split(s, "/")
return tokens[0], tokens[1]
func TestGetSaltedPassword(t *testing.T) {
password := "123456"
salt := "123"
cm := NewSha256SaltCredManager()
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
}

View File

@ -1,5 +1,6 @@
version: '3.1'
services:
restart: always
casdoor:
build:
context: ./
@ -8,6 +9,8 @@ services:
- "8000:8000"
depends_on:
- db
environment:
RUNNING_IN_DOCKER: "true"
volumes:
- ./conf:/conf/
db:
@ -16,6 +19,6 @@ services:
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 123
MYSQL_ROOT_PASSWORD: 123456
volumes:
- /usr/local/docker/mysql:/var/lib/mysql

20
go.mod
View File

@ -1,6 +1,6 @@
module github.com/casbin/casdoor
module github.com/casdoor/casdoor
go 1.15
go 1.16
require (
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
@ -8,27 +8,33 @@ require (
github.com/aws/aws-sdk-go v1.37.30
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.3.1
github.com/casdoor/go-sms-sender v0.0.3
github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.0.5
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-sql-driver/mysql v1.5.0
github.com/golang-jwt/jwt/v4 v4.1.0
github.com/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect
github.com/mileusna/crontab v1.0.1
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.6.0
github.com/russellhaering/goxmldsig v1.1.1
github.com/satori/go.uuid v1.2.0 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
xorm.io/core v0.7.2
xorm.io/xorm v1.0.3
)

106
go.sum
View File

@ -13,6 +13,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.67.0 h1:YIkzmqUfVGiGPpT98L8sVvUIkDno6UlrDxw4NR6z5ak=
cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -62,6 +64,8 @@ github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -70,13 +74,13 @@ github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/casbin/casbin/v2 v2.25.5/go.mod h1:wUgota0cQbTXE6Vd+KWpg41726jFRi7upxio0sR+Xd0=
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.3.1 h1:RVGsM6KYFP9s4OQJXrP/gv56Wmt5P40mzvcyXgv5xeg=
github.com/casbin/xorm-adapter/v2 v2.3.1/go.mod h1:GZ+nlIdasVFunQ71SlvkL/HcQQBvFncphDf+2Yl167c=
github.com/casdoor/go-sms-sender v0.0.3 h1:17/dzAP/ZgSY4AORzcsR/48AKyBycQcHUGg00R9tnSI=
github.com/casdoor/go-sms-sender v0.0.3/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -89,6 +93,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -96,8 +101,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
@ -106,7 +109,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
@ -129,6 +131,10 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -170,8 +176,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -182,6 +189,7 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
@ -190,6 +198,14 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
@ -197,6 +213,7 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@ -204,6 +221,9 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
@ -218,30 +238,41 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 h1:JibQrkJapVsb0pweJ5T14jZuuYZZTjll0PZBw4XfSCI=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mileusna/crontab v1.0.1 h1:YrDLc7l3xOiznmXq2FtAgg+1YQ3yC6pfFVPe+ywXNtg=
github.com/mileusna/crontab v1.0.1/go.mod h1:dbns64w/u3tUnGZGf8pAa76ZqOfeBX4olW4U1ZwExmc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -253,8 +284,10 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -278,7 +311,17 @@ github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbO
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russellhaering/gosaml2 v0.6.0 h1:OED8FLgczXxXAPlKhnJHQfmEig52tDX2qeXdPtZRIKc=
github.com/russellhaering/gosaml2 v0.6.0/go.mod h1:CtzxpPr4+bevsATaqR0rw3aqrNlX274b+3C6vFTLCk8=
github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM=
github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
@ -306,6 +349,8 @@ github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2K
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
@ -317,6 +362,7 @@ github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqI
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -331,8 +377,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -393,14 +440,17 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -443,17 +493,20 @@ golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -500,6 +553,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -521,6 +576,7 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -558,6 +614,8 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -570,6 +628,8 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -587,8 +647,9 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@ -598,6 +659,8 @@ gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -606,8 +669,9 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

97
i18n/generate.go Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2021 The casbin 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 i18n
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/casdoor/casdoor/util"
)
type I18nData map[string]map[string]string
var reI18n *regexp.Regexp
func init() {
reI18n, _ = regexp.Compile("i18next.t\\(\"(.*?)\"\\)")
}
func getAllI18nStrings(fileContent string) []string {
res := []string{}
matches := reI18n.FindAllStringSubmatch(fileContent, -1)
if matches == nil {
return res
}
for _, match := range matches {
res = append(res, match[1])
}
return res
}
func getAllJsFilePaths() []string {
path := "../web/src"
res := []string{}
err := filepath.Walk(path,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(info.Name(), ".js") {
return nil
}
res = append(res, path)
fmt.Println(path, info.Name())
return nil
})
if err != nil {
panic(err)
}
return res
}
func parseToData() *I18nData {
allWords := []string{}
paths := getAllJsFilePaths()
for _, path := range paths {
fileContent := util.ReadStringFromPath(path)
words := getAllI18nStrings(fileContent)
allWords = append(allWords, words...)
}
fmt.Printf("%v\n", allWords)
data := I18nData{}
for _, word := range allWords {
tokens := strings.Split(word, ":")
namespace := tokens[0]
key := tokens[1]
if _, ok := data[namespace]; !ok {
data[namespace] = map[string]string{}
}
data[namespace][key] = key
}
return &data
}

37
i18n/generate_test.go Normal file
View File

@ -0,0 +1,37 @@
// Copyright 2021 The casbin 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 i18n
import "testing"
func applyToOtherLanguage(dataEn *I18nData, lang string) {
dataOther := readI18nFile(lang)
println(dataOther)
applyData(dataEn, dataOther)
writeI18nFile(lang, dataEn)
}
func TestGenerateI18nStrings(t *testing.T) {
dataEn := parseToData()
writeI18nFile("en", dataEn)
applyToOtherLanguage(dataEn, "de")
applyToOtherLanguage(dataEn, "fr")
applyToOtherLanguage(dataEn, "ja")
applyToOtherLanguage(dataEn, "ko")
applyToOtherLanguage(dataEn, "ru")
applyToOtherLanguage(dataEn, "zh")
}

63
i18n/util.go Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2021 The casbin 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 i18n
import (
"fmt"
"strings"
"github.com/casdoor/casdoor/util"
)
func getI18nFilePath(language string) string {
return fmt.Sprintf("../web/src/locales/%s/data.json", language)
}
func readI18nFile(language string) *I18nData {
s := util.ReadStringFromPath(getI18nFilePath(language))
data := &I18nData{}
err := util.JsonToStruct(s, data)
if err != nil {
panic(err)
}
return data
}
func writeI18nFile(language string, data *I18nData) {
s := util.StructToJsonFormatted(data)
s = strings.ReplaceAll(s, "\\u0026", "&")
println(s)
util.WriteStringToPath(s, getI18nFilePath(language))
}
func applyData(data1 *I18nData, data2 *I18nData) {
for namespace, pairs2 := range *data2 {
if _, ok := (*data1)[namespace]; !ok {
continue
}
pairs1 := (*data1)[namespace]
for key, value := range pairs2 {
if _, ok := pairs1[key]; !ok {
continue
}
pairs1[key] = value
}
}
}

116
idp/baidu.go Normal file
View File

@ -0,0 +1,116 @@
// Copyright 2021 The casbin 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 (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"golang.org/x/oauth2"
)
type BaiduIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewBaiduIdProvider(clientId string, clientSecret string, redirectUrl string) *BaiduIdProvider {
idp := &BaiduIdProvider{}
config := idp.getConfig()
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
return idp
}
func (idp *BaiduIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *BaiduIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
AuthURL: "https://openapi.baidu.com/oauth/2.0/authorize",
TokenURL: "https://openapi.baidu.com/oauth/2.0/token",
}
var config = &oauth2.Config{
Scopes: []string{"email"},
Endpoint: endpoint,
}
return config
}
func (idp *BaiduIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
/*
{
"userid":"2097322476",
"username":"wl19871011",
"realname":"阳光",
"userdetail":"喜欢自由",
"birthday":"1987-01-01",
"marriage":"恋爱",
"sex":"男",
"blood":"O",
"constellation":"射手",
"figure":"小巧",
"education":"大学/专科",
"trade":"计算机/电子产品",
"job":"未知",
"birthday_year":"1987",
"birthday_month":"01",
"birthday_day":"01",
}
*/
type BaiduUserInfo struct {
OpenId string `json:"openid"`
Username string `json:"username"`
Portrait string `json:"portrait"`
}
func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
resp, err := idp.Client.Get(fmt.Sprintf("https://openapi.baidu.com/rest/2.0/passport/users/getInfo?access_token=%s", token.AccessToken))
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
baiduUser := BaiduUserInfo{}
if err = json.Unmarshal(data, &baiduUser); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: baiduUser.OpenId,
Username: baiduUser.Username,
DisplayName: baiduUser.Username,
AvatarUrl: fmt.Sprintf("https://himg.bdimg.com/sys/portrait/item/%s", baiduUser.Portrait),
}
return &userInfo, nil
}

View File

@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -132,7 +131,7 @@ func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
}(resp.Body)
body, _ := ioutil.ReadAll(resp.Body)
body, _ := io.ReadAll(resp.Body)
info := InfoResp{}
_ = json.Unmarshal(body, &info)
errCode := info.Errcode
@ -148,7 +147,7 @@ func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
return
}
}(resp.Body)
body, _ = ioutil.ReadAll(resp.Body)
body, _ = io.ReadAll(resp.Body)
tokenResp := DingTalkAccessToken{}
_ = json.Unmarshal(body, &tokenResp)
if tokenResp.ErrCode != 0 {

View File

@ -19,7 +19,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -93,7 +92,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := ioutil.ReadAll(resp.Body)
rbs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"context"
"encoding/json"
"io/ioutil"
"io"
"net/http"
"strconv"
"time"
@ -172,7 +172,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

230
idp/gitlab.go Normal file
View File

@ -0,0 +1,230 @@
// Copyright 2021 The casbin 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"
"strconv"
"time"
"golang.org/x/oauth2"
)
type GitlabIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewGitlabIdProvider(clientId string, clientSecret string, redirectUrl string) *GitlabIdProvider {
idp := &GitlabIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *GitlabIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *GitlabIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://gitlab.com/oauth/token",
}
var config = &oauth2.Config{
Scopes: []string{"read_user+profile"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type GitlabProviderToken struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
CreatedAt int `json:"created_at"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://docs.gitlab.com/ee/api/oauth2.html
func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := url.Values{}
params.Add("grant_type", "authorization_code")
params.Add("client_id", idp.Config.ClientID)
params.Add("client_secret", idp.Config.ClientSecret)
params.Add("code", code)
params.Add("redirect_uri", idp.Config.RedirectURL)
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
resp, err := idp.Client.Post(accessTokenUrl, "application/json;charset=UTF-8", nil)
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
gtoken := &GitlabProviderToken{}
if err = json.Unmarshal(data, gtoken); err != nil {
return nil, err
}
// gtoken.ExpiresIn always returns 0, so we set Expiry=7200 to avoid verification errors.
token := &oauth2.Token{
AccessToken: gtoken.AccessToken,
TokenType: gtoken.TokenType,
RefreshToken: gtoken.RefreshToken,
Expiry: time.Unix(time.Now().Unix()+int64(7200), 0),
}
return token, nil
}
/*
{
"id":5162115,
"name":"shiluo",
"username":"shiluo",
"state":"active",
"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5162115/avatar.png",
"web_url":"https://gitlab.com/shiluo",
"created_at":"2019-12-23T02:50:10.348Z",
"bio":"",
"bio_html":"",
"location":"China",
"public_email":"silo1999@163.com",
"skype":"",
"linkedin":"",
"twitter":"",
"website_url":"",
"organization":"",
"job_title":"",
"pronouns":null,
"bot":false,
"work_information":null,
"followers":0,
"following":0,
"last_sign_in_at":"2019-12-26T13:24:42.941Z",
"confirmed_at":"2019-12-23T02:52:10.778Z",
"last_activity_on":"2021-08-19",
"email":"silo1999@163.com",
"theme_id":1,
"color_scheme_id":1,
"projects_limit":100000,
"current_sign_in_at":"2021-08-19T09:46:46.004Z",
"identities":[
{
"provider":"github",
"extern_uid":"51157931",
"saml_provider_id":null
}
],
"can_create_group":true,
"can_create_project":true,
"two_factor_enabled":false,
"external":false,
"private_profile":false,
"commit_email":"silo1999@163.com",
"shared_runners_minutes_limit":null,
"extra_shared_runners_minutes_limit":null
}
*/
type GitlabUserInfo struct {
Id int `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
State string `json:"state"`
AvatarUrl string `json:"avatar_url"`
WebUrl string `json:"web_url"`
CreatedAt time.Time `json:"created_at"`
Bio string `json:"bio"`
BioHtml string `json:"bio_html"`
Location string `json:"location"`
PublicEmail string `json:"public_email"`
Skype string `json:"skype"`
Linkedin string `json:"linkedin"`
Twitter string `json:"twitter"`
WebsiteUrl string `json:"website_url"`
Organization string `json:"organization"`
JobTitle string `json:"job_title"`
Pronouns interface{} `json:"pronouns"`
Bot bool `json:"bot"`
WorkInformation interface{} `json:"work_information"`
Followers int `json:"followers"`
Following int `json:"following"`
LastSignInAt time.Time `json:"last_sign_in_at"`
ConfirmedAt time.Time `json:"confirmed_at"`
LastActivityOn string `json:"last_activity_on"`
Email string `json:"email"`
ThemeId int `json:"theme_id"`
ColorSchemeId int `json:"color_scheme_id"`
ProjectsLimit int `json:"projects_limit"`
CurrentSignInAt time.Time `json:"current_sign_in_at"`
Identities []struct {
Provider string `json:"provider"`
ExternUid string `json:"extern_uid"`
SamlProviderId interface{} `json:"saml_provider_id"`
} `json:"identities"`
CanCreateGroup bool `json:"can_create_group"`
CanCreateProject bool `json:"can_create_project"`
TwoFactorEnabled bool `json:"two_factor_enabled"`
External bool `json:"external"`
PrivateProfile bool `json:"private_profile"`
CommitEmail string `json:"commit_email"`
SharedRunnersMinutesLimit interface{} `json:"shared_runners_minutes_limit"`
ExtraSharedRunnersMinutesLimit interface{} `json:"extra_shared_runners_minutes_limit"`
}
// GetUserInfo use GitlabProviderToken gotten before return GitlabUserInfo
func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
resp, err := idp.Client.Get("https://gitlab.com/api/v4/user?access_token=" + token.AccessToken)
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
guser := GitlabUserInfo{}
if err = json.Unmarshal(data, &guser); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: strconv.Itoa(guser.Id),
Username: guser.Username,
DisplayName: guser.Name,
AvatarUrl: guser.AvatarUrl,
Email: guser.Email,
}
return &userInfo, nil
}

View File

@ -19,7 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"golang.org/x/oauth2"
@ -95,7 +95,7 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

271
idp/goth.go Normal file
View File

@ -0,0 +1,271 @@
// Copyright 2021 The casbin 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 (
"fmt"
"net/http"
"net/url"
"reflect"
"time"
"github.com/casdoor/casdoor/util"
"github.com/markbates/goth"
"github.com/markbates/goth/providers/amazon"
"github.com/markbates/goth/providers/apple"
"github.com/markbates/goth/providers/azuread"
"github.com/markbates/goth/providers/bitbucket"
"github.com/markbates/goth/providers/digitalocean"
"github.com/markbates/goth/providers/discord"
"github.com/markbates/goth/providers/dropbox"
"github.com/markbates/goth/providers/facebook"
"github.com/markbates/goth/providers/gitea"
"github.com/markbates/goth/providers/github"
"github.com/markbates/goth/providers/gitlab"
"github.com/markbates/goth/providers/google"
"github.com/markbates/goth/providers/heroku"
"github.com/markbates/goth/providers/instagram"
"github.com/markbates/goth/providers/kakao"
"github.com/markbates/goth/providers/line"
"github.com/markbates/goth/providers/linkedin"
"github.com/markbates/goth/providers/microsoftonline"
"github.com/markbates/goth/providers/paypal"
"github.com/markbates/goth/providers/salesforce"
"github.com/markbates/goth/providers/shopify"
"github.com/markbates/goth/providers/slack"
"github.com/markbates/goth/providers/tumblr"
"github.com/markbates/goth/providers/twitter"
"github.com/markbates/goth/providers/yahoo"
"github.com/markbates/goth/providers/yandex"
"github.com/markbates/goth/providers/zoom"
"golang.org/x/oauth2"
)
type GothIdProvider struct {
Provider goth.Provider
Session goth.Session
}
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) *GothIdProvider {
var idp GothIdProvider
switch providerType {
case "Amazon":
idp = GothIdProvider{
Provider: amazon.New(clientId, clientSecret, redirectUrl),
Session: &amazon.Session{},
}
case "Apple":
idp = GothIdProvider{
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
Session: &apple.Session{},
}
case "AzureAD":
idp = GothIdProvider{
Provider: azuread.New(clientId, clientSecret, redirectUrl, nil),
Session: &azuread.Session{},
}
case "Bitbucket":
idp = GothIdProvider{
Provider: bitbucket.New(clientId, clientSecret, redirectUrl),
Session: &bitbucket.Session{},
}
case "DigitalOcean":
idp = GothIdProvider{
Provider: digitalocean.New(clientId, clientSecret, redirectUrl),
Session: &digitalocean.Session{},
}
case "Discord":
idp = GothIdProvider{
Provider: discord.New(clientId, clientSecret, redirectUrl),
Session: &discord.Session{},
}
case "Dropbox":
idp = GothIdProvider{
Provider: dropbox.New(clientId, clientSecret, redirectUrl),
Session: &dropbox.Session{},
}
case "Facebook":
idp = GothIdProvider{
Provider: facebook.New(clientId, clientSecret, redirectUrl),
Session: &facebook.Session{},
}
case "Gitea":
idp = GothIdProvider{
Provider: gitea.New(clientId, clientSecret, redirectUrl),
Session: &gitea.Session{},
}
case "GitHub":
idp = GothIdProvider{
Provider: github.New(clientId, clientSecret, redirectUrl),
Session: &github.Session{},
}
case "GitLab":
idp = GothIdProvider{
Provider: gitlab.New(clientId, clientSecret, redirectUrl),
Session: &gitlab.Session{},
}
case "Google":
idp = GothIdProvider{
Provider: google.New(clientId, clientSecret, redirectUrl),
Session: &google.Session{},
}
case "Heroku":
idp = GothIdProvider{
Provider: heroku.New(clientId, clientSecret, redirectUrl),
Session: &heroku.Session{},
}
case "Instagram":
idp = GothIdProvider{
Provider: instagram.New(clientId, clientSecret, redirectUrl),
Session: &instagram.Session{},
}
case "Kakao":
idp = GothIdProvider{
Provider: kakao.New(clientId, clientSecret, redirectUrl),
Session: &kakao.Session{},
}
case "Linkedin":
idp = GothIdProvider{
Provider: linkedin.New(clientId, clientSecret, redirectUrl),
Session: &linkedin.Session{},
}
case "Line":
idp = GothIdProvider{
Provider: line.New(clientId, clientSecret, redirectUrl),
Session: &line.Session{},
}
case "MicrosoftOnline":
idp = GothIdProvider{
Provider: microsoftonline.New(clientId, clientSecret, redirectUrl),
Session: &microsoftonline.Session{},
}
case "Paypal":
idp = GothIdProvider{
Provider: paypal.New(clientId, clientSecret, redirectUrl),
Session: &paypal.Session{},
}
case "SalesForce":
idp = GothIdProvider{
Provider: salesforce.New(clientId, clientSecret, redirectUrl),
Session: &salesforce.Session{},
}
case "Shopify":
idp = GothIdProvider{
Provider: shopify.New(clientId, clientSecret, redirectUrl),
Session: &shopify.Session{},
}
case "Slack":
idp = GothIdProvider{
Provider: slack.New(clientId, clientSecret, redirectUrl),
Session: &slack.Session{},
}
case "Tumblr":
idp = GothIdProvider{
Provider: tumblr.New(clientId, clientSecret, redirectUrl),
Session: &tumblr.Session{},
}
case "Twitter":
idp = GothIdProvider{
Provider: twitter.New(clientId, clientSecret, redirectUrl),
Session: &twitter.Session{},
}
case "Yahoo":
idp = GothIdProvider{
Provider: yahoo.New(clientId, clientSecret, redirectUrl),
Session: &yahoo.Session{},
}
case "Yandex":
idp = GothIdProvider{
Provider: yandex.New(clientId, clientSecret, redirectUrl),
Session: &yandex.Session{},
}
case "Zoom":
idp = GothIdProvider{
Provider: zoom.New(clientId, clientSecret, redirectUrl),
Session: &zoom.Session{},
}
}
return &idp
}
//Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
idpClient.Set(reflect.ValueOf(client))
}
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
var expireAt time.Time
//Need to construct variables supported by goth
//to call the function to obtain accessToken
value := url.Values{}
value.Add("code", code)
accessToken, err := idp.Session.Authorize(idp.Provider, value)
//Get ExpiresAt's value
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
if valueOfExpire.IsValid() {
expireAt = valueOfExpire.Interface().(time.Time)
}
token := oauth2.Token{
AccessToken: accessToken,
Expiry: expireAt,
}
return &token, err
}
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
gothUser, err := idp.Provider.FetchUser(idp.Session)
if err != nil {
return nil, err
}
return getUser(gothUser), nil
}
func getUser(gothUser goth.User) *UserInfo {
user := UserInfo{
Id: gothUser.UserID,
Username: gothUser.Name,
DisplayName: gothUser.NickName,
Email: gothUser.Email,
AvatarUrl: gothUser.AvatarURL,
}
//Some idp return an empty Name
//so construct the Name with firstname and lastname or nickname
if user.Username == "" {
if gothUser.FirstName != "" && gothUser.LastName != "" {
user.Username = getName(gothUser.FirstName, gothUser.LastName)
} else {
user.Username = gothUser.NickName
}
}
if user.DisplayName == "" {
if gothUser.FirstName != "" && gothUser.LastName != "" {
user.DisplayName = getName(gothUser.FirstName, gothUser.LastName)
} else {
user.DisplayName = user.Username
}
}
return &user
}
func getName(firstName, lastName string) string {
if util.IsChinese(firstName) || util.IsChinese(lastName) {
return fmt.Sprintf("%s%s", lastName, firstName)
} else {
return fmt.Sprintf("%s %s", firstName, lastName)
}
}

View File

@ -17,7 +17,6 @@ package idp
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -169,7 +168,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
resp, err := idp.Client.Do(req)
data, err = ioutil.ReadAll(resp.Body)
data, err = io.ReadAll(resp.Body)
err = resp.Body.Close()
if err != nil {
return nil, err
@ -201,7 +200,7 @@ func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, e
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -85,7 +84,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := ioutil.ReadAll(resp.Body)
rbs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -323,7 +322,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
}
}(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
bs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -16,6 +16,7 @@ package idp
import (
"net/http"
"strings"
"golang.org/x/oauth2"
)
@ -57,7 +58,24 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Lark" {
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "GitLab" {
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Baidu" {
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
} else if isGothSupport(providerType) {
return NewGothIdProvider(providerType, clientId, clientSecret, redirectUrl)
}
return nil
}
var gothList = []string{"Apple", "AzureAd", "Slack"}
func isGothSupport(provider string) bool {
for _, value := range gothList {
if strings.EqualFold(value, provider) {
return true
}
}
return false
}

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"regexp"
@ -75,7 +75,7 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
defer resp.Body.Close()
tokenContent, err := ioutil.ReadAll(resp.Body)
tokenContent, err := io.ReadAll(resp.Body)
re := regexp.MustCompile("token=(.*?)&")
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
@ -145,7 +145,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
openIdBody, err := ioutil.ReadAll(resp.Body)
openIdBody, err := io.ReadAll(resp.Body)
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
@ -161,7 +161,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
userInfoBody, err := ioutil.ReadAll(resp.Body)
userInfoBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -179,6 +179,7 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
userInfo := UserInfo{
Id: id,
Username: wechatUserInfo.Nickname,
DisplayName: wechatUserInfo.Nickname,
AvatarUrl: wechatUserInfo.Headimgurl,
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -195,7 +194,7 @@ func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte,
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

167
idp/wecom_inter.go Normal file
View File

@ -0,0 +1,167 @@
// Copyright 2021 The casbin 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"
"time"
"golang.org/x/oauth2"
)
//This idp is using wecom internal application api as idp
type WeComInternalIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewWeComInternalIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComInternalIdProvider {
idp := &WeComInternalIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *WeComInternalIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *WeComInternalIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var config = &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type WecomInterToken struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://developer.work.weixin.qq.com/document/path/91039
func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
CorpId string `json:"corpid"`
Corpsecret string `json:"corpsecret"`
}{idp.Config.ClientID, idp.Config.ClientSecret}
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", pTokenParams.CorpId, pTokenParams.Corpsecret))
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
pToken := &WecomInterToken{}
err = json.Unmarshal(data, pToken)
if err != nil {
return nil, err
}
if pToken.Errcode != 0 {
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", pToken.Errcode, pToken.Errmsg)
}
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
raw := make(map[string]interface{})
raw["code"] = code
token = token.WithExtra(raw)
return token, nil
}
type WecomInternalUserResp struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
UserId string `json:"UserId"`
OpenId string `json:"OpenId"`
}
type WecomInternalUserInfo struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
Name string `json:"name"`
Email string `json:"email"`
Avatar string `json:"avatar"`
OpenId string `json:"open_userid"`
}
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
//Get userid first
accessToken := token.AccessToken
code := token.Extra("code").(string)
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s", accessToken, code))
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
userResp := &WecomInternalUserResp{}
err = json.Unmarshal(data, userResp)
if err != nil {
return nil, err
}
if userResp.Errcode != 0 {
return nil, fmt.Errorf("userIdResp.Errcode = %d, userIdResp.Errmsg = %s", userResp.Errcode, userResp.Errmsg)
}
if userResp.OpenId != "" {
return nil, fmt.Errorf("not an internal user")
}
//Use userid and accesstoken to get user information
resp, err = idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", accessToken, userResp.UserId))
if err != nil {
return nil, err
}
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
infoResp := &WecomInternalUserInfo{}
err = json.Unmarshal(data, infoResp)
if err != nil {
return nil, err
}
if infoResp.Errcode != 0 {
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
}
userInfo := UserInfo{
Id: infoResp.OpenId,
Username: infoResp.Name,
DisplayName: infoResp.Name,
Email: infoResp.Email,
AvatarUrl: infoResp.Avatar,
}
return &userInfo, nil
}

View File

@ -19,7 +19,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -92,7 +91,7 @@ func (idp *WeiBoIdProvider) GetToken(code string) (*oauth2.Token, error) {
return
}
}(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
bs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

56
k8s.yaml Normal file
View File

@ -0,0 +1,56 @@
# this is only an EXAMPLE of deploying casddor in kubernetes
# please modify this file according to your requirements
apiVersion: v1
kind: Service
metadata:
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
#namespace: casdoor
name: casdoor-svc
labels:
app: casdoor
spec:
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this filed
type: NodePort
ports:
- port: 8000
selector:
app: casdoor
---
apiVersion: apps/v1
kind: Deployment
metadata:
#EDIT IT: if you don't want to run casdoor in default namespace, please modify this field
#namespace: casdoor
name: casdoor-deployment
labels:
app: casdoor
spec:
#EDIT IT: if you don't use redis, casdoor should not have multiple replicas
replicas: 1
selector:
matchLabels:
app: casdoor
template:
metadata:
labels:
app: casdoor
spec:
containers:
- name: casdoor-container
image: casbin/casdoor:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
volumeMounts:
# the mounted directory path in THE CONTAINER
- mountPath: /conf
name: conf
env:
- name: RUNNING_IN_DOCKER
value: "true"
#if you want to deploy this in real prod env, consider the config map
volumes:
- name: conf
hostPath:
#EDIT IT: the mounted directory path in THE HOST
path: /conf

29
main.go
View File

@ -15,24 +15,31 @@
package main
import (
"flag"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/plugins/cors"
_ "github.com/astaxie/beego/session/redis"
"github.com/casbin/casdoor/authz"
"github.com/casbin/casdoor/controllers"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/routers"
_ "github.com/casbin/casdoor/routers"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/routers"
_ "github.com/casdoor/casdoor/routers"
)
func main() {
object.InitAdapter()
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
flag.Parse()
object.InitAdapter(*createDatabase)
object.InitDb()
controllers.InitHttpClient()
object.InitDefaultStorageProvider()
object.InitLdapAutoSynchronizer()
proxy.InitHttpClient()
authz.InitAuthz()
go object.RunSyncUsersJob()
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "PUT", "PATCH"},
@ -60,14 +67,14 @@ func main() {
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 30
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
err := logs.SetLogger("file", `{"filename":"logs/casdoor.log","maxdays":99999}`)
err := logs.SetLogger("file", `{"filename":"logs/casdoor.log","maxdays":99999,"perm":"0770"}`)
if err != nil {
panic(err)
}
logs.SetLevel(logs.LevelInformational)
//logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false)
beego.Run()

View File

@ -17,10 +17,14 @@ package object
import (
"fmt"
"runtime"
"xorm.io/core"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
//_ "github.com/lib/pq" // db = postgres
"xorm.io/xorm"
)
@ -32,11 +36,15 @@ func InitConfig() {
panic(err)
}
InitAdapter()
InitAdapter(true)
}
func InitAdapter() {
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("dbName"))
func InitAdapter(createDatabase bool) {
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
if createDatabase {
adapter.CreateDatabase()
}
adapter.createTable()
}
@ -72,7 +80,7 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapte
return a
}
func (a *Adapter) createDatabase() error {
func (a *Adapter) CreateDatabase() error {
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
if err != nil {
return err
@ -84,13 +92,12 @@ func (a *Adapter) createDatabase() error {
}
func (a *Adapter) open() {
if a.driverName != "postgres" {
if err := a.createDatabase(); err != nil {
panic(err)
}
dataSourceName := a.dataSourceName + a.dbName
if a.driverName != "mysql" {
dataSourceName = a.dataSourceName
}
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
engine, err := xorm.NewEngine(a.driverName, dataSourceName)
if err != nil {
panic(err)
}
@ -104,6 +111,13 @@ func (a *Adapter) close() {
}
func (a *Adapter) createTable() {
showSql, _ := beego.AppConfig.Bool("showSql")
a.Engine.ShowSQL(showSql)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper)
err := a.Engine.Sync2(new(Organization))
if err != nil {
panic(err)
@ -114,6 +128,16 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Role))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Permission))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)
@ -124,6 +148,11 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Resource))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Token))
if err != nil {
panic(err)
@ -133,7 +162,23 @@ func (a *Adapter) createTable() {
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Records))
err = a.Engine.Sync2(new(Record))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Webhook))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Syncer))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Cert))
if err != nil {
panic(err)
}
@ -143,3 +188,27 @@ func (a *Adapter) createTable() {
panic(err)
}
}
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := adapter.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
session = session.And("owner=?", owner)
}
if field != "" && value != "" {
if filterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
return session
}

View File

@ -15,7 +15,9 @@
package object
import (
"github.com/casbin/casdoor/util"
"fmt"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -24,26 +26,43 @@ type Application struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Logo string `xorm:"varchar(100)" json:"logo"`
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
Description string `xorm:"varchar(100)" json:"description"`
Organization string `xorm:"varchar(100)" json:"organization"`
EnablePassword bool `json:"enablePassword"`
EnableSignUp bool `json:"enableSignUp"`
Providers []*ProviderItem `xorm:"varchar(10000)" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Logo string `xorm:"varchar(100)" json:"logo"`
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
Description string `xorm:"varchar(100)" json:"description"`
Organization string `xorm:"varchar(100)" json:"organization"`
Cert string `xorm:"varchar(100)" json:"cert"`
EnablePassword bool `json:"enablePassword"`
EnableSignUp bool `json:"enableSignUp"`
EnableSigninSession bool `json:"enableSigninSession"`
EnableCodeSignin bool `json:"enableCodeSignin"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
ExpireInHours int `json:"expireInHours"`
SignupUrl string `xorm:"varchar(100)" json:"signupUrl"`
SigninUrl string `xorm:"varchar(100)" json:"signinUrl"`
ForgetUrl string `xorm:"varchar(100)" json:"forgetUrl"`
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
TermsOfUse string `xorm:"varchar(1000)" json:"termsOfUse"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
ExpireInHours int `json:"expireInHours"`
RefreshExpireInHours int `json:"refreshExpireInHours"`
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
}
func GetApplicationCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Application{})
if err != nil {
panic(err)
}
return int(count)
}
func GetApplications(owner string) []*Application {
@ -56,6 +75,27 @@ func GetApplications(owner string) []*Application {
return applications
}
func GetPaginationApplications(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
applications := []*Application{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&applications)
if err != nil {
panic(err)
}
return applications
}
func GetApplicationsByOrganizationName(owner string, organization string) []*Application {
applications := []*Application{}
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
if err != nil {
panic(err)
}
return applications
}
func getProviderMap(owner string) map[string]*Provider {
providers := GetProviders(owner)
m := map[string]*Provider{}
@ -64,7 +104,7 @@ func getProviderMap(owner string) map[string]*Provider {
// continue
//}
m[provider.Name] = getMaskedProvider(provider)
m[provider.Name] = GetMaskedProvider(provider)
}
return m
}
@ -127,6 +167,21 @@ func GetApplicationByUser(user *User) *Application {
}
}
func GetApplicationByUserId(userId string) (*Application, *User) {
var application *Application
owner, name := util.GetOwnerAndNameFromId(userId)
if owner == "app" {
application = getApplication("admin", name)
return application, nil
}
user := GetUser(userId)
application = GetApplicationByUser(user)
return application, user
}
func GetApplicationByClientId(clientId string) *Application {
application := Application{}
existed, err := adapter.Engine.Where("client_id=?", clientId).Get(&application)
@ -143,30 +198,47 @@ func GetApplicationByClientId(clientId string) *Application {
}
}
func GetApplicationByClientIdAndSecret(clientId, clientSecret string) *Application {
if util.IsStrsEmpty(clientId, clientSecret) {
return nil
}
app := GetApplicationByClientId(clientId)
if app == nil || app.ClientSecret != clientSecret {
return nil
}
return app
}
func GetApplication(id string) *Application {
owner, name := util.GetOwnerAndNameFromId(id)
return getApplication(owner, name)
}
func GetMaskedApplication(application *Application, userId string) *Application {
if isUserIdGlobalAdmin(userId) {
return application
}
if application == nil {
return nil
}
if application.ClientSecret != "" {
application.ClientSecret = "***"
}
return application
}
func GetMaskedApplications(applications []*Application, userId string) []*Application {
if isUserIdGlobalAdmin(userId) {
return applications
}
for _, application := range applications {
application = GetMaskedApplication(application, userId)
}
return applications
}
func UpdateApplication(id string, application *Application) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getApplication(owner, name) == nil {
return false
}
if name == "app-built-in" {
application.Name = name
}
for _, providerItem := range application.Providers {
providerItem.Provider = nil
}
@ -195,6 +267,10 @@ func AddApplication(application *Application) bool {
}
func DeleteApplication(application *Application) bool {
if application.Name == "app-built-in" {
return false
}
affected, err := adapter.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{})
if err != nil {
panic(err)
@ -202,3 +278,7 @@ func DeleteApplication(application *Application) bool {
return affected != 0
}
func (application *Application) GetId() string {
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
}

View File

@ -14,7 +14,7 @@
package object
func (application *Application) getProviderByCategory(category string) *Provider {
func (application *Application) GetProviderByCategory(category string) *Provider {
providers := GetProviders(application.Owner)
m := map[string]*Provider{}
for _, provider := range providers {
@ -35,15 +35,15 @@ func (application *Application) getProviderByCategory(category string) *Provider
}
func (application *Application) GetEmailProvider() *Provider {
return application.getProviderByCategory("Email")
return application.GetProviderByCategory("Email")
}
func (application *Application) GetSmsProvider() *Provider {
return application.getProviderByCategory("SMS")
return application.GetProviderByCategory("SMS")
}
func (application *Application) GetStorageProvider() *Provider {
return application.getProviderByCategory("Storage")
return application.GetProviderByCategory("Storage")
}
func (application *Application) getSignupItem(itemName string) *SignupItem {
@ -55,10 +55,6 @@ func (application *Application) getSignupItem(itemName string) *SignupItem {
return nil
}
func (application *Application) IsSignupItemEnabled(itemName string) bool {
return application.getSignupItem(itemName) != nil
}
func (application *Application) IsSignupItemVisible(itemName string) bool {
signupItem := application.getSignupItem(itemName)
if signupItem == nil {
@ -68,6 +64,15 @@ func (application *Application) IsSignupItemVisible(itemName string) bool {
return signupItem.Visible
}
func (application *Application) IsSignupItemRequired(itemName string) bool {
signupItem := application.getSignupItem(itemName)
if signupItem == nil {
return false
}
return signupItem.Required
}
func (application *Application) GetSignupItemRule(itemName string) string {
signupItem := application.getSignupItem(itemName)
if signupItem == nil {

72
object/avatar.go Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"fmt"
"io"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/proxy"
)
var defaultStorageProvider *Provider = nil
func InitDefaultStorageProvider() {
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
if defaultStorageProviderStr != "" {
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
}
}
func downloadFile(url string) (*bytes.Buffer, error) {
httpClient := proxy.GetHttpClient(url)
resp, err := httpClient.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
fileBuffer := bytes.NewBuffer(nil)
_, err = io.Copy(fileBuffer, resp.Body)
if err != nil {
return nil, err
}
return fileBuffer, nil
}
func getPermanentAvatarUrl(organization string, username string, url string) string {
if defaultStorageProvider == nil {
return ""
}
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)
fileBuffer, err := downloadFile(url)
if err != nil {
panic(err)
}
_, _, err = UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer)
if err != nil {
panic(err)
}
return uploadedFileUrl
}

39
object/avatar_test.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2021 The casbin 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"
"testing"
"github.com/casdoor/casdoor/proxy"
)
func TestSyncPermanentAvatars(t *testing.T) {
InitConfig()
InitDefaultStorageProvider()
proxy.InitHttpClient()
users := GetGlobalUsers()
for i, user := range users {
if user.Avatar == "" {
continue
}
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
updateUserColumn("permanent_avatar", user)
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
}
}

161
object/cert.go Normal file
View File

@ -0,0 +1,161 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type Cert 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"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Scope string `xorm:"varchar(100)" json:"scope"`
Type string `xorm:"varchar(100)" json:"type"`
CryptoAlgorithm string `xorm:"varchar(100)" json:"cryptoAlgorithm"`
BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"`
PublicKey string `xorm:"mediumtext" json:"publicKey"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
}
func GetMaskedCert(cert *Cert) *Cert {
if cert == nil {
return nil
}
return cert
}
func GetMaskedCerts(certs []*Cert) []*Cert {
for _, cert := range certs {
cert = GetMaskedCert(cert)
}
return certs
}
func GetCertCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Cert{})
if err != nil {
panic(err)
}
return int(count)
}
func GetCerts(owner string) []*Cert {
certs := []*Cert{}
err := adapter.Engine.Desc("created_time").Find(&certs, &Cert{Owner: owner})
if err != nil {
panic(err)
}
return certs
}
func GetPaginationCerts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Cert {
certs := []*Cert{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&certs)
if err != nil {
panic(err)
}
return certs
}
func getCert(owner string, name string) *Cert {
if owner == "" || name == "" {
return nil
}
cert := Cert{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&cert)
if err != nil {
panic(err)
}
if existed {
return &cert
} else {
return nil
}
}
func GetCert(id string) *Cert {
owner, name := util.GetOwnerAndNameFromId(id)
return getCert(owner, name)
}
func UpdateCert(id string, cert *Cert) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getCert(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
if err != nil {
panic(err)
}
return affected != 0
}
func AddCert(cert *Cert) bool {
if cert.PublicKey == "" || cert.PrivateKey == "" {
publicKey, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
cert.PublicKey = publicKey
cert.PrivateKey = privateKey
}
affected, err := adapter.Engine.Insert(cert)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteCert(cert *Cert) bool {
affected, err := adapter.Engine.ID(core.PK{cert.Owner, cert.Name}).Delete(&Cert{})
if err != nil {
panic(err)
}
return affected != 0
}
func (p *Cert) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}
func getCertByApplication(application *Application) *Cert {
if application.Cert != "" {
return getCert("admin", application.Cert)
} else {
return GetDefaultCert()
}
}
func GetDefaultCert() *Cert {
return getCert("admin", "cert-built-in")
}

View File

@ -18,13 +18,19 @@ import (
"fmt"
"regexp"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
)
var reWhiteSpace *regexp.Regexp
var (
reWhiteSpace *regexp.Regexp
reFieldWhiteList *regexp.Regexp
)
func init() {
reWhiteSpace, _ = regexp.Compile(`\s`)
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
}
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
@ -47,6 +53,14 @@ func CheckUserSignup(application *Application, organization *Organization, usern
}
if application.IsSignupItemVisible("Email") {
if email == "" {
if application.IsSignupItemRequired("Email") {
return "email cannot be empty"
} else {
return ""
}
}
if HasUserByField(organization.Name, "email", email) {
return "email already exists"
} else if !util.IsEmailValid(email) {
@ -55,6 +69,14 @@ func CheckUserSignup(application *Application, organization *Organization, usern
}
if application.IsSignupItemVisible("Phone") {
if phone == "" {
if application.IsSignupItemRequired("Phone") {
return "phone cannot be empty"
} else {
return ""
}
}
if HasUserByField(organization.Name, "phone", phone) {
return "phone already exists"
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
@ -83,33 +105,76 @@ func CheckUserSignup(application *Application, organization *Organization, usern
func CheckPassword(user *User, password string) string {
organization := GetOrganizationByUser(user)
if organization == nil {
return "organization does not exist"
}
if organization.PasswordType == "plain" {
if password == user.Password {
return ""
} else {
return "password incorrect"
credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil {
if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
return ""
}
}
} else if organization.PasswordType == "salt" {
if password == user.Password || getSaltedPassword(password, organization.PasswordSalt) == user.Password {
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
return ""
} else {
return "password incorrect"
}
return "password incorrect"
} else {
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
}
}
func CheckUserLogin(organization string, username string, password string) (*User, string) {
func checkLdapUserPassword(user *User, password string) (*User, string) {
ldaps := GetLdaps(user.Owner)
ldapLoginSuccess := false
for _, ldapServer := range ldaps {
conn, err := GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
if err != nil {
continue
}
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, []string{}, nil)
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
return nil, err.Error()
}
if len(searchResult.Entries) == 0 {
continue
} else if len(searchResult.Entries) > 1 {
return nil, "Error: multiple accounts with same uid, please check your ldap server"
}
dn := searchResult.Entries[0].DN
if err := conn.Conn.Bind(dn, password); err == nil {
ldapLoginSuccess = true
break
}
}
if !ldapLoginSuccess {
return nil, "ldap user name or password incorrect"
}
return user, ""
}
func CheckUserPassword(organization string, username string, password string) (*User, string) {
user := GetUserByFields(organization, username)
if user == nil {
if user == nil || user.IsDeleted == true {
return nil, "the user does not exist, please sign up first"
}
if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator"
}
//for ldap users
if user.Ldap != "" {
return checkLdapUserPassword(user, password)
}
msg := CheckPassword(user, password)
if msg != "" {
@ -118,3 +183,7 @@ func CheckUserLogin(organization string, username string, password string) (*Use
return user, ""
}
func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}

View File

@ -18,7 +18,7 @@ package object
import "github.com/go-gomail/gomail"
func SendEmail(provider *Provider, title, content, dest, sender string) string {
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
message := gomail.NewMessage()
@ -27,10 +27,5 @@ func SendEmail(provider *Provider, title, content, dest, sender string) string {
message.SetHeader("Subject", title)
message.SetBody("text/html", content)
err := dialer.DialAndSend(message)
if err == nil {
return ""
} else {
return err.Error()
}
return dialer.DialAndSend(message)
}

View File

@ -14,12 +14,23 @@
package object
import "github.com/casbin/casdoor/util"
import (
_ "embed"
"github.com/casdoor/casdoor/util"
)
//go:embed token_jwt_key.pem
var tokenJwtPublicKey string
//go:embed token_jwt_key.key
var tokenJwtPrivateKey string
func InitDb() {
initBuiltInOrganization()
initBuiltInUser()
initBuiltInApplication()
initBuiltInCert()
initBuiltInLdap()
}
@ -50,23 +61,28 @@ func initBuiltInUser() {
}
user = &User{
Owner: "built-in",
Name: "admin",
CreatedTime: util.GetCurrentTime(),
Id: util.GenerateId(),
Type: "normal-user",
Password: "123",
DisplayName: "Admin",
Avatar: "https://casbin.org/img/casbin.svg",
Email: "admin@example.com",
Phone: "12345678910",
Address: []string{},
Affiliation: "Example Inc.",
Tag: "staff",
IsAdmin: true,
IsGlobalAdmin: true,
IsForbidden: false,
Properties: make(map[string]string),
Owner: "built-in",
Name: "admin",
CreatedTime: util.GetCurrentTime(),
Id: util.GenerateId(),
Type: "normal-user",
Password: "123",
DisplayName: "Admin",
Avatar: "https://casbin.org/img/casbin.svg",
Email: "admin@example.com",
Phone: "12345678910",
Address: []string{},
Affiliation: "Example Inc.",
Tag: "staff",
Score: 2000,
Ranking: 1,
IsAdmin: true,
IsGlobalAdmin: true,
IsForbidden: false,
IsDeleted: false,
SignupApplication: "built-in-app",
CreatedIp: "127.0.0.1",
Properties: make(map[string]string),
}
AddUser(user)
}
@ -85,16 +101,48 @@ func initBuiltInApplication() {
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
HomepageUrl: "https://casdoor.org",
Organization: "built-in",
Cert: "cert-built-in",
EnablePassword: true,
EnableSignUp: true,
Providers: []*ProviderItem{},
SignupItems: []*SignupItem{},
RedirectUris: []string{},
ExpireInHours: 168,
SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
},
RedirectUris: []string{},
ExpireInHours: 168,
}
AddApplication(application)
}
func initBuiltInCert() {
cert := getCert("admin", "cert-built-in")
if cert != nil {
return
}
cert = &Cert{
Owner: "admin",
Name: "cert-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Cert",
Scope: "JWT",
Type: "x509",
CryptoAlgorithm: "RSA",
BitSize: 4096,
ExpireInYears: 20,
PublicKey: tokenJwtPublicKey,
PrivateKey: tokenJwtPrivateKey,
}
AddCert(cert)
}
func initBuiltInLdap() {
ldap := GetLdap("ldap-built-in")
if ldap != nil {

View File

@ -17,10 +17,11 @@ package object
import (
"errors"
"fmt"
"github.com/casbin/casdoor/util"
"strings"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
"strings"
)
type Ldap struct {
@ -77,6 +78,32 @@ type LdapRespUser struct {
Address string `json:"address"`
}
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
returnAnyNotEmpty := func(strs ...string) string {
for _, str := range strs {
if str != "" {
return str
}
}
return ""
}
res := make([]LdapRespUser, 0)
for _, user := range users {
res = append(res, LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
Uuid: user.Uuid,
Email: returnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
Phone: returnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
Address: returnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
})
}
return res
}
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
@ -285,15 +312,16 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
existUuids := CheckLdapUuidExist(owner, uuids)
for _, user := range users {
found := false
if len(existUuids) > 0 {
for index, existUuid := range existUuids {
for _, existUuid := range existUuids {
if user.Uuid == existUuid {
existUsers = append(existUsers, user)
existUuids = append(existUuids[:index], existUuids[index+1:]...)
found = true
}
}
}
if !AddUser(&User{
if !found && !AddUser(&User{
Owner: owner,
Name: buildLdapUserName(user.Uid, user.UidNumber),
CreatedTime: util.GetCurrentTime(),
@ -305,6 +333,7 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
Address: []string{user.Address},
Affiliation: "Example Inc.",
Tag: "staff",
Score: 2000,
Ldap: user.Uuid,
}) {
failedUsers = append(failedUsers, user)
@ -325,6 +354,7 @@ func UpdateLdapSyncTime(ldapId string) {
func CheckLdapUuidExist(owner string, uuids []string) []string {
var results []User
var existUuids []string
existUuidSet := make(map[string]struct{})
//whereStr := ""
//for i, uuid := range uuids {
@ -342,9 +372,13 @@ func CheckLdapUuidExist(owner string, uuids []string) []string {
if len(results) > 0 {
for _, result := range results {
existUuids = append(existUuids, result.Ldap)
existUuidSet[result.Ldap] = struct{}{}
}
}
for uuid, _ := range existUuidSet {
existUuids = append(existUuids, uuid)
}
return existUuids
}

111
object/ldap_autosync.go Normal file
View File

@ -0,0 +1,111 @@
package object
import (
"fmt"
"sync"
"time"
"github.com/astaxie/beego/logs"
)
type LdapAutoSynchronizer struct {
sync.Mutex
ldapIdToStopChan map[string]chan struct{}
}
var globalLdapAutoSynchronizer *LdapAutoSynchronizer
func InitLdapAutoSynchronizer() {
globalLdapAutoSynchronizer = NewLdapAutoSynchronizer()
globalLdapAutoSynchronizer.LdapAutoSynchronizerStartUpAll()
}
func NewLdapAutoSynchronizer() *LdapAutoSynchronizer {
return &LdapAutoSynchronizer{
ldapIdToStopChan: make(map[string]chan struct{}),
}
}
func GetLdapAutoSynchronizer() *LdapAutoSynchronizer {
return globalLdapAutoSynchronizer
}
//start autosync for specified ldap, old existing autosync goroutine will be ceased
func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
l.Lock()
defer l.Unlock()
ldap := GetLdap(ldapId)
if ldap == nil {
return fmt.Errorf("ldap %s doesn't exist", ldapId)
}
if res, ok := l.ldapIdToStopChan[ldapId]; ok {
res <- struct{}{}
delete(l.ldapIdToStopChan, ldapId)
}
stopChan := make(chan struct{})
l.ldapIdToStopChan[ldapId] = stopChan
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
go l.syncRoutine(ldap, stopChan)
return nil
}
func (l *LdapAutoSynchronizer) StopAutoSync(ldapId string) {
l.Lock()
defer l.Unlock()
if res, ok := l.ldapIdToStopChan[ldapId]; ok {
res <- struct{}{}
delete(l.ldapIdToStopChan, ldapId)
}
}
//autosync goroutine
func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
ticker := time.NewTicker(time.Duration(ldap.AutoSync) * time.Minute)
defer ticker.Stop()
for {
UpdateLdapSyncTime(ldap.Id)
//fetch all users
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
if err != nil {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
users, err := conn.GetLdapUsers(ldap.BaseDn)
if err != nil {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users))
if len(*failed) != 0 {
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
} else {
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
}
select {
case <-stopChan:
logs.Info(fmt.Sprintf("autoSync goroutine for %s stopped", ldap.Id))
return
case <-ticker.C:
}
}
}
//start all autosync goroutine for existing ldap servers in each organizations
func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
organizations := []*Organization{}
err := adapter.Engine.Desc("created_time").Find(&organizations)
if err != nil {
logs.Info("failed to Star up LdapAutoSynchronizer; ")
}
for _, org := range organizations {
for _, ldap := range GetLdaps(org.Name) {
if ldap.AutoSync != 0 {
l.StartAutoSync(ldap.Id)
}
}
}
}

93
object/oidc_discovery.go Normal file
View File

@ -0,0 +1,93 @@
// Copyright 2021 The casbin 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 (
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/astaxie/beego"
"gopkg.in/square/go-jose.v2"
)
type OidcDiscovery struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
JwksUri string `json:"jwks_uri"`
ResponseTypesSupported []string `json:"response_types_supported"`
ResponseModesSupported []string `json:"response_modes_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
SubjectTypesSupported []string `json:"subject_types_supported"`
IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
ScopesSupported []string `json:"scopes_supported"`
ClaimsSupported []string `json:"claims_supported"`
RequestParameterSupported bool `json:"request_parameter_supported"`
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
}
var oidcDiscovery OidcDiscovery
func init() {
origin := beego.AppConfig.String("origin")
// Examples:
// https://login.okta.com/.well-known/openid-configuration
// https://auth0.auth0.com/.well-known/openid-configuration
// https://accounts.google.com/.well-known/openid-configuration
// https://access.line.me/.well-known/openid-configuration
oidcDiscovery = OidcDiscovery{
Issuer: origin,
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", origin),
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", origin),
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", origin),
JwksUri: fmt.Sprintf("%s/api/certs", origin),
ResponseTypesSupported: []string{"id_token"},
ResponseModesSupported: []string{"login", "code", "link"},
GrantTypesSupported: []string{"password", "authorization_code"},
SubjectTypesSupported: []string{"public"},
IdTokenSigningAlgValuesSupported: []string{"RS256"},
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isGlobalAdmin", "isForbidden", "signupApplication", "ldap"},
RequestParameterSupported: true,
RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"},
}
}
func GetOidcDiscovery() OidcDiscovery {
return oidcDiscovery
}
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
cert := GetDefaultCert()
//follows the protocol rfc 7517(draft)
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
certPemBlock := []byte(cert.PublicKey)
certDerBlock, _ := pem.Decode(certPemBlock)
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
var jwk jose.JSONWebKey
jwk.Key = x509Cert.PublicKey
jwk.Certificates = []*x509.Certificate{x509Cert}
jwk.KeyID = cert.Name
var jwks jose.JSONWebKeySet
jwks.Keys = []jose.JSONWebKey{jwk}
return jwks, nil
}

View File

@ -15,7 +15,8 @@
package object
import (
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -24,13 +25,25 @@ type Organization struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
}
func GetOrganizationCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Organization{})
if err != nil {
panic(err)
}
return int(count)
}
func GetOrganizations(owner string) []*Organization {
@ -43,6 +56,17 @@ func GetOrganizations(owner string) []*Organization {
return organizations
}
func GetPaginationOrganizations(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Organization {
organizations := []*Organization{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&organizations)
if err != nil {
panic(err)
}
return organizations
}
func getOrganization(owner string, name string) *Organization {
if owner == "" || name == "" {
return nil
@ -56,9 +80,9 @@ func getOrganization(owner string, name string) *Organization {
if existed {
return &organization
} else {
return nil
}
return nil
}
func GetOrganization(id string) *Organization {
@ -66,12 +90,50 @@ func GetOrganization(id string) *Organization {
return getOrganization(owner, name)
}
func GetMaskedOrganization(organization *Organization) *Organization {
if organization == nil {
return nil
}
if organization.MasterPassword != "" {
organization.MasterPassword = "***"
}
return organization
}
func GetMaskedOrganizations(organizations []*Organization) []*Organization {
for _, organization := range organizations {
organization = GetMaskedOrganization(organization)
}
return organizations
}
func UpdateOrganization(id string, organization *Organization) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getOrganization(owner, name) == nil {
return false
}
if name == "built-in" {
organization.Name = name
}
if name != organization.Name {
applications := GetApplicationsByOrganizationName("admin", name)
for _, application := range applications {
application.Organization = organization.Name
UpdateApplication(application.GetId(), application)
}
}
if organization.MasterPassword != "" {
credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil {
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
organization.MasterPassword = hashedPassword
}
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
if err != nil {
panic(err)
@ -90,6 +152,10 @@ func AddOrganization(organization *Organization) bool {
}
func DeleteOrganization(organization *Organization) bool {
if organization.Name == "built-in" {
return false
}
affected, err := adapter.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{})
if err != nil {
panic(err)

129
object/permission.go Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type Permission 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"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
Resources []string `xorm:"mediumtext" json:"resources"`
Actions []string `xorm:"mediumtext" json:"actions"`
Effect string `xorm:"varchar(100)" json:"effect"`
IsEnabled bool `json:"isEnabled"`
}
func GetPermissionCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Permission{})
if err != nil {
panic(err)
}
return int(count)
}
func GetPermissions(owner string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner})
if err != nil {
panic(err)
}
return permissions
}
func GetPaginationPermissions(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Permission {
permissions := []*Permission{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&permissions)
if err != nil {
panic(err)
}
return permissions
}
func getPermission(owner string, name string) *Permission {
if owner == "" || name == "" {
return nil
}
permission := Permission{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&permission)
if err != nil {
panic(err)
}
if existed {
return &permission
} else {
return nil
}
}
func GetPermission(id string) *Permission {
owner, name := util.GetOwnerAndNameFromId(id)
return getPermission(owner, name)
}
func UpdatePermission(id string, permission *Permission) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getPermission(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(permission)
if err != nil {
panic(err)
}
return affected != 0
}
func AddPermission(permission *Permission) bool {
affected, err := adapter.Engine.Insert(permission)
if err != nil {
panic(err)
}
return affected != 0
}
func DeletePermission(permission *Permission) bool {
affected, err := adapter.Engine.ID(core.PK{permission.Owner, permission.Name}).Delete(&Permission{})
if err != nil {
panic(err)
}
return affected != 0
}
func (permission *Permission) GetId() string {
return fmt.Sprintf("%s/%s", permission.Owner, permission.Name)
}

View File

@ -17,7 +17,7 @@ package object
import (
"fmt"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -26,11 +26,14 @@ type Provider struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"`
Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
@ -42,24 +45,49 @@ type Provider struct {
TemplateCode string `xorm:"varchar(100)" json:"templateCode"`
AppId string `xorm:"varchar(100)" json:"appId"`
Endpoint string `xorm:"varchar(100)" json:"endpoint"`
Domain string `xorm:"varchar(100)" json:"domain"`
Bucket string `xorm:"varchar(100)" json:"bucket"`
Endpoint string `xorm:"varchar(1000)" json:"endpoint"`
IntranetEndpoint string `xorm:"varchar(100)" json:"intranetEndpoint"`
Domain string `xorm:"varchar(100)" json:"domain"`
Bucket string `xorm:"varchar(100)" json:"bucket"`
Metadata string `xorm:"mediumtext" json:"metadata"`
IdP string `xorm:"mediumtext" json:"idP"`
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
}
func getMaskedProvider(provider *Provider) *Provider {
p := &Provider{
Owner: provider.Owner,
Name: provider.Name,
CreatedTime: provider.CreatedTime,
DisplayName: provider.DisplayName,
Category: provider.Category,
Type: provider.Type,
ClientId: provider.ClientId,
func GetMaskedProvider(provider *Provider) *Provider {
if provider == nil {
return nil
}
return p
if provider.ClientSecret != "" {
provider.ClientSecret = "***"
}
if provider.ClientSecret2 != "" {
provider.ClientSecret2 = "***"
}
return provider
}
func GetMaskedProviders(providers []*Provider) []*Provider {
for _, provider := range providers {
provider = GetMaskedProvider(provider)
}
return providers
}
func GetProviderCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Provider{})
if err != nil {
panic(err)
}
return int(count)
}
func GetProviders(owner string) []*Provider {
@ -72,6 +100,17 @@ func GetProviders(owner string) []*Provider {
return providers
}
func GetPaginationProviders(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Provider {
providers := []*Provider{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&providers)
if err != nil {
panic(err)
}
return providers
}
func getProvider(owner string, name string) *Provider {
if owner == "" || name == "" {
return nil

View File

@ -34,7 +34,7 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
}
func (pi *ProviderItem) IsProviderVisible() bool {
return pi.Provider.Category == "OAuth"
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
}
func (pi *ProviderItem) isProviderPrompted() bool {

View File

@ -15,19 +15,85 @@
package object
import (
"github.com/casbin/casdoor/util"
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/util"
)
type Records struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Record util.Record `xorm:"extends"`
var logPostOnly bool
func init() {
var err error
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
if err != nil {
//panic(err)
}
}
func AddRecord(record *util.Record) bool {
records := new(Records)
records.Record = *record
type Record struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
affected, err := adapter.Engine.Insert(records)
Owner string `xorm:"varchar(100) index" json:"owner"`
Name string `xorm:"varchar(100) index" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Organization string `xorm:"varchar(100)" json:"organization"`
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
User string `xorm:"varchar(100)" json:"user"`
Method string `xorm:"varchar(100)" json:"method"`
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
Action string `xorm:"varchar(1000)" json:"action"`
ExtendedUser *User `xorm:"-" json:"extendedUser"`
IsTriggered bool `json:"isTriggered"`
}
func NewRecord(ctx *context.Context) *Record {
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
if len(requestUri) > 1000 {
requestUri = requestUri[0:1000]
}
record := Record{
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
ClientIp: ip,
User: "",
Method: ctx.Request.Method,
RequestUri: requestUri,
Action: action,
IsTriggered: false,
}
return &record
}
func AddRecord(record *Record) bool {
if logPostOnly {
if record.Method == "GET" {
return false
}
}
if record.Organization == "app" {
return false
}
record.Owner = record.Organization
errWebhook := SendWebhooks(record)
if errWebhook == nil {
record.IsTriggered = true
} else {
fmt.Println(errWebhook)
}
affected, err := adapter.Engine.Insert(record)
if err != nil {
panic(err)
}
@ -35,8 +101,9 @@ func AddRecord(record *util.Record) bool {
return affected != 0
}
func GetRecordCount() int {
count, err := adapter.Engine.Count(&Records{})
func GetRecordCount(field, value string) int {
session := GetSession("", -1, -1, field, value, "", "")
count, err := session.Count(&Record{})
if err != nil {
panic(err)
}
@ -44,8 +111,8 @@ func GetRecordCount() int {
return int(count)
}
func GetRecords() []*Records {
records := []*Records{}
func GetRecords() []*Record {
records := []*Record{}
err := adapter.Engine.Desc("id").Find(&records)
if err != nil {
panic(err)
@ -54,8 +121,19 @@ func GetRecords() []*Records {
return records
}
func GetRecordsByField(record *Records) []*Records {
records := []*Records{}
func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string) []*Record {
records := []*Record{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&records)
if err != nil {
panic(err)
}
return records
}
func GetRecordsByField(record *Record) []*Record {
records := []*Record{}
err := adapter.Engine.Find(&records, record)
if err != nil {
panic(err)
@ -63,3 +141,34 @@ func GetRecordsByField(record *Records) []*Records {
return records
}
func SendWebhooks(record *Record) error {
webhooks := getWebhooksByOrganization(record.Organization)
for _, webhook := range webhooks {
if !webhook.IsEnabled {
continue
}
matched := false
for _, event := range webhook.Events {
if record.Action == event {
matched = true
break
}
}
if matched {
if webhook.IsUserExtended {
user := getUser(record.Organization, record.User)
record.ExtendedUser = user
}
err := sendWebhook(webhook, record)
if err != nil {
return err
}
}
}
return nil
}

145
object/resource.go Normal file
View File

@ -0,0 +1,145 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type Resource 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"`
User string `xorm:"varchar(100)" json:"user"`
Provider string `xorm:"varchar(100)" json:"provider"`
Application string `xorm:"varchar(100)" json:"application"`
Tag string `xorm:"varchar(100)" json:"tag"`
Parent string `xorm:"varchar(100)" json:"parent"`
FileName string `xorm:"varchar(100)" json:"fileName"`
FileType string `xorm:"varchar(100)" json:"fileType"`
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
FileSize int `json:"fileSize"`
Url string `xorm:"varchar(1000)" json:"url"`
Description string `xorm:"varchar(1000)" json:"description"`
}
func GetResourceCount(owner, user, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Resource{User: user})
if err != nil {
panic(err)
}
return int(count)
}
func GetResources(owner string, user string) []*Resource {
if owner == "built-in" {
owner = ""
user = ""
}
resources := []*Resource{}
err := adapter.Engine.Desc("created_time").Find(&resources, &Resource{Owner: owner, User: user})
if err != nil {
panic(err)
}
return resources
}
func GetPaginationResources(owner, user string, offset, limit int, field, value, sortField, sortOrder string) []*Resource {
if owner == "built-in" {
owner = ""
user = ""
}
resources := []*Resource{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&resources, &Resource{User: user})
if err != nil {
panic(err)
}
return resources
}
func getResource(owner string, name string) *Resource {
resource := Resource{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&resource)
if err != nil {
panic(err)
}
if existed {
return &resource
}
return nil
}
func GetResource(id string) *Resource {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
return getResource(owner, name)
}
func UpdateResource(id string, resource *Resource) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
if getResource(owner, name) == nil {
return false
}
_, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(resource)
if err != nil {
panic(err)
}
//return affected != 0
return true
}
func AddResource(resource *Resource) bool {
affected, err := adapter.Engine.Insert(resource)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteResource(resource *Resource) bool {
affected, err := adapter.Engine.ID(core.PK{resource.Owner, resource.Name}).Delete(&Resource{})
if err != nil {
panic(err)
}
return affected != 0
}
func (resource *Resource) GetId() string {
return fmt.Sprintf("%s/%s", resource.Owner, resource.Name)
}
func AddOrUpdateResource(resource *Resource) bool {
if getResource(resource.Owner, resource.Name) == nil {
return AddResource(resource)
} else {
return UpdateResource(resource.GetId(), resource)
}
}

123
object/role.go Normal file
View File

@ -0,0 +1,123 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type Role 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"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`
IsEnabled bool `json:"isEnabled"`
}
func GetRoleCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Role{})
if err != nil {
panic(err)
}
return int(count)
}
func GetRoles(owner string) []*Role {
roles := []*Role{}
err := adapter.Engine.Desc("created_time").Find(&roles, &Role{Owner: owner})
if err != nil {
panic(err)
}
return roles
}
func GetPaginationRoles(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Role {
roles := []*Role{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&roles)
if err != nil {
panic(err)
}
return roles
}
func getRole(owner string, name string) *Role {
if owner == "" || name == "" {
return nil
}
role := Role{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&role)
if err != nil {
panic(err)
}
if existed {
return &role
} else {
return nil
}
}
func GetRole(id string) *Role {
owner, name := util.GetOwnerAndNameFromId(id)
return getRole(owner, name)
}
func UpdateRole(id string, role *Role) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getRole(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(role)
if err != nil {
panic(err)
}
return affected != 0
}
func AddRole(role *Role) bool {
affected, err := adapter.Engine.Insert(role)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteRole(role *Role) bool {
affected, err := adapter.Engine.ID(core.PK{role.Owner, role.Name}).Delete(&Role{})
if err != nil {
panic(err)
}
return affected != 0
}
func (role *Role) GetId() string {
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
}

135
object/saml.go Normal file
View File

@ -0,0 +1,135 @@
// Copyright 2021 The casbin 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 (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"net/url"
"regexp"
"strings"
"github.com/astaxie/beego"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
func ParseSamlResponse(samlResponse string, providerType string) (string, error) {
samlResponse, _ = url.QueryUnescape(samlResponse)
sp, err := buildSp(&Provider{Type: providerType}, samlResponse)
if err != nil {
return "", err
}
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
if err != nil {
panic(err)
}
return assertionInfo.NameID, nil
}
func GenerateSamlLoginUrl(id, relayState string) (string, string, error) {
provider := GetProvider(id)
if provider.Category != "SAML" {
return "", "", fmt.Errorf("Provider %s's category is not SAML", provider.Name)
}
sp, err := buildSp(provider, "")
if err != nil {
return "", "", err
}
auth := ""
method := ""
if provider.EnableSignAuthnRequest {
post, err := sp.BuildAuthBodyPost(relayState)
if err != nil {
return "", "", err
}
auth = string(post[:])
method = "POST"
} else {
auth, err = sp.BuildAuthURL(relayState)
if err != nil {
return "", "", err
}
method = "GET"
}
return auth, method, nil
}
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
origin := beego.AppConfig.String("origin")
certEncodedData := ""
if samlResponse != "" {
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
} else if provider.IdP != "" {
certEncodedData = provider.IdP
}
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
if err != nil {
return nil, err
}
idpCert, err := x509.ParseCertificate(certData)
if err != nil {
return nil, err
}
certStore.Roots = append(certStore.Roots, idpCert)
sp := &saml2.SAMLServiceProvider{
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
IDPCertificateStore: &certStore,
SignAuthnRequests: false,
SPKeyStore: dsig.RandomKeyStoreForTest(),
}
if provider.Endpoint != "" {
sp.IdentityProviderSSOURL = provider.Endpoint
sp.IdentityProviderIssuer = provider.IssuerUrl
}
if provider.EnableSignAuthnRequest {
sp.SignAuthnRequests = true
sp.SPKeyStore = buildSpKeyStore()
}
return sp, nil
}
func parseSamlResponse(samlResponse string, providerType string) string {
de, err := base64.StdEncoding.DecodeString(samlResponse)
if err != nil {
panic(err)
}
deStr := strings.Replace(string(de), "\n", "", -1)
tagMap := map[string]string{
"Aliyun IDaaS": "ds",
"Keycloak": "dsig",
}
tag := tagMap[providerType]
expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
return res[1]
}
func buildSpKeyStore() dsig.X509KeyStore {
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
if err != nil {
panic(err)
}
return &dsig.TLSCertKeyStore {
PrivateKey: keyPair.PrivateKey,
Certificate: keyPair.Certificate,
}
}

View File

@ -14,24 +14,21 @@
package object
import (
"fmt"
import "github.com/casdoor/go-sms-sender"
"github.com/casdoor/go-sms-sender"
)
func SendCodeToPhone(provider *Provider, phone, code string) string {
client := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode, provider.AppId)
if client == nil {
return fmt.Sprintf("Unsupported provide type: %s", provider.Type)
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
if err != nil {
return err
}
param := make(map[string]string)
params := map[string]string{}
if provider.Type == go_sms_sender.TencentCloud {
param["0"] = code
params["0"] = content
} else {
param["code"] = code
params["code"] = content
}
client.SendMessage(param, phone)
return ""
err = client.SendMessage(params, phoneNumbers...)
return err
}

View File

@ -19,26 +19,31 @@ import (
"fmt"
"strings"
"github.com/casbin/casdoor/storage"
"github.com/casbin/casdoor/util"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/storage"
"github.com/casdoor/casdoor/util"
)
func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, error) {
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
if storageProvider == nil {
return "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
}
var isCloudIntranet bool
if provider.Domain == "" {
provider.Domain = storageProvider.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
path := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
_, err := storageProvider.Put(path, fileBuffer)
func init() {
var err error
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
if err != nil {
return "", err
//panic(err)
}
}
func getProviderEndpoint(provider *Provider) string {
endpoint := provider.Endpoint
if provider.IntranetEndpoint != "" && isCloudIntranet {
endpoint = provider.IntranetEndpoint
}
return endpoint
}
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
host := ""
if provider.Type != "Local File System" {
@ -52,6 +57,66 @@ func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
host = util.UrlJoin(provider.Domain, "/files")
}
fileUrl := fmt.Sprintf("%s?time=%s", util.UrlJoin(host, path), util.GetCurrentUnixTime())
return fileUrl, nil
fileUrl := util.UrlJoin(host, objectKey)
if hasTimestamp {
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
}
return fileUrl, objectKey
}
func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
endpoint := getProviderEndpoint(provider)
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
if storageProvider == nil {
return "", "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
}
if provider.Domain == "" {
provider.Domain = storageProvider.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
fileUrl, objectKey := getUploadFileUrl(provider, fullFilePath, true)
_, err := storageProvider.Put(objectKey, fileBuffer)
if err != nil {
return "", "", err
}
return fileUrl, objectKey, nil
}
func UploadFileSafe(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
var fileUrl string
var objectKey string
var err error
times := 0
for {
fileUrl, objectKey, err = uploadFile(provider, fullFilePath, fileBuffer)
if err != nil {
times += 1
if times >= 5 {
return "", "", err
}
} else {
break
}
}
return fileUrl, objectKey, nil
}
func DeleteFile(provider *Provider, objectKey string) error {
endpoint := getProviderEndpoint(provider)
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
if storageProvider == nil {
return fmt.Errorf("the provider type: %s is not supported", provider.Type)
}
if provider.Domain == "" {
provider.Domain = storageProvider.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
return storageProvider.Delete(objectKey)
}

208
object/syncer.go Normal file
View File

@ -0,0 +1,208 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type TableColumn struct {
Name string `json:"name"`
Type string `json:"type"`
CasdoorName string `json:"casdoorName"`
IsHashed bool `json:"isHashed"`
Values []string `json:"values"`
}
type Syncer 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"`
Organization string `xorm:"varchar(100)" json:"organization"`
Type string `xorm:"varchar(100)" json:"type"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"`
TablePrimaryKey string `xorm:"varchar(100)" json:"tablePrimaryKey"`
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
ErrorText string `xorm:"mediumtext" json:"errorText"`
SyncInterval int `json:"syncInterval"`
IsEnabled bool `json:"isEnabled"`
Adapter *Adapter `xorm:"-" json:"-"`
}
func GetSyncerCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Syncer{})
if err != nil {
panic(err)
}
return int(count)
}
func GetSyncers(owner string) []*Syncer {
syncers := []*Syncer{}
err := adapter.Engine.Desc("created_time").Find(&syncers, &Syncer{Owner: owner})
if err != nil {
panic(err)
}
return syncers
}
func GetPaginationSyncers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer {
syncers := []*Syncer{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&syncers)
if err != nil {
panic(err)
}
return syncers
}
func getSyncer(owner string, name string) *Syncer {
if owner == "" || name == "" {
return nil
}
syncer := Syncer{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&syncer)
if err != nil {
panic(err)
}
if existed {
return &syncer
} else {
return nil
}
}
func GetSyncer(id string) *Syncer {
owner, name := util.GetOwnerAndNameFromId(id)
return getSyncer(owner, name)
}
func GetMaskedSyncer(syncer *Syncer) *Syncer {
if syncer == nil {
return nil
}
if syncer.Password != "" {
syncer.Password = "***"
}
return syncer
}
func GetMaskedSyncers(syncers []*Syncer) []*Syncer {
for _, syncer := range syncers {
syncer = GetMaskedSyncer(syncer)
}
return syncers
}
func UpdateSyncer(id string, syncer *Syncer) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getSyncer(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(syncer)
if err != nil {
panic(err)
}
if affected == 1 {
addSyncerJob(syncer)
}
return affected != 0
}
func updateSyncerErrorText(syncer *Syncer, line string) bool {
s := getSyncer(syncer.Owner, syncer.Name)
if s == nil {
return false
}
s.ErrorText = s.ErrorText + line
affected, err := adapter.Engine.ID(core.PK{s.Owner, s.Name}).Cols("error_text").Update(s)
if err != nil {
panic(err)
}
return affected != 0
}
func AddSyncer(syncer *Syncer) bool {
affected, err := adapter.Engine.Insert(syncer)
if err != nil {
panic(err)
}
if affected == 1 {
addSyncerJob(syncer)
}
return affected != 0
}
func DeleteSyncer(syncer *Syncer) bool {
affected, err := adapter.Engine.ID(core.PK{syncer.Owner, syncer.Name}).Delete(&Syncer{})
if err != nil {
panic(err)
}
if affected == 1 {
deleteSyncerJob(syncer)
}
return affected != 0
}
func (syncer *Syncer) GetId() string {
return fmt.Sprintf("%s/%s", syncer.Owner, syncer.Name)
}
func (syncer *Syncer) getTableColumnsTypeMap() map[string]string {
m := map[string]string{}
for _, tableColumn := range syncer.TableColumns {
m[tableColumn.Name] = tableColumn.Type
}
return m
}
func (syncer *Syncer) getTable() string {
if syncer.DatabaseType == "mssql" {
return fmt.Sprintf("[%s]", syncer.Table)
} else {
return syncer.Table
}
}

View File

@ -12,20 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package original
package object
type Affiliation struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Name string `xorm:"varchar(128)" json:"name"`
}
func (Affiliation) TableName() string {
return affiliationTableName
}
func getAffiliations() []*Affiliation {
func (syncer *Syncer) getAffiliations() []*Affiliation {
affiliations := []*Affiliation{}
err := adapter.Engine.Asc("id").Find(&affiliations)
err := syncer.Adapter.Engine.Table(syncer.AffiliationTable).Asc("id").Find(&affiliations)
if err != nil {
panic(err)
}
@ -33,8 +29,8 @@ func getAffiliations() []*Affiliation {
return affiliations
}
func getAffiliationMap() ([]*Affiliation, map[int]string) {
affiliations := getAffiliations()
func (syncer *Syncer) getAffiliationMap() ([]*Affiliation, map[int]string) {
affiliations := syncer.getAffiliations()
m := map[int]string{}
for _, affiliation := range affiliations {

69
object/syncer_cron.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/robfig/cron/v3"
)
var cronMap map[string]*cron.Cron
func init() {
cronMap = map[string]*cron.Cron{}
}
func getCronMap(name string) *cron.Cron {
m, ok := cronMap[name]
if !ok {
m = cron.New()
cronMap[name] = m
}
return m
}
func clearCron(name string) {
cron, ok := cronMap[name]
if ok {
cron.Stop()
delete(cronMap, name)
}
}
func addSyncerJob(syncer *Syncer) {
deleteSyncerJob(syncer)
if !syncer.IsEnabled {
return
}
syncer.initAdapter()
syncer.syncUsers()
schedule := fmt.Sprintf("@every %ds", syncer.SyncInterval)
cron := getCronMap(syncer.Name)
_, err := cron.AddFunc(schedule, syncer.syncUsers)
if err != nil {
panic(err)
}
cron.Start()
}
func deleteSyncerJob(syncer *Syncer) {
clearCron(syncer.Name)
}

View File

@ -0,0 +1,61 @@
// Copyright 2021 The casbin 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"
func getDbSyncerForUser(user *User) *Syncer {
syncers := GetSyncers("admin")
for _, syncer := range syncers {
if syncer.Organization == user.Owner && syncer.IsEnabled && syncer.Type == "Database" {
return syncer
}
}
return nil
}
func getEnabledSyncerForOrganization(organization string) *Syncer {
syncers := GetSyncers("admin")
for _, syncer := range syncers {
if syncer.Organization == organization && syncer.IsEnabled {
return syncer
}
}
return nil
}
func AddUserToOriginalDatabase(user *User) {
syncer := getEnabledSyncerForOrganization(user.Owner)
if syncer == nil {
return
}
updatedOUser := syncer.createOriginalUserFromUser(user)
syncer.addUser(updatedOUser)
fmt.Printf("Add from user to oUser: %v\n", updatedOUser)
}
func UpdateUserToOriginalDatabase(user *User) {
syncer := getEnabledSyncerForOrganization(user.Owner)
if syncer == nil {
return
}
newUser := GetUser(user.GetId())
updatedOUser := syncer.createOriginalUserFromUser(newUser)
syncer.updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
}

97
object/syncer_sync.go Normal file
View File

@ -0,0 +1,97 @@
// Copyright 2021 The casbin 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"
"time"
)
func (syncer *Syncer) syncUsers() {
fmt.Printf("Running syncUsers()..\n")
users, userMap := syncer.getUserMap()
oUsers, oUserMap, err := syncer.getOriginalUserMap()
if err != nil {
fmt.Printf(err.Error())
timestamp := time.Now().Format("2006-01-02 15:04:05")
line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error())
updateSyncerErrorText(syncer, line)
return
}
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
var affiliationMap map[int]string
if syncer.AffiliationTable != "" {
_, affiliationMap = syncer.getAffiliationMap()
}
newUsers := []*User{}
for _, oUser := range oUsers {
id := oUser.Id
if _, ok := userMap[id]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
} else {
user := userMap[id]
oHash := syncer.calculateHash(oUser)
if user.Hash == user.PreHash {
if user.Hash != oHash {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
syncer.updateUserForOriginalFields(updatedUser)
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
}
} else {
if user.PreHash == oHash {
updatedOUser := syncer.createOriginalUserFromUser(user)
syncer.updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
// update preHash
user.PreHash = user.Hash
SetUserField(user, "pre_hash", user.PreHash)
} else {
if user.Hash == oHash {
// update preHash
user.PreHash = user.Hash
SetUserField(user, "pre_hash", user.PreHash)
} else {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
syncer.updateUserForOriginalFields(updatedUser)
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
}
}
}
}
}
AddUsersInBatch(newUsers)
for _, user := range users {
id := user.Id
if _, ok := oUserMap[id]; !ok {
newOUser := syncer.createOriginalUserFromUser(user)
syncer.addUser(newOUser)
fmt.Printf("New oUser: %v\n", newOUser)
}
}
}

168
object/syncer_user.go Normal file
View File

@ -0,0 +1,168 @@
// Copyright 2021 The casbin 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"
"strings"
"time"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type OriginalUser = User
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
sql := fmt.Sprintf("select * from %s", syncer.getTable())
results, err := syncer.Adapter.Engine.QueryString(sql)
if err != nil {
return nil, err
}
return syncer.getOriginalUsersFromMap(results), nil
}
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser, error) {
users, err := syncer.getOriginalUsers()
if err != nil {
return users, nil, err
}
m := map[string]*OriginalUser{}
for _, user := range users {
m[user.Id] = user
}
return users, m, nil
}
func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
m := syncer.getMapFromOriginalUser(user)
keyString, valueString := syncer.getSqlKeyValueStringFromMap(m)
sql := fmt.Sprintf("insert into %s (%s) values (%s)", syncer.getTable(), keyString, valueString)
res, err := syncer.Adapter.Engine.Exec(sql)
if err != nil {
return false, err
}
affected, err := res.RowsAffected()
if err != nil {
return false, err
}
return affected != 0, nil
}
/*func (syncer *Syncer) getOriginalColumns() []string {
res := []string{}
for _, tableColumn := range syncer.TableColumns {
if tableColumn.CasdoorName != "Id" {
res = append(res, tableColumn.Name)
}
}
return res
}*/
func (syncer *Syncer) getCasdoorColumns() []string {
res := []string{}
for _, tableColumn := range syncer.TableColumns {
if tableColumn.CasdoorName != "Id" {
v := util.CamelToSnakeCase(tableColumn.CasdoorName)
res = append(res, v)
}
}
return res
}
func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) {
m := syncer.getMapFromOriginalUser(user)
pkValue := m[syncer.TablePrimaryKey]
delete(m, syncer.TablePrimaryKey)
setString := syncer.getSqlSetStringFromMap(m)
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, syncer.TablePrimaryKey, pkValue)
res, err := syncer.Adapter.Engine.Exec(sql)
if err != nil {
return false, err
}
affected, err := res.RowsAffected()
if err != nil {
return false, err
}
return affected != 0, nil
}
func (syncer *Syncer) updateUserForOriginalFields(user *User) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(user.GetId())
oldUser := getUserById(owner, name)
if oldUser == nil {
return false, nil
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
columns := syncer.getCasdoorColumns()
columns = append(columns, "affiliation", "hash", "pre_hash")
affected, err := adapter.Engine.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
if err != nil {
return false, err
}
return affected != 0, nil
}
func (syncer *Syncer) calculateHash(user *OriginalUser) string {
values := []string{}
m := syncer.getMapFromOriginalUser(user)
for _, tableColumn := range syncer.TableColumns {
if tableColumn.IsHashed {
values = append(values, m[tableColumn.Name])
}
}
s := strings.Join(values, "|")
return util.GetMd5Hash(s)
}
func (syncer *Syncer) initAdapter() {
if syncer.Adapter == nil {
var dataSourceName string
if syncer.DatabaseType == "mssql" {
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else {
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
}
if !isCloudIntranet {
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
}
syncer.Adapter = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
}
}
func RunSyncUsersJob() {
syncers := GetSyncers("admin")
for _, syncer := range syncers {
addSyncerJob(syncer)
}
time.Sleep(time.Duration(1<<63 - 1))
}

View File

@ -12,38 +12,27 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package original
package object
import (
"fmt"
"testing"
"time"
"github.com/casbin/casdoor/object"
)
func TestGetUsers(t *testing.T) {
initConfig()
initAdapter()
InitConfig()
users := getUsersOriginal()
syncers := GetSyncers("admin")
syncer := syncers[0]
syncer.initAdapter()
users := syncer.getOriginalUsers()
for _, user := range users {
fmt.Printf("%v\n", user)
}
}
func TestSyncUsers(t *testing.T) {
initConfig()
initAdapter()
object.InitAdapter()
InitConfig()
syncUsers()
// run at every minute
schedule := "* * * * *"
err := ctab.AddJob(schedule, syncUsers)
if err != nil {
panic(err)
}
time.Sleep(time.Duration(1<<63 - 1))
RunSyncUsersJob()
}

250
object/syncer_util.go Normal file
View File

@ -0,0 +1,250 @@
// Copyright 2021 The casbin 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"
"strconv"
"strings"
"github.com/casdoor/casdoor/util"
)
func (syncer *Syncer) getFullAvatarUrl(avatar string) string {
if syncer.AvatarBaseUrl == "" {
return avatar
}
if !strings.HasPrefix(avatar, "http") {
return fmt.Sprintf("%s%s", syncer.AvatarBaseUrl, avatar)
}
return avatar
}
func (syncer *Syncer) getPartialAvatarUrl(avatar string) string {
if strings.HasPrefix(avatar, syncer.AvatarBaseUrl) {
return avatar[len(syncer.AvatarBaseUrl):]
}
return avatar
}
func (syncer *Syncer) createUserFromOriginalUser(originalUser *OriginalUser, affiliationMap map[int]string) *User {
user := *originalUser
user.Owner = syncer.Organization
if user.Name == "" {
user.Name = originalUser.Id
}
if user.CreatedTime == "" {
user.CreatedTime = util.GetCurrentTime()
}
if user.Type == "" {
user.Type = "normal-user"
}
user.Avatar = syncer.getFullAvatarUrl(user.Avatar)
if affiliationMap != nil {
if originalUser.Score != 0 {
affiliation, ok := affiliationMap[originalUser.Score]
if !ok {
panic(fmt.Sprintf("Affiliation not found: %d", originalUser.Score))
}
user.Affiliation = affiliation
}
}
if user.Properties == nil {
user.Properties = map[string]string{}
}
return &user
}
func (syncer *Syncer) createOriginalUserFromUser(user *User) *OriginalUser {
originalUser := *user
originalUser.Avatar = syncer.getPartialAvatarUrl(user.Avatar)
return &originalUser
}
func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
switch key {
case "Name":
user.Name = value
case "CreatedTime":
user.CreatedTime = value
case "UpdatedTime":
user.UpdatedTime = value
case "Id":
user.Id = value
case "Type":
user.Type = value
case "Password":
user.Password = value
case "PasswordSalt":
user.PasswordSalt = value
case "DisplayName":
user.DisplayName = value
case "Avatar":
user.Avatar = syncer.getPartialAvatarUrl(value)
case "PermanentAvatar":
user.PermanentAvatar = value
case "Email":
user.Email = value
case "Phone":
user.Phone = value
case "Location":
user.Location = value
case "Address":
user.Address = []string{value}
case "Affiliation":
user.Affiliation = value
case "Title":
user.Title = value
case "IdCardType":
user.IdCardType = value
case "IdCard":
user.IdCard = value
case "Homepage":
user.Homepage = value
case "Bio":
user.Bio = value
case "Tag":
user.Tag = value
case "Region":
user.Region = value
case "Language":
user.Language = value
case "Gender":
user.Gender = value
case "Birthday":
user.Birthday = value
case "Education":
user.Education = value
case "Score":
user.Score = util.ParseInt(value)
case "Ranking":
user.Ranking = util.ParseInt(value)
case "IsDefaultAvatar":
user.IsDefaultAvatar = util.ParseBool(value)
case "IsOnline":
user.IsOnline = util.ParseBool(value)
case "IsAdmin":
user.IsAdmin = util.ParseBool(value)
case "IsGlobalAdmin":
user.IsGlobalAdmin = util.ParseBool(value)
case "IsForbidden":
user.IsForbidden = util.ParseBool(value)
case "IsDeleted":
user.IsDeleted = util.ParseBool(value)
case "CreatedIp":
user.CreatedIp = value
}
}
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser {
users := []*OriginalUser{}
for _, result := range results {
originalUser := &OriginalUser{
Address: []string{},
Properties: map[string]string{},
}
for _, tableColumn := range syncer.TableColumns {
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name])
}
users = append(users, originalUser)
}
return users
}
func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]string {
m := map[string]string{}
m["Name"] = user.Name
m["CreatedTime"] = user.CreatedTime
m["UpdatedTime"] = user.UpdatedTime
m["Id"] = user.Id
m["Type"] = user.Type
m["Password"] = user.Password
m["PasswordSalt"] = user.PasswordSalt
m["DisplayName"] = user.DisplayName
m["Avatar"] = syncer.getFullAvatarUrl(user.Avatar)
m["PermanentAvatar"] = user.PermanentAvatar
m["Email"] = user.Email
m["Phone"] = user.Phone
m["Location"] = user.Location
m["Address"] = strings.Join(user.Address, "|")
m["Affiliation"] = user.Affiliation
m["Title"] = user.Title
m["IdCardType"] = user.IdCardType
m["IdCard"] = user.IdCard
m["Homepage"] = user.Homepage
m["Bio"] = user.Bio
m["Tag"] = user.Tag
m["Region"] = user.Region
m["Language"] = user.Language
m["Gender"] = user.Gender
m["Birthday"] = user.Birthday
m["Education"] = user.Education
m["Score"] = strconv.Itoa(user.Score)
m["Ranking"] = strconv.Itoa(user.Ranking)
m["IsDefaultAvatar"] = util.BoolToString(user.IsDefaultAvatar)
m["IsOnline"] = util.BoolToString(user.IsOnline)
m["IsAdmin"] = util.BoolToString(user.IsAdmin)
m["IsGlobalAdmin"] = util.BoolToString(user.IsGlobalAdmin)
m["IsForbidden"] = util.BoolToString(user.IsForbidden)
m["IsDeleted"] = util.BoolToString(user.IsDeleted)
m["CreatedIp"] = user.CreatedIp
m2 := map[string]string{}
for _, tableColumn := range syncer.TableColumns {
m2[tableColumn.Name] = m[tableColumn.CasdoorName]
}
return m2
}
func (syncer *Syncer) getSqlSetStringFromMap(m map[string]string) string {
typeMap := syncer.getTableColumnsTypeMap()
tokens := []string{}
for k, v := range m {
token := fmt.Sprintf("%s = %s", k, v)
if typeMap[k] == "string" {
token = fmt.Sprintf("%s = '%s'", k, v)
}
tokens = append(tokens, token)
}
return strings.Join(tokens, ", ")
}
func (syncer *Syncer) getSqlKeyValueStringFromMap(m map[string]string) (string, string) {
typeMap := syncer.getTableColumnsTypeMap()
keys := []string{}
values := []string{}
for k, v := range m {
if typeMap[k] == "string" {
v = fmt.Sprintf("'%s'", v)
}
keys = append(keys, k)
values = append(values, v)
}
return strings.Join(keys, ", "), strings.Join(values, ", ")
}

View File

@ -12,21 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package original
package object
import "github.com/casbin/casdoor/object"
func getUsers() []*object.User {
users := object.GetUsers(orgName)
func (syncer *Syncer) getUsers() []*User {
users := GetUsers(syncer.Organization)
return users
}
func getUserMap() ([]*object.User, map[string]*object.User) {
users := getUsers()
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User) {
users := syncer.getUsers()
m := map[string]*object.User{}
m := map[string]*User{}
for _, user := range users {
m[user.Name] = user
m[user.Id] = user
}
return users, m
}

View File

@ -15,9 +15,12 @@
package object
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -35,18 +38,32 @@ type Token struct {
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
Code string `xorm:"varchar(100)" json:"code"`
AccessToken string `xorm:"mediumtext" json:"accessToken"`
ExpiresIn int `json:"expiresIn"`
Scope string `xorm:"varchar(100)" json:"scope"`
TokenType string `xorm:"varchar(100)" json:"tokenType"`
Code string `xorm:"varchar(100)" json:"code"`
AccessToken string `xorm:"mediumtext" json:"accessToken"`
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
ExpiresIn int `json:"expiresIn"`
Scope string `xorm:"varchar(100)" json:"scope"`
TokenType string `xorm:"varchar(100)" json:"tokenType"`
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
}
type TokenWrapper struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
AccessToken string `json:"access_token"`
IdToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
func GetTokenCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Token{})
if err != nil {
panic(err)
}
return int(count)
}
func GetTokens(owner string) []*Token {
@ -59,6 +76,17 @@ func GetTokens(owner string) []*Token {
return tokens
}
func GetPaginationTokens(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Token {
tokens := []*Token{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&tokens)
if err != nil {
panic(err)
}
return tokens
}
func getToken(owner string, name string) *Token {
if owner == "" || name == "" {
return nil
@ -72,9 +100,9 @@ func getToken(owner string, name string) *Token {
if existed {
return &token
} else {
return nil
}
return nil
}
func getTokenByCode(code string) *Token {
@ -86,9 +114,9 @@ func getTokenByCode(code string) *Token {
if existed {
return &token
} else {
return nil
}
return nil
}
func GetToken(id string) *Token {
@ -146,17 +174,25 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
}
}
if !validUri {
return "redirect_uri doesn't exist in the allowed Redirect URL list", application
return fmt.Sprintf("Redirect URI: \"%s\" doesn't exist in the allowed Redirect URI list", redirectUri), application
}
// Mask application for /api/get-app-login
application.ClientSecret = ""
return "", application
}
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string) *Code {
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string) *Code {
user := GetUser(userId)
if user == nil {
return &Code{
Message: "Invalid user_id",
Message: fmt.Sprintf("The user: %s doesn't exist", userId),
Code: "",
}
}
if user.IsForbidden {
return &Code{
Message: "error: the user is forbidden to sign in, please contact the administrator",
Code: "",
}
}
@ -169,23 +205,29 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
accessToken, err := generateJwtToken(application, user)
accessToken, refreshToken, err := generateJwtToken(application, user, nonce, scope)
if err != nil {
panic(err)
}
if challenge == "null" {
challenge = ""
}
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: scope,
TokenType: "Bearer",
Owner: application.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: scope,
TokenType: "Bearer",
CodeChallenge: challenge,
}
AddToken(token)
@ -195,7 +237,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string) *TokenWrapper {
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string) *TokenWrapper {
application := GetApplicationByClientId(clientId)
if application == nil {
return &TokenWrapper{
@ -251,13 +293,122 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
Scope: "",
}
}
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return &TokenWrapper{
AccessToken: "error: incorrect code_verifier",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
AccessToken: token.AccessToken,
IdToken: token.AccessToken,
RefreshToken: token.RefreshToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
}
return tokenWrapper
}
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string) *TokenWrapper {
// check parameters
if grantType != "refresh_token" {
return &TokenWrapper{
AccessToken: "error: grant_type should be \"refresh_token\"",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
application := GetApplicationByClientId(clientId)
if application == nil {
return &TokenWrapper{
AccessToken: "error: invalid client_id",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
if application.ClientSecret != clientSecret {
return &TokenWrapper{
AccessToken: "error: invalid client_secret",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
// check whether the refresh token is valid, and has not expired.
token := Token{RefreshToken: refreshToken}
existed, err := adapter.Engine.Get(&token)
if err != nil || !existed {
return &TokenWrapper{
AccessToken: "error: invalid refresh_token",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
cert := getCertByApplication(application)
_, err = ParseJwtToken(refreshToken, cert)
if err != nil {
return &TokenWrapper{
AccessToken: fmt.Sprintf("error: %s", err.Error()),
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
// generate a new token
user := getUser(application.Organization, token.User)
if user.IsForbidden {
return &TokenWrapper{
AccessToken: "error: the user is forbidden to sign in, please contact the administrator",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope)
if err != nil {
panic(err)
}
newToken := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: newAccessToken,
RefreshToken: newRefreshToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: scope,
TokenType: "Bearer",
}
AddToken(newToken)
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,
IdToken: token.AccessToken,
RefreshToken: token.RefreshToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
}
return tokenWrapper
}
// PkceChallenge: base64-URL-encoded SHA256 hash of verifier, per rfc 7636
func pkceChallenge(verifier string) string {
sum := sha256.Sum256([]byte(verifier))
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
return challenge
}

View File

@ -15,48 +15,127 @@
package object
import (
_ "embed"
"fmt"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/astaxie/beego"
"github.com/golang-jwt/jwt/v4"
)
var jwtSecret = []byte("CasdoorSecret")
type Claims struct {
User
jwt.StandardClaims
*User
Nonce string `json:"nonce,omitempty"`
Tag string `json:"tag,omitempty"`
Scope string `json:"scope,omitempty"`
jwt.RegisteredClaims
}
func generateJwtToken(application *Application, user *User) (string, error) {
type UserShort struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
}
type ClaimsShort struct {
*UserShort
Nonce string `json:"nonce,omitempty"`
Scope string `json:"scope,omitempty"`
jwt.RegisteredClaims
}
func getShortUser(user *User) *UserShort {
res := &UserShort{
Owner: user.Owner,
Name: user.Name,
}
return res
}
func getShortClaims(claims Claims) ClaimsShort {
res := ClaimsShort{
UserShort: getShortUser(claims.User),
Nonce: claims.Nonce,
Scope: claims.Scope,
RegisteredClaims: claims.RegisteredClaims,
}
return res
}
func generateJwtToken(application *Application, user *User, nonce string, scope string) (string, string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
user.Password = ""
claims := Claims{
User: *user,
StandardClaims: jwt.StandardClaims{
Audience: application.ClientId,
ExpiresAt: expireTime.Unix(),
Id: "",
IssuedAt: nowTime.Unix(),
Issuer: "casdoor",
NotBefore: nowTime.Unix(),
User: user,
Nonce: nonce,
// FIXME: A workaround for custom claim by reusing `tag` in user info
Tag: user.Tag,
Scope: scope,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: beego.AppConfig.String("origin"),
Subject: user.Id,
Audience: []string{application.ClientId},
ExpiresAt: jwt.NewNumericDate(expireTime),
NotBefore: jwt.NewNumericDate(nowTime),
IssuedAt: jwt.NewNumericDate(nowTime),
ID: "",
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
var token *jwt.Token
var refreshToken *jwt.Token
return token, err
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
if application.TokenFormat == "JWT-Empty" {
claimsShort := getShortClaims(claims)
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
} else {
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
}
cert := getCertByApplication(application)
// RSA private key
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(cert.PrivateKey))
if err != nil {
return "", "", err
}
token.Header["kid"] = cert.Name
tokenString, err := token.SignedString(key)
if err != nil {
return "", "", err
}
refreshTokenString, err := refreshToken.SignedString(key)
return tokenString, refreshTokenString, err
}
func ParseJwtToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// RSA public key
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.PublicKey))
if err != nil {
return nil, err
}
return publicKey, nil
})
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
if t != nil {
if claims, ok := t.Claims.(*Claims); ok && t.Valid {
return claims, nil
}
}

69
object/token_jwt_key.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2021 The casbin 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 (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
)
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string) {
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
// Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
panic(err)
}
// Encode private key to PKCS#1 ASN.1 PEM.
privateKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
},
)
tml := x509.Certificate{
// you can add any attr that you need
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(expireInYears, 0, 0),
// you have to generate a different serial number each execution
SerialNumber: big.NewInt(123456),
Subject: pkix.Name{
CommonName: commonName,
Organization: []string{organization},
},
BasicConstraintsValid: true,
}
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil {
panic(err)
}
// Generate a pem block with the certificate
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert,
})
return string(certPem), string(privateKeyPem)
}

51
object/token_jwt_key.key Normal file
View File

@ -0,0 +1,51 @@
-----BEGIN PRIVATE KEY-----
MIIJKQIBAAKCAgEAsInpb5E1/ym0f1RfSDSSE8IR7y+lw+RJjI74e5ejrq4b8zMY
k7HeHCyZr/hmNEwEVXnhXu1P0mBeQ5ypp/QGo8vgEmjAETNmzkI1NjOQCjCYwUra
sO/f/MnI1C0j13vx6mV1kHZjSrKsMhYY1vaxTEP3+VB8Hjg3MHFWrb07uvFMCJe5
W8+0rKErZCKTR8+9VB3janeBz//zQePFVh79bFZate/hLirPK0Go9P1gOvwIoC1A
3sarHTP4Qm/LQRt0rHqZFybdySpyWAQvhNaDFE7mTstRSBb/wUjNCUBDPTSLVjC0
4WllSf6Nkfx0Z7KvmbPstSj+btvcqsvRAGtvdsB9h62Kptjs1Yn7GAuoI3qt/4zo
KbiURYxkQJXIvwCQsEftUuk5ew5zuPSlDRLoLByQTLbx0JqLAFNfW3g/pzSDjgd/
60d6HTmvbZni4SmjdyFhXCDb1Kn7N+xTojnfaNkwep2REV+RMc0fx4GuhRsnLsmk
mUDeyIZ9aBL9oj11YEQfM2JZEq+RVtUx+wB4y8K/tD1bcY+IfnG5rBpwIDpS262b
oq4SRSvb3Z7bB0w4ZxvOfJ/1VLoRftjPbLIf0bhfr/AeZMHpIKOXvfz4yE+hqzi6
8wdF0VR9xYc/RbSAf7323OsjYnjjEgInUtRohnRgCpjIk/Mt2Kt84Kb0wn8CAwEA
AQKCAgAHP7JxHVJNRuYdcFZ1PYtd+lMIMjmpQH9woRI86O4UpxuIselpbx1CpOYu
npF7xj9LTzTc0/u6FLDqL82bkt6O7TknKFvymNy4zWkn75gTgwlSroMqTr8wvwxb
Aft9xp4ZVM8t/l53W7zMVbHxabHAAu50s0RVbVN+zriTa7i/JVdM5wX6ah3uFLQW
aYEIqtQIVy3WWk/fPZA8fWDF94HKaAVTgSUK40EccpbAcIL6CQ1FnnYSb6/pBBBG
khaTdtAkoOgWVkc3EmIdkRZuaux48gBs7dZJkoAv7JBWt+fK5JRwFpHmy5AYKLah
bu9Mrr6dHhEzIxrHbIm0DahoTwEFmso8kbU26caGEhufo4YiMk+B4bE6QsmNsNR9
Msau8qkSLprYo6Mrj1Q7y1q3rShf8SuZBa3Kxk0hBy8Zs9TjkJ+Dbtq2zakQWzDG
JLEttbGgdeYUMS2ycC/FUYUN/YPCdn769kw7lmOR2Kl56wpbFYwR9JYgynQqb3jd
4AORgsR3ADaxVDXw1ol7Bcie334WusvxNCJVRuzBY/DK0/W7ijxjJvdXevHxGrhe
1Gc+FkKebfiNfq6Dzdlkx66N80nyZuZvyrnRiavm9bVcrarb5XhS5ICfEOHOw0gH
5GdesqMuqTSOeveD1RUncF1CWWMvvPeEushW9jbL3BBh4wfLsQKCAQEAy4x+c+F8
IcbaKfssrhPRMfYUjWee39tOvHDtxM/3sx60ysrEUx1LsjagQz7noLljF7XfJ0H+
vc0G6A0ojwA6J+QdoEf8fevO4t56uMae3dFWP4J000vHCIvrgAo9GC2HOc940/Yn
6EqjWYqc2AXu156RA3P/XetgsxivVCFlGzPxFylbqzKB71Yb9hc5zz26G3K8+FVZ
dp+DYFjHBo6LRuVwXdXKi/QgUXv7iFR7zFqvkGhm5ZqvcKXfqWSpx4H71kQAUTQE
cJ8lvgquFTouViopOJD6DBII0PJ/TBtg5g9a+jsnXxcpCjj9XychGsj1dTzSDmQd
ha/rKyN4dNHy2QKCAQEA3gekrROPdglcooamedrvlwpIrxryKI5MrhEAgIBeJidp
98HhNcJ08wra5XuMS6ZC7R0xOKikSQLp4giT22W59lq/nPQ7PN5PdN2RzOlrmHcS
kAGF3Qg9x0cCelSqzyTl4RKrefYPLJUElVLmTxEgBiis7s2gxpsJ3q92WtWf8aU/
7Pc3ztAV/DqzeUCOACVz74l4QuE9LJlro0shs0TaHWM0AZ9eVk4V8xBM8VJ2DAX8
vpTYcNDxjEeByEMgWdXlyOndvolRDMCMrQMVg+ZoeX5SpDe2b6fgw5ZjZSngnrJ4
ifesFEbwmXmb3XDDNyWADG5/xAkpCGPCa34JmG7ZFwKCAQEAlsrrNx/ZnRA6uRUZ
wZBuzut1yFf2i/JlPxcOHlrPLwRVfVJ/5O70D/+F9KtaX2hXr84NloC+no+QSULO
RDov2zOUexQ5SnPyHYIiOlbyhHO7yGr17z7ZIUy+12k+X3YDEuHPqn9WizEYGJKm
pSaoDVasKXm6ujJQvf1Qjiv7Qg7V0YnTHl3ZgpwxNLt6GTyqbgEvW22nTEjZw/ug
3gulxIzfFLT4S3w8oQEPk6y61eZs37doWzqgM/y+WDh5ypJSJibUcVPu4hwUkthI
pPMoNq8fQIeupliJ7XlostIpk+XWSUCfZ0O6JJeZpO9RCA3OQd8f4odqk4qC1r99
UlXi6QKCAQEApSF+IpNXsWxJDz+h9SMV6nnlkQYzcGJVOWi/vNK8MxhBQdlajEcx
/8jlAKQgterT/9IkV4Vlmj+mf0vt29EOu+DGfg9PN3gIFFzuIT7BnUWB8sSPMNL+
T4XKm/z4hNNmfT0Ld8u/gWLbY8uiKtALx0jdRUZ9+vg4IPzSw7/6ExjaMH21bgVp
NIzcCqQueIFidpcBcIxgmRkJ6wrn55KfvheYCFTlLr8op/xJnXm8/jg9v+ioCU/9
Nl3AcpcqKmZhXkpBd4JdW2Shu9N9XvowXZvMDwK4ltZ+3jiteAHrY1xNNh+URgh0
zVCa0dkZ95vWXmiYcc52TB0V7ihxLoPSxQKCAQAwP3D0JyRvWepLasQd0NQeVt0q
0/ExXjq+qmIeT6q2GHhgZJjm6Ysovr+cEdZC6sV6JiqW9NhHfTYO4EkJetzlHMt5
jseFq14QMAgVuo7cYLkONgGxpYGo1DaddxlkMKpmpsTeFsvRyCnyWpHVG/sA1eVp
caanw6S2tnhxx2Yq78bv8Vj8jA0k8j34j2bMBFcuIEsaQ2Sdfw/1TSkKGB2k2crP
txbblVR4BN1DC8wpK/M67B097uMUmWe1UQxsjc0P9S/rlWUhKyBEeLOV3/yRPDSM
GMRXh780wMWlS34RUYxxv6dtWB9KI7++XRrnrBPrZa0xUUOpSYILm7OLeuQ5
-----END PRIVATE KEY-----

29
object/token_jwt_key.pem Normal file
View File

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIE+TCCAuGgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMDYxHTAbBgNVBAoTFENh
c2Rvb3IgT3JnYW5pemF0aW9uMRUwEwYDVQQDEwxDYXNkb29yIENlcnQwHhcNMjEx
MDE1MDgxMTUyWhcNNDExMDE1MDgxMTUyWjA2MR0wGwYDVQQKExRDYXNkb29yIE9y
Z2FuaXphdGlvbjEVMBMGA1UEAxMMQ2FzZG9vciBDZXJ0MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAsInpb5E1/ym0f1RfSDSSE8IR7y+lw+RJjI74e5ej
rq4b8zMYk7HeHCyZr/hmNEwEVXnhXu1P0mBeQ5ypp/QGo8vgEmjAETNmzkI1NjOQ
CjCYwUrasO/f/MnI1C0j13vx6mV1kHZjSrKsMhYY1vaxTEP3+VB8Hjg3MHFWrb07
uvFMCJe5W8+0rKErZCKTR8+9VB3janeBz//zQePFVh79bFZate/hLirPK0Go9P1g
OvwIoC1A3sarHTP4Qm/LQRt0rHqZFybdySpyWAQvhNaDFE7mTstRSBb/wUjNCUBD
PTSLVjC04WllSf6Nkfx0Z7KvmbPstSj+btvcqsvRAGtvdsB9h62Kptjs1Yn7GAuo
I3qt/4zoKbiURYxkQJXIvwCQsEftUuk5ew5zuPSlDRLoLByQTLbx0JqLAFNfW3g/
pzSDjgd/60d6HTmvbZni4SmjdyFhXCDb1Kn7N+xTojnfaNkwep2REV+RMc0fx4Gu
hRsnLsmkmUDeyIZ9aBL9oj11YEQfM2JZEq+RVtUx+wB4y8K/tD1bcY+IfnG5rBpw
IDpS262boq4SRSvb3Z7bB0w4ZxvOfJ/1VLoRftjPbLIf0bhfr/AeZMHpIKOXvfz4
yE+hqzi68wdF0VR9xYc/RbSAf7323OsjYnjjEgInUtRohnRgCpjIk/Mt2Kt84Kb0
wn8CAwEAAaMQMA4wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn2lf
DKkLX+F1vKRO/5gJ+Plr8P5NKuQkmwH97b8CS2gS1phDyNgIc4/LSdzuf4Awe6ve
C06lVdWSIis8UPUPdjmT2uMPSNjwLxG3QsrimMURNwFlLTfRem/heJe0Zgur9J1M
8haawdSdJjH2RgmFoDeE2r8NVRfhbR8KnCO1ddTJKuS1N0/irHz21W4jt4rxzCvl
2nR42Fybap3O/g2JXMhNNROwZmNjgpsF7XVENCSuFO1jTywLaqjuXCg54IL7XVLG
omKNNNcc8h1FCeKj/nnbGMhodnFWKDTsJcbNmcOPNHo6ixzqMy/Hqc+mWYv7maAG
Jtevs3qgMZ8F9Qzr3HpUc6R3ZYYWDY/xxPisuKftOPZgtH979XC4mdf0WPnOBLqL
2DJ1zaBmjiGJolvb7XNVKcUfDXYw85ZTZQ5b9clI4e+6bmyWqQItlwt+Ati/uFEV
XzCj70B4lALX6xau1kLEpV9O1GERizYRz5P9NJNA7KoO5AVMp9w0DQTkt+LbXnZE
HHnWKy8xHQKZF9sR7YBPGLs/Ac6tviv5Ua15OgJ/8dLRZ/veyFfGo2yZsI+hKVU5
nCCJHBcAyFnm1hdvdwEdH33jDBjNB6ciotJZrf/3VYaIWSalADosHAgMWfXuWP+h
8XKXmzlxuHbTMQYtZPDgspS5aK+S4Q9wb8RRAYo=
-----END CERTIFICATE-----

View File

@ -15,23 +15,19 @@
package object
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"testing"
"github.com/casdoor/casdoor/util"
)
func getSha256(data []byte) []byte {
hash := sha256.Sum256(data)
return hash[:]
}
func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key"
publicKey, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
func getSha256HexDigest(s string) string {
b := getSha256([]byte(s))
res := hex.EncodeToString(b)
return res
}
// Write certificate (aka public key) to file.
util.WriteStringToPath(publicKey, fmt.Sprintf("%s.pem", fileId))
func getSaltedPassword(password string, salt string) string {
hash1 := getSha256HexDigest(password)
res := getSha256HexDigest(hash1 + salt)
return res
// Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))
}

View File

@ -16,8 +16,9 @@ package object
import (
"fmt"
"strings"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -27,26 +28,45 @@ type User struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
Id string `xorm:"varchar(100)" json:"id"`
Id string `xorm:"varchar(100) index" json:"id"`
Type string `xorm:"varchar(100)" json:"type"`
Password string `xorm:"varchar(100)" json:"password"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Avatar string `xorm:"varchar(255)" json:"avatar"`
Email string `xorm:"varchar(100)" json:"email"`
Phone string `xorm:"varchar(100)" json:"phone"`
Avatar string `xorm:"varchar(500)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"`
Phone string `xorm:"varchar(100) index" json:"phone"`
Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"`
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
Title string `xorm:"varchar(100)" json:"title"`
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
IdCard string `xorm:"varchar(100) index" json:"idCard"`
Homepage string `xorm:"varchar(100)" json:"homepage"`
Bio string `xorm:"varchar(100)" json:"bio"`
Tag string `xorm:"varchar(100)" json:"tag"`
Region string `xorm:"varchar(100)" json:"region"`
Language string `xorm:"varchar(100)" json:"language"`
Gender string `xorm:"varchar(100)" json:"gender"`
Birthday string `xorm:"varchar(100)" json:"birthday"`
Education string `xorm:"varchar(100)" json:"education"`
Score int `json:"score"`
Ranking int `json:"ranking"`
IsDefaultAvatar bool `json:"isDefaultAvatar"`
IsOnline bool `json:"isOnline"`
IsAdmin bool `json:"isAdmin"`
IsGlobalAdmin bool `json:"isGlobalAdmin"`
IsForbidden bool `json:"isForbidden"`
IsDeleted bool `json:"isDeleted"`
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"`
@ -58,11 +78,26 @@ type User struct {
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
Lark string `xorm:"lark varchar(100)" json:"lark"`
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
Slack string `xorm:"slack varchar(100)" json:"slack"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
}
func GetGlobalUserCount(field, value string) int {
session := GetSession("", -1, -1, field, value, "", "")
count, err := session.Count(&User{})
if err != nil {
panic(err)
}
return int(count)
}
func GetGlobalUsers() []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Find(&users)
@ -73,6 +108,36 @@ func GetGlobalUsers() []*User {
return users
}
func GetPaginationGlobalUsers(offset, limit int, field, value, sortField, sortOrder string) []*User {
users := []*User{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&users)
if err != nil {
panic(err)
}
return users
}
func GetUserCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&User{})
if err != nil {
panic(err)
}
return int(count)
}
func GetOnlineUserCount(owner string, isOnline int) int {
count, err := adapter.Engine.Where("is_online = ?", isOnline).Count(&User{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
}
func GetUsers(owner string) []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Find(&users, &User{Owner: owner})
@ -83,6 +148,27 @@ func GetUsers(owner string) []*User {
return users
}
func GetSortedUsers(owner string, sorter string, limit int) []*User {
users := []*User{}
err := adapter.Engine.Desc(sorter).Limit(limit, 0).Find(&users, &User{Owner: owner})
if err != nil {
panic(err)
}
return users
}
func GetPaginationUsers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*User {
users := []*User{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&users)
if err != nil {
panic(err)
}
return users
}
func getUser(owner string, name string) *User {
if owner == "" || name == "" {
return nil
@ -101,11 +187,52 @@ func getUser(owner string, name string) *User {
}
}
func getUserById(owner string, id string) *User {
if owner == "" || id == "" {
return nil
}
user := User{Owner: owner, Id: id}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUserByEmail(owner string, email string) *User {
if owner == "" || email == "" {
return nil
}
user := User{Owner: owner, Email: email}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUser(id string) *User {
owner, name := util.GetOwnerAndNameFromId(id)
return getUser(owner, name)
}
func GetUserNoCheck(id string) *User {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
return getUser(owner, name)
}
func GetMaskedUser(user *User) *User {
if user == nil {
return nil
@ -138,17 +265,29 @@ func GetLastUser(owner string) *User {
return nil
}
func UpdateUser(id string, user *User) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getUser(owner, name) == nil {
func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
oldUser := getUser(owner, name)
if oldUser == nil {
return false
}
user.UpdateUserHash()
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols("owner", "display_name", "avatar",
"address", "region", "language", "affiliation", "score", "tag", "is_admin", "is_global_admin", "is_forbidden",
"hash", "properties").Update(user)
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
if len(columns) == 0 {
columns = []string{"owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
}
if isGlobalAdmin {
columns = append(columns, "name")
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
if err != nil {
panic(err)
}
@ -156,14 +295,19 @@ func UpdateUser(id string, user *User) bool {
return affected != 0
}
func UpdateUserInternal(id string, user *User) bool {
func UpdateUserForAllFields(id string, user *User) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getUser(owner, name) == nil {
oldUser := getUser(owner, name)
if oldUser == nil {
return false
}
user.UpdateUserHash()
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
if err != nil {
panic(err)
@ -172,26 +316,23 @@ func UpdateUserInternal(id string, user *User) bool {
return affected != 0
}
func UpdateUserForOriginal(user *User) bool {
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("display_name", "password", "phone", "avatar", "affiliation", "score", "is_forbidden", "hash", "pre_hash").Update(user)
if err != nil {
panic(err)
}
return affected != 0
}
func AddUser(user *User) bool {
if user.Id == "" {
user.Id = util.GenerateId()
}
if user.Owner == "" || user.Name == "" {
return false
}
organization := GetOrganizationByUser(user)
user.UpdateUserPassword(organization)
user.UpdateUserHash()
user.PreHash = user.Hash
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
affected, err := adapter.Engine.Insert(user)
if err != nil {
panic(err)
@ -205,12 +346,15 @@ func AddUsers(users []*User) bool {
return false
}
organization := GetOrganizationByUser(users[0])
//organization := GetOrganizationByUser(users[0])
for _, user := range users {
user.UpdateUserPassword(organization)
// this function is only used for syncer or batch upload, so no need to encrypt the password
//user.UpdateUserPassword(organization)
user.UpdateUserHash()
user.PreHash = user.Hash
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
affected, err := adapter.Engine.Insert(users)
@ -221,7 +365,7 @@ func AddUsers(users []*User) bool {
return affected != 0
}
func AddUsersSafe(users []*User) bool {
func AddUsersInBatch(users []*User) bool {
batchSize := 1000
if len(users) == 0 {
@ -237,7 +381,8 @@ func AddUsersSafe(users []*User) bool {
}
tmp := users[start:end]
fmt.Printf("Add users: [%d - %d].\n", start, end)
// TODO: save to log instead of standard output
// fmt.Printf("Add users: [%d - %d].\n", start, end)
if AddUsers(tmp) {
affected = true
}
@ -262,3 +407,7 @@ func LinkUserAccount(user *User, field string, value string) bool {
func (user *User) GetId() string {
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
}
func isUserIdGlobalAdmin(userId string) bool {
return strings.HasPrefix(userId, "built-in/")
}

View File

@ -14,16 +14,15 @@
package object
import (
"strconv"
"strings"
"github.com/casbin/casdoor/util"
)
import "github.com/casdoor/casdoor/cred"
func calculateHash(user *User) string {
s := strings.Join([]string{user.Id, user.Password, user.DisplayName, user.Avatar, user.Phone, strconv.Itoa(user.Score)}, "|")
return util.GetMd5Hash(s)
syncer := getDbSyncerForUser(user)
if syncer == nil {
return ""
}
return syncer.calculateHash(user)
}
func (user *User) UpdateUserHash() {
@ -32,7 +31,9 @@ func (user *User) UpdateUserHash() {
}
func (user *User) UpdateUserPassword(organization *Organization) {
if organization.PasswordType == "salt" {
user.Password = getSaltedPassword(user.Password, organization.PasswordSalt)
credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil {
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
user.Password = hashedPassword
}
}

View File

@ -19,7 +19,7 @@ import (
"reflect"
"testing"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -74,12 +74,6 @@ func TestSyncHashes(t *testing.T) {
}
}
func TestGetSaltedPassword(t *testing.T) {
password := "123456"
salt := "123"
fmt.Printf("%s -> %s\n", password, getSaltedPassword(password, salt))
}
func TestGetMaskedUsers(t *testing.T) {
type args struct {
users []*User
@ -91,8 +85,8 @@ func TestGetMaskedUsers(t *testing.T) {
}{
{
name: "1",
args: args{users: []*User{{Password: "casdoor"},{Password: "casbin"}}},
want: []*User{{Password: "***"},{Password: "***"}},
args: args{users: []*User{{Password: "casdoor"}, {Password: "casbin"}}},
want: []*User{{Password: "***"}, {Password: "***"}},
},
}
for _, tt := range tests {
@ -102,4 +96,4 @@ func TestGetMaskedUsers(t *testing.T) {
}
})
}
}
}

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