Compare commits

...

481 Commits
ts ... doorPay

Author SHA1 Message Date
MRGUOKING
16f427d4d7 feat: Remove useless dependencies
Signed-off-by: MRGUOKING <420919469@qq.com>

Remove useless dependencies

Signed-off-by: MRGUOKING <420919469@qq.com>
2021-09-15 00:13:43 +08:00
MRGUOKING
6ad8f9fa0b feat: Add PayPal pay function
Signed-off-by: MRGUOKING <420919469@qq.com>

Add PayPal pay function

Signed-off-by: MRGUOKING <420919469@qq.com>
2021-09-15 00:00:56 +08:00
Yang Luo
b93a29fbc6 Improve language code. 2021-09-14 01:22:13 +08:00
Yang Luo
09f430266b Improve menu key. 2021-09-13 23:56:25 +08:00
sh1luo
52d9017611 fix: swagger bug in dev mode (#291)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-12 12:09:44 +08:00
ffyuanda
c70c62f52e docs: Update README.md for backend port caution (#293) 2021-09-08 22:02:58 +08:00
Yang Luo
355b0b35d0 Fix gitee login link. 2021-09-07 21:46:44 +08:00
Yang Luo
e4846807cd Show resource list page to users. 2021-09-06 00:49:10 +08:00
Yang Luo
f4a59de3a5 Improve resource list page. 2021-09-06 00:08:16 +08:00
Yang Luo
a1b16f88d1 Add user to Resource. 2021-09-05 23:46:56 +08:00
Yang Luo
90ec8ec787 Add GetOwnerAndNameFromIdNoCheck() to fix bug. 2021-09-05 23:46:55 +08:00
github-actions[bot]
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
Yang Luo
bd41425039 Improve format. 2021-09-05 22:09:54 +08:00
sh1luo
9d9a1da07f fix: remove routers/util (#287)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-05 22:02:32 +08:00
Yang Luo
465d25a272 Improve router base.go 2021-09-05 14:44:27 +08:00
Yang Luo
ef1195960e Improve SendSms() API. 2021-09-05 13:15:38 +08:00
Yang Luo
089f4ff480 Handle error in go-sms-sender. 2021-09-05 10:56:11 +08:00
Yang Luo
88aa444ad1 Improve SendEmail() and SendSms() APIs. 2021-09-05 10:30:51 +08:00
Yang Luo
1c5ce46bd5 Refactor GetProviderFromContext(). 2021-09-05 09:44:15 +08:00
Yang Luo
14d09cad2c Support server-side upload-resource call. 2021-09-05 01:03:29 +08:00
Yang Luo
06006c87b8 Improve filter code. 2021-09-05 00:22:08 +08:00
sh1luo
a4edf47dc4 fix: improvde code logic (#285)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-04 22:20:47 +08:00
sh1luo
e68b0198f1 fix: go proxy of dockerfile (#283)
Signed-off-by: sh1luo <690898835@qq.com>
2021-09-04 22:10:16 +08:00
Yang Luo
015961bc3c Add application to Resource. 2021-09-04 16:50:26 +08:00
Yang Luo
5d98cc6ac5 Use objectKey as resource name. 2021-09-04 15:02:11 +08:00
Yang Luo
b3eec024b8 Add getInitScore(). 2021-08-30 01:06:05 +08:00
Yang Luo
eefcfd8440 Fix address null bug. 2021-08-27 23:43:43 +08:00
Yang Luo
c6b2106c94 Add bio to user. 2021-08-25 08:07:08 +08:00
sh1luo
edf621f4d5 feat: support web-auth way for wecom (#275)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-23 23:12:53 +08:00
Yang Luo
e50c6cd4b5 Add PermanentAvatar to user. 2021-08-21 23:17:33 +08:00
Yang Luo
9c3117beb0 Rename UpdateUser functions. 2021-08-21 22:54:53 +08:00
Yang Luo
4ca307564c Add proxy pkg. 2021-08-21 22:16:25 +08:00
Yang Luo
15a6f64fdc Add 5 new user properties. 2021-08-21 10:58:34 +08:00
sh1luo
75e917a070 feat: add gitlab provider (#273)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-19 22:13:40 +08:00
github-actions[bot]
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
ffyuanda
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
sh1luo
2def51ad99 fix: remove forked repo workflow (#267)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-16 21:04:23 +08:00
Yang Luo
398ba19fa5 Add CheckUserPassword() API. 2021-08-15 21:57:36 +08:00
Yang Luo
8674b4853a Add provider to file API. 2021-08-15 01:14:21 +08:00
Yang Luo
518c3f9f69 Add DeleteFile(). 2021-08-15 00:41:51 +08:00
Yang Luo
495b64995f Add resource list page. 2021-08-15 00:25:46 +08:00
sh1luo
f3c10c59cb fix: improve seletRegionBox (#266)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 19:43:39 +08:00
sh1luo
c05b27c38f feat: add lark provider (#264)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 16:00:38 +08:00
Yang Luo
004282f970 Improve UploadFile() API. 2021-08-14 15:58:47 +08:00
Yang Luo
b7e0a4fe4e Return 200 for denied request. 2021-08-14 14:26:42 +08:00
Kininaru
d3c4f14bd4 feat: support volc engine sms (#252)
Signed-off-by: Kininaru <shiftregister233@outlook.com>

fix

label -> row
2021-08-14 14:24:08 +08:00
Yang Luo
8d9451124c Improve UploadFile() error handling. 2021-08-14 14:24:00 +08:00
sh1luo
cfd876226a docs: add docker and build badges (#251)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 14:23:40 +08:00
sh1luo
8c84ba53b4 fix: dockerhub address (#250)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 14:23:37 +08:00
sh1luo
d103a2bd8c fix: support master branch trigger (#247)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 14:23:34 +08:00
sh1luo
b58d5ebb2c feat: support semantic release (#244)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 14:23:30 +08:00
WindSpiritSR
8c6f0a31b6 feat: support storage provider to terms of use file (#221)
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2021-08-14 14:23:15 +08:00
sh1luo
718fc4df74 fix: improve goland experience (#238)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 14:22:18 +08:00
ERIK
3f7e83261d docs: Add i18n notice in README (#241)
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-08-14 14:22:10 +08:00
Yang Luo
5c2f96bda0 Add UploadFile() API. 2021-08-14 14:22:01 +08:00
Yang Luo
40fbca7db4 Remove UploadAvatarToStorage(). 2021-08-14 14:20:24 +08:00
Yang Luo
1b74a58d06 Fix compile errors. 2021-08-14 14:20:13 +08:00
Yang Luo
9feefc31f9 Use c.ResponseOk() for all places. 2021-08-14 14:19:50 +08:00
Yang Luo
a5783598ff Rename to UploadFile(). 2021-08-14 14:19:48 +08:00
sh1luo
c55fa4f452 feat: add local file system storage provider (#224)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 14:19:45 +08:00
Yang Luo
44150a6781 Refactor the storage provider interface. 2021-08-14 14:19:37 +08:00
Yang Luo
d16569d461 Use c.ResponseError() for all places. 2021-08-14 14:19:33 +08:00
Yang Luo
6aeadfa3bd Improve provider check. 2021-08-14 14:19:23 +08:00
ffyuanda
05222c7a29 fix: typo in Applications tab (#237)
Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com>
2021-08-14 14:19:16 +08:00
sh1luo
8c66ef6860 fix: improve code specification (#231) 2021-08-14 14:18:08 +08:00
Yang Luo
a271ef0719 Set ShowGithubCorner to false. 2021-08-14 14:15:55 +08:00
Yang Luo
d79544f34a Use User in Claims. 2021-08-14 14:15:52 +08:00
Yang Luo
ea692c4e73 Rename to AutoSigninFilter 2021-08-14 14:15:49 +08:00
Yang Luo
0a461bf19e Directly login with the only OAuth provider. 2021-08-14 14:13:30 +08:00
Yang Luo
9fb4b4d394 Change text to "3rd-party logins". 2021-08-14 14:13:13 +08:00
Yang Luo
af3def97bf Fix avatar upload. 2021-08-14 14:08:29 +08:00
Yang Luo
8ea906a132 Improve provider options. 2021-08-14 14:08:29 +08:00
Yang Luo
5afe3ed650 Set autoSignin to true by default. 2021-08-14 14:08:29 +08:00
sh1luo
ca9fcf7edc fix: add 404 page (#228)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-14 14:08:20 +08:00
Kininaru
c0ab24205d feat: add user initScore in app.conf (#220)
Signed-off-by: Kininaru <shiftregister233@outlook.com>

-> 2000
2021-08-03 23:28:02 +08:00
Yang Luo
0c657f0487 Get real IP for verification log. 2021-08-03 22:34:12 +08:00
Yang Luo
acca9eacdc Add app's TermsOfUse. 2021-08-03 21:00:17 +08:00
sh1luo
9f5c6b1e05 feat: mount app.conf to container (#217)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-03 10:22:55 +08:00
Yang Luo
4001e63f16 Disable CustomGithubCorner by default. 2021-08-02 19:21:38 +08:00
github-actions[bot]
5a6f3cdea5 refactor: New Crowdin translations by Github Action (#214)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2021-08-02 00:53:36 +08:00
sh1luo
bf77992457 fix: add missing provider buttons (#215)
Signed-off-by: sh1luo <690898835@qq.com>
2021-08-01 22:10:47 +08:00
Yang Luo
1a2d85102c Support defaultHttpClient. 2021-08-01 17:36:03 +08:00
Yang Luo
629ae5a54b Improve OAuth params. 2021-08-01 14:27:53 +08:00
Yang Luo
3be7d3b273 Support redis session. 2021-08-01 00:30:12 +08:00
Yang Luo
e39c3b7613 Fix signup page silent bug for sign-up-without-prompt-items case. 2021-08-01 00:16:21 +08:00
sh1luo
bc5e748e5c feat: add country/region select (#207)
Signed-off-by: sh1luo <690898835@qq.com>
2021-07-31 16:02:48 +08:00
github-actions[bot]
eabeef094a refactor: New Crowdin translations by Github Action (#199)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2021-07-31 00:24:04 +08:00
Kininaru
7a2230f63e feat: expose email and sms APIs as services to SDK (#202)
Signed-off-by: Kininaru <shiftregister233@outlook.com>

invalid receivers
2021-07-30 14:15:10 +08:00
sh1luo
512a451800 feat: add wecom provider (#200)
Signed-off-by: sh1luo <690898835@qq.com>
2021-07-28 20:36:27 +08:00
Yang Luo
c9ae582802 Improve menu keys. 2021-07-27 18:43:55 +08:00
Kininaru
3d493b8d8f feat: integrate Storage config into providers (#198)
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-07-26 11:39:49 +08:00
turbodog03
1c01a34814 feat: improve EditPages for touchscreen (#195)
Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-07-25 23:13:34 +08:00
sh1luo
21b4e0e51d fix: clear all console warnings (#192)
Signed-off-by: sh1luo <690898835@qq.com>
2021-07-25 11:10:45 +08:00
sh1luo
c632c3c307 fix: replace casdoor with casbin (#194)
Signed-off-by: sh1luo <690898835@qq.com>
2021-07-25 09:34:25 +08:00
turbodog03
3bbb0fb58e feat: improve border (#190)
Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-07-24 20:38:33 +08:00
turbodog03
170ae241dc feat: improve ApplicationEditPage (#189)
Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-07-24 10:16:48 +08:00
turbodog03
8d7fdb93ac feat: fix header overflow and account disappear bug, improve UI, optimize touchscreen experience (#187)
Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-07-23 09:46:01 +08:00
WindSpiritSR
66f36d780a fix: header selected (#186)
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2021-07-20 21:29:06 +08:00
github-actions[bot]
aa5b0f168a refactor: New Crowdin translations by Github Action (#184)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2021-07-19 16:41:53 +08:00
ERIK
521cda7b4e fix: Fix PR failing checks (#183)
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-19 16:39:04 +08:00
ErikQQY
07178a7125 fix: Using Crowdin action implement translation sync
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-19 12:16:05 +08:00
Yang Luo
ded859aebc Improve footer. 2021-07-19 10:14:25 +08:00
Yang Luo
bb09b24b0f Use signup app for GetApplicationByUser(). 2021-07-19 10:14:25 +08:00
Kininaru
4a930121c4 feat: session without autosignin will expire
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-07-19 10:14:14 +08:00
Kininaru
d83b86df37 refactor: SessionUser -> SessionUsername
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-07-19 10:14:14 +08:00
killer
59602a1325 fix: Fix the user list cannot be displayed completely
Signed-off-by: killer <1533063601@qq.com>
2021-07-19 10:14:06 +08:00
WindSpiritSR
705d2ede6b feat: support LDAP (#160)
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2021-07-19 10:13:23 +08:00
WindSpiritSR
a22dfd8954 fix: db data init and frontend warning
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2021-07-19 10:13:01 +08:00
turbodog03
e986d3ab8e feat: add language select box and background color change when hover
Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-07-19 10:12:44 +08:00
killer
0fc388d662 feat: add run casdoor through docker
Signed-off-by: killer <1533063601@qq.com>
2021-07-19 10:12:29 +08:00
MRGUOKING
896f8b808f fix: The count-down will be disabled
Signed-off-by: MRGUOKING <420919469@qq.com>

The count-down will be disabled after sending the code

Signed-off-by: MRGUOKING <420919469@qq.com>
2021-07-19 10:12:11 +08:00
Yang Luo
08a2b5a69d Fix adding provider UI bug. 2021-07-19 10:12:05 +08:00
Yang Luo
2e5602d4c1 Add defaultAvatar column. 2021-07-19 10:12:05 +08:00
Yang Luo
dc0712c8a5 Replace getDefaultApplication() with getUserApplication(). 2021-07-19 10:12:04 +08:00
casbin-bot
2be7d28a6c Sync from crowdin 2021-07-19 10:11:58 +08:00
killer
21b36bbb47 feat: add UI to view logs
Signed-off-by: killer <1533063601@qq.com>
2021-07-19 10:11:01 +08:00
Yang Luo
6ae8e537b9 Improve translation. 2021-07-19 10:00:26 +08:00
ErikQQY
49ede30fb8 fix: Change commit author to casbin bot
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-19 09:58:22 +08:00
Yang Luo
89b9f73b32 Restrict app edit page values. 2021-07-19 09:58:22 +08:00
Yang Luo
59ac7d6317 Restrict rule options. 2021-07-19 09:58:22 +08:00
Yang Luo
4a0f22355e Refactor out Setting.getDeduplicatedArray() 2021-07-19 09:58:22 +08:00
Yang Luo
1bfddc5d53 Refactor out Setting.getArrayItem() 2021-07-19 09:58:21 +08:00
ErikQQY
3923574d57 fix: Update yarn.lock
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-19 09:58:06 +08:00
wasabi
c963ba6098 feat: add linkedin provider
Signed-off-by: wasabi <690898835@qq.com>
2021-07-19 09:57:43 +08:00
Yang Luo
6cfe3c1586 Merge pull request #154 from ErikQQY/master
fix: Fix Crowdin failing
2021-07-09 16:15:50 +08:00
ErikQQY
857c9e540f fix: Fix Crowdin failing
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-09 16:01:43 +08:00
ErikQQY
64950e900f Sync from crowdin 2021-07-04 09:41:24 +00:00
Yang Luo
00b00a20de Merge pull request #138 from WindSpiritSR/patch-proxy-config
fix: Move proxy address config to app.conf
2021-07-04 16:00:39 +08:00
WindSpiritSR
1bca883f4f fix: Move proxy address config to app.conf
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2021-07-04 15:48:29 +08:00
hsluoyz
66bb34f051 Sync from crowdin 2021-07-04 07:04:11 +00:00
Yang Luo
122792d247 Merge pull request #145 from sh1luo/fix-null-properties-bug
fix: null properties bug
2021-07-04 15:02:30 +08:00
wasabi
8e6e427ce6 fix: null properties bug
Signed-off-by: wasabi <690898835@qq.com>
2021-07-04 12:19:54 +08:00
hsluoyz
264bf5c447 Sync from crowdin 2021-07-03 06:54:19 +00:00
Yang Luo
d59ba750f4 Merge pull request #143 from ErikQQY/master
feat: Integrate Crowdin
2021-07-03 14:52:59 +08:00
ErikQQY
dcf85591e2 feat: Integrate Crowdin
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-03 10:09:29 +08:00
Yang Luo
98e0c1aa85 Merge pull request #136 from sh1luo/fix-empty-email-bug
fix: empty email bug
2021-07-01 22:46:26 +08:00
wasabi
0124bb27a4 fix: empty email bug
Signed-off-by: wasabi <690898835@qq.com>
2021-07-01 19:22:44 +08:00
wasabi
7eb4da6e54 feat: add tooltips for xxxeditPage fields (#135)
Signed-off-by: wasabi <690898835@qq.com>
2021-06-30 00:58:25 +08:00
Yang Luo
f26b6c2d11 Merge pull request #133 from oranges-eating/master
feat: admin can update user's organization
2021-06-29 20:20:26 +08:00
Yang Luo
8343007afa Set focus for code modal. 2021-06-27 01:49:52 +08:00
Yang Luo
fbfae75780 Improve code modal style. 2021-06-27 01:34:25 +08:00
Yang Luo
a42a76400e Improve "Send Code" button style. 2021-06-27 00:52:15 +08:00
Yang Luo
2ba44748c2 Add maskEmail(), fix Safari bug. 2021-06-25 21:35:20 +08:00
killer
62397ce3b1 feat: admin can update user's organization
Signed-off-by: killer <1533063601@qq.com>
2021-06-25 16:28:10 +08:00
Yang Luo
234a9b9060 Add one tooltip. 2021-06-25 00:13:53 +08:00
Yang Luo
8b8eeea73d Merge pull request #129 from sh1luo/feat-Send-code-button-should-be-gray-out-with-empty-or-invalid-Email-or-phone
feat: Send-code-button-should-be-gray-out-with-empty-or-invalid-Email…
2021-06-24 22:05:09 +08:00
wasabi
6ac364074a feat: Send-code-button-should-be-gray-out-with-empty-or-invalid-Email-or-phone
Signed-off-by: wasabi <690898835@qq.com>
2021-06-24 20:49:37 +08:00
Yang Luo
1e9cca02fc Merge pull request #117 from turbodog03/turbodog03
fix: improve i18n and add some translation
2021-06-22 23:10:34 +08:00
Yang Luo
79258e1dd0 Merge pull request #126 from oranges-eating/master
fix: Application null error
2021-06-22 14:00:25 +08:00
killer
c9f93b0785 fix: Application null error
Signed-off-by: killer <1533063601@qq.com>
2021-06-22 13:07:02 +08:00
Yang Luo
8f21ad0928 Merge pull request #125 from oranges-eating/master
fix: The user can update the language saved in the database
2021-06-22 10:46:34 +08:00
killer
81a2f91d83 fix: Users can update the language to the database
Signed-off-by: killer <1533063601@qq.com>
2021-06-22 09:15:03 +08:00
Yang Luo
9c233e42c0 Add SignupApplication to user. 2021-06-21 10:22:47 +08:00
Yang Luo
64f85fdc6c Fix get null object bug. 2021-06-21 01:09:02 +08:00
Yang Luo
54e97d57bf Use OAuth avatar to update default avatar. 2021-06-21 00:54:07 +08:00
Yang Luo
68018c6d12 Check orgName when sync to old db. 2021-06-21 00:27:56 +08:00
Yang Luo
b189993547 Login page can also enter prompt page. 2021-06-20 22:17:03 +08:00
Yang Luo
a43db3e55a Fix prompt redirect logic and db sync bug. 2021-06-20 13:56:32 +08:00
Yang Luo
d3a8ab8347 Finish the prompt page logic. 2021-06-20 11:52:04 +08:00
Yang Luo
e0b6270f50 Make OAuthWidget work for prompt page. 2021-06-20 02:13:15 +08:00
Yang Luo
0f69f4f07c Refactor our OAuthWidget.js 2021-06-20 00:42:32 +08:00
turbodog
3a806c14ff feat: improve i18n and add some translation
Signed-off-by: turbodog <turbodog1003@gmail.com>
2021-06-20 00:32:16 +08:00
Yang Luo
165e6a9876 Improve UI. 2021-06-19 23:50:45 +08:00
Yang Luo
504ab7a9ed Merge pull request #121 from sh1luo/feat-show-users-for-each-org
feat: reuse component Users
2021-06-19 22:16:18 +08:00
wasabi
50a9ca2b4c feat: reuse component Users
Signed-off-by: wasabi <690898835@qq.com>
2021-06-19 11:38:24 +08:00
Yang Luo
910ea04384 Add renderAffiliation(). 2021-06-18 23:43:36 +08:00
Yang Luo
6dc3fd0f45 Avoid linking the same account twice. 2021-06-18 23:25:24 +08:00
Yang Luo
01d5f3b776 Merge pull request #118 from oranges-eating/master
fix: Properties can now be updated
2021-06-18 17:17:21 +08:00
killer
00134dda4f fix: Properties can now be updated
Signed-off-by: killer <1533063601@qq.com>
2021-06-18 16:16:40 +08:00
Yang Luo
92610f9971 Add prompt page. 2021-06-18 02:09:19 +08:00
Yang Luo
32afafd56c Add Prompted field. 2021-06-18 02:02:43 +08:00
Yang Luo
ce60a76920 Rename canUnbind to canUnlink. 2021-06-17 13:45:10 +08:00
Yang Luo
586da5caac Add isValidPersonalName(). 2021-06-17 11:55:06 +08:00
Yang Luo
5b9a91db85 Fix bug in isProviderVisible(). 2021-06-17 01:49:05 +08:00
Yang Luo
9dd43238f0 Add DefaultAvatar to org. 2021-06-17 01:44:19 +08:00
Yang Luo
b11b3b6021 Use signup table in Signup API. 2021-06-17 00:49:02 +08:00
Yang Luo
3274bd0c7c Improve signup page. 2021-06-16 15:25:54 +08:00
Yang Luo
02b1feb2e5 Add signup table. 2021-06-16 14:07:39 +08:00
Yang Luo
7ea469e876 Add provider_item.go 2021-06-16 00:18:56 +08:00
Yang Luo
8bc9163def Improve i18n. 2021-06-15 22:12:50 +08:00
Yang Luo
7871541cd5 Use providerItem.canUnbind 2021-06-14 23:53:43 +08:00
Yang Luo
985205a978 Improve buttons. 2021-06-14 23:43:09 +08:00
wasabi
134b2db3d7 feat: support long OAuth buttons for providers (#106)
* Show signup preview.

* feat: support other mode,dingtalk,weibo,wechat,gitee,facebook

Signed-off-by: wasabi <690898835@qq.com>

* feat: convert svg codes into files.

Signed-off-by: wasabi <690898835@qq.com>

* feat: convert svg codes into files.

Signed-off-by: wasabi <690898835@qq.com>

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2021-06-14 23:29:22 +08:00
Yang Luo
c122e89fc1 Improve jump links. 2021-06-14 23:23:59 +08:00
Yang Luo
a7912eecc1 Add SigninUrl to application. 2021-06-14 22:55:08 +08:00
Yang Luo
cc31c1d666 Add login page "signup" mode. 2021-06-14 22:42:58 +08:00
Yang Luo
5a852bfd1d Use new providerItem. 2021-06-14 22:42:34 +08:00
Yang Luo
9fe310f2b7 Add provider table. 2021-06-14 19:09:04 +08:00
Yang Luo
2e68f8c6d5 Improve i18n. 2021-06-14 17:18:40 +08:00
Yang Luo
7f22f05073 Show signup preview. 2021-06-14 13:55:53 +08:00
wasabi
d14ba476b9 feat: add gitee provider (#104)
* feat: add gitee provider

Signed-off-by: wasabi <690898835@qq.com>

* Update gitee.go

Signed-off-by: wasabi <690898835@qq.com>
2021-06-14 12:53:07 +08:00
Yang Luo
1025a7c118 Allow to oauth login by name. 2021-06-12 12:46:25 +08:00
Yang Luo
00ba783fc4 Use static resources. 2021-06-12 12:29:07 +08:00
Yang Luo
56b2c91217 Update StaticBaseUrl. 2021-06-12 12:07:24 +08:00
Yang Luo
e1673c1541 Add StaticBaseUrl. 2021-06-12 11:52:59 +08:00
Yang Luo
c4de5449af Fix properties in "sign up via OAuth". 2021-06-11 23:37:06 +08:00
Yang Luo
8ddc0cd626 Improve oauth button UI. 2021-06-11 20:48:42 +08:00
Yang Luo
8132ea9621 Merge pull request #100 from sh1luo/fix-Unable-to-close-the-modal-by-clicking-the-inner-layer
fix: unable to close the modal by clicking the inner layer
2021-06-10 20:19:50 +08:00
wasabi
35443f82c6 fix: unable to close the modal by clicking the inner layer
Signed-off-by: wasabi <690898835@qq.com>
2021-06-10 20:05:00 +08:00
Yang Luo
222b00f0a8 Merge pull request #98 from sh1luo/feat-add-weibo-provider
feat: add weibo provider
2021-06-10 19:19:31 +08:00
wasabi
3d3a331784 feat: add weibo provider
Signed-off-by: wasabi <690898835@qq.com>
2021-06-10 16:55:36 +08:00
Yang Luo
30bed3cb47 Override session when signed in by link. 2021-06-10 13:27:03 +08:00
Yang Luo
8e5f3c18e1 Add UpdateUserInternal(). 2021-06-09 21:41:27 +08:00
Yang Luo
f672045b45 Allow to sign up with OAuth. 2021-06-09 21:28:33 +08:00
Yang Luo
440aad2369 Only show some providers in signup page. 2021-06-09 20:42:23 +08:00
Yang Luo
ec91ded5aa Remove header for some pages. 2021-06-09 20:38:46 +08:00
Yang Luo
04a246355e Handle json error in Login(). 2021-06-09 19:54:26 +08:00
wasabi
4162ab4984 feat: add dingtalk provider (#94)
* feat: add dingtalk provider

Signed-off-by: zKen <690898835@qq.com>

* fix: amend the comment

Signed-off-by: zKen <690898835@qq.com>
2021-06-09 19:53:22 +08:00
Yang Luo
cd89f7a559 Merge pull request #80 from 1340908470/master
feat: find back password using email/phone instead of username
2021-06-07 20:12:27 +08:00
Weihao
c983ee7ca6 feat: won't send verification code if there is no account bounded to phone/email
Signed-off-by: Weihao <1340908470@qq.com>
2021-06-07 19:13:06 +08:00
Yang Luo
89fb4e555e Merge pull request #77 from WindSpiritSR/patch
fix: <Link> to <a> and table key
2021-06-06 23:36:36 +08:00
Yang Luo
98ec187019 Fix RedirectUris null bug. 2021-06-06 22:17:47 +08:00
Yang Luo
a9e68385ef Merge pull request #84 from Kininaru/master
fix: wrong usement in getUsername
2021-06-06 20:20:20 +08:00
Kininaru
b65ae4eec1 fix: wrong usement in getUsername
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-06-06 18:56:00 +08:00
Yang Luo
741d1feec8 Merge pull request #76 from Kininaru/master
feat: authorize via clientId and clientSecret
2021-06-06 18:08:20 +08:00
Kininaru
56be5f9a51 feat: authorize via clientId and clientSecret
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-06-06 17:27:03 +08:00
Yang Luo
ec5a574ce6 Show null provider error. 2021-06-06 11:19:28 +08:00
WindSpiritSR
6142ca52ca feat: add facebook provider (#79)
* feat: add facebook provider

Signed-off-by: WindSpirit <simon343riley@gmail.com>

* fix: set fb id to username and reformat

Signed-off-by: WindSpirit <simon343riley@gmail.com>
2021-06-06 10:06:54 +08:00
Yang Luo
eb658ad8ee Support affiliation sync. 2021-06-04 23:26:30 +08:00
WindSpirit
860fe91725 fix: <Link> to <a> and table key
Signed-off-by: WindSpirit <simon343riley@gmail.com>
2021-06-04 21:09:34 +08:00
Yang Luo
081da18403 Add address to user. 2021-06-04 20:54:17 +08:00
Yang Luo
7434d0f3d3 Fix "forget password" back bug. 2021-06-04 01:27:20 +08:00
Weihao Chen
1cb5ae54c5 feat: add "forget password" [front & backend] (#75)
* feat: add "forget password" [front & backend]

Signed-off-by: Weihao <1340908470@qq.com>

* fix: verification code can be sent even if no mobile phone or email is selected
refactor: forgetPassword -> forget; GetEmailAndPhoneByUsername -> GetEmailAndPhone; remove useless note

Signed-off-by: Weihao <1340908470@qq.com>
2021-06-02 13:39:01 +08:00
Yang Luo
29049297d8 Fix old-format oauth data bugs. 2021-06-01 23:32:34 +08:00
Yang Luo
58c7a60220 Show error in AccessToken. 2021-06-01 22:03:04 +08:00
Yang Luo
2e7ef69f07 Add some fields to User. 2021-06-01 20:37:00 +08:00
Yang Luo
90e617b2dc Improve "Third-party logins" UI. 2021-05-31 01:49:38 +08:00
Yang Luo
95cda41732 Improve UserInfo. 2021-05-31 01:23:32 +08:00
Yang Luo
0960578c35 Improve providers. 2021-05-31 00:24:52 +08:00
Yang Luo (罗杨)
645f8e5418 Merge pull request #73 from Kininaru/master
fix: removed outdated lines in README
2021-05-30 18:37:29 +08:00
Yang Luo
11f1f8f440 Show properties in user page. 2021-05-30 18:35:05 +08:00
Kininaru
57a73edc4a fix: removed outdated lines in README
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-30 18:26:20 +08:00
Yang Luo
dc89f2b0f6 Add Properties to user. 2021-05-30 18:24:22 +08:00
Yang Luo
badd75b894 Refactor out user_util.go 2021-05-30 13:37:34 +08:00
Yang Luo
215b96c881 Let SendEmail() return string. 2021-05-29 15:25:41 +08:00
Yang Luo
0f8a46fe0b Fix args in initBuiltInApplication(). 2021-05-28 22:38:12 +08:00
Yang Luo
089ff6c6eb Add AffiliationUrl. 2021-05-24 21:27:00 +08:00
Yang Luo
b23e8cf90c Improve i18n. 2021-05-24 20:48:04 +08:00
Yang Luo
2fff5f79ce Can set email title and content now. 2021-05-24 20:21:41 +08:00
Yang Luo
705c0159d4 Add id to VerificationRecord. 2021-05-24 19:25:35 +08:00
Yang Luo
290c5aac9e Add user to VerificationRecord. 2021-05-24 01:18:21 +08:00
Yang Luo
dbf11d61a7 Add sms and email providers to app. 2021-05-24 01:02:38 +08:00
Yang Luo
bd888fad38 Fix translation. 2021-05-23 23:40:44 +08:00
Yang Luo
31d981baf1 Fix small issues. 2021-05-23 23:38:38 +08:00
hsluoyz
02edb89602 Merge pull request #70 from Kininaru/master
feat: turing test before sending code
2021-05-23 18:01:33 +08:00
Kininaru
f39378562f feat: turing test before send code
Signed-off-by: Kininaru <shiftregister233@outlook.com>

i18n

i18n

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-22 21:03:49 +08:00
hsluoyz
665e77b797 Merge pull request #66 from Kininaru/master
feat: intergrate verification code countdown as a component
2021-05-20 21:43:46 +08:00
Kininaru
b2a75a527b feat: intergrate verification code countdown as a component
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-20 21:09:12 +08:00
hsluoyz
3ccf54581a Merge pull request #63 from Kininaru/master
feat: check email and phone number when signing up
2021-05-18 22:41:00 +08:00
Kininaru
66d953a6c1 feat: check user email and phone when signing up
Signed-off-by: Kininaru <shiftregister233@outlook.com>

phone prefix error

Signed-off-by: Kininaru <shiftregister233@outlook.com>

fix i18n

Signed-off-by: Kininaru <shiftregister233@outlook.com>

fix i18n error

Signed-off-by: Kininaru <shiftregister233@outlook.com>

removed useless file

Signed-off-by: Kininaru <shiftregister233@outlook.com>

move timeout to app.conf

Signed-off-by: Kininaru <shiftregister233@outlook.com>

i18n

Signed-off-by: Kininaru <shiftregister233@outlook.com>

made verification code reusable

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-18 22:20:26 +08:00
Kininaru
9bc29e25ef fix: add primary key Type to table verification_record
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-18 19:42:03 +08:00
Yang Luo
947d132362 Refactor out c.RequireSignedIn() 2021-05-17 23:25:28 +08:00
Weihao Chen
3e41ce0104 feat: add WeChatIdProvider (#62)
* feat: add WeChatIdProvider

Signed-off-by: Weihao <1340908470@qq.com>

* refactor: refactor WeChatIdProvider to suit to IdProvider interface; translate the annotation to English

Signed-off-by: Weihao <1340908470@qq.com>

* refactor: remove unnecessary comment

Signed-off-by: Weihao <1340908470@qq.com>
2021-05-17 14:17:46 +08:00
Yang Luo
85c76e2ed1 Fix SetUserField()'s affected bug. 2021-05-16 23:08:55 +08:00
Yang Luo
b103683fea Refactor GetDefaultApplication(). 2021-05-16 23:07:45 +08:00
Yang Luo
6508d96162 Remove GetOrganizationByName(). 2021-05-16 22:58:30 +08:00
Yang Luo
e6862713bb Improve password length check. 2021-05-16 22:01:22 +08:00
Yang Luo
798386655f Fix getFullAvatarUrl(). 2021-05-16 21:26:30 +08:00
Yang Luo
45bd3b316b Fix hash update. 2021-05-16 21:22:20 +08:00
Yang Luo
4b9ce5f401 Fix add/update salted password. 2021-05-16 21:04:26 +08:00
Yang Luo
18806f07a8 Support cred auto-login. 2021-05-16 18:18:55 +08:00
hsluoyz
338c589e51 Merge pull request #61 from 1340908470/refactor-IdProvider
refactor: remove ClientId/ClientSecret/RedirectUrl in IdProvider
2021-05-16 09:46:24 +08:00
Weihao
f48e04ef15 refactor: remove ClientId/ClientSecret/RedirectUrl in IdProvider int /idp/github.go & google.go & qq.go; alter Scopes in QqIdProvider.Config: profile&email -> get_user_info
Signed-off-by: Weihao <1340908470@qq.com>
2021-05-16 00:24:36 +08:00
Yang Luo
df7ac93feb Show error "Invalid JWT token". 2021-05-15 23:34:06 +08:00
Yang Luo
55e0fe14ca Improve reset buttons. 2021-05-15 20:44:40 +08:00
Yang Luo
e206ec5057 Update antd to latest v4.15.5 2021-05-15 20:32:00 +08:00
Yang Luo
2ea5e7ec78 Remove user's PhonePrefix. 2021-05-15 13:54:23 +08:00
Yang Luo
43db2de8d8 Add phone provider. 2021-05-15 13:43:21 +08:00
hsluoyz
08779b607d Merge pull request #57 from Kininaru/master
fix: remove all dom APIs from the project
2021-05-15 12:43:41 +08:00
Kininaru
18ea101db8 fix: remove all dom APIs from the project
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-15 12:35:49 +08:00
hsluoyz
23955c4450 Merge pull request #56 from Kininaru/master
Fixed reset phone email data error and removed dom API from ResetModal.js
2021-05-15 12:14:03 +08:00
Kininaru
d8e5194e99 fix: send code button background color
Signed-off-by: Kininaru <shiftregister233@outlook.com>

fixed error

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-15 11:58:23 +08:00
Kininaru
22aeebe8d3 fix: reset phone email data error and removed dom API from it
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-15 11:47:09 +08:00
Yang Luo
162c7261a3 Add getDefaultEmailProvider(). 2021-05-15 10:13:12 +08:00
Yang Luo
fb9f18af2c Add Email provider. 2021-05-14 23:45:20 +08:00
Yang Luo
b7cbc0e015 Return sms error message. 2021-05-14 18:19:47 +08:00
Yang Luo
d361a39ffc Add phone prefix to UI. 2021-05-14 17:39:53 +08:00
Yang Luo
db5ecddd3f Remove /get-default-providers API. 2021-05-14 16:07:39 +08:00
Yang Luo
9346587ca6 Filter github providers. 2021-05-14 15:55:50 +08:00
Yang Luo
fce4efcb84 Call Enforcer.ClearPolicy() to remove previous rules in DB. 2021-05-13 22:50:50 +08:00
Yang Luo
f4db41acb3 Fix console warnings. 2021-05-13 22:23:04 +08:00
hsluoyz
e4b6f758a7 Merge pull request #55 from Kininaru/master
feat: add reset Email and phone by using verification code.
2021-05-13 10:19:52 +08:00
Kininaru
1589da0a62 fix: check cn phone regex bug and add check to verification code
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-13 09:55:37 +08:00
Kininaru
892cb39e3e feat: move User.PhonePrefix to Organization.PhonePrefix
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-13 09:39:07 +08:00
Kininaru
827930a020 feat: add reset phone by verification code
Signed-off-by: Kininaru <shiftregister233@outlook.com>

fixed client bug

Signed-off-by: Kininaru <shiftregister233@outlook.com>

feat: add i18n

Signed-off-by: Kininaru <shiftregister233@outlook.com>

add Casbin License

Signed-off-by: Kininaru <shiftregister233@outlook.com>

removed port from remoteaddr

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-12 22:59:42 +08:00
Kininaru
400e335e68 feat: add reset email by verification code
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-12 21:38:31 +08:00
hsluoyz
7bea33dabd Merge pull request #54 from Kininaru/master
feat: set password for users
2021-05-09 21:12:41 +08:00
Kininaru
33e61cc5cf feat: set password for users
Signed-off-by: Kininaru <shiftregister233@outlook.com>

removed useless blank

Signed-off-by: Kininaru <shiftregister233@outlook.com>

add license header

Signed-off-by: Kininaru <shiftregister233@outlook.com>

i18n

Signed-off-by: Kininaru <shiftregister233@outlook.com>

database logic

Signed-off-by: Kininaru <shiftregister233@outlook.com>

i18n

Signed-off-by: Kininaru <shiftregister233@outlook.com>

i18n

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-09 20:56:52 +08:00
Yang Luo
19c1ef1b68 Fix bug in update column names. 2021-05-09 16:14:05 +08:00
Yang Luo
6e5aa2bc40 Finish db update sync. 2021-05-09 15:44:12 +08:00
Yang Luo
e2f6efd1ab Add user.PreHash 2021-05-09 14:54:17 +08:00
Yang Luo
d2d4c411ab Add password button. 2021-05-09 14:53:05 +08:00
Yang Luo
efab90ef3f Fix avatar URL. 2021-05-09 12:07:09 +08:00
Yang Luo
5a3abdbc95 Rename to authState, useProxy, delete EnableDocs. 2021-05-09 12:02:20 +08:00
hsluoyz
aa706019f2 Merge pull request #53 from HatsuneBQ/master
Added email sender and added owner to avatar url
2021-05-09 11:51:03 +08:00
Kininaru
5d1133dac9 fix: add owner to avatar url
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-09 11:42:22 +08:00
Kininaru
885592d774 feat: add email sender
Signed-off-by: Kininaru <shiftregister233@outlook.com>

update go mod sum

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-05-09 11:39:42 +08:00
Yang Luo
a1851c2b78 Add github.com/dchest/captcha imports. 2021-05-09 11:29:25 +08:00
Yang Luo
de67ee9014 Can sync update from 2nd DB. 2021-05-08 22:04:45 +08:00
Yang Luo
c76a432003 Add user.UpdateUserHash() 2021-05-08 18:51:37 +08:00
Yang Luo
e3b3a76088 Add willLog() and control access to signup page. 2021-05-08 16:52:02 +08:00
Yang Luo
75f23478d1 Add GetMaskedUser(). 2021-05-08 00:06:30 +08:00
Yang Luo
a24665714a Add logger. 2021-05-07 20:51:29 +08:00
Yang Luo
8a4311c85c Add PasswordSalt to org. 2021-05-06 19:13:03 +08:00
Yang Luo
f442f11568 Move passwordType to org. 2021-05-05 23:32:21 +08:00
Yang Luo
fffada894c Add organization and user to token. 2021-05-04 22:36:05 +08:00
Yang Luo
6095af0512 Add checkPassword(). 2021-05-03 10:13:32 +08:00
Yang Luo
6e47cd5bd5 Add getSaltedPassword(). 2021-05-03 01:23:45 +08:00
Yang Luo
813204194f Add SignupUrl and ForgetUrl to Application. 2021-05-03 00:48:02 +08:00
Yang Luo
76e4490aa7 Add cron job to run syncUsers(). 2021-05-02 23:18:12 +08:00
Yang Luo
d8aa6a1c95 Add TestSyncUsers(). 2021-05-02 23:06:08 +08:00
Yang Luo
1407ba5af7 Add db sync test. 2021-05-02 13:57:57 +08:00
Yang Luo
82f4f542ed Add User's IsForbidden. 2021-05-02 12:18:28 +08:00
Yang Luo
79f1c62ff7 Move adapter.createTable() out, make engine public. 2021-05-02 10:34:31 +08:00
Yang Luo
be0cfa132e Rename db to driverName. 2021-05-02 00:34:15 +08:00
Yang Luo
d509c61816 Fix bug in Casdoor's own 3rd-party login. 2021-05-02 00:20:40 +08:00
Yang Luo
0f7cd56441 Support Email and phone login. 2021-05-01 20:23:20 +08:00
Yang Luo
5b1b8662ac Improve CheckUserLogin(). 2021-05-01 19:45:40 +08:00
Yang Luo
7b32207443 Check more in CheckUserSignup(). 2021-05-01 18:39:40 +08:00
Yang Luo
904faae6eb Improve CheckUserSignup(). 2021-05-01 17:45:01 +08:00
Yang Luo
a093f3af5a Add username check. 2021-05-01 16:54:06 +08:00
hsluoyz
9971b368d0 Merge pull request #51 from Kininaru/master
feat: add info about MySQL and Postgres in readme
2021-04-30 20:21:24 +08:00
Kininaru
6ad1f5d33b feat: add info about MySQL and Postgres in readme
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-04-30 19:35:03 +08:00
Yang Luo
fa2653c836 Fix comment. 2021-04-29 22:56:35 +08:00
Kininaru
6dcece3990 feat: add pg for db adapter (#50)
Signed-off-by: Kininaru <shiftregister233@outlook.com>

fix: postgres creating failed

Signed-off-by: Kininaru <shiftregister233@outlook.com>

nil

Signed-off-by: Kininaru <shiftregister233@outlook.com>

rewrite postgres logic

Signed-off-by: Kininaru <shiftregister233@outlook.com>

removed pq from dependencies

Signed-off-by: Kininaru <shiftregister233@outlook.com>

comment pq

Signed-off-by: Kininaru <shiftregister233@outlook.com>

typo

Signed-off-by: Kininaru <shiftregister233@outlook.com>

comment

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-04-29 21:53:42 +08:00
Yang Luo
785f306c4b Use helmet on login, signup pages. 2021-04-29 21:28:24 +08:00
Yang Luo
3065bacad2 Add org's favicon. 2021-04-29 21:27:38 +08:00
Yang Luo
5779d57960 Set org's document title and favicon. 2021-04-29 19:51:03 +08:00
Yang Luo
cfe1eb9d50 Improve panic message. 2021-04-29 15:43:15 +08:00
kuchaguangjie
93564e8389 fix: add cross-env to set port for both windows & linux (#49)
* add cross-env to set port for both windows & linux,

* add package-lock.json back,

* don't commit package-lock.json
2021-04-29 15:42:59 +08:00
hsluoyz
92c35948e7 Merge pull request #48 from kuchaguangjie/master
.gitignore add ".swp"
2021-04-29 12:15:17 +08:00
eric
ac6c82f9d8 .gitignore add ".swp" 2021-04-29 11:58:28 +08:00
Yang Luo
c26c9ba0e8 Fix page links. 2021-04-28 22:40:21 +08:00
Yang Luo
35e482f24e Fix the signup. 2021-04-28 21:56:14 +08:00
Yang Luo
ea9bdbf45e Improve UploadAvatar API. 2021-04-28 19:02:01 +08:00
Yang Luo
ee8f2a6046 Improve signup page. 2021-04-28 15:54:50 +08:00
Yang Luo
ef1995de4f Translate signup page. 2021-04-28 09:43:11 +08:00
Yang Luo
6fbdc556ac Improve UI text. 2021-04-28 00:13:50 +08:00
Yang Luo
7a0ed4ebaf Replace "register" with "sign up". 2021-04-27 22:47:44 +08:00
Yang Luo
3793029491 Adjust register page text. 2021-04-27 22:35:14 +08:00
Yang Luo
8ce8169d65 Translate login page. 2021-04-27 22:03:22 +08:00
Yang Luo
0faeaae78f Improve i18n. 2021-04-27 20:59:50 +08:00
Yang Luo
43113a0ee4 Fix "/api/unlink" bug. 2021-04-27 20:42:19 +08:00
Yang Luo
c317d601a5 Sync info from 3rd-party if possible. 2021-04-27 19:35:40 +08:00
Yang Luo
6002395d12 Support oss domain. 2021-04-27 19:00:34 +08:00
Yang Luo
3437885723 Improve oss key names. 2021-04-27 18:11:41 +08:00
Yang Luo
f0ffacb6a5 Improve user page i18n. 2021-04-27 18:08:21 +08:00
Yang Luo
5326cafbed Add policy to allow account page access. 2021-04-27 00:45:37 +08:00
Yang Luo
987bb14a6b Add more fields to Claims. 2021-04-27 00:43:38 +08:00
Yang Luo
bb5e110049 Add TestSyncIds(). 2021-04-26 20:37:29 +08:00
Yang Luo
0fc7015c61 Add user type. 2021-04-26 19:00:23 +08:00
Yang Luo
4dd0c1a457 Improve Third-party Logins UI. 2021-04-26 18:33:06 +08:00
Yang Luo
085f0ca239 Improve avatar upload UI. 2021-04-26 18:23:06 +08:00
Yang Luo
d586efc242 Add user's phonePrefix. 2021-04-26 17:57:32 +08:00
Yang Luo
36895801f0 Add Link API. 2021-04-19 01:14:41 +08:00
Yang Luo
6774b0379c Add Unlink API. 2021-04-18 23:14:46 +08:00
Yang Luo
2934d6bdeb Rename userObj to user. 2021-04-18 20:47:40 +08:00
hsluoyz
39f2f609c3 Merge pull request #34 from Kininaru/master
feat: add go backend API docs
2021-03-30 00:28:28 +08:00
Kininaru
3a13b8e73c feat: add go backend API docs
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-03-29 23:40:25 +08:00
Yang Luo
5c49693bc0 Use react-social-login-buttons. 2021-03-29 22:11:28 +08:00
Kininaru
45071bb8c7 fix: Avatar upload doesn't work since the backend logic has been rewritten (#32)
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-03-29 11:19:55 +08:00
Yang Luo
4ea6529969 Add Avatar to claims. 2021-03-28 21:57:22 +08:00
Yang Luo
bc368a4361 Add TestSyncAvatarsFromGitHub(). 2021-03-28 21:50:00 +08:00
Yang Luo
cc8485a7db Add renderAvatar(). 2021-03-28 21:18:41 +08:00
Yang Luo
d6c8bb87c2 Fix provider type. 2021-03-28 20:20:40 +08:00
Yang Luo
73ffb22c32 Improve login page. 2021-03-28 19:34:21 +08:00
Yang Luo
658dd79d81 Add "My Account" tile. 2021-03-28 17:36:31 +08:00
Yang Luo
f5ceae901b Support auto-login. 2021-03-28 16:53:49 +08:00
Yang Luo
2029a28d7b Fix getAccount() response. 2021-03-28 10:51:08 +08:00
Yang Luo
0127f8fb63 Improve response message. 2021-03-28 10:36:47 +08:00
Yang Luo
d6715c7601 Improve API error handling. 2021-03-28 00:48:34 +08:00
Yang Luo
6c2c5be33d Fix React warnings. 2021-03-27 11:56:16 +08:00
Yang Luo
2063f1d27d Update antd to 4.14.1 2021-03-26 23:41:18 +08:00
Yang Luo
b1ac60fa02 Improve provide list. 2021-03-26 23:25:09 +08:00
Yang Luo
3bcc292da2 Change antd primary-color. 2021-03-26 23:18:24 +08:00
Yang Luo
437289438f Improve login page. 2021-03-26 21:58:30 +08:00
Yang Luo
d11bb49eac Improve login failure handling. 2021-03-26 21:58:19 +08:00
Yang Luo
fa358654e6 Use Link. 2021-03-26 21:58:10 +08:00
Yang Luo
6880c28194 Remove onLoggedIn(). 2021-03-26 21:57:54 +08:00
Yang Luo
9af2ac49be Add RegisterPage and ResultPage. 2021-03-26 21:57:41 +08:00
Yang Luo
3d4c987d32 Rename Face to LoginPage. 2021-03-26 21:56:51 +08:00
Yang Luo
7fe9705303 Rename to SelfLoginPage. 2021-03-26 21:56:27 +08:00
Yang Luo
b6b433f26a Add EnableSignUp. 2021-03-26 21:55:39 +08:00
Yang Luo
da38f7a6ba Improve login error handling. 2021-03-25 23:22:34 +08:00
Yang Luo
540adfed20 Improve IDP username handling. 2021-03-24 00:44:41 +08:00
Yang Luo
39d208ccd0 Fix QqIdProvider. 2021-03-23 23:48:18 +08:00
Yang Luo
c24de7d180 Change provider interface. 2021-03-23 23:23:59 +08:00
Yang Luo
fa141a0edb Add QqIdProvider. 2021-03-22 20:00:35 +08:00
Yang Luo
1c97501016 Simplify callback URL. 2021-03-21 16:05:00 +08:00
Yang Luo
6368298715 Update logo. 2021-03-21 15:31:31 +08:00
Yang Luo
26502350d3 Add innerParams. 2021-03-21 13:45:55 +08:00
Yang Luo
55b62e9786 Make user profile public. 2021-03-21 00:44:19 +08:00
Yang Luo
7f367ae346 Fix AuthCallback's code handling. 2021-03-21 00:38:00 +08:00
Yang Luo
8b921b2c1e Fix double GET params issue, fix double state bug. 2021-03-20 23:50:34 +08:00
Yang Luo
808e6c6283 Finish /login/oauth/authorize 2021-03-20 22:34:22 +08:00
Yang Luo
f89f454e0e Add getOAuthGetParameters(). 2021-03-20 16:51:10 +08:00
Yang Luo
63a4066a8d Return code for /api/login 2021-03-20 13:05:34 +08:00
Yang Luo
7049e09570 Add error handling to face page. 2021-03-20 12:29:34 +08:00
Yang Luo
80c47dd8c6 Add frontend /login/oauth 2021-03-20 11:34:04 +08:00
Yang Luo
9980ef1975 Add /api/get-app-login 2021-03-20 10:51:00 +08:00
Yang Luo
c8cd37058e Add codeToResponse(). 2021-03-20 09:12:24 +08:00
Yang Luo
18c021b009 Merge two login functions. 2021-03-20 00:30:37 +08:00
hsluoyz
d0e243fca3 Merge pull request #23 from Kininaru/master
feat: added avatar tailoring and uploading
2021-03-17 13:25:53 +08:00
Kininaru
1908f528c8 feat: added avatar tailoring and uploading
Signed-off-by: Kininaru <shiftregister233@outlook.com>

fixed type errors

Signed-off-by: Kininaru <shiftregister233@outlook.com>

fixed the wrong folder

Signed-off-by: Kininaru <shiftregister233@outlook.com>

rewrite login check logic, added unix time to avatar url

Signed-off-by: Kininaru <shiftregister233@outlook.com>

fixed a bug about strconv

Signed-off-by: Kininaru <shiftregister233@outlook.com>

supported oss

Signed-off-by: Kininaru <shiftregister233@outlook.com>

disabled oss provide qiniu

Signed-off-by: Kininaru <shiftregister233@outlook.com>

Fixed avatar url error

Signed-off-by: Kininaru <shiftregister233@outlook.com>

Fixed avatar length bug

Signed-off-by: Kininaru <shiftregister233@outlook.com>

Fixed avatar length bug

Signed-off-by: Kininaru <shiftregister233@outlook.com>

fixed oss.conf

Signed-off-by: Kininaru <shiftregister233@outlook.com>

Put uploading avatar into UserEditPage

Signed-off-by: Kininaru <shiftregister233@outlook.com>

removed avatar dir

Signed-off-by: Kininaru <shiftregister233@outlook.com>

removed avatar in main.go

Signed-off-by: Kininaru <shiftregister233@outlook.com>

Made CropperDiv a reusable component, and updated README for OSS config

Signed-off-by: Kininaru <shiftregister233@outlook.com>

Convert ts to js

Signed-off-by: Kininaru <shiftregister233@outlook.com>

removed ts

Signed-off-by: Kininaru <shiftregister233@outlook.com>

fix: set avatar link to string 255

Signed-off-by: Kininaru <shiftregister233@outlook.com>

fix: updated yarn lock

Signed-off-by: Kininaru <shiftregister233@outlook.com>

add: Casbin license

Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-03-17 13:19:23 +08:00
Yang Luo
0ce7420907 Add HandleLoggedIn(). 2021-03-15 19:11:41 +08:00
Yang Luo
4a170d1d56 Change /api/auth/login to POST. 2021-03-15 00:49:16 +08:00
Yang Luo
2a481d6a2e Add frontend getOAuthCode() API. 2021-03-15 00:28:34 +08:00
Yang Luo
4e22e0523d Rename redirectUrl to redirectUri. 2021-03-15 00:01:21 +08:00
Yang Luo
df948e9e34 Fix JWT token bugs. 2021-03-14 23:08:08 +08:00
Yang Luo
f014554415 Generate real access token. 2021-03-14 22:48:09 +08:00
Yang Luo
1fd6ee388c Add /api/oauth/code API. 2021-03-14 18:18:03 +08:00
Yang Luo
202a94a8e5 Add /api/oauth/token API. 2021-03-14 00:18:11 +08:00
Yang Luo
85523fa9d4 Add token pages. 2021-03-14 00:00:29 +08:00
Yang Luo
64c9548019 Update license header. 2021-03-13 23:09:14 +08:00
hsluoyz
d38cfa28b8 Merge pull request #21 from Kininaru/master
added explainations to Dev and Prod usage in README
2021-03-12 11:32:06 +08:00
Kininaru
65f01c1a09 added explainations to Dev and Prod usage in README
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-03-12 11:15:07 +08:00
Yang Luo
5e04b8c726 Add HomepageUrl and Description to app. 2021-03-06 21:50:48 +08:00
Yang Luo
45ec95b859 Add RedirectUrls to app. 2021-03-06 21:41:24 +08:00
Yang Luo
9e8bde509a Add clientId and clientSecret to app. 2021-03-06 16:39:17 +08:00
Yang Luo
e792e814d4 Add other authz rules. 2021-03-06 00:40:11 +08:00
Yang Luo
1dfcfb307f Set session cookie to: casdoor_session_id 2021-03-06 00:19:06 +08:00
Yang Luo
f3f902af45 Parse subOwner, subName. 2021-02-28 23:14:48 +08:00
Yang Luo
ba4185c9b1 Add AuthzFilter. 2021-02-28 20:23:50 +08:00
Yang Luo
dfa77ab25d Add GoogleIdProvider. 2021-02-21 23:51:40 +08:00
Yang Luo
e91fdca5e7 Improve SelectLanguageBox. 2021-02-21 22:35:50 +08:00
Yang Luo
40fb336e95 Add IdProvider interface. 2021-02-21 22:33:53 +08:00
Yang Luo
62c69a89c1 Improve passwordType control. 2021-02-20 23:27:30 +08:00
hsluoyz
717d655fdb Merge pull request #14 from RobotHuang/master
feat: support i18n
2021-02-20 18:00:12 +08:00
RobotHuang
e301121c21 feat: support i18n
Signed-off-by: RobotHuang <1183598761@qq.com>
2021-02-20 10:25:29 +08:00
Yang Luo
a09a8b2af0 Add README. 2021-02-16 17:58:51 +08:00
Yang Luo
df5514ef31 Add getUsers() to Auth folder. 2021-02-15 23:28:25 +08:00
Yang Luo
60ad52f7ae Add User.isGlobalAdmin 2021-02-15 22:14:19 +08:00
Yang Luo
d44a5ee2c1 Update app default logo URL. 2021-02-15 17:19:48 +08:00
Yang Luo
bf4bbeeb15 Fix initAuthWithConfig() arg. 2021-02-15 17:08:48 +08:00
Yang Luo
475f79ffc8 Set face logo width to 250. 2021-02-15 16:54:05 +08:00
Yang Luo
abe1e5eccb Add initBuiltInApplication(). 2021-02-15 10:32:14 +08:00
Yang Luo
e4778b967f Add dbName config. 2021-02-15 10:11:26 +08:00
Yang Luo
e71311b1af Add InitDb(). 2021-02-15 10:05:14 +08:00
Yang Luo
906fe9758e Add profile links. 2021-02-14 22:49:06 +08:00
Yang Luo
65eee22099 Check user under org. 2021-02-14 21:45:40 +08:00
Yang Luo
7203dcbe22 Set CookieSameSite to None. 2021-02-14 19:41:51 +08:00
Yang Luo
46fb2b4bff Use a href for oauth icon. 2021-02-14 17:40:15 +08:00
Yang Luo
15f9d71127 Add UseProxy config. 2021-02-14 17:24:05 +08:00
Yang Luo
b5b86262d6 Refactor the auth code. 2021-02-14 17:12:08 +08:00
Yang Luo
9b45e5fe43 Add setAuthServerUrl(). 2021-02-14 15:59:25 +08:00
Yang Luo
277dec0af3 Define auth.ServerUrl. 2021-02-14 15:51:56 +08:00
Yang Luo
20b70c323d Rename to method. 2021-02-14 15:45:48 +08:00
Yang Luo
faeb93494c Improve auth folder. 2021-02-14 15:40:57 +08:00
Yang Luo
001496b90f Add auth folder. 2021-02-14 14:34:03 +08:00
Yang Luo
40587f35e3 Add user's Affiliation and IsAdmin. 2021-02-14 13:43:55 +08:00
Yang Luo
4b66c24bb6 Add app's EnablePassword. 2021-02-14 10:56:37 +08:00
Yang Luo
8543d09d05 Improve UI. 2021-02-14 10:20:42 +08:00
Yang Luo
2dd4afbe7b Fix github login. 2021-02-14 01:04:51 +08:00
Yang Luo
101d418257 Fix code format. 2021-02-14 00:55:17 +08:00
Yang Luo
c81118feff Add github oauth. 2021-02-14 00:22:24 +08:00
Yang Luo
ea09beffe2 Show oauth logos. 2021-02-13 23:00:43 +08:00
Yang Luo
bac289d387 Add github oauth logo. 2021-02-13 21:04:23 +08:00
Yang Luo
cf2152846c Change CRLF to LF. 2021-02-13 20:35:41 +08:00
Yang Luo
7bffb03b29 Fix favicon. 2021-02-13 20:15:53 +08:00
Yang Luo
6e015a959f Add user's id. 2021-02-13 20:10:41 +08:00
Yang Luo
34f87c78d8 Use new account page. 2021-02-13 17:56:58 +08:00
Yang Luo
e57590ab70 Fix menu key conflict. 2021-02-13 17:56:15 +08:00
Yang Luo
6824d558ca Add HomePage. 2021-02-13 17:34:32 +08:00
Yang Luo
9677282017 Add user's avatar. 2021-02-13 14:49:31 +08:00
Yang Luo
4daa4416c1 Add react-github-corner. 2021-02-13 14:22:49 +08:00
Yang Luo
35df55cf70 Add license header. 2021-02-13 13:30:51 +08:00
Yang Luo
09b39d69f6 Change CRLF to LF. 2021-02-13 12:15:19 +08:00
Yang Luo
7304e62f95 Add login and account pages. 2021-02-13 01:33:06 +08:00
Yang Luo
cdc1445883 Fix login. 2021-02-13 00:11:12 +08:00
Yang Luo
d30c96038d Fix links. 2021-02-12 01:07:23 +08:00
Yang Luo
ee1e08ae20 Use react links. 2021-02-12 00:31:53 +08:00
Yang Luo
8790e95147 Add Organization to Application. 2021-02-11 23:51:48 +08:00
Yang Luo
3ea58a0cdc Add account APIs. 2021-02-11 22:56:08 +08:00
Yang Luo
09ffe69382 Add door page. 2021-02-11 16:43:30 +08:00
Yang Luo
1483d08df7 Set server port to 8000. 2021-01-18 20:26:06 +08:00
Yang Luo
cd39695443 Add face. 2021-01-12 23:21:07 +08:00
Yang Luo
6459926a0f Add application logo. 2021-01-12 22:44:26 +08:00
Yang Luo
b13ec27981 Add providers to app. 2020-12-20 23:33:38 +08:00
Yang Luo
84c1f2634e Add application list and edit pages. 2020-12-20 23:24:09 +08:00
Yang Luo
b9adda2277 Add provider list and edit pages. 2020-12-20 21:25:23 +08:00
Yang Luo
f0692985f1 Add organization list and edit pages. 2020-12-20 20:31:48 +08:00
211 changed files with 33000 additions and 6089 deletions

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
web/node_modules/

12
.github/semantic.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# Always validate the PR title AND all the commits
titleAndCommits: true
# Require at least one commit to be valid
# this is only relevant when using commitsOnly: true or titleAndCommits: true,
# which validate all commits by default
anyCommit: true
# Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowMergeCommits: false
# Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowRevertCommits: false

67
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Build
on: [push, pull_request]
jobs:
frontend:
name: Front-end
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14.17.0'
- run: yarn install && CI=false yarn run build
working-directory: ./web
backend:
name: Back-end
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.16.5'
- run: go version
- name: Build
run: |
go build -race -ldflags "-extldflags '-static'"
working-directory: ./
release:
name: Release
runs-on: ubuntu-latest
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
needs: [ frontend, backend ]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 12
- 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: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push to Docker Hub
uses: docker/build-push-action@v2
with:
push: true
tags: casbin/casdoor:latest

35
.github/workflows/sync.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Crowdin Action
on:
push:
branches: [ master ]
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
steps:
- name: Checkout
uses: actions/checkout@v2
- name: crowdin action
uses: crowdin/github-action@1.2.0
with:
upload_translations: true
download_translations: true
push_translations: true
commit_message: 'refactor: New Crowdin translations by Github Action'
localization_branch_name: l10n_crowdin_action
create_pull_request: true
pull_request_title: 'refactor: New Crowdin translations'
crowdin_branch_name: l10n_branch
config: './web/crowdin.yml'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: '463556'
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

4
.gitignore vendored
View File

@@ -4,6 +4,7 @@
*.dll
*.so
*.dylib
*.swp
# Test binary, built with `go test -c`
*.test
@@ -21,5 +22,6 @@ tmp/
tmpFiles/
*.tmp
logs/
files/
lastupdate.tmp
commentsRouter*.go
commentsRouter*.go

23
.releaserc.json Normal file
View File

@@ -0,0 +1,23 @@
{
"debug": true,
"branches": [
"+([0-9])?(.{+([0-9]),x}).x",
"master",
{
"name": "rc"
},
{
"name": "beta",
"prerelease": true
},
{
"name": "alpha",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}

19
Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
FROM golang:1.16 AS BACK
WORKDIR /go/src/casdoor
COPY . .
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
WORKDIR /web
COPY ./web .
RUN npm install && npm run build
FROM alpine:latest
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

171
README.md
View File

@@ -1 +1,170 @@
# casdoor
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
<p align="center">
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
<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">
<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">
<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">
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
</a>
</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>
<a href="https://github.com/casbin/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">
<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">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
</a>
</p>
## Online demo
Deployed site: https://door.casbin.com/
## Quick Start
Run your own casdoor program in a few minutes:smiley:
### Download
There are two methods, get code via go subcommand `get`:
```shell
go get github.com/casbin/casdoor
```
or `git`:
```bash
git clone https://github.com/casbin/casdoor
```
Finally, change directory:
```bash
cd casdoor/
```
We provide two start up methods for all kinds of users.
### Manual
#### Simple configuration
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
```bash
username:password@tcp(database_ip:database_port)/
```
#### Run
Casdoor provides two run modes, the difference is binary size and user prompt.
##### Dev Mode
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
```bash
cd web/ && npm install && npm run start
```
*❗ A word of caution ❗: the `npm` commands above need a recommended system RAM of at least 4GB. It has a potential failure during building the files if your RAM is not sufficient.*
Then build back-end binary file, change directory to root(Relative to casdoor):
```bash
go run main.go
```
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
```
Then build back-end binary file, change directory to root(Relative to casdoor):
```bash
go build main.go && sudo ./main
```
> Notice, you should visit back-end port, default 8000. Now try to visit **http://SERVER_IP:8000/**
### Docker
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
Edit `conf/app.conf`, modify `dataSourceName` to the fixed content:
```bash
dataSourceName = root:123@tcp(db:3306)/
```
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
#### Run
```bash
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.
## Other examples
These all use casdoor as a centralized authentication platform.
- [Casnode](https://github.com/casbin/casnode): Next-generation forum software based on React + Golang.
- [Casbin-OA](https://github.com/casbin/casbin-oa): A full-featured OA(Office Assistant) system.
- ......
## 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).
### I18n notice
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
## License
[Apache-2.0](https://github.com/casbin/casdoor/blob/master/LICENSE)

125
authz/authz.go Normal file
View File

@@ -0,0 +1,125 @@
// 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 authz
import (
"github.com/astaxie/beego"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
stringadapter "github.com/qiangmzsx/string-adapter/v2"
)
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)
if err != nil {
panic(err)
}
modelText := `
[request_definition]
r = subOwner, subName, method, urlPath, objOwner, objName
[policy_definition]
p = subOwner, subName, method, urlPath, objOwner, objName
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
(r.subName == p.subName || p.subName == "*" || r.subName != "anonymous" && p.subName == "!anonymous") && \
(r.method == p.method || p.method == "*") && \
(r.urlPath == p.urlPath || p.urlPath == "*") && \
(r.objOwner == p.objOwner || p.objOwner == "*") && \
(r.objName == p.objName || p.objName == "*") || \
(r.urlPath == "/api/update-user" && r.subOwner == r.objOwner && r.subName == r.objName)
`
m, err := model.NewModelFromString(modelText)
if err != nil {
panic(err)
}
Enforcer, err = casbin.NewEnforcer(m, a)
if err != nil {
panic(err)
}
Enforcer.ClearPolicy()
//if len(Enforcer.GetPolicy()) == 0 {
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, *, *, POST, /api/login/oauth/access_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, *, *, POST, /api/paypal, *, *
p, *, *, GET, /api/success-pay, *, *
p, *, *, GET, /api/get-application-clientId, *, *
`
sa := stringadapter.NewAdapter(ruleText)
// load all rules from string adapter to enforcer's memory
err := sa.LoadPolicy(Enforcer.GetModel())
if err != nil {
panic(err)
}
// save all rules from enforcer's memory to Xorm adapter (DB)
// same as:
// a.SavePolicy(Enforcer.GetModel())
err = Enforcer.SavePolicy()
if err != nil {
panic(err)
}
}
}
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
if err != nil {
panic(err)
}
return res
}

View File

@@ -1,6 +1,14 @@
appname = casdoor
httpport = 7000
httpport = 8000
runmode = dev
SessionOn = true
copyrequestbody = true
dataSourceName = root:123@tcp(localhost:3306)/
driverName = mysql
dataSourceName = root:123@tcp(localhost:3306)/
dbName = casdoor
redisEndpoint =
defaultStorageProvider =
authState = "casdoor"
httpProxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000

227
controllers/account.go Normal file
View File

@@ -0,0 +1,227 @@
// 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"
"fmt"
"strconv"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/original"
"github.com/casbin/casdoor/util"
)
const (
ResponseTypeLogin = "login"
ResponseTypeCode = "code"
)
type RequestForm struct {
Type string `json:"type"`
Organization string `json:"organization"`
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
Region string `json:"region"`
Application string `json:"application"`
Provider string `json:"provider"`
Code string `json:"code"`
State string `json:"state"`
RedirectUri string `json:"redirectUri"`
Method string `json:"method"`
EmailCode string `json:"emailCode"`
PhoneCode string `json:"phoneCode"`
PhonePrefix string `json:"phonePrefix"`
AutoSignin bool `json:"autoSignin"`
}
type Response struct {
Status string `json:"status"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
Data2 interface{} `json:"data2"`
}
type HumanCheck struct {
Type string `json:"type"`
AppKey string `json:"appKey"`
Scene string `json:"scene"`
CaptchaId string `json:"captchaId"`
CaptchaImage interface{} `json:"captchaImage"`
}
// Signup
// @Title Signup
// @Description sign up a new user
// @Param username formData string true "The username to sign up"
// @Param password formData string true "The password"
// @Success 200 {object} controllers.Response The Response object
// @router /signup [post]
func (c *ApiController) Signup() {
if c.GetSessionUsername() != "" {
c.ResponseError("Please sign out first before signing up", c.GetSessionUsername())
return
}
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
panic(err)
}
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if !application.EnableSignUp {
c.ResponseError("The application does not allow to sign up new account")
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 != "" {
c.ResponseError(msg)
return
}
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization)
lastIdInt := util.ParseInt(lastUser.Id)
id = strconv.Itoa(lastIdInt + 1)
}
username := form.Username
if !application.IsSignupItemVisible("Username") {
username = id
}
user := &object.User{
Owner: form.Organization,
Name: username,
CreatedTime: util.GetCurrentTime(),
Id: id,
Type: "normal-user",
Password: form.Password,
DisplayName: form.Name,
Avatar: organization.DefaultAvatar,
Email: form.Email,
Phone: form.Phone,
Address: []string{},
Affiliation: form.Affiliation,
Region: form.Region,
Score: getInitScore(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
SignupApplication: application.Name,
Properties: map[string]string{},
}
affected := object.AddUser(user)
if affected {
original.AddUserToOriginalDatabase(user)
}
if application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId())
}
object.DisableVerificationCode(form.Email)
object.DisableVerificationCode(checkPhone)
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId)
}
// Logout
// @Title Logout
// @Description logout the current user
// @Success 200 {object} controllers.Response The Response object
// @router /logout [post]
func (c *ApiController) Logout() {
user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ResponseOk(user)
}
// GetAccount
// @Title GetAccount
// @Description get the details of the current account
// @Success 200 {object} controllers.Response The Response object
// @router /get-account [get]
func (c *ApiController) GetAccount() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
organization := object.GetOrganizationByUser(user)
c.ResponseOk(user, organization)
}
// GetHumanCheck ...
func (c *ApiController) GetHumanCheck() {
c.Data["json"] = HumanCheck{Type: "none"}
provider := object.GetDefaultHumanCheckProvider()
if provider == nil {
id, img := object.GetCaptcha()
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
c.ServeJSON()
return
}
c.ServeJSON()
}

126
controllers/application.go Normal file
View File

@@ -0,0 +1,126 @@
// 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/casbin/casdoor/object"
)
// GetApplications
// @Title GetApplications
// @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() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetApplications(owner)
c.ServeJSON()
}
// GetApplication
// @Title GetApplication
// @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() {
id := c.Input().Get("id")
c.Data["json"] = object.GetApplication(id)
c.ServeJSON()
}
// GetUserApplication
// @Title GetUserApplication
// @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() {
id := c.Input().Get("id")
user := object.GetUser(id)
if user == nil {
c.ResponseError("No such user.")
return
}
c.Data["json"] = object.GetApplicationByUser(user)
c.ServeJSON()
}
// UpdateApplication
// @Title UpdateApplication
// @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"
// @Success 200 {object} controllers.Response The Response object
// @router /update-application [post]
func (c *ApiController) UpdateApplication() {
id := c.Input().Get("id")
var application object.Application
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
c.ServeJSON()
}
// AddApplication
// @Title AddApplication
// @Description add an application
// @Param body body object.Application true "The details of the application"
// @Success 200 {object} controllers.Response The Response object
// @router /add-application [post]
func (c *ApiController) AddApplication() {
var application object.Application
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
c.ServeJSON()
}
// DeleteApplication
// @Title DeleteApplication
// @Description delete an application
// @Param body body object.Application true "The details of the application"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-application [post]
func (c *ApiController) DeleteApplication() {
var application object.Application
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteApplication(&application))
c.ServeJSON()
}
func (c *ApiController) GetApplicationByClientId() {
clientId := c.Input().Get("clientId")
c.Data["json"] = object.GetApplicationByClientId(clientId)
c.ServeJSON()
}

350
controllers/auth.go Normal file
View File

@@ -0,0 +1,350 @@
// 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"
"fmt"
"strconv"
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/idp"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/proxy"
"github.com/casbin/casdoor/util"
)
func codeToResponse(code *object.Code) *Response {
if code.Code == "" {
return &Response{Status: "error", Msg: code.Message, Data: code.Code}
}
return &Response{Status: "ok", Msg: "", Data: code.Code}
}
// HandleLoggedIn ...
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
userId := user.GetId()
if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
resp = &Response{Status: "ok", Msg: "", Data: userId}
} else if form.Type == ResponseTypeCode {
clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType")
redirectUri := c.Input().Get("redirectUri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state)
resp = codeToResponse(code)
if application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
} else {
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
}
// if user did not check auto signin
if resp.Status == "ok" && !form.AutoSignin {
timestamp := time.Now().Unix()
timestamp += 3600 * 24
c.SetSessionData(&SessionData{
ExpireTime: timestamp,
})
}
return resp
}
// GetApplicationLogin ...
// @Title GetApplicationLogin
// @Description get application login
// @Param clientId query string true "client id"
// @Param responseType query string true "response type"
// @Param redirectUri query string true "redirect uri"
// @Param scope query string true "scope"
// @Param state query string true "state"
// @Success 200 {object} controllers.api_controller.Response The Response object
// @router /update-application [get]
func (c *ApiController) GetApplicationLogin() {
clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType")
redirectUri := c.Input().Get("redirectUri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
if msg != "" {
c.ResponseError(msg, application)
} else {
c.ResponseOk(application)
}
}
func setHttpClient(idProvider idp.IdProvider, providerType string) {
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" {
idProvider.SetHttpClient(proxy.ProxyHttpClient)
} else {
idProvider.SetHttpClient(proxy.DefaultHttpClient)
}
}
// Login ...
// @Title Login
// @Description login
// @Param oAuthParams query string true "oAuth parameters"
// @Param body body RequestForm true "Login information"
// @Success 200 {object} controllers.api_controller.Response The Response object
// @router /login [post]
func (c *ApiController) Login() {
resp := &Response{}
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
c.ResponseError(err.Error())
return
}
if form.Username != "" {
if form.Type == ResponseTypeLogin {
if c.GetSessionUsername() != "" {
c.ResponseError("Please sign out first before signing in", c.GetSessionUsername())
return
}
}
var user *object.User
var msg string
if form.Password == "" {
var verificationCodeType string
// check result through Email or Phone
if strings.Contains(form.Email, "@") {
verificationCodeType = "email"
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
responseText := fmt.Sprintf("Email%s", checkResult)
c.ResponseError(responseText)
return
}
} 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)
c.ResponseError(responseText)
return
}
}
// 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.")
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)
}
} else {
password := form.Password
user, msg = object.CheckUserPassword(form.Organization, form.Username, password)
}
if msg != "" {
resp = &Response{Status: "error", Msg: msg}
} else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
}
} else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
providerItem := application.GetProviderItem(provider.Name)
if !providerItem.IsProviderVisible() {
c.ResponseError(fmt.Sprintf("The provider: %s is not enabled for the application", provider.Name))
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
}
setHttpClient(idProvider, provider.Type)
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
}
// 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)
}
if user != nil {
// Sign in via OAuth (want to sign up but already have account)
if user.IsForbidden {
c.ResponseError("the user is forbidden to sign in, please contact the administrator")
}
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
} else {
// 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))
return
}
if !providerItem.CanSignUp {
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 via %s, please use another way to sign up", provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
return
}
properties := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
user := &object.User{
Owner: application.Organization,
Name: userInfo.Username,
CreatedTime: util.GetCurrentTime(),
Id: util.GenerateId(),
Type: "normal-user",
DisplayName: userInfo.DisplayName,
Avatar: userInfo.AvatarUrl,
Address: []string{},
Email: userInfo.Email,
Score: getInitScore(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
SignupApplication: application.Name,
Properties: properties,
}
object.AddUser(user)
// sync info from 3rd-party if possible
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
object.LinkUserAccount(user, provider.Type, userInfo.Id)
resp = c.HandleLoggedIn(application, user, &form)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.Username = user.Name
object.AddRecord(record)
}
//resp = &Response{Status: "ok", Msg: "", Data: res}
} else { // form.Method != "signup"
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("The account does not exist", userInfo)
return
}
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if oldUser == nil {
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if oldUser != nil {
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
return
}
user := object.GetUser(userId)
// sync info from 3rd-party if possible
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
isLinked := object.LinkUserAccount(user, provider.Type, userInfo.Id)
if isLinked {
resp = &Response{Status: "ok", Msg: "", Data: isLinked}
} else {
resp = &Response{Status: "error", Msg: "Failed to link user account", Data: isLinked}
}
}
} else {
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
return
}
c.Data["json"] = resp
c.ServeJSON()
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
// 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.
@@ -14,13 +14,33 @@
package controllers
import "github.com/astaxie/beego"
import (
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/util"
)
type ApiController struct {
beego.Controller
}
func (c *ApiController) GetSessionUser() string {
type SessionData struct {
ExpireTime int64
}
// GetSessionUsername ...
func (c *ApiController) GetSessionUsername() string {
// check if user session expired
sessionData := c.GetSessionData()
if sessionData != nil &&
sessionData.ExpireTime != 0 &&
sessionData.ExpireTime < time.Now().Unix() {
c.SetSessionUsername("")
c.SetSessionData(nil)
return ""
}
user := c.GetSession("username")
if user == nil {
return ""
@@ -29,6 +49,41 @@ func (c *ApiController) GetSessionUser() string {
return user.(string)
}
func (c *ApiController) SetSessionUser(user string) {
// SetSessionUsername ...
func (c *ApiController) SetSessionUsername(user string) {
c.SetSession("username", user)
}
// GetSessionData ...
func (c *ApiController) GetSessionData() *SessionData {
session := c.GetSession("SessionData")
if session == nil {
return nil
}
sessionData := &SessionData{}
err := util.JsonToStruct(session.(string), sessionData)
if err != nil {
panic(err)
}
return sessionData
}
// SetSessionData ...
func (c *ApiController) SetSessionData(s *SessionData) {
if s == nil {
c.DelSession("SessionData")
return
}
c.SetSession("SessionData", util.StructToJson(s))
}
func wrapActionResponse(affected bool) *Response {
if affected {
return &Response{Status: "ok", Msg: "", Data: "Affected"}
} else {
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
}
}

205
controllers/ldap.go Normal file
View File

@@ -0,0 +1,205 @@
// 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/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
type LdapServer struct {
Host string `json:"host"`
Port int `json:"port"`
Admin string `json:"admin"`
Passwd string `json:"passwd"`
BaseDn string `json:"baseDn"`
}
type LdapResp struct {
//Groups []LdapRespGroup `json:"groups"`
Users []object.LdapRespUser `json:"users"`
}
//type LdapRespGroup struct {
// GroupId string
// GroupName string
//}
type LdapSyncResp struct {
Exist []object.LdapRespUser `json:"exist"`
Failed []object.LdapRespUser `json:"failed"`
}
func (c *ApiController) GetLdapUser() {
ldapServer := LdapServer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
c.ResponseError("Missing parameter")
return
}
var resp LdapResp
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
if err != nil {
c.ResponseError(err.Error())
return
}
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
//if err != nil {
// c.ResponseError(err.Error())
// return
//}
//for _, group := range groupsMap {
// resp.Groups = append(resp.Groups, LdapRespGroup{
// GroupId: group.GidNumber,
// GroupName: group.Cn,
// })
//}
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
if err != nil {
c.ResponseError(err.Error())
return
}
for _, user := range users {
resp.Users = append(resp.Users, object.LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
//GroupName: groupsMap[user.GidNumber].Cn,
Uuid: user.Uuid,
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
})
}
c.Data["json"] = Response{Status: "ok", Data: resp}
c.ServeJSON()
}
func (c *ApiController) GetLdaps() {
owner := c.Input().Get("owner")
c.Data["json"] = Response{Status: "ok", Data: object.GetLdaps(owner)}
c.ServeJSON()
}
func (c *ApiController) GetLdap() {
id := c.Input().Get("id")
if util.IsStrsEmpty(id) {
c.ResponseError("Missing parameter")
return
}
c.Data["json"] = Response{Status: "ok", Data: object.GetLdap(id)}
c.ServeJSON()
}
func (c *ApiController) AddLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil {
c.ResponseError("Missing parameter")
return
}
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
c.ResponseError("Missing parameter")
return
}
if object.CheckLdapExist(&ldap) {
c.ResponseError("Ldap server exist")
return
}
affected := object.AddLdap(&ldap)
resp := wrapActionResponse(affected)
if affected {
resp.Data2 = ldap
}
c.Data["json"] = resp
c.ServeJSON()
}
func (c *ApiController) UpdateLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
c.ResponseError("Missing parameter")
return
}
affected := object.UpdateLdap(&ldap)
resp := wrapActionResponse(affected)
if affected {
resp.Data2 = ldap
}
c.Data["json"] = resp
c.ServeJSON()
}
func (c *ApiController) DeleteLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
c.ServeJSON()
}
func (c *ApiController) SyncLdapUsers() {
owner := c.Input().Get("owner")
ldapId := c.Input().Get("ldapId")
var users []object.LdapRespUser
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
if err != nil {
panic(err)
}
object.UpdateLdapSyncTime(ldapId)
exist, failed := object.SyncLdapUsers(owner, users)
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
Exist: *exist,
Failed: *failed,
}}
c.ServeJSON()
}
func (c *ApiController) CheckLdapUsersExist() {
owner := c.Input().Get("owner")
var uuids []string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
if err != nil {
panic(err)
}
exist := object.CheckLdapUuidExist(owner, uuids)
c.Data["json"] = &Response{Status: "ok", Data: exist}
c.ServeJSON()
}

53
controllers/link.go Normal file
View File

@@ -0,0 +1,53 @@
// 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/casbin/casdoor/object"
)
type LinkForm struct {
ProviderType string `json:"providerType"`
}
// Unlink ...
func (c *ApiController) Unlink() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
var form LinkForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
panic(err)
}
providerType := form.ProviderType
user := object.GetUser(userId)
value := object.GetUserField(user, providerType)
if value == "" {
c.ResponseError("Please link first", value)
return
}
object.ClearUserOAuthProperties(user, providerType)
object.LinkUserAccount(user, providerType, "")
c.ResponseOk()
}

101
controllers/organization.go Normal file
View File

@@ -0,0 +1,101 @@
// 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/casbin/casdoor/object"
)
// GetOrganizations ...
// @Title GetOrganizations
// @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()
}
// GetOrganization ...
// @Title GetOrganization
// @Description get organization
// @Param id query string true "organization id"
// @Success 200 {object} object.Organization The Response object
// @router /get-organization [get]
func (c *ApiController) GetOrganization() {
id := c.Input().Get("id")
c.Data["json"] = object.GetOrganization(id)
c.ServeJSON()
}
// UpdateOrganization ...
// @Title UpdateOrganization
// @Description update organization
// @Param id query string true "The id of the organization"
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object
// @router /update-organization [post]
func (c *ApiController) UpdateOrganization() {
id := c.Input().Get("id")
var organization object.Organization
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
c.ServeJSON()
}
// AddOrganization ...
// @Title AddOrganization
// @Description add organization
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object
// @router /add-organization [post]
func (c *ApiController) AddOrganization() {
var organization object.Organization
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
c.ServeJSON()
}
// DeleteOrganization ...
// @Title DeleteOrganization
// @Description delete organization
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-organization [post]
func (c *ApiController) DeleteOrganization() {
var organization object.Organization
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteOrganization(&organization))
c.ServeJSON()
}

45
controllers/payment.go Normal file
View File

@@ -0,0 +1,45 @@
package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/payment"
)
func (c *ApiController) PaypalPay() {
clientId := c.Input().Get("clientId")
redirectUri := c.Input().Get("redirectUri")
var payItem object.PayItem
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payItem)
if err != nil {
panic(err)
}
msg := payment.Paypal(payItem, clientId, redirectUri)
c.Data["json"] = msg
c.ServeJSON()
}
func (c *ApiController) GetPayments() {
c.Data["json"] = object.GetPayments()
c.ServeJSON()
}
func (c *ApiController) DeletePayment() {
var payment object.Payment
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
c.ServeJSON()
}
func (c *ApiController) SuccessPay() {
token := c.Input().Get("paymentId")
c.Data["json"] = payment.SuccessPay(token)
c.ServeJSON()
}

97
controllers/provider.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 controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
)
// GetProviders
// @Title GetProviders
// @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()
}
// @Title GetProvider
// @Description get provider
// @Param id query string true "The id of the provider"
// @Success 200 {object} object.Provider The Response object
// @router /get-provider [get]
func (c *ApiController) GetProvider() {
id := c.Input().Get("id")
c.Data["json"] = object.GetProvider(id)
c.ServeJSON()
}
// @Title UpdateProvider
// @Description update provider
// @Param id query string true "The id of the provider"
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /update-provider [post]
func (c *ApiController) UpdateProvider() {
id := c.Input().Get("id")
var provider object.Provider
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
c.ServeJSON()
}
// @Title AddProvider
// @Description add provider
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /add-provider [post]
func (c *ApiController) AddProvider() {
var provider object.Provider
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
c.ServeJSON()
}
// @Title DeleteProvider
// @Description delete provider
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-provider [post]
func (c *ApiController) DeleteProvider() {
var provider object.Provider
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
c.ServeJSON()
}

48
controllers/record.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 controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
)
// GetRecords
// @Title GetRecords
// @Description get all records
// @Success 200 {array} object.Records The Response object
// @router /get-records [get]
func (c *ApiController) GetRecords() {
c.Data["json"] = object.GetRecords()
c.ServeJSON()
}
// GetRecordsByFilter
// @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)
if err != nil {
panic(err)
}
c.Data["json"] = object.GetRecordsByField(&record)
c.ServeJSON()
}

169
controllers/resource.go Normal file
View File

@@ -0,0 +1,169 @@
// 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/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
func (c *ApiController) GetResources() {
owner := c.Input().Get("owner")
user := c.Input().Get("user")
c.Data["json"] = object.GetResources(owner, user)
c.ServeJSON()
}
func (c *ApiController) GetResource() {
id := c.Input().Get("id")
c.Data["json"] = object.GetResource(id)
c.ServeJSON()
}
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()
}
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()
}
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()
}
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")
file, header, err := c.GetFile("file")
if err != nil {
c.ResponseError(err.Error())
return
}
defer file.Close()
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.UploadFile(provider, fullFilePath, fileBuffer)
if err != nil {
c.ResponseError(err.Error())
return
}
fileFormat := filepath.Ext(fullFilePath)
fileSize := int(header.Size)
resource := &object.Resource{
Owner: owner,
Name: objectKey,
CreatedTime: util.GetCurrentTime(),
User: username,
Provider: provider.Name,
Application: application,
Tag: tag,
Parent: parent,
FileName: filename,
FileType: fileType,
FileFormat: fileFormat,
FileSize: fileSize,
Url: fileUrl,
}
object.AddOrUpdateResource(resource)
switch tag {
case "avatar":
if user == nil {
c.ResponseError("user is nil for tag: \"avatar\"")
return
}
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)
}
c.ResponseOk(fileUrl, objectKey)
}

125
controllers/service.go Normal file
View File

@@ -0,0 +1,125 @@
// 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.
// Casdoor will expose its providers as services to SDK
// We are going to implement those services as APIs here
package controllers
import (
"encoding/json"
"fmt"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
// SendEmail
// @Title SendEmail
// @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"
// @Success 200 {object} Response object
// @router /api/send-email [post]
func (c *ApiController) SendEmail() {
provider, _, ok := c.GetProviderFromContext("Email")
if !ok {
return
}
var emailForm struct {
Title string `json:"title"`
Content string `json:"content"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
if err != nil {
c.ResponseError(err.Error())
return
}
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
return
}
invalidReceivers := []string{}
for _, receiver := range emailForm.Receivers {
if !util.IsEmailValid(receiver) {
invalidReceivers = append(invalidReceivers, receiver)
}
}
if len(invalidReceivers) != 0 {
c.ResponseError(fmt.Sprintf("Invalid Email receivers: %s", invalidReceivers))
return
}
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
// @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"`
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil {
c.ResponseError(err.Error())
return
}
var invalidReceivers []string
for _, receiver := range smsForm.Receivers {
if !util.IsPhoneCnValid(receiver) {
invalidReceivers = append(invalidReceivers, 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()
}

120
controllers/token.go Normal file
View File

@@ -0,0 +1,120 @@
// 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/casbin/casdoor/object"
)
// GetTokens
// @Title GetTokens
// @Description get tokens
// @Param owner query string true "The owner of tokens"
// @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()
}
// GetToken
// @Title GetToken
// @Description get token
// @Param id query string true "The id of token"
// @Success 200 {object} object.Token The Response object
// @router /get-token [get]
func (c *ApiController) GetToken() {
id := c.Input().Get("id")
c.Data["json"] = object.GetToken(id)
c.ServeJSON()
}
// UpdateToken
// @Title UpdateToken
// @Description update token
// @Param id query string true "The id of token"
// @Param body body object.Token true "Details of the token"
// @Success 200 {object} controllers.Response The Response object
// @router /update-token [post]
func (c *ApiController) UpdateToken() {
id := c.Input().Get("id")
var token object.Token
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateToken(id, &token))
c.ServeJSON()
}
// AddToken
// @Title AddToken
// @Description add token
// @Param body body object.Token true "Details of the token"
// @Success 200 {object} controllers.Response The Response object
// @router /add-token [post]
func (c *ApiController) AddToken() {
var token object.Token
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddToken(&token))
c.ServeJSON()
}
// DeleteToken
// @Title DeleteToken
// @Description delete token
// @Param body body object.Token true "Details of the token"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-token [post]
func (c *ApiController) DeleteToken() {
var token object.Token
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteToken(&token))
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"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
clientId := c.Input().Get("client_id")
clientSecret := c.Input().Get("client_secret")
code := c.Input().Get("code")
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code)
c.ServeJSON()
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
// 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.
@@ -16,24 +16,56 @@ package controllers
import (
"encoding/json"
"fmt"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/original"
)
// GetGlobalUsers
// @Title GetGlobalUsers
// @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()
}
// GetUsers
// @Title GetUsers
// @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.GetUsers(owner)
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
c.ServeJSON()
}
// GetUser
// @Title GetUser
// @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")
c.Data["json"] = object.GetUser(id)
c.Data["json"] = object.GetMaskedUser(object.GetUser(id))
c.ServeJSON()
}
// UpdateUser
// @Title UpdateUser
// @Description update user
// @Param id query string true "The id of the user"
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
// @router /update-user [post]
func (c *ApiController) UpdateUser() {
id := c.Input().Get("id")
@@ -43,10 +75,27 @@ func (c *ApiController) UpdateUser() {
panic(err)
}
c.Data["json"] = object.UpdateUser(id, &user)
if user.DisplayName == "" {
c.ResponseError("Display name cannot be empty")
return
}
affected := object.UpdateUser(id, &user)
if affected {
newUser := object.GetUser(user.GetId())
original.UpdateUserToOriginalDatabase(newUser)
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
// AddUser
// @Title AddUser
// @Description add user
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
// @router /add-user [post]
func (c *ApiController) AddUser() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
@@ -54,10 +103,16 @@ func (c *ApiController) AddUser() {
panic(err)
}
c.Data["json"] = object.AddUser(&user)
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
c.ServeJSON()
}
// DeleteUser
// @Title DeleteUser
// @Description delete user
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-user [post]
func (c *ApiController) DeleteUser() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
@@ -65,6 +120,129 @@ func (c *ApiController) DeleteUser() {
panic(err)
}
c.Data["json"] = object.DeleteUser(&user)
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
c.ServeJSON()
}
// GetEmailAndPhone
// @Title GetEmailAndPhone
// @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"
// @Success 200 {object} controllers.Response The Response object
// @router /get-email-and-phone [post]
func (c *ApiController) GetEmailAndPhone() {
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
panic(err)
}
user := object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError("No such user.")
return
}
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
var contentType string
switch form.Username {
case user.Email:
contentType = "email"
case user.Phone:
contentType = "phone"
case user.Name:
contentType = "username"
}
c.ResponseOk(respUser, contentType)
}
// SetPassword
// @Title SetPassword
// @Description set password
// @Param userOwner formData string true "The owner of the user"
// @Param userName formData string true "The name of the user"
// @Param oldPassword formData string true "The old password of the user"
// @Param newPassword formData string true "The new password of the user"
// @Success 200 {object} controllers.Response The Response object
// @router /set-password [post]
func (c *ApiController) SetPassword() {
userOwner := c.Ctx.Request.Form.Get("userOwner")
userName := c.Ctx.Request.Form.Get("userName")
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
newPassword := c.Ctx.Request.Form.Get("newPassword")
requestUserId := c.GetSessionUsername()
if requestUserId == "" {
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.")
return
}
hasPermission := false
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
}
if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword)
if msg != "" {
c.ResponseError(msg)
return
}
}
if strings.Contains(newPassword, " ") {
c.ResponseError("New password cannot contain blank space.")
return
}
if len(newPassword) <= 5 {
c.ResponseError("New password must have at least 6 characters")
return
}
c.SetSessionUsername("")
targetUser.Password = newPassword
object.SetUserField(targetUser, "password", targetUser.Password)
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
}
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)
}
}

102
controllers/util.go Normal file
View File

@@ -0,0 +1,102 @@
// 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"
"strconv"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
// ResponseOk ...
func (c *ApiController) ResponseOk(data ...interface{}) {
resp := Response{Status: "ok"}
switch len(data) {
case 2:
resp.Data2 = data[1]
fallthrough
case 1:
resp.Data = data[0]
}
c.Data["json"] = resp
c.ServeJSON()
}
// ResponseError ...
func (c *ApiController) ResponseError(error string, data ...interface{}) {
resp := Response{Status: "error", Msg: error}
switch len(data) {
case 2:
resp.Data2 = data[1]
fallthrough
case 1:
resp.Data = data[0]
}
c.Data["json"] = resp
c.ServeJSON()
}
// RequireSignedIn ...
func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please sign in first")
return "", false
}
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
}

152
controllers/verification.go Normal file
View File

@@ -0,0 +1,152 @@
// 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 (
"errors"
"fmt"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
func (c *ApiController) getCurrentUser() *object.User {
var user *object.User
userId := c.GetSessionUsername()
if userId == "" {
user = nil
} else {
user = object.GetUser(userId)
}
return user
}
// SendVerificationCode ...
func (c *ApiController) SendVerificationCode() {
destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest")
orgId := c.Ctx.Request.Form.Get("organizationId")
checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey")
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 {
c.ResponseError("Missing parameter.")
return
}
isHuman := false
captchaProvider := object.GetDefaultHumanCheckProvider()
if captchaProvider == nil {
isHuman = object.VerifyCaptcha(checkId, checkKey)
}
if !isHuman {
c.ResponseError("Turing test failed.")
return
}
user := c.getCurrentUser()
organization := object.GetOrganization(orgId)
application := object.GetApplicationByOrganizationName(organization.Name)
sendResp := errors.New("Invalid dest type.")
switch destType {
case "email":
if !util.IsEmailValid(dest) {
c.ResponseError("Invalid Email address")
return
}
provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone":
if !util.IsPhoneCnValid(dest) {
c.ResponseError("Invalid phone number")
return
}
org := object.GetOrganization(orgId)
if org == nil {
c.ResponseError("Missing parameter.")
return
}
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
provider := application.GetSmsProvider()
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
}
status := "ok"
if sendResp != nil {
status = "error"
}
c.Data["json"] = Response{Status: status, Msg: sendResp.Error()}
c.ServeJSON()
}
// ResetEmailOrPhone ...
func (c *ApiController) ResetEmailOrPhone() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError("No such user.")
return
}
destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest")
code := c.Ctx.Request.Form.Get("code")
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
c.ResponseError("Missing parameter.")
return
}
checkDest := dest
if destType == "phone" {
org := object.GetOrganizationByUser(user)
phonePrefix := "86"
if org != nil && org.PhonePrefix != "" {
phonePrefix = org.PhonePrefix
}
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
}
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
c.ResponseError(ret)
return
}
switch destType {
case "email":
user.Email = dest
object.SetUserField(user, "email", user.Email)
case "phone":
user.Phone = dest
object.SetUserField(user, "phone", user.Phone)
default:
c.ResponseError("Unknown type.")
return
}
object.DisableVerificationCode(checkDest)
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
}

21
docker-compose.yml Normal file
View File

@@ -0,0 +1,21 @@
version: '3.1'
services:
casdoor:
build:
context: ./
dockerfile: Dockerfile
ports:
- "8000:8000"
depends_on:
- db
volumes:
- ./conf:/conf/
db:
restart: always
image: mysql:8.0.25
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: 123
volumes:
- /usr/local/docker/mysql:/var/lib/mysql

31
go.mod
View File

@@ -1,10 +1,35 @@
module github.com/casdoor/casdoor
module github.com/casbin/casdoor
go 1.15
require (
github.com/astaxie/beego v1.12.2
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
github.com/astaxie/beego v1.12.3
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.4
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/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect
github.com/mileusna/crontab v1.0.1
github.com/plutov/paypal/v4 v4.3.7
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
github.com/satori/go.uuid v1.2.0 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/thanhpk/randstr v1.0.4
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
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
xorm.io/core v0.7.2
xorm.io/xorm v0.8.1
xorm.io/xorm v1.0.6
)

458
go.sum
View File

@@ -1,19 +1,65 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
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/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=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/astaxie/beego v1.12.2 h1:CajUexhSX5ONWDiSCpeQBNVfTzOtPb9e9d+3vuU5FuU=
github.com/astaxie/beego v1.12.2/go.mod h1:TMcqhsbhN3UFpN+RCfysaxPAbrhox6QSS3NIAEp/uzE=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4=
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
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/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -21,28 +67,59 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
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.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.4 h1:UekC70YueeA5E2LrKJVQKCGntdTlYwal/7og4vao66U=
github.com/casdoor/go-sms-sender v0.0.4/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=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
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/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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
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=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
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=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
@@ -50,94 +127,162 @@ github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/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-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=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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/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=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
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/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=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
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/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=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
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/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=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
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/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=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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/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/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
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/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/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=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
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/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/plutov/paypal/v4 v4.3.7 h1:wPvhAJ3RkDkV+UDrGX/UivXAl5JEPOOJuzsdgnTMJHc=
github.com/plutov/paypal/v4 v4.3.7/go.mod h1:D56boafCRGcF/fEM0w282kj0fCDKIyrwOPX/Te1jCmw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
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=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
@@ -145,95 +290,318 @@ github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKz
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
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/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=
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
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/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.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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/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=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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-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/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-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=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-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/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 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
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/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=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
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/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=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
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/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=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
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/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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
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/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=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
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/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=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -241,12 +609,22 @@ 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=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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=
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=
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v0.8.1 h1:4f2KXuQxVdaX3RdI3Fw81NzMiSpZeyCZt8m3sEVeIkQ=
xorm.io/xorm v0.8.1/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.0.6 h1:7eco1c8QUpGz+3dztpLDj9gU1bTiQdFC/KtmPaLxUJk=
xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=

366
idp/dingtalk.go Normal file
View File

@@ -0,0 +1,366 @@
// 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 (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"golang.org/x/oauth2"
)
// A total of three steps are required:
//
// 1. Construct the link and get the temporary authorization code
// tmp_auth_code through the code at the end of the url.
//
// 2. Use hmac256 to calculate the signature, and then submit it together with timestamp,
// tmp_auth_code, accessKey to obtain unionid, userid, accessKey.
//
// 3. Get detailed information through userid.
type DingTalkIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
// NewDingTalkIdProvider ...
func NewDingTalkIdProvider(clientId string, clientSecret string, redirectUrl string) *DingTalkIdProvider {
idp := &DingTalkIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
// SetHttpClient ...
func (idp *DingTalkIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *DingTalkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
AuthURL: "https://oapi.dingtalk.com/sns/getuserinfo_bycode",
TokenURL: "https://oapi.dingtalk.com/gettoken",
}
var config = &oauth2.Config{
// DingTalk not allow to set scopes,here it is just a placeholder,
// convenient to use later
Scopes: []string{"", ""},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type DingTalkAccessToken struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
AccessToken string `json:"access_token"` // Interface call credentials
ExpiresIn int64 `json:"expires_in"` // access_token interface call credential timeout time, unit (seconds)
}
type DingTalkIds struct {
UserId string `json:"user_id"`
UnionId string `json:"union_id"`
}
type InfoResp struct {
Errcode int `json:"errcode"`
UserInfo struct {
Nick string `json:"nick"`
Unionid string `json:"unionid"`
Openid string `json:"openid"`
MainOrgAuthHighLevel bool `json:"main_org_auth_high_level"`
} `json:"user_info"`
Errmsg string `json:"errmsg"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://developers.dingtalk.com/document/app/dingtalk-retrieve-user-information?spm=ding_open_doc.document.0.0.51b91a31wWV3tY#doc-api-dingtalk-GetUser
func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
timestamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
signature := EncodeSHA256(timestamp, idp.Config.ClientSecret)
u := fmt.Sprintf(
"%s?accessKey=%s&timestamp=%s&signature=%s", idp.Config.Endpoint.AuthURL,
idp.Config.ClientID, timestamp, signature)
tmpCode := struct {
TmpAuthCode string `json:"tmp_auth_code"`
}{code}
bs, _ := json.Marshal(tmpCode)
r := strings.NewReader(string(bs))
resp, err := http.Post(u, "application/json;charset=UTF-8", r)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
body, _ := ioutil.ReadAll(resp.Body)
info := InfoResp{}
_ = json.Unmarshal(body, &info)
errCode := info.Errcode
if errCode != 0 {
return nil, fmt.Errorf("%d: %s", errCode, info.Errmsg)
}
u2 := fmt.Sprintf("%s?appkey=%s&appsecret=%s", idp.Config.Endpoint.TokenURL, idp.Config.ClientID, idp.Config.ClientSecret)
resp, _ = http.Get(u2)
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
body, _ = ioutil.ReadAll(resp.Body)
tokenResp := DingTalkAccessToken{}
_ = json.Unmarshal(body, &tokenResp)
if tokenResp.ErrCode != 0 {
return nil, fmt.Errorf("%d: %s", tokenResp.ErrCode, tokenResp.ErrMsg)
}
// use unionid to get userid
unionid := info.UserInfo.Unionid
userid, err := idp.GetUseridByUnionid(tokenResp.AccessToken, unionid)
if err != nil {
return nil, err
}
// Since DingTalk does not require scopes, put userid and unionid into
// idp.config.scopes to facilitate GetUserInfo() to obtain these two parameters.
idp.Config.Scopes = []string{unionid, userid}
token := &oauth2.Token{
AccessToken: tokenResp.AccessToken,
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
}
return token, nil
}
type UnionIdResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
Result struct {
ContactType string `json:"contact_type"`
Userid string `json:"userid"`
} `json:"result"`
RequestId string `json:"request_id"`
}
// GetUseridByUnionid ...
func (idp *DingTalkIdProvider) GetUseridByUnionid(accesstoken, unionid string) (userid string, err error) {
u := fmt.Sprintf("https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=%s&unionid=%s",
accesstoken, unionid)
useridInfo, err := idp.GetUrlResp(u)
if err != nil {
return "", err
}
uresp := UnionIdResponse{}
_ = json.Unmarshal([]byte(useridInfo), &uresp)
errcode := uresp.Errcode
if errcode != 0 {
return "", fmt.Errorf("%d: %s", errcode, uresp.Errmsg)
}
return uresp.Result.Userid, nil
}
/*
{
"errcode":0,
"result":{
"boss":false,
"unionid":"5M6zgZBKQPCxdiPdANeJ6MgiEiE",
"role_list":[
{
"group_name":"默认",
"name":"主管理员",
"id":2062489174
}
],
"exclusive_account":false,
"mobile":"15236176076",
"active":true,
"admin":true,
"avatar":"https://static-legacy.dingtalk.com/media/lALPDeRETW9WAnnNAyDNAyA_800_800.png",
"hide_mobile":false,
"userid":"manager4713",
"senior":false,
"dept_order_list":[
{
"dept_id":1,
"order":176294576350761512
}
],
"real_authed":true,
"name":"刘继坤",
"dept_id_list":[
1
],
"state_code":"86",
"email":"",
"leader_in_dept":[
{
"leader":false,
"dept_id":1
}
]
},
"errmsg":"ok",
"request_id":"3sug9d2exsla"
}
*/
type DingTalkUserResponse struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
Result struct {
Extension string `json:"extension"`
Unionid string `json:"unionid"`
Boss bool `json:"boss"`
UnionEmpExt struct {
CorpId string `json:"corpId"`
Userid string `json:"userid"`
UnionEmpMapList []struct {
CorpId string `json:"corpId"`
Userid string `json:"userid"`
} `json:"unionEmpMapList"`
} `json:"unionEmpExt"`
RoleList []struct {
GroupName string `json:"group_name"`
Id int `json:"id"`
Name string `json:"name"`
} `json:"role_list"`
Admin bool `json:"admin"`
Remark string `json:"remark"`
Title string `json:"title"`
HiredDate int64 `json:"hired_date"`
Userid string `json:"userid"`
WorkPlace string `json:"work_place"`
DeptOrderList []struct {
DeptId int `json:"dept_id"`
Order int64 `json:"order"`
} `json:"dept_order_list"`
RealAuthed bool `json:"real_authed"`
DeptIdList []int `json:"dept_id_list"`
JobNumber string `json:"job_number"`
Email string `json:"email"`
LeaderInDept []struct {
DeptId int `json:"dept_id"`
Leader bool `json:"leader"`
} `json:"leader_in_dept"`
ManagerUserid string `json:"manager_userid"`
Mobile string `json:"mobile"`
Active bool `json:"active"`
Telephone string `json:"telephone"`
Avatar string `json:"avatar"`
HideMobile bool `json:"hide_mobile"`
Senior bool `json:"senior"`
Name string `json:"name"`
StateCode string `json:"state_code"`
} `json:"result"`
RequestId string `json:"request_id"`
}
// GetUserInfo Use userid and access_token to get UserInfo
// get more detail via: https://developers.dingtalk.com/document/app/query-user-details
func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
var dtUserInfo DingTalkUserResponse
accessToken := token.AccessToken
u := fmt.Sprintf("https://oapi.dingtalk.com/topapi/v2/user/get?access_token=%s&userid=%s",
accessToken, idp.Config.Scopes[1])
userinfoResp, err := idp.GetUrlResp(u)
if err != nil {
return nil, err
}
if err = json.Unmarshal([]byte(userinfoResp), &dtUserInfo); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: strconv.Itoa(dtUserInfo.Result.RoleList[0].Id),
Username: dtUserInfo.Result.RoleList[0].Name,
DisplayName: dtUserInfo.Result.Name,
Email: dtUserInfo.Result.Email,
AvatarUrl: dtUserInfo.Result.Avatar,
}
return &userInfo, nil
}
func (idp *DingTalkIdProvider) GetUrlResp(url string) (string, error) {
resp, err := idp.Client.Get(url)
if err != nil {
return "", err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buf.String(), nil
}
// EncodeSHA256 Use the HmacSHA256 algorithm to sign, the signature data is the current timestamp,
// and the key is the appSecret corresponding to the appId. Use this key to calculate the timestamp signature value.
// get more detail via: https://developers.dingtalk.com/document/app/signature-calculation-for-logon-free-scenarios-1?spm=ding_open_doc.document.0.0.63262ea7l6iEm1#topic-2021698
func EncodeSHA256(message, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
sum := h.Sum(nil)
msg1 := base64.StdEncoding.EncodeToString(sum)
uv := url.Values{}
uv.Add("0", msg1)
msg2 := uv.Encode()[2:]
return msg2
}

194
idp/facebook.go Normal file
View File

@@ -0,0 +1,194 @@
// 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 (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"golang.org/x/oauth2"
)
type FacebookIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewFacebookIdProvider(clientId string, clientSecret string, redirectUrl string) *FacebookIdProvider {
idp := &FacebookIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *FacebookIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *FacebookIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://graph.facebook.com/oauth/access_token",
}
var config = &oauth2.Config{
Scopes: []string{"email,public_profile"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type FacebookAccessToken struct {
AccessToken string `json:"access_token"` //Interface call credentials
TokenType string `json:"token_type"` //Access token type
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
}
type FacebookCheckToken struct {
Data string `json:"data"`
}
// Get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
type FacebookCheckTokenData struct {
UserId string `json:"user_id"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#confirm
func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := url.Values{}
params.Add("client_id", idp.Config.ClientID)
params.Add("redirect_uri", idp.Config.RedirectURL)
params.Add("client_secret", idp.Config.ClientSecret)
params.Add("code", code)
accessTokenUrl := fmt.Sprintf("https://graph.facebook.com/oauth/access_token?%s", params.Encode())
accessTokenResp, err := idp.GetUrlResp(accessTokenUrl)
if err != nil {
return nil, err
}
var facebookAccessToken FacebookAccessToken
if err = json.Unmarshal([]byte(accessTokenResp), &facebookAccessToken); err != nil {
return nil, err
}
token := oauth2.Token{
AccessToken: facebookAccessToken.AccessToken,
TokenType: "FacebookAccessToken",
Expiry: time.Time{},
}
return &token, nil
}
//{
// "id": "123456789",
// "name": "Example Name",
// "name_format": "{first} {last}",
// "picture": {
// "data": {
// "height": 50,
// "is_silhouette": false,
// "url": "https://example.com",
// "width": 50
// }
// },
// "email": "test@example.com"
//}
type FacebookUserInfo struct {
Id string `json:"id"` // The app user's App-Scoped User ID. This ID is unique to the app and cannot be used by other apps.
Name string `json:"name"` // The person's full name.
NameFormat string `json:"name_format"` // The person's name formatted to correctly handle Chinese, Japanese, or Korean ordering.
Picture struct { // The person's profile picture.
Data struct { // This struct is different as https://developers.facebook.com/docs/graph-api/reference/user/picture/
Height int `json:"height"`
IsSilhouette bool `json:"is_silhouette"`
Url string `json:"url"`
Width int `json:"width"`
} `json:"data"`
} `json:"picture"`
Email string `json:"email"` // The User's primary email address listed on their profile. This field will not be returned if no valid email address is available.
}
// GetUserInfo use FacebookAccessToken gotten before return FacebookUserInfo
// get more detail via: https://developers.facebook.com/docs/graph-api/reference/user
func (idp *FacebookIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
var facebookUserInfo FacebookUserInfo
accessToken := token.AccessToken
userIdUrl := fmt.Sprintf("https://graph.facebook.com/me?access_token=%s", accessToken)
userIdResp, err := idp.GetUrlResp(userIdUrl)
if err != nil {
return nil, err
}
if err = json.Unmarshal([]byte(userIdResp), &facebookUserInfo); err != nil {
return nil, err
}
userInfoUrl := fmt.Sprintf("https://graph.facebook.com/%s?fields=id,name,name_format,picture,email&access_token=%s", facebookUserInfo.Id, accessToken)
userInfoResp, err := idp.GetUrlResp(userInfoUrl)
if err != nil {
return nil, err
}
if err = json.Unmarshal([]byte(userInfoResp), &facebookUserInfo); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: facebookUserInfo.Id,
DisplayName: facebookUserInfo.Name,
Email: facebookUserInfo.Email,
AvatarUrl: facebookUserInfo.Picture.Data.Url,
}
return &userInfo, nil
}
func (idp *FacebookIdProvider) GetUrlResp(url string) (string, error) {
resp, err := idp.Client.Get(url)
if err != nil {
return "", err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buf.String(), nil
}

230
idp/gitee.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 (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"golang.org/x/oauth2"
)
type GiteeIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewGiteeIdProvider(clientId string, clientSecret string, redirectUrl string) *GiteeIdProvider {
idp := &GiteeIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *GiteeIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *GiteeIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://gitee.com/oauth/token",
}
var config = &oauth2.Config{
Scopes: []string{"user_info emails"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type GiteeAccessToken struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
CreatedAt int `json:"created_at"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// The POST Url format of submission is: https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
// get more detail via: https://gitee.com/api/v5/oauth_doc#/
func (idp *GiteeIdProvider) 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())
bs, _ := json.Marshal(params.Encode())
req, _ := http.NewRequest("POST", accessTokenUrl, strings.NewReader(string(bs)))
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
rbs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
tokenResp := GiteeAccessToken{}
if err = json.Unmarshal(rbs, &tokenResp); err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: tokenResp.AccessToken,
TokenType: tokenResp.TokenType,
RefreshToken: tokenResp.RefreshToken,
Expiry: time.Unix(time.Now().Unix()+int64(tokenResp.ExpiresIn), 0),
}
return token, nil
}
/*
{
"id": 999999,
"login": "xxx",
"name": "xxx",
"avatar_url": "https://gitee.com/assets/no_portrait.png",
"url": "https://gitee.com/api/v5/users/xxx",
"html_url": "https://gitee.com/xxx",
"followers_url": "https://gitee.com/api/v5/users/xxx/followers",
"following_url": "https://gitee.com/api/v5/users/xxx/following_url{/other_user}",
"gists_url": "https://gitee.com/api/v5/users/xxx/gists{/gist_id}",
"starred_url": "https://gitee.com/api/v5/users/xxx/starred{/owner}{/repo}",
"subscriptions_url": "https://gitee.com/api/v5/users/xxx/subscriptions",
"organizations_url": "https://gitee.com/api/v5/users/xxx/orgs",
"repos_url": "https://gitee.com/api/v5/users/xxx/repos",
"events_url": "https://gitee.com/api/v5/users/xxx/events{/privacy}",
"received_events_url": "https://gitee.com/api/v5/users/xxx/received_events",
"type": "User",
"blog": null,
"weibo": null,
"bio": "个人博客https://gitee.com/xxx/xxx/pages",
"public_repos": 2,
"public_gists": 0,
"followers": 0,
"following": 0,
"stared": 0,
"watched": 2,
"created_at": "2019-08-03T23:21:16+08:00",
"updated_at": "2021-06-14T12:47:09+08:00",
"email": null
}
*/
type GiteeUserResponse struct {
AvatarUrl string `json:"avatar_url"`
Bio string `json:"bio"`
Blog string `json:"blog"`
CreatedAt string `json:"created_at"`
Email string `json:"email"`
EventsUrl string `json:"events_url"`
Followers int `json:"followers"`
FollowersUrl string `json:"followers_url"`
Following int `json:"following"`
FollowingUrl string `json:"following_url"`
GistsUrl string `json:"gists_url"`
HtmlUrl string `json:"html_url"`
Id int `json:"id"`
Login string `json:"login"`
MemberRole string `json:"member_role"`
Name string `json:"name"`
OrganizationsUrl string `json:"organizations_url"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsUrl string `json:"received_events_url"`
ReposUrl string `json:"repos_url"`
Stared int `json:"stared"`
StarredUrl string `json:"starred_url"`
SubscriptionsUrl string `json:"subscriptions_url"`
Type string `json:"type"`
UpdatedAt string `json:"updated_at"`
Url string `json:"url"`
Watched int `json:"watched"`
Weibo string `json:"weibo"`
}
// GetUserInfo Use userid and access_token to get UserInfo
// get more detail via: https://gitee.com/api/v5/swagger#/getV5User
func (idp *GiteeIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
var gtUserInfo GiteeUserResponse
accessToken := token.AccessToken
u := fmt.Sprintf("https://gitee.com/api/v5/user?access_token=%s",
accessToken)
userinfoResp, err := idp.GetUrlResp(u)
if err != nil {
return nil, err
}
if err = json.Unmarshal([]byte(userinfoResp), &gtUserInfo); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: strconv.Itoa(gtUserInfo.Id),
Username: gtUserInfo.Name,
DisplayName: gtUserInfo.Name,
Email: gtUserInfo.Email,
AvatarUrl: gtUserInfo.AvatarUrl,
}
return &userInfo, nil
}
func (idp *GiteeIdProvider) GetUrlResp(url string) (string, error) {
resp, err := idp.Client.Get(url)
if err != nil {
return "", err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buf.String(), nil
}

194
idp/github.go Normal file
View File

@@ -0,0 +1,194 @@
// 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"
"io/ioutil"
"net/http"
"strconv"
"time"
"golang.org/x/oauth2"
)
type GithubIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewGithubIdProvider(clientId string, clientSecret string, redirectUrl string) *GithubIdProvider {
idp := &GithubIdProvider{}
config := idp.getConfig()
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
return idp
}
func (idp *GithubIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *GithubIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
}
var config = &oauth2.Config{
Scopes: []string{"user:email", "read:user"},
Endpoint: endpoint,
}
return config
}
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
//{
// "login": "jimgreen",
// "id": 3781234,
// "node_id": "MDQ6VXNlcjM3O123456=",
// "avatar_url": "https://avatars.githubusercontent.com/u/3781234?v=4",
// "gravatar_id": "",
// "url": "https://api.github.com/users/jimgreen",
// "html_url": "https://github.com/jimgreen",
// "followers_url": "https://api.github.com/users/jimgreen/followers",
// "following_url": "https://api.github.com/users/jimgreen/following{/other_user}",
// "gists_url": "https://api.github.com/users/jimgreen/gists{/gist_id}",
// "starred_url": "https://api.github.com/users/jimgreen/starred{/owner}{/repo}",
// "subscriptions_url": "https://api.github.com/users/jimgreen/subscriptions",
// "organizations_url": "https://api.github.com/users/jimgreen/orgs",
// "repos_url": "https://api.github.com/users/jimgreen/repos",
// "events_url": "https://api.github.com/users/jimgreen/events{/privacy}",
// "received_events_url": "https://api.github.com/users/jimgreen/received_events",
// "type": "User",
// "site_admin": false,
// "name": "Jim Green",
// "company": "Casbin",
// "blog": "https://casbin.org",
// "location": "Bay Area",
// "email": "jimgreen@gmail.com",
// "hireable": true,
// "bio": "My bio",
// "twitter_username": null,
// "public_repos": 45,
// "public_gists": 3,
// "followers": 123,
// "following": 31,
// "created_at": "2016-03-06T13:16:13Z",
// "updated_at": "2020-05-30T12:15:29Z",
// "private_gists": 0,
// "total_private_repos": 12,
// "owned_private_repos": 12,
// "disk_usage": 46331,
// "collaborators": 5,
// "two_factor_authentication": true,
// "plan": {
// "name": "free",
// "space": 976562499,
// "collaborators": 0,
// "private_repos": 10000
// }
//}
type GitHubUserInfo struct {
Login string `json:"login"`
Id int `json:"id"`
NodeId string `json:"node_id"`
AvatarUrl string `json:"avatar_url"`
GravatarId string `json:"gravatar_id"`
Url string `json:"url"`
HtmlUrl string `json:"html_url"`
FollowersUrl string `json:"followers_url"`
FollowingUrl string `json:"following_url"`
GistsUrl string `json:"gists_url"`
StarredUrl string `json:"starred_url"`
SubscriptionsUrl string `json:"subscriptions_url"`
OrganizationsUrl string `json:"organizations_url"`
ReposUrl string `json:"repos_url"`
EventsUrl string `json:"events_url"`
ReceivedEventsUrl string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
Name string `json:"name"`
Company string `json:"company"`
Blog string `json:"blog"`
Location string `json:"location"`
Email string `json:"email"`
Hireable bool `json:"hireable"`
Bio string `json:"bio"`
TwitterUsername interface{} `json:"twitter_username"`
PublicRepos int `json:"public_repos"`
PublicGists int `json:"public_gists"`
Followers int `json:"followers"`
Following int `json:"following"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PrivateGists int `json:"private_gists"`
TotalPrivateRepos int `json:"total_private_repos"`
OwnedPrivateRepos int `json:"owned_private_repos"`
DiskUsage int `json:"disk_usage"`
Collaborators int `json:"collaborators"`
TwoFactorAuthentication bool `json:"two_factor_authentication"`
Plan struct {
Name string `json:"name"`
Space int `json:"space"`
Collaborators int `json:"collaborators"`
PrivateRepos int `json:"private_repos"`
} `json:"plan"`
}
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
panic(err)
}
req.Header.Add("Authorization", "token "+token.AccessToken)
resp, err := idp.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var githubUserInfo GitHubUserInfo
err = json.Unmarshal(body, &githubUserInfo)
if err != nil {
return nil, err
}
userInfo := UserInfo{
Id: strconv.Itoa(githubUserInfo.Id),
Username: githubUserInfo.Login,
DisplayName: githubUserInfo.Name,
Email: githubUserInfo.Email,
AvatarUrl: githubUserInfo.AvatarUrl,
}
return &userInfo, nil
}

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/ioutil"
"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 := ioutil.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 := ioutil.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
}

121
idp/google.go Normal file
View File

@@ -0,0 +1,121 @@
// 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"
"errors"
"fmt"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
)
type GoogleIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewGoogleIdProvider(clientId string, clientSecret string, redirectUrl string) *GoogleIdProvider {
idp := &GoogleIdProvider{}
config := idp.getConfig()
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
return idp
}
func (idp *GoogleIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *GoogleIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
var config = &oauth2.Config{
Scopes: []string{"profile", "email"},
Endpoint: endpoint,
}
return config
}
func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
//{
// "id": "110613473084924141234",
// "email": "jimgreen@gmail.com",
// "verified_email": true,
// "name": "Jim Green",
// "given_name": "Jim",
// "family_name": "Green",
// "picture": "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
// "locale": "en"
//}
type GoogleUserInfo struct {
Id string `json:"id"`
Email string `json:"email"`
VerifiedEmail bool `json:"verified_email"`
Name string `json:"name"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Picture string `json:"picture"`
Locale string `json:"locale"`
}
func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
url := fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=%s", token.AccessToken)
resp, err := idp.Client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var googleUserInfo GoogleUserInfo
err = json.Unmarshal(body, &googleUserInfo)
if err != nil {
return nil, err
}
if googleUserInfo.Email == "" {
return nil, errors.New("google email is empty")
}
userInfo := UserInfo{
Id: googleUserInfo.Id,
Username: googleUserInfo.Email,
DisplayName: googleUserInfo.Name,
Email: googleUserInfo.Email,
AvatarUrl: googleUserInfo.Picture,
}
return &userInfo, nil
}

216
idp/lark.go Normal file
View File

@@ -0,0 +1,216 @@
// 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"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
)
type LarkIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewLarkIdProvider(clientId string, clientSecret string, redirectUrl string) *LarkIdProvider {
idp := &LarkIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *LarkIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *LarkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
}
var config = &oauth2.Config{
Scopes: []string{},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
/*
{
"code": 0,
"msg": "success",
"tenant_access_token": "t-caecc734c2e3328a62489fe0648c4b98779515d3",
"expire": 7140
}
*/
type LarkAccessToken struct {
Code int `json:"code"`
Msg string `json:"msg"`
TenantAccessToken string `json:"tenant_access_token"`
Expire int `json:"expire"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://docs.microsoft.com/en-us/linkedIn/shared/authentication/authorization-code-flow?context=linkedIn%2Fcontext&tabs=HTTPS
func (idp *LarkIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := &struct {
AppID string `json:"app_id"`
AppSecret string `json:"app_secret"`
}{idp.Config.ClientID, idp.Config.ClientSecret}
data, err := idp.postWithBody(params, idp.Config.Endpoint.TokenURL)
appToken := &LarkAccessToken{}
if err = json.Unmarshal(data, appToken); err != nil || appToken.Code != 0 {
return nil, err
}
t := &oauth2.Token{
AccessToken: appToken.TenantAccessToken,
TokenType: "Bearer",
Expiry: time.Unix(time.Now().Unix()+int64(appToken.Expire), 0),
}
raw := make(map[string]interface{})
raw["code"] = code
t = t.WithExtra(raw)
return t, nil
}
/*
{
"code": 0,
"msg": "success",
"data": {
"access_token": "u-6U1SbDiM6XIH2DcTCPyeub",
"token_type": "Bearer",
"expires_in": 7140,
"name": "zhangsan",
"en_name": "Three Zhang",
"avatar_url": "www.feishu.cn/avatar/icon",
"avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
"avatar_middle": "www.feishu.cn/avatar/icon_middle",
"avatar_big": "www.feishu.cn/avatar/icon_big",
"open_id": "ou-caecc734c2e3328a62489fe0648c4b98779515d3",
"union_id": "on-d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj",
"email": "zhangsan@feishu.cn",
"user_id": "5d9bdxxx",
"mobile": "+86130002883xx",
"tenant_key": "736588c92lxf175d",
"refresh_expires_in": 2591940,
"refresh_token": "ur-t9HHgRCjMqGqIU9v05Zhos"
}
}
*/
type LarkUserInfo struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Name string `json:"name"`
EnName string `json:"en_name"`
AvatarUrl string `json:"avatar_url"`
AvatarThumb string `json:"avatar_thumb"`
AvatarMiddle string `json:"avatar_middle"`
AvatarBig string `json:"avatar_big"`
OpenId string `json:"open_id"`
UnionId string `json:"union_id"`
Email string `json:"email"`
UserId string `json:"user_id"`
Mobile string `json:"mobile"`
TenantKey string `json:"tenant_key"`
RefreshExpiresIn int `json:"refresh_expires_in"`
RefreshToken string `json:"refresh_token"`
} `json:"data"`
}
// GetUserInfo use LarkAccessToken gotten before return LinkedInUserInfo
// get more detail via: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
body := &struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
}{"authorization_code", token.Extra("code").(string)}
data, _ := json.Marshal(body)
req, err := http.NewRequest("POST", "https://open.feishu.cn/open-apis/authen/v1/access_token", strings.NewReader(string(data)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
resp, err := idp.Client.Do(req)
data, err = ioutil.ReadAll(resp.Body)
err = resp.Body.Close()
if err != nil {
return nil, err
}
var larkUserInfo LarkUserInfo
if err = json.Unmarshal(data, &larkUserInfo); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: larkUserInfo.Data.OpenId,
DisplayName: larkUserInfo.Data.EnName,
Username: larkUserInfo.Data.Name,
Email: larkUserInfo.Data.Email,
AvatarUrl: larkUserInfo.Data.AvatarUrl,
}
return &userInfo, nil
}
func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
r := strings.NewReader(string(bs))
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
return data, nil
}

331
idp/linkedin.go Normal file
View File

@@ -0,0 +1,331 @@
// 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"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"golang.org/x/oauth2"
)
type LinkedInIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewLinkedInIdProvider(clientId string, clientSecret string, redirectUrl string) *LinkedInIdProvider {
idp := &LinkedInIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *LinkedInIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *LinkedInIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://www.linkedIn.com/oauth/v2/accessToken",
}
var config = &oauth2.Config{
Scopes: []string{"email,public_profile"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type LinkedInAccessToken struct {
AccessToken string `json:"access_token"` //Interface call credentials
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://docs.microsoft.com/en-us/linkedIn/shared/authentication/authorization-code-flow?context=linkedIn%2Fcontext&tabs=HTTPS
func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := url.Values{}
params.Add("grant_type", "authorization_code")
params.Add("redirect_uri", idp.Config.RedirectURL)
params.Add("client_id", idp.Config.ClientID)
params.Add("client_secret", idp.Config.ClientSecret)
params.Add("code", code)
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
bs, _ := json.Marshal(params.Encode())
req, _ := http.NewRequest("POST", accessTokenUrl, strings.NewReader(string(bs)))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
rbs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
tokenResp := LinkedInAccessToken{}
if err = json.Unmarshal(rbs, &tokenResp); err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: tokenResp.AccessToken,
TokenType: "Bearer",
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
}
return token, nil
}
/*
{
"firstName": {
"localized": {
"zh_CN": "继坤"
},
"preferredLocale": {
"country": "CN",
"language": "zh"
}
},
"lastName": {
"localized": {
"zh_CN": "刘"
},
"preferredLocale": {
"country": "CN",
"language": "zh"
}
},
"profilePicture": {
"displayImage": "urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg",
"displayImage~": {
"paging": {
"count": 10,
"start": 0,
"links": []
},
"elements": [
{
"artifact": "urn:li:digitalmediaMediaArtifact:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100)",
"authorizationMethod": "PUBLIC",
"data": {
"com.linkedin.digitalmedia.mediaartifact.StillImage": {
"mediaType": "image/jpeg",
"rawCodecSpec": {
"name": "jpeg",
"type": "image"
},
"displaySize": {
"width": 100.0,
"uom": "PX",
"height": 100.0
},
"storageSize": {
"width": 100,
"height": 100
},
"storageAspectRatio": {
"widthAspect": 1.0,
"heightAspect": 1.0,
"formatted": "1.00:1.00"
},
"displayAspectRatio": {
"widthAspect": 1.0,
"heightAspect": 1.0,
"formatted": "1.00:1.00"
}
}
},
"identifiers": [
{
"identifier": "https://media.licdn.cn/dms/image/C5603AQHbdR8RkG62yg/profile-displayphoto-shrink_100_100/0/1625279434135?e=1630540800&v=beta&t=Z-bQKf_jFv8L1uwr6X5AJLoTQRWZrueT7qrITDSvxWM",
"index": 0,
"mediaType": "image/jpeg",
"file": "urn:li:digitalmediaFile:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100,0)",
"identifierType": "EXTERNAL_URL",
"identifierExpiresInSeconds": 1630540800
}
]
},
// ...
}
]
}
},
"id": "vvMfLsLIRs"
}
*/
type LinkedInUserInfo struct {
FirstName struct {
Localized map[string]string `json:"localized"`
PreferredLocale struct {
Country string `json:"country"`
Language string `json:"language"`
} `json:"preferredLocale"`
} `json:"firstName"`
LastName struct {
Localized map[string]string `json:"localized"`
PreferredLocale struct {
Country string `json:"country"`
Language string `json:"language"`
} `json:"preferredLocale"`
} `json:"lastName"`
ProfilePicture struct {
DisplayImage string `json:"displayImage"`
DisplayImage1 struct {
Paging struct {
Count int `json:"count"`
Start int `json:"start"`
Links []interface{} `json:"links"`
} `json:"paging"`
Elements []struct {
Artifact string `json:"artifact"`
AuthorizationMethod string `json:"authorizationMethod"`
Data struct {
ComLinkedinDigitalmediaMediaartifactStillImage struct {
MediaType string `json:"mediaType"`
RawCodecSpec struct {
Name string `json:"name"`
Type string `json:"type"`
} `json:"rawCodecSpec"`
DisplaySize struct {
Width float64 `json:"width"`
Uom string `json:"uom"`
Height float64 `json:"height"`
} `json:"displaySize"`
StorageSize struct {
Width int `json:"width"`
Height int `json:"height"`
} `json:"storageSize"`
StorageAspectRatio struct {
WidthAspect float64 `json:"widthAspect"`
HeightAspect float64 `json:"heightAspect"`
Formatted string `json:"formatted"`
} `json:"storageAspectRatio"`
DisplayAspectRatio struct {
WidthAspect float64 `json:"widthAspect"`
HeightAspect float64 `json:"heightAspect"`
Formatted string `json:"formatted"`
} `json:"displayAspectRatio"`
} `json:"com.linkedin.digitalmedia.mediaartifact.StillImage"`
} `json:"data"`
Identifiers []struct {
Identifier string `json:"identifier"`
Index int `json:"index"`
MediaType string `json:"mediaType"`
File string `json:"file"`
IdentifierType string `json:"identifierType"`
IdentifierExpiresInSeconds int `json:"identifierExpiresInSeconds"`
} `json:"identifiers"`
} `json:"elements"`
} `json:"displayImage~"`
} `json:"profilePicture"`
Id string `json:"id"`
}
/*
{
"handle": "urn:li:emailAddress:3775708763",
"handle~": {
"emailAddress": "hsimpson@linkedin.com"
}
}
*/
type LinkedInUserEmail struct {
Elements []struct {
Handle struct {
EmailAddress string `json:"emailAddress"`
} `json:"handle~"`
Handle1 string `json:"handle"`
} `json:"elements"`
}
// GetUserInfo use LinkedInAccessToken gotten before return LinkedInUserInfo
// get more detail via: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
func (idp *LinkedInIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
var linkedInUserInfo LinkedInUserInfo
bs, err := idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))", token.AccessToken)
if err != nil {
return nil, err
}
if err = json.Unmarshal(bs, &linkedInUserInfo); err != nil {
return nil, err
}
var linkedInUserEmail LinkedInUserEmail
bs, err = idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/emailAddress?q=members&projection=(elements*(handle~))", token.AccessToken)
if err != nil {
return nil, err
}
if err = json.Unmarshal(bs, &linkedInUserEmail); err != nil {
return nil, err
}
username := ""
for _, name := range linkedInUserInfo.FirstName.Localized {
username += name
}
for _, name := range linkedInUserInfo.LastName.Localized {
username += name
}
userInfo := UserInfo{
Id: linkedInUserInfo.Id,
DisplayName: username,
Username: username,
Email: linkedInUserEmail.Elements[0].Handle.EmailAddress,
AvatarUrl: linkedInUserInfo.ProfilePicture.DisplayImage1.Elements[0].Identifiers[0].Identifier,
}
return &userInfo, nil
}
func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([]byte, error) {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return bs, nil
}

65
idp/provider.go Normal file
View File

@@ -0,0 +1,65 @@
// 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 (
"net/http"
"golang.org/x/oauth2"
)
type UserInfo struct {
Id string
Username string
DisplayName string
Email string
AvatarUrl string
}
type IdProvider interface {
SetHttpClient(client *http.Client)
GetToken(code string) (*oauth2.Token, error)
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
func GetIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) IdProvider {
if providerType == "GitHub" {
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Google" {
return NewGoogleIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "QQ" {
return NewQqIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "WeChat" {
return NewWeChatIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Facebook" {
return NewFacebookIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "DingTalk" {
return NewDingTalkIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Weibo" {
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Gitee" {
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "LinkedIn" {
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "WeCom" {
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Lark" {
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "GitLab" {
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
}
return nil
}

185
idp/qq.go Normal file
View File

@@ -0,0 +1,185 @@
// 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"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"golang.org/x/oauth2"
)
type QqIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewQqIdProvider(clientId string, clientSecret string, redirectUrl string) *QqIdProvider {
idp := &QqIdProvider{}
config := idp.getConfig()
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
return idp
}
func (idp *QqIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *QqIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://graph.qq.com/oauth2.0/token",
}
var config = &oauth2.Config{
Scopes: []string{"get_user_info"},
Endpoint: endpoint,
}
return config
}
func (idp *QqIdProvider) 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("https://graph.qq.com/oauth2.0/token?%s", params.Encode())
resp, err := idp.Client.Get(accessTokenUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
tokenContent, err := ioutil.ReadAll(resp.Body)
re := regexp.MustCompile("token=(.*?)&")
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
accessToken := matched[0][1]
token := &oauth2.Token{
AccessToken: accessToken,
TokenType: "Bearer",
}
return token, nil
}
//{
// "ret": 0,
// "msg": "",
// "is_lost": 0,
// "nickname": "飞翔的企鹅",
// "gender": "男",
// "gender_type": 1,
// "province": "",
// "city": "安道尔城",
// "year": "1968",
// "constellation": "",
// "figureurl": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/30",
// "figureurl_1": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/50",
// "figureurl_2": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/100",
// "figureurl_qq_1": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=40&t=1557635654",
// "figureurl_qq_2": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=100&t=1557635654",
// "figureurl_qq": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=640&t=1557635654",
// "figureurl_type": "1",
// "is_yellow_vip": "0",
// "vip": "0",
// "yellow_vip_level": "0",
// "level": "0",
// "is_yellow_year_vip": "0"
//}
type QqUserInfo struct {
Ret int `json:"ret"`
Msg string `json:"msg"`
IsLost int `json:"is_lost"`
Nickname string `json:"nickname"`
Gender string `json:"gender"`
GenderType int `json:"gender_type"`
Province string `json:"province"`
City string `json:"city"`
Year string `json:"year"`
Constellation string `json:"constellation"`
Figureurl string `json:"figureurl"`
Figureurl1 string `json:"figureurl_1"`
Figureurl2 string `json:"figureurl_2"`
FigureurlQq1 string `json:"figureurl_qq_1"`
FigureurlQq2 string `json:"figureurl_qq_2"`
FigureurlQq string `json:"figureurl_qq"`
FigureurlType string `json:"figureurl_type"`
IsYellowVip string `json:"is_yellow_vip"`
Vip string `json:"vip"`
YellowVipLevel string `json:"yellow_vip_level"`
Level string `json:"level"`
IsYellowYearVip string `json:"is_yellow_year_vip"`
}
func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
openIdUrl := fmt.Sprintf("https://graph.qq.com/oauth2.0/me?access_token=%s", token.AccessToken)
resp, err := idp.Client.Get(openIdUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
openIdBody, err := ioutil.ReadAll(resp.Body)
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
openId := matched[0][1]
if openId == "" {
return nil, errors.New("openId is empty")
}
userInfoUrl := fmt.Sprintf("https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s", token.AccessToken, idp.Config.ClientID, openId)
resp, err = idp.Client.Get(userInfoUrl)
if err != nil {
return nil, err
}
defer resp.Body.Close()
userInfoBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var qqUserInfo QqUserInfo
err = json.Unmarshal(userInfoBody, &qqUserInfo)
if err != nil {
return nil, err
}
if qqUserInfo.Ret != 0 {
return nil, fmt.Errorf("ret expected 0, got %d", qqUserInfo.Ret)
}
userInfo := UserInfo{
Id: openId,
DisplayName: qqUserInfo.Nickname,
AvatarUrl: qqUserInfo.FigureurlQq1,
}
return &userInfo, nil
}

186
idp/wechat.go Normal file
View File

@@ -0,0 +1,186 @@
// 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 (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"golang.org/x/oauth2"
)
type WeChatIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewWeChatIdProvider(clientId string, clientSecret string, redirectUrl string) *WeChatIdProvider {
idp := &WeChatIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *WeChatIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *WeChatIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://graph.qq.com/oauth2.0/token",
}
var config = &oauth2.Config{
Scopes: []string{"snsapi_login"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type WechatAccessToken struct {
AccessToken string `json:"access_token"` //Interface call credentials
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
RefreshToken string `json:"refresh_token"` //User refresh access_token
Openid string `json:"openid"` //Unique ID of authorized user
Scope string `json:"scope"` //The scope of user authorization, separated by commas. (,)
Unionid string `json:"unionid"` //This field will appear if and only if the website application has been authorized by the user's UserInfo.
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
func (idp *WeChatIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := url.Values{}
params.Add("grant_type", "authorization_code")
params.Add("appid", idp.Config.ClientID)
params.Add("secret", idp.Config.ClientSecret)
params.Add("code", code)
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?%s", params.Encode())
tokenResponse, err := idp.Client.Get(accessTokenUrl)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(tokenResponse.Body)
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(tokenResponse.Body)
if err != nil {
return nil, err
}
var wechatAccessToken WechatAccessToken
if err = json.Unmarshal(buf.Bytes(), &wechatAccessToken); err != nil {
return nil, err
}
token := oauth2.Token{
AccessToken: wechatAccessToken.AccessToken,
TokenType: "WeChatAccessToken",
RefreshToken: wechatAccessToken.RefreshToken,
Expiry: time.Time{},
}
raw := make(map[string]string)
raw["Openid"] = wechatAccessToken.Openid
token.WithExtra(raw)
return &token, nil
}
//{
// "openid": "of_Hl5zVpyj0vwzIlAyIlnXe1234",
// "nickname": "飞翔的企鹅",
// "sex": 1,
// "language": "zh_CN",
// "city": "Shanghai",
// "province": "Shanghai",
// "country": "CN",
// "headimgurl": "https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/Q0j4TwGTfTK6xc7vGca4KtibJib5dslRianc9VHt9k2N7fewYOl8fak7grRM7nS5V6HcvkkIkGThWUXPjDbXkQFYA\/132",
// "privilege": [],
// "unionid": "oxW9O1VAL8x-zfWP2hrqW9c81234"
//}
type WechatUserInfo struct {
Openid string `json:"openid"` // The ID of an ordinary user, which is unique to the current developer account
Nickname string `json:"nickname"` // Ordinary user nickname
Sex int `json:"sex"` // Ordinary user gender, 1 is male, 2 is female
Language string `json:"language"`
City string `json:"city"` // City filled in by general user's personal data
Province string `json:"province"` // Province filled in by ordinary user's personal information
Country string `json:"country"` // Country, such as China is CN
Headimgurl string `json:"headimgurl"` // User avatar, the last value represents the size of the square avatar (there are optional values of 0, 46, 64, 96, 132, 0 represents a 640*640 square avatar), this item is empty when the user does not have a avatar
Privilege []string `json:"privilege"` // User Privilege information, json array, such as Wechat Woka user (chinaunicom)
Unionid string `json:"unionid"` // Unified user identification. For an application under a WeChat open platform account, the unionid of the same user is unique.
}
// GetUserInfo use WechatAccessToken gotten before return WechatUserInfo
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
var wechatUserInfo WechatUserInfo
accessToken := token.AccessToken
openid := token.Extra("Openid")
userInfoUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", accessToken, openid)
resp, err := idp.Client.Get(userInfoUrl)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(buf.Bytes(), &wechatUserInfo); err != nil {
return nil, err
}
id := wechatUserInfo.Unionid
if id == "" {
id = wechatUserInfo.Openid
}
userInfo := UserInfo{
Id: id,
DisplayName: wechatUserInfo.Nickname,
AvatarUrl: wechatUserInfo.Headimgurl,
}
return &userInfo, nil
}

210
idp/wecom.go Normal file
View File

@@ -0,0 +1,210 @@
// 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"
"io/ioutil"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
)
type WeComIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewWeComIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComIdProvider {
idp := &WeComIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *WeComIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *WeComIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://graph.qq.com/oauth2.0/token",
}
var config = &oauth2.Config{
Scopes: []string{"snsapi_login"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type WeComProviderToken struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
ProviderAccessToken string `json:"provider_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://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
func (idp *WeComIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
CorpId string `json:"corpid"`
ProviderSecret string `json:"provider_secret"`
}{idp.Config.ClientID, idp.Config.ClientSecret}
data, err := idp.postWithBody(pTokenParams, "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token")
pToken := &WeComProviderToken{}
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.ProviderAccessToken,
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
}
/*
{
"errcode":0,
"errmsg":"ok",
"usertype": 1,
"user_info":{
"userid":"xxxx",
"open_userid":"xxx",
"name":"xxxx",
"avatar":"xxxx"
},
"corp_info":{
"corpid":"wxCorpId",
},
"agent":[
{"agentid":0,"auth_type":1},
{"agentid":1,"auth_type":1},
{"agentid":2,"auth_type":1}
],
"auth_info":{
"department":[
{
"id":2,
"writable":true
}
]
}
}
*/
type WeComUserInfo struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
Usertype int `json:"usertype"`
UserInfo struct {
Userid string `json:"userid"`
OpenUserid string `json:"open_userid"`
Name string `json:"name"`
Avatar string `json:"avatar"`
} `json:"user_info"`
CorpInfo struct {
Corpid string `json:"corpid"`
} `json:"corp_info"`
Agent []struct {
Agentid int `json:"agentid"`
AuthType int `json:"auth_type"`
} `json:"agent"`
AuthInfo struct {
Department []struct {
Id int `json:"id"`
Writable bool `json:"writable"`
} `json:"department"`
} `json:"auth_info"`
}
// GetUserInfo use WeComProviderToken gotten before return WeComUserInfo
// get more detail via: https://work.weixin.qq.com/api/doc/90001/90143/91125
func (idp *WeComIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
accessToken := token.AccessToken
code := token.Extra("code").(string)
requestBody := &struct {
AuthCode string `json:"auth_code"`
}{code}
data, err := idp.postWithBody(requestBody, fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/service/get_login_info?access_token=%s", accessToken))
if err != nil {
return nil, err
}
wecomUserInfo := WeComUserInfo{}
err = json.Unmarshal(data, &wecomUserInfo)
if err != nil {
return nil, err
}
if wecomUserInfo.Errcode != 0 {
return nil, fmt.Errorf("wecomUserInfo.Errcode = %d, wecomUserInfo.Errmsg = %s", wecomUserInfo.Errcode, wecomUserInfo.Errmsg)
}
userInfo := UserInfo{
Id: wecomUserInfo.UserInfo.OpenUserid,
Username: wecomUserInfo.UserInfo.Name,
DisplayName: wecomUserInfo.UserInfo.Name,
AvatarUrl: wecomUserInfo.UserInfo.Avatar,
}
return &userInfo, nil
}
func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
r := strings.NewReader(string(bs))
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
return data, nil
}

267
idp/weibo.go Normal file
View File

@@ -0,0 +1,267 @@
// 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 (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"time"
"golang.org/x/oauth2"
)
type WeiBoIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewWeiBoIdProvider(clientId string, clientSecret string, redirectUrl string) *WeiBoIdProvider {
idp := &WeiBoIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *WeiBoIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *WeiBoIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://api.weibo.com/oauth2/access_token",
}
var config = &oauth2.Config{
Scopes: []string{""},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type WeiboAccessToken struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RemindIn string `json:"remind_in"` // This parameter is about to be obsolete, developers please use expires_in
Uid string `json:"uid"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
func (idp *WeiBoIdProvider) 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.PostForm(idp.Config.Endpoint.TokenURL, params)
// resp, err := idp.GetUrlResp(accessTokenUrl)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var weiboAccessToken WeiboAccessToken
if err = json.Unmarshal(bs, &weiboAccessToken); err != nil {
return nil, err
}
token := oauth2.Token{
AccessToken: weiboAccessToken.AccessToken,
TokenType: "WeiboAccessToken",
Expiry: time.Unix(time.Now().Unix()+int64(weiboAccessToken.ExpiresIn), 0),
}
idp.Config.Scopes[0] = weiboAccessToken.Uid
return &token, nil
}
/*
{
"id": 1404376560,
"screen_name": "zaku",
"name": "zaku",
"province": "11",
"city": "5",
"location": "北京 朝阳区",
"description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
"url": "http://blog.sina.com.cn/zaku",
"profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
"domain": "zaku",
"gender": "m",
"followers_count": 1204,
"friends_count": 447,
"statuses_count": 2908,
"favourites_count": 0,
"created_at": "Fri Aug 28 00:00:00 +0800 2009",
"following": false,
"allow_all_act_msg": false,
"geo_enabled": true,
"verified": false,
"status": {
"created_at": "Tue May 24 18:04:53 +0800 2011",
"id": 11142488790,
"text": "我的相机到了。",
"source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>",
"favorited": false,
"truncated": false,
"in_reply_to_status_id": "",
"in_reply_to_user_id": "",
"in_reply_to_screen_name": "",
"geo": null,
"mid": "5610221544300749636",
"annotations": [],
"reposts_count": 5,
"comments_count": 8
},
"allow_all_comment": true,
"avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
"verified_reason": "",
"follow_me": false,
"online_status": 0,
"bi_followers_count": 215
}
*/
type WeiboUserinfo struct {
Id int `json:"id"`
ScreenName string `json:"screen_name"`
Name string `json:"name"`
Province string `json:"province"`
City string `json:"city"`
Location string `json:"location"`
Description string `json:"description"`
Url string `json:"url"`
ProfileImageUrl string `json:"profile_image_url"`
Domain string `json:"domain"`
Gender string `json:"gender"`
FollowersCount int `json:"followers_count"`
FriendsCount int `json:"friends_count"`
StatusesCount int `json:"statuses_count"`
FavouritesCount int `json:"favourites_count"`
CreatedAt string `json:"created_at"`
Following bool `json:"following"`
AllowAllActMsg bool `json:"allow_all_act_msg"`
GeoEnabled bool `json:"geo_enabled"`
Verified bool `json:"verified"`
Status struct {
CreatedAt string `json:"created_at"`
Id int64 `json:"id"`
Text string `json:"text"`
Source string `json:"source"`
Favorited bool `json:"favorited"`
Truncated bool `json:"truncated"`
InReplyToStatusId string `json:"in_reply_to_status_id"`
InReplyToUserId string `json:"in_reply_to_user_id"`
InReplyToScreenName string `json:"in_reply_to_screen_name"`
Geo interface{} `json:"geo"`
Mid string `json:"mid"`
Annotations []interface{} `json:"annotations"`
RepostsCount int `json:"reposts_count"`
CommentsCount int `json:"comments_count"`
} `json:"status"`
AllowAllComment bool `json:"allow_all_comment"`
AvatarLarge string `json:"avatar_large"`
VerifiedReason string `json:"verified_reason"`
FollowMe bool `json:"follow_me"`
OnlineStatus int `json:"online_status"`
BiFollowersCount int `json:"bi_followers_count"`
}
// GetUserInfo use WeiboAccessToken gotten before return UserInfo
// get more detail via: https://open.weibo.com/wiki/2/users/show
func (idp *WeiBoIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
var weiboUserInfo WeiboUserinfo
accessToken := token.AccessToken
uid := idp.Config.Scopes[0]
id, _ := strconv.Atoi(uid)
userInfoUrl := fmt.Sprintf("https://api.weibo.com/2/users/show.json?access_token=%s&uid=%d", accessToken, id)
resp, err := idp.GetUrlResp(userInfoUrl)
if err != nil {
return nil, err
}
if err = json.Unmarshal([]byte(resp), &weiboUserInfo); err != nil {
return nil, err
}
// weibo user email need to get separately through this url, need user authorization.
e := struct {
Email string `json:"email"`
}{}
emailUrl := fmt.Sprintf("https://api.weibo.com/2/account/profile/email.json?access_token=%s", accessToken)
resp, err = idp.GetUrlResp(emailUrl)
if err != nil {
return nil, err
}
if err = json.Unmarshal([]byte(resp), &e); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: strconv.Itoa(weiboUserInfo.Id),
Username: weiboUserInfo.Name,
DisplayName: weiboUserInfo.Name,
AvatarUrl: weiboUserInfo.AvatarLarge,
Email: e.Email,
}
return &userInfo, nil
}
func (idp *WeiBoIdProvider) GetUrlResp(url string) (string, error) {
resp, err := idp.Client.Get(url)
if err != nil {
return "", err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buf.String(), nil
}

45
main.go
View File

@@ -1,4 +1,4 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
// 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.
@@ -16,15 +16,23 @@ package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/plugins/cors"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/routers"
_ "github.com/astaxie/beego/session/redis"
"github.com/casbin/casdoor/authz"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/proxy"
"github.com/casbin/casdoor/routers"
_ "github.com/casdoor/casdoor/routers"
_ "github.com/casbin/casdoor/routers"
)
func main() {
object.InitAdapter()
object.InitDb()
object.InitDefaultStorageProvider()
proxy.InitHttpClient()
authz.InitAuthz()
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
AllowOrigins: []string{"*"},
@@ -36,13 +44,32 @@ func main() {
//beego.DelStaticPath("/static")
beego.SetStaticPath("/static", "web/build/static")
beego.BConfig.WebConfig.DirectoryIndex = true
beego.SetStaticPath("/swagger", "swagger")
beego.SetStaticPath("/files", "files")
// https://studygolang.com/articles/2303
beego.InsertFilter("/", beego.BeforeRouter, routers.TransparentStatic) // must has this for default page
beego.InsertFilter("/*", beego.BeforeRouter, routers.TransparentStatic)
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionProvider="file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 365
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
if beego.AppConfig.String("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
} else {
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.SessionCookieSameSite = http.SameSiteNoneMode
err := logs.SetLogger("file", `{"filename":"logs/casdoor.log","maxdays":99999}`)
if err != nil {
panic(err)
}
logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false)
beego.Run()
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
// 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.
@@ -15,39 +15,53 @@
package object
import (
"fmt"
"runtime"
"github.com/astaxie/beego"
_ "github.com/go-sql-driver/mysql"
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
"xorm.io/xorm"
)
var adapter *Adapter
func InitConfig() {
err := beego.LoadAppConfig("ini", "../conf/app.conf")
if err != nil {
panic(err)
}
InitAdapter()
}
func InitAdapter() {
adapter = NewAdapter("mysql", beego.AppConfig.String("dataSourceName"))
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("dbName"))
adapter.createTable()
}
// Adapter represents the MySQL adapter for policy storage.
type Adapter struct {
driverName string
dataSourceName string
engine *xorm.Engine
dbName string
Engine *xorm.Engine
}
// finalizer is the destructor for Adapter.
func finalizer(a *Adapter) {
err := a.engine.Close()
err := a.Engine.Close()
if err != nil {
panic(err)
}
}
// NewAdapter is the constructor for Adapter.
func NewAdapter(driverName string, dataSourceName string) *Adapter {
func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapter {
a := &Adapter{}
a.driverName = driverName
a.dataSourceName = dataSourceName
a.dbName = dbName
// Open the DB, create it if not existed.
a.open()
@@ -65,31 +79,76 @@ func (a *Adapter) createDatabase() error {
}
defer engine.Close()
_, err = engine.Exec("CREATE DATABASE IF NOT EXISTS casdoor default charset utf8 COLLATE utf8_general_ci")
_, err = engine.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci", a.dbName))
return err
}
func (a *Adapter) open() {
if err := a.createDatabase(); err != nil {
panic(err)
if a.driverName != "postgres" {
if err := a.createDatabase(); err != nil {
panic(err)
}
}
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+"casdoor")
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
if err != nil {
panic(err)
}
a.engine = engine
a.createTable()
a.Engine = engine
}
func (a *Adapter) close() {
a.engine.Close()
a.engine = nil
_ = a.Engine.Close()
a.Engine = nil
}
func (a *Adapter) createTable() {
err := a.engine.Sync2(new(User))
err := a.Engine.Sync2(new(Organization))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(User))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Application))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Resource))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Token))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(VerificationRecord))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Records))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Ldap))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Payment))
if err != nil {
panic(err)
}

219
object/application.go Normal file
View File

@@ -0,0 +1,219 @@
// 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 (
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
type Application 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"`
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"`
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"`
}
func GetApplications(owner string) []*Application {
applications := []*Application{}
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner})
if err != nil {
panic(err)
}
return applications
}
func getProviderMap(owner string) map[string]*Provider {
providers := GetProviders(owner)
m := map[string]*Provider{}
for _, provider := range providers {
//if provider.Category != "OAuth" {
// continue
//}
m[provider.Name] = getMaskedProvider(provider)
}
return m
}
func extendApplicationWithProviders(application *Application) {
m := getProviderMap(application.Owner)
for _, providerItem := range application.Providers {
if provider, ok := m[providerItem.Name]; ok {
providerItem.Provider = provider
}
}
}
func extendApplicationWithOrg(application *Application) {
organization := getOrganization(application.Owner, application.Organization)
application.OrganizationObj = organization
}
func getApplication(owner string, name string) *Application {
if owner == "" || name == "" {
return nil
}
application := Application{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&application)
if err != nil {
panic(err)
}
if existed {
extendApplicationWithProviders(&application)
extendApplicationWithOrg(&application)
return &application
} else {
return nil
}
}
func GetApplicationByOrganizationName(organization string) *Application {
application := Application{}
existed, err := adapter.Engine.Where("organization=?", organization).Get(&application)
if err != nil {
panic(err)
}
if existed {
extendApplicationWithProviders(&application)
extendApplicationWithOrg(&application)
return &application
} else {
return nil
}
}
func GetApplicationByUser(user *User) *Application {
if user.SignupApplication != "" {
return getApplication("admin", user.SignupApplication)
} else {
return GetApplicationByOrganizationName(user.Owner)
}
}
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)
if err != nil {
panic(err)
}
if existed {
extendApplicationWithProviders(&application)
extendApplicationWithOrg(&application)
return &application
} else {
return nil
}
}
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 UpdateApplication(id string, application *Application) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getApplication(owner, name) == nil {
return false
}
for _, providerItem := range application.Providers {
providerItem.Provider = nil
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(application)
if err != nil {
panic(err)
}
return affected != 0
}
func AddApplication(application *Application) bool {
application.ClientId = util.GenerateClientId()
application.ClientSecret = util.GenerateClientSecret()
for _, providerItem := range application.Providers {
providerItem.Provider = nil
}
affected, err := adapter.Engine.Insert(application)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteApplication(application *Application) bool {
affected, err := adapter.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{})
if err != nil {
panic(err)
}
return affected != 0
}

110
object/application_item.go Normal file
View File

@@ -0,0 +1,110 @@
// 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
func (application *Application) GetProviderByCategory(category string) *Provider {
providers := GetProviders(application.Owner)
m := map[string]*Provider{}
for _, provider := range providers {
if provider.Category != category {
continue
}
m[provider.Name] = provider
}
for _, providerItem := range application.Providers {
if provider, ok := m[providerItem.Name]; ok {
return provider
}
}
return nil
}
func (application *Application) GetEmailProvider() *Provider {
return application.GetProviderByCategory("Email")
}
func (application *Application) GetSmsProvider() *Provider {
return application.GetProviderByCategory("SMS")
}
func (application *Application) GetStorageProvider() *Provider {
return application.GetProviderByCategory("Storage")
}
func (application *Application) GetPayProvider() *Provider {
return application.GetProviderByCategory("Pay")
}
func (application *Application) getSignupItem(itemName string) *SignupItem {
for _, signupItem := range application.SignupItems {
if signupItem.Name == itemName {
return 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 {
return false
}
return signupItem.Visible
}
func (application *Application) GetSignupItemRule(itemName string) string {
signupItem := application.getSignupItem(itemName)
if signupItem == nil {
return ""
}
return signupItem.Rule
}
func (application *Application) getAllPromptedProviderItems() []*ProviderItem {
res := []*ProviderItem{}
for _, providerItem := range application.Providers {
if providerItem.isProviderPrompted() {
res = append(res, providerItem)
}
}
return res
}
func (application *Application) isAffiliationPrompted() bool {
signupItem := application.getSignupItem("Affiliation")
if signupItem == nil {
return false
}
return signupItem.Prompted
}
func (application *Application) HasPromptPage() bool {
providerItems := application.getAllPromptedProviderItems()
if len(providerItems) != 0 {
return true
}
return application.isAffiliationPrompted()
}

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/casbin/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 = UploadFile(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/casbin/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)
}
}

40
object/captcha.go Normal file
View File

@@ -0,0 +1,40 @@
// 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"
"github.com/dchest/captcha"
)
func GetCaptcha() (string, []byte) {
id := captcha.NewLen(5)
var buffer bytes.Buffer
err := captcha.WriteImage(&buffer, id, 200, 80)
if err != nil {
panic(err)
}
return id, buffer.Bytes()
}
func VerifyCaptcha(id string, digits string) bool {
res := captcha.VerifyString(id, digits)
return res
}

121
object/check.go Normal file
View File

@@ -0,0 +1,121 @@
// 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"
"regexp"
"github.com/casbin/casdoor/util"
)
var reWhiteSpace *regexp.Regexp
func init() {
reWhiteSpace, _ = regexp.Compile(`\s`)
}
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
if organization == nil {
return "organization does not exist"
}
if application.IsSignupItemVisible("Username") {
if len(username) <= 1 {
return "username must have at least 2 characters"
} else if reWhiteSpace.MatchString(username) {
return "username cannot contain white spaces"
} else if HasUserByField(organization.Name, "name", username) {
return "username already exists"
}
}
if len(password) <= 5 {
return "password must have at least 6 characters"
}
if application.IsSignupItemVisible("Email") {
if HasUserByField(organization.Name, "email", email) {
return "email already exists"
} else if !util.IsEmailValid(email) {
return "email is invalid"
}
}
if application.IsSignupItemVisible("Phone") {
if HasUserByField(organization.Name, "phone", phone) {
return "phone already exists"
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
return "phone number is invalid"
}
}
if application.IsSignupItemVisible("Display name") {
if displayName == "" {
return "displayName cannot be blank"
} else if application.GetSignupItemRule("Display name") == "Personal" {
if !isValidPersonalName(displayName) {
return "displayName is not valid personal name"
}
}
}
if application.IsSignupItemVisible("Affiliation") {
if affiliation == "" {
return "affiliation cannot be blank"
}
}
return ""
}
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 ""
}
return "password incorrect"
} else if organization.PasswordType == "salt" {
if password == user.Password || getSaltedPassword(password, organization.PasswordSalt) == user.Password {
return ""
}
return "password incorrect"
} else {
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
}
}
func CheckUserPassword(organization string, username string, password string) (*User, string) {
user := GetUserByFields(organization, username)
if user == nil {
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"
}
msg := CheckPassword(user, password)
if msg != "" {
return nil, msg
}
return user, ""
}

31
object/check_util.go Normal file
View File

@@ -0,0 +1,31 @@
// 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 "regexp"
var rePersonalName *regexp.Regexp
func init() {
var err error
rePersonalName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
if err != nil {
panic(err)
}
}
func isValidPersonalName(s string) bool {
return rePersonalName.MatchString(s)
}

31
object/email.go Normal file
View File

@@ -0,0 +1,31 @@
// 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.
// modified from https://github.com/casbin/casnode/blob/master/service/mail.go
package object
import "github.com/go-gomail/gomail"
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()
message.SetAddressHeader("From", provider.ClientId, sender)
message.SetHeader("To", dest)
message.SetHeader("Subject", title)
message.SetBody("text/html", content)
return dialer.DialAndSend(message)
}

118
object/init.go Normal file
View File

@@ -0,0 +1,118 @@
// 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 "github.com/casbin/casdoor/util"
func InitDb() {
initBuiltInOrganization()
initBuiltInUser()
initBuiltInApplication()
initBuiltInLdap()
}
func initBuiltInOrganization() {
organization := getOrganization("admin", "built-in")
if organization != nil {
return
}
organization = &Organization{
Owner: "admin",
Name: "built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com",
Favicon: "https://cdn.casbin.com/static/favicon.ico",
PhonePrefix: "86",
DefaultAvatar: "https://casbin.org/img/casbin.svg",
PasswordType: "plain",
}
AddOrganization(organization)
}
func initBuiltInUser() {
user := getUser("built-in", "admin")
if user != nil {
return
}
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",
Score: 2000,
IsAdmin: true,
IsGlobalAdmin: true,
IsForbidden: false,
Properties: make(map[string]string),
}
AddUser(user)
}
func initBuiltInApplication() {
application := getApplication("admin", "app-built-in")
if application != nil {
return
}
application = &Application{
Owner: "admin",
Name: "app-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Casdoor",
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
HomepageUrl: "https://casdoor.org",
Organization: "built-in",
EnablePassword: true,
EnableSignUp: true,
Providers: []*ProviderItem{},
SignupItems: []*SignupItem{},
RedirectUris: []string{},
ExpireInHours: 168,
}
AddApplication(application)
}
func initBuiltInLdap() {
ldap := GetLdap("ldap-built-in")
if ldap != nil {
return
}
ldap = &Ldap{
Id: "ldap-built-in",
Owner: "built-in",
ServerName: "BuildIn LDAP Server",
Host: "example.com",
Port: 389,
Admin: "cn=buildin,dc=example,dc=com",
Passwd: "123",
BaseDn: "ou=BuildIn,dc=example,dc=com",
AutoSync: 0,
LastSync: "",
}
AddLdap(ldap)
}

369
object/ldap.go Normal file
View File

@@ -0,0 +1,369 @@
// 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 (
"errors"
"fmt"
"github.com/casbin/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
"strings"
)
type Ldap struct {
Id string `xorm:"varchar(100) notnull pk" json:"id"`
Owner string `xorm:"varchar(100)" json:"owner"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
ServerName string `xorm:"varchar(100)" json:"serverName"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
Admin string `xorm:"varchar(100)" json:"admin"`
Passwd string `xorm:"varchar(100)" json:"passwd"`
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
AutoSync int `json:"autoSync"`
LastSync string `xorm:"varchar(100)" json:"lastSync"`
}
type ldapConn struct {
Conn *goldap.Conn
}
//type ldapGroup struct {
// GidNumber string
// Cn string
//}
type ldapUser struct {
UidNumber string
Uid string
Cn string
GidNumber string
//Gcn string
Uuid string
Mail string
Email string
EmailAddress string
TelephoneNumber string
Mobile string
MobileTelephoneNumber string
RegisteredAddress string
PostalAddress string
}
type LdapRespUser struct {
UidNumber string `json:"uidNumber"`
Uid string `json:"uid"`
Cn string `json:"cn"`
GroupId string `json:"groupId"`
//GroupName string `json:"groupName"`
Uuid string `json:"uuid"`
Email string `json:"email"`
Phone string `json:"phone"`
Address string `json:"address"`
}
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 {
return nil, err
}
err = conn.Bind(adminUser, adminPasswd)
if err != nil {
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
}
return &ldapConn{Conn: conn}, nil
}
//FIXME: The Base DN does not necessarily contain the Group
//func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
// SearchFilter := "(objectClass=posixGroup)"
// SearchAttributes := []string{"cn", "gidNumber"}
// groupMap := make(map[string]ldapGroup)
//
// searchReq := goldap.NewSearchRequest(baseDn,
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
// SearchFilter, SearchAttributes, nil)
// searchResult, err := l.Conn.Search(searchReq)
// if err != nil {
// return nil, err
// }
//
// if len(searchResult.Entries) == 0 {
// return nil, errors.New("no result")
// }
//
// for _, entry := range searchResult.Entries {
// var ldapGroupItem ldapGroup
// for _, attribute := range entry.Attributes {
// switch attribute.Name {
// case "gidNumber":
// ldapGroupItem.GidNumber = attribute.Values[0]
// break
// case "cn":
// ldapGroupItem.Cn = attribute.Values[0]
// break
// }
// }
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
// }
//
// return groupMap, nil
//}
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
SearchFilter := "(objectClass=posixAccount)"
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
searchReq := goldap.NewSearchRequest(baseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, SearchAttributes, nil)
searchResult, err := l.Conn.Search(searchReq)
if err != nil {
return nil, err
}
if len(searchResult.Entries) == 0 {
return nil, errors.New("no result")
}
var ldapUsers []ldapUser
for _, entry := range searchResult.Entries {
var ldapUserItem ldapUser
for _, attribute := range entry.Attributes {
switch attribute.Name {
case "uidNumber":
ldapUserItem.UidNumber = attribute.Values[0]
case "uid":
ldapUserItem.Uid = attribute.Values[0]
case "cn":
ldapUserItem.Cn = attribute.Values[0]
case "gidNumber":
ldapUserItem.GidNumber = attribute.Values[0]
case "entryUUID":
ldapUserItem.Uuid = attribute.Values[0]
case "mail":
ldapUserItem.Mail = attribute.Values[0]
case "email":
ldapUserItem.Email = attribute.Values[0]
case "emailAddress":
ldapUserItem.EmailAddress = attribute.Values[0]
case "telephoneNumber":
ldapUserItem.TelephoneNumber = attribute.Values[0]
case "mobile":
ldapUserItem.Mobile = attribute.Values[0]
case "mobileTelephoneNumber":
ldapUserItem.MobileTelephoneNumber = attribute.Values[0]
case "registeredAddress":
ldapUserItem.RegisteredAddress = attribute.Values[0]
case "postalAddress":
ldapUserItem.PostalAddress = attribute.Values[0]
}
}
ldapUsers = append(ldapUsers, ldapUserItem)
}
return ldapUsers, nil
}
func AddLdap(ldap *Ldap) bool {
if len(ldap.Id) == 0 {
ldap.Id = util.GenerateId()
}
if len(ldap.CreatedTime) == 0 {
ldap.CreatedTime = util.GetCurrentTime()
}
affected, err := adapter.Engine.Insert(ldap)
if err != nil {
panic(err)
}
return affected != 0
}
func CheckLdapExist(ldap *Ldap) bool {
var result []*Ldap
err := adapter.Engine.Find(&result, &Ldap{
Owner: ldap.Owner,
Host: ldap.Host,
Port: ldap.Port,
Admin: ldap.Admin,
Passwd: ldap.Passwd,
BaseDn: ldap.BaseDn,
})
if err != nil {
panic(err)
}
if len(result) > 0 {
return true
}
return false
}
func GetLdaps(owner string) []*Ldap {
var ldaps []*Ldap
err := adapter.Engine.Desc("created_time").Find(&ldaps, &Ldap{Owner: owner})
if err != nil {
panic(err)
}
return ldaps
}
func GetLdap(id string) *Ldap {
if util.IsStrsEmpty(id) {
return nil
}
ldap := Ldap{Id: id}
existed, err := adapter.Engine.Get(&ldap)
if err != nil {
panic(err)
}
if existed {
return &ldap
} else {
return nil
}
}
func UpdateLdap(ldap *Ldap) bool {
if GetLdap(ldap.Id) == nil {
return false
}
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
"port", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteLdap(ldap *Ldap) bool {
affected, err := adapter.Engine.ID(ldap.Id).Delete(&Ldap{})
if err != nil {
panic(err)
}
return affected != 0
}
func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]LdapRespUser) {
var existUsers []LdapRespUser
var failedUsers []LdapRespUser
var uuids []string
for _, user := range users {
uuids = append(uuids, user.Uuid)
}
existUuids := CheckLdapUuidExist(owner, uuids)
for _, user := range users {
if len(existUuids) > 0 {
for index, existUuid := range existUuids {
if user.Uuid == existUuid {
existUsers = append(existUsers, user)
existUuids = append(existUuids[:index], existUuids[index+1:]...)
}
}
}
if !AddUser(&User{
Owner: owner,
Name: buildLdapUserName(user.Uid, user.UidNumber),
CreatedTime: util.GetCurrentTime(),
Password: "123",
DisplayName: user.Cn,
Avatar: "https://casbin.org/img/casbin.svg",
Email: user.Email,
Phone: user.Phone,
Address: []string{user.Address},
Affiliation: "Example Inc.",
Tag: "staff",
Score: 2000,
Ldap: user.Uuid,
}) {
failedUsers = append(failedUsers, user)
continue
}
}
return &existUsers, &failedUsers
}
func UpdateLdapSyncTime(ldapId string) {
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
if err != nil {
panic(err)
}
}
func CheckLdapUuidExist(owner string, uuids []string) []string {
var results []User
var existUuids []string
//whereStr := ""
//for i, uuid := range uuids {
// if i == 0 {
// whereStr = fmt.Sprintf("'%s'", uuid)
// } else {
// whereStr = fmt.Sprintf(",'%s'", uuid)
// }
//}
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
if err != nil {
panic(err)
}
if len(results) > 0 {
for _, result := range results {
existUuids = append(existUuids, result.Ldap)
}
}
return existUuids
}
func buildLdapUserName(uid, uidNum string) string {
var result User
uidWithNumber := fmt.Sprintf("%s_%s", uid, uidNum)
has, err := adapter.Engine.Where("name = ? or name = ?", uid, uidWithNumber).Get(&result)
if err != nil {
panic(err)
}
if has {
if result.Name == uid {
return uidWithNumber
}
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
}
return uid
}

103
object/organization.go Normal file
View File

@@ -0,0 +1,103 @@
// 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 (
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
type Organization 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"`
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"`
}
func GetOrganizations(owner string) []*Organization {
organizations := []*Organization{}
err := adapter.Engine.Desc("created_time").Find(&organizations, &Organization{Owner: owner})
if err != nil {
panic(err)
}
return organizations
}
func getOrganization(owner string, name string) *Organization {
if owner == "" || name == "" {
return nil
}
organization := Organization{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&organization)
if err != nil {
panic(err)
}
if existed {
return &organization
}
return nil
}
func GetOrganization(id string) *Organization {
owner, name := util.GetOwnerAndNameFromId(id)
return getOrganization(owner, name)
}
func UpdateOrganization(id string, organization *Organization) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getOrganization(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
if err != nil {
panic(err)
}
return affected != 0
}
func AddOrganization(organization *Organization) bool {
affected, err := adapter.Engine.Insert(organization)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteOrganization(organization *Organization) bool {
affected, err := adapter.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{})
if err != nil {
panic(err)
}
return affected != 0
}
func GetOrganizationByUser(user *User) *Organization {
return getOrganization("admin", user.Owner)
}

37
object/password.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 object
import (
"crypto/sha256"
"encoding/hex"
)
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 getSaltedPassword(password string, salt string) string {
hash1 := getSha256HexDigest(password)
res := getSha256HexDigest(hash1 + salt)
return res
}

8
object/pay_item.go Normal file
View File

@@ -0,0 +1,8 @@
package object
type PayItem struct {
Invoice string `json:"invoice"`
Price string `json:"price"`
Description string `json:"description"`
Currency string `json:"currency"`
}

74
object/payment.go Normal file
View File

@@ -0,0 +1,74 @@
package object
import (
"github.com/plutov/paypal/v4"
"xorm.io/core"
)
type Payment struct {
Id string `xorm:"varchar(100) notnull pk" json:"id"`
Invoice string `xorm:"varchar(100)" json:"invoice"`
Application string `xorm:"varchar(100)" json:"application"`
PayItem PayItem `xorm:"json varchar(1000)" json:"pay_item"`
Payer *paypal.PayerWithNameAndPhone `xorm:"json varchar(1000)" json:"payer"`
Purchase []paypal.CapturedPurchaseUnit `xorm:"varchar(10000)" json:"purchase"`
Status string `xorm:"varchar(100)" json:"status"`
CreateTime string `xorm:"varchar(100) created" json:"create_time"`
UpdateTime string `xorm:"varchar(100) updated" json:"update_time"`
Callback string `xorm:"varchar(1000)" json:"callback"`
}
func AddPayment(pay *Payment) bool {
affected, err := adapter.Engine.Insert(pay)
if err != nil {
panic(err)
}
return affected != 0
}
func GetPayments() []*Payment {
pays := []*Payment{}
err := adapter.Engine.Desc("create_time").Find(&pays)
if err != nil {
panic(err)
}
return pays
}
func GetPayment(id string) *Payment {
pay := Payment{Id: id}
existed, err := adapter.Engine.Get(&pay)
if err != nil {
panic(err)
}
if existed {
return &pay
} else {
return nil
}
}
func DeletePayment(payment *Payment) bool {
affected, err := adapter.Engine.ID(core.PK{payment.Id}).Delete(&Payment{})
if err != nil {
panic(err)
}
return affected != 0
}
func UpdatePay(id string, pay *Payment) bool {
if GetPayment(id) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{id}).AllCols().Update(pay)
if err != nil {
panic(err)
}
return affected != 0
}

148
object/provider.go Normal file
View File

@@ -0,0 +1,148 @@
// 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/casbin/casdoor/util"
"xorm.io/core"
)
type Provider 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"`
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"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
Title string `xorm:"varchar(100)" json:"title"`
Content string `xorm:"varchar(1000)" json:"content"`
RegionId string `xorm:"varchar(100)" json:"regionId"`
SignName string `xorm:"varchar(100)" json:"signName"`
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"`
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,
Method: provider.Method,
ClientId: provider.ClientId,
}
return p
}
func GetProviders(owner string) []*Provider {
providers := []*Provider{}
err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner})
if err != nil {
panic(err)
}
return providers
}
func getProvider(owner string, name string) *Provider {
if owner == "" || name == "" {
return nil
}
provider := Provider{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&provider)
if err != nil {
panic(err)
}
if existed {
return &provider
} else {
return nil
}
}
func GetProvider(id string) *Provider {
owner, name := util.GetOwnerAndNameFromId(id)
return getProvider(owner, name)
}
func GetDefaultHumanCheckProvider() *Provider {
provider := Provider{Owner: "admin", Category: "HumanCheck"}
existed, err := adapter.Engine.Get(&provider)
if err != nil {
panic(err)
}
if !existed {
return nil
}
return &provider
}
func UpdateProvider(id string, provider *Provider) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getProvider(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(provider)
if err != nil {
panic(err)
}
return affected != 0
}
func AddProvider(provider *Provider) bool {
affected, err := adapter.Engine.Insert(provider)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteProvider(provider *Provider) bool {
affected, err := adapter.Engine.ID(core.PK{provider.Owner, provider.Name}).Delete(&Provider{})
if err != nil {
panic(err)
}
return affected != 0
}
func (p *Provider) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}

42
object/provider_item.go Normal file
View File

@@ -0,0 +1,42 @@
// 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
type ProviderItem struct {
Name string `json:"name"`
CanSignUp bool `json:"canSignUp"`
CanSignIn bool `json:"canSignIn"`
CanUnlink bool `json:"canUnlink"`
Prompted bool `json:"prompted"`
AlertType string `json:"alertType"`
Provider *Provider `json:"provider"`
}
func (application *Application) GetProviderItem(providerName string) *ProviderItem {
for _, providerItem := range application.Providers {
if providerItem.Name == providerName {
return providerItem
}
}
return nil
}
func (pi *ProviderItem) IsProviderVisible() bool {
return pi.Provider.Category == "OAuth"
}
func (pi *ProviderItem) isProviderPrompted() bool {
return pi.IsProviderVisible() && pi.Prompted
}

65
object/record.go Normal file
View File

@@ -0,0 +1,65 @@
// 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 (
"github.com/casbin/casdoor/util"
)
type Records struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Record util.Record `xorm:"extends"`
}
func AddRecord(record *util.Record) bool {
records := new(Records)
records.Record = *record
affected, err := adapter.Engine.Insert(records)
if err != nil {
panic(err)
}
return affected != 0
}
func GetRecordCount() int {
count, err := adapter.Engine.Count(&Records{})
if err != nil {
panic(err)
}
return int(count)
}
func GetRecords() []*Records {
records := []*Records{}
err := adapter.Engine.Desc("id").Find(&records)
if err != nil {
panic(err)
}
return records
}
func GetRecordsByField(record *Records) []*Records {
records := []*Records{}
err := adapter.Engine.Find(&records, record)
if err != nil {
panic(err)
}
return records
}

118
object/resource.go Normal file
View File

@@ -0,0 +1,118 @@
// 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/casbin/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(100)" json:"url"`
}
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 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)
}
}

23
object/signup_item.go Normal file
View File

@@ -0,0 +1,23 @@
// 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
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
Rule string `json:"rule"`
}

34
object/sms.go Normal file
View File

@@ -0,0 +1,34 @@
// 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 "github.com/casdoor/go-sms-sender"
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode, provider.AppId)
if err != nil {
return err
}
params := map[string]string{}
if provider.Type == go_sms_sender.TencentCloud {
params["0"] = content
} else {
params["code"] = content
}
err = client.SendMessage(params, phoneNumbers...)
return err
}

82
object/storage.go Normal file
View File

@@ -0,0 +1,82 @@
// 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"
"strings"
"github.com/casbin/casdoor/storage"
"github.com/casbin/casdoor/util"
)
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" {
// provider.Domain = "https://cdn.casbin.com/casdoor/"
host = util.GetUrlHost(provider.Domain)
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = fmt.Sprintf("https://%s", host)
}
} else {
// provider.Domain = "http://localhost:8000" or "https://door.casbin.com"
host = util.UrlJoin(provider.Domain, "/files")
}
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) {
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)
}
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 DeleteFile(provider *Provider, objectKey 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)
}
if provider.Domain == "" {
provider.Domain = storageProvider.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
return storageProvider.Delete(objectKey)
}

263
object/token.go Normal file
View File

@@ -0,0 +1,263 @@
// 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 (
"strings"
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
type Code struct {
Message string `xorm:"varchar(100)" json:"message"`
Code string `xorm:"varchar(100)" json:"code"`
}
type Token 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"`
Application string `xorm:"varchar(100)" json:"application"`
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"`
}
type TokenWrapper struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
func GetTokens(owner string) []*Token {
tokens := []*Token{}
err := adapter.Engine.Desc("created_time").Find(&tokens, &Token{Owner: owner})
if err != nil {
panic(err)
}
return tokens
}
func getToken(owner string, name string) *Token {
if owner == "" || name == "" {
return nil
}
token := Token{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&token)
if err != nil {
panic(err)
}
if existed {
return &token
}
return nil
}
func getTokenByCode(code string) *Token {
token := Token{}
existed, err := adapter.Engine.Where("code=?", code).Get(&token)
if err != nil {
panic(err)
}
if existed {
return &token
}
return nil
}
func GetToken(id string) *Token {
owner, name := util.GetOwnerAndNameFromId(id)
return getToken(owner, name)
}
func UpdateToken(id string, token *Token) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getToken(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(token)
if err != nil {
panic(err)
}
return affected != 0
}
func AddToken(token *Token) bool {
affected, err := adapter.Engine.Insert(token)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteToken(token *Token) bool {
affected, err := adapter.Engine.ID(core.PK{token.Owner, token.Name}).Delete(&Token{})
if err != nil {
panic(err)
}
return affected != 0
}
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
if responseType != "code" {
return "response_type should be \"code\"", nil
}
application := GetApplicationByClientId(clientId)
if application == nil {
return "Invalid client_id", nil
}
validUri := false
for _, tmpUri := range application.RedirectUris {
if strings.Contains(redirectUri, tmpUri) {
validUri = true
break
}
}
if !validUri {
return "redirect_uri doesn't exist in the allowed Redirect URL list", application
}
return "", application
}
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string) *Code {
user := GetUser(userId)
if user == nil {
return &Code{
Message: "Invalid user_id",
Code: "",
}
}
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
if msg != "" {
return &Code{
Message: msg,
Code: "",
}
}
accessToken, err := generateJwtToken(application, user)
if err != nil {
panic(err)
}
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",
}
AddToken(token)
return &Code{
Message: "",
Code: token.Code,
}
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string) *TokenWrapper {
application := GetApplicationByClientId(clientId)
if application == nil {
return &TokenWrapper{
AccessToken: "error: invalid client_id",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
if grantType != "authorization_code" {
return &TokenWrapper{
AccessToken: "error: grant_type should be \"authorization_code\"",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
if code == "" {
return &TokenWrapper{
AccessToken: "error: code should not be empty",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
token := getTokenByCode(code)
if token == nil {
return &TokenWrapper{
AccessToken: "error: invalid code",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
if application.Name != token.Application {
return &TokenWrapper{
AccessToken: "error: the token is for wrong application (client_id)",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
if application.ClientSecret != clientSecret {
return &TokenWrapper{
AccessToken: "error: invalid client_secret",
TokenType: "",
ExpiresIn: 0,
Scope: "",
}
}
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
}
return tokenWrapper
}

65
object/token_jwt.go Normal file
View File

@@ -0,0 +1,65 @@
// 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 (
"time"
"github.com/dgrijalva/jwt-go"
)
var jwtSecret = []byte("CasdoorSecret")
type Claims struct {
User
jwt.StandardClaims
}
func generateJwtToken(application *Application, user *User) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
claims := Claims{
User: *user,
StandardClaims: jwt.StandardClaims{
Audience: application.ClientId,
ExpiresAt: expireTime.Unix(),
Id: "",
IssuedAt: nowTime.Unix(),
Issuer: "casdoor",
NotBefore: nowTime.Unix(),
Subject: user.Id,
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}
func ParseJwtToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, err
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
// 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.
@@ -15,7 +15,9 @@
package object
import (
"github.com/casdoor/casdoor/util"
"fmt"
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
@@ -23,17 +25,65 @@ type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
Password string `xorm:"varchar(100)" json:"password"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Email string `xorm:"varchar(100)" json:"email"`
Phone string `xorm:"varchar(100)" json:"phone"`
Id string `xorm:"varchar(100)" json:"id"`
Type string `xorm:"varchar(100)" json:"type"`
Password string `xorm:"varchar(100)" json:"password"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Avatar string `xorm:"varchar(255)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(255)" json:"permanentAvatar"`
Email string `xorm:"varchar(100)" json:"email"`
Phone string `xorm:"varchar(100)" 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"`
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"`
Score int `json:"score"`
Ranking int `json:"ranking"`
IsOnline bool `json:"isOnline"`
IsAdmin bool `json:"isAdmin"`
IsGlobalAdmin bool `json:"isGlobalAdmin"`
IsForbidden bool `json:"isForbidden"`
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
Github string `xorm:"varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"`
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
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"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
}
func GetGlobalUsers() []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Find(&users)
if err != nil {
panic(err)
}
return users
}
func GetUsers(owner string) []*User {
users := []*User{}
err := adapter.engine.Desc("created_time").Find(&users, &User{Owner: owner})
err := adapter.Engine.Desc("created_time").Find(&users, &User{Owner: owner})
if err != nil {
panic(err)
}
@@ -42,8 +92,12 @@ func GetUsers(owner string) []*User {
}
func getUser(owner string, name string) *User {
if owner == "" || name == "" {
return nil
}
user := User{Owner: owner, Name: name}
existed, err := adapter.engine.Get(&user)
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
@@ -60,23 +114,115 @@ func GetUser(id string) *User {
return getUser(owner, name)
}
func GetMaskedUser(user *User) *User {
if user == nil {
return nil
}
if user.Password != "" {
user.Password = "***"
}
return user
}
func GetMaskedUsers(users []*User) []*User {
for _, user := range users {
user = GetMaskedUser(user)
}
return users
}
func GetLastUser(owner string) *User {
user := User{Owner: owner}
existed, err := adapter.Engine.Desc("created_time", "id").Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
}
return nil
}
func UpdateUser(id string, user *User) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getUser(owner, name) == nil {
oldUser := getUser(owner, name)
if oldUser == nil {
return false
}
_, err := adapter.engine.Id(core.PK{owner, name}).AllCols().Update(user)
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}).Cols("owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "is_admin", "is_global_admin", "is_forbidden",
"hash", "properties").Update(user)
if err != nil {
panic(err)
}
//return affected != 0
return true
return affected != 0
}
func UpdateUserForAllFields(id string, user *User) bool {
owner, name := util.GetOwnerAndNameFromId(id)
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)
}
return affected != 0
}
func UpdateUserForOriginalFields(user *User) bool {
owner, name := util.GetOwnerAndNameFromId(user.GetId())
oldUser := getUser(owner, name)
if oldUser == nil {
return false
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
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 {
affected, err := adapter.engine.Insert(user)
if user.Id == "" {
user.Id = util.GenerateId()
}
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)
}
@@ -84,11 +230,68 @@ func AddUser(user *User) bool {
return affected != 0
}
func AddUsers(users []*User) bool {
if len(users) == 0 {
return false
}
organization := GetOrganizationByUser(users[0])
for _, user := range users {
user.UpdateUserPassword(organization)
user.UpdateUserHash()
user.PreHash = user.Hash
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
affected, err := adapter.Engine.Insert(users)
if err != nil {
panic(err)
}
return affected != 0
}
func AddUsersSafe(users []*User) bool {
batchSize := 1000
if len(users) == 0 {
return false
}
affected := false
for i := 0; i < (len(users)-1)/batchSize+1; i++ {
start := i * batchSize
end := (i + 1) * batchSize
if end > len(users) {
end = len(users)
}
tmp := users[start:end]
// TODO: save to log instead of standard output
// fmt.Printf("Add users: [%d - %d].\n", start, end)
if AddUsers(tmp) {
affected = true
}
}
return affected
}
func DeleteUser(user *User) bool {
affected, err := adapter.engine.Id(core.PK{user.Owner, user.Name}).Delete(&User{})
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{})
if err != nil {
panic(err)
}
return affected != 0
}
func LinkUserAccount(user *User, field string, value string) bool {
return SetUserField(user, field, value)
}
func (user *User) GetId() string {
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
}

38
object/user_cred.go Normal file
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 object
import (
"strconv"
"strings"
"github.com/casbin/casdoor/util"
)
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)
}
func (user *User) UpdateUserHash() {
hash := calculateHash(user)
user.Hash = hash
}
func (user *User) UpdateUserPassword(organization *Organization) {
if organization.PasswordType == "salt" {
user.Password = getSaltedPassword(user.Password, organization.PasswordSalt)
}
}

105
object/user_test.go Normal file
View File

@@ -0,0 +1,105 @@
// 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"
"reflect"
"testing"
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
func updateUserColumn(column string, user *User) bool {
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols(column).Update(user)
if err != nil {
panic(err)
}
return affected != 0
}
func TestSyncAvatarsFromGitHub(t *testing.T) {
InitConfig()
users := GetGlobalUsers()
for _, user := range users {
if user.Github == "" {
continue
}
user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.Github)
updateUserColumn("avatar", user)
}
}
func TestSyncIds(t *testing.T) {
InitConfig()
users := GetGlobalUsers()
for _, user := range users {
if user.Id != "" {
continue
}
user.Id = util.GenerateId()
updateUserColumn("id", user)
}
}
func TestSyncHashes(t *testing.T) {
InitConfig()
users := GetGlobalUsers()
for _, user := range users {
if user.Hash != "" {
continue
}
user.UpdateUserHash()
updateUserColumn("hash", user)
}
}
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
}
tests := []struct {
name string
args args
want []*User
}{
{
name: "1",
args: args{users: []*User{{Password: "casdoor"}, {Password: "casbin"}}},
want: []*User{{Password: "***"}, {Password: "***"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetMaskedUsers(tt.args.users); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetMaskedUsers() = %v, want %v", got, tt.want)
}
})
}
}

156
object/user_util.go Normal file
View File

@@ -0,0 +1,156 @@
// 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"
"reflect"
"strings"
"github.com/casbin/casdoor/idp"
"xorm.io/core"
)
func GetUserByField(organizationName string, field string, value string) *User {
if field == "" || value == "" {
return nil
}
user := User{Owner: organizationName}
existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", field), value).Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func HasUserByField(organizationName string, field string, value string) bool {
return GetUserByField(organizationName, field, value) != nil
}
func GetUserByFields(organization string, field string) *User {
// check username
user := GetUserByField(organization, "name", field)
if user != nil {
return user
}
// check email
user = GetUserByField(organization, "email", field)
if user != nil {
return user
}
// check phone
user = GetUserByField(organization, "phone", field)
if user != nil {
return user
}
return nil
}
func SetUserField(user *User, field string, value string) bool {
if field == "password" {
organization := GetOrganizationByUser(user)
user.UpdateUserPassword(organization)
value = user.Password
}
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(map[string]interface{}{field: value})
if err != nil {
panic(err)
}
user = getUser(user.Owner, user.Name)
user.UpdateUserHash()
_, err = adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("hash").Update(user)
if err != nil {
panic(err)
}
return affected != 0
}
func GetUserField(user *User, field string) string {
// https://socketloop.com/tutorials/golang-how-to-get-struct-field-and-value-by-name
u := reflect.ValueOf(user)
f := reflect.Indirect(u).FieldByName(field)
return f.String()
}
func setUserProperty(user *User, field string, value string) {
if value == "" {
delete(user.Properties, field)
} else {
user.Properties[field] = value
}
}
func SetUserOAuthProperties(organization *Organization, user *User, providerType string, userInfo *idp.UserInfo) bool {
if userInfo.Id != "" {
propertyName := fmt.Sprintf("oauth_%s_id", providerType)
setUserProperty(user, propertyName, userInfo.Id)
}
if userInfo.Username != "" {
propertyName := fmt.Sprintf("oauth_%s_username", providerType)
setUserProperty(user, propertyName, userInfo.Username)
}
if userInfo.DisplayName != "" {
propertyName := fmt.Sprintf("oauth_%s_displayName", providerType)
setUserProperty(user, propertyName, userInfo.DisplayName)
if user.DisplayName == "" {
user.DisplayName = userInfo.DisplayName
}
}
if userInfo.Email != "" {
propertyName := fmt.Sprintf("oauth_%s_email", providerType)
setUserProperty(user, propertyName, userInfo.Email)
if user.Email == "" {
user.Email = userInfo.Email
}
}
if userInfo.AvatarUrl != "" {
propertyName := fmt.Sprintf("oauth_%s_avatarUrl", providerType)
setUserProperty(user, propertyName, userInfo.AvatarUrl)
if user.Avatar == "" || user.Avatar == organization.DefaultAvatar {
user.Avatar = userInfo.AvatarUrl
}
}
affected := UpdateUserForAllFields(user.GetId(), user)
return affected
}
func ClearUserOAuthProperties(user *User, providerType string) bool {
for k := range user.Properties {
prefix := fmt.Sprintf("oauth_%s_", providerType)
if strings.HasPrefix(k, prefix) {
delete(user.Properties, k)
}
}
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("properties").Update(user)
if err != nil {
panic(err)
}
return affected != 0
}

172
object/verification.go Normal file
View File

@@ -0,0 +1,172 @@
// 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 (
"errors"
"fmt"
"math/rand"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
type VerificationRecord 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"`
RemoteAddr string `xorm:"varchar(100)"`
Type string `xorm:"varchar(10)"`
User string `xorm:"varchar(100) notnull"`
Provider string `xorm:"varchar(100) notnull"`
Receiver string `xorm:"varchar(100) notnull"`
Code string `xorm:"varchar(10) notnull"`
Time int64 `xorm:"notnull"`
IsUsed bool
}
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
if provider == nil {
return fmt.Errorf("Please set an Email provider first")
}
sender := organization.DisplayName
title := provider.Title
code := getRandomCode(5)
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(provider.Content, code)
if err := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); err != nil {
return err
}
return SendEmail(provider, title, content, dest, sender)
}
func SendVerificationCodeToPhone(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
if provider == nil {
return errors.New("Please set a SMS provider first")
}
code := getRandomCode(5)
if err := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); err != nil {
return err
}
return SendSms(provider, dest, code)
}
func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordType, dest, code string) error {
var record VerificationRecord
record.RemoteAddr = remoteAddr
record.Type = recordType
if user != nil {
record.User = user.GetId()
}
has, err := adapter.Engine.Desc("created_time").Get(&record)
if err != nil {
return err
}
now := time.Now().Unix()
if has && now-record.Time < 60 {
return errors.New("You can only send one code in 60s.")
}
record.Owner = provider.Owner
record.Name = util.GenerateId()
record.CreatedTime = util.GetCurrentTime()
if user != nil {
record.User = user.GetId()
}
record.Provider = provider.Name
record.Receiver = dest
record.Code = code
record.Time = now
record.IsUsed = false
_, err = adapter.Engine.Insert(record)
if err != nil {
return err
}
return nil
}
func getVerificationRecord(dest string) *VerificationRecord {
var record VerificationRecord
record.Receiver = dest
has, err := adapter.Engine.Desc("time").Where("is_used = 0").Get(&record)
if err != nil {
panic(err)
}
if !has {
return nil
}
return &record
}
func CheckVerificationCode(dest, code string) string {
record := getVerificationRecord(dest)
if record == nil {
return "Code has not been sent yet!"
}
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout")
if err != nil {
panic(err)
}
now := time.Now().Unix()
if now-record.Time > timeout*60 {
return fmt.Sprintf("You should verify your code in %d min!", timeout)
}
if record.Code != code {
return "Wrong code!"
}
return ""
}
func DisableVerificationCode(dest string) {
record := getVerificationRecord(dest)
if record == nil {
return
}
record.IsUsed = true
_, err := adapter.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record)
if err != nil {
panic(err)
}
}
// from Casnode/object/validateCode.go line 116
var stdNums = []byte("0123456789")
func getRandomCode(length int) string {
var result []byte
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < length; i++ {
result = append(result, stdNums[r.Intn(len(stdNums))])
}
return string(result)
}

50
original/adapter.go Normal file
View File

@@ -0,0 +1,50 @@
// 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 original
import (
"github.com/astaxie/beego"
"github.com/casbin/casdoor/object"
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
)
var adapter *object.Adapter
func initConfig() {
err := beego.LoadAppConfig("ini", "../conf/app.conf")
if err != nil {
panic(err)
}
initAdapter()
}
func initAdapter() {
if dbName == "dbName" {
adapter = nil
return
}
adapter = object.NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), dbName)
createTable(adapter)
}
func createTable(a *object.Adapter) {
err := a.Engine.Sync2(new(User))
if err != nil {
panic(err)
}
}

44
original/affiliation.go Normal file
View File

@@ -0,0 +1,44 @@
// 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 original
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 {
affiliations := []*Affiliation{}
err := adapter.Engine.Asc("id").Find(&affiliations)
if err != nil {
panic(err)
}
return affiliations
}
func getAffiliationMap() ([]*Affiliation, map[int]string) {
affiliations := getAffiliations()
m := map[int]string{}
for _, affiliation := range affiliations {
m[affiliation.Id] = affiliation.Name
}
return affiliations, m
}

22
original/conf.go Normal file
View File

@@ -0,0 +1,22 @@
// 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 original
var dbName = "dbName"
var userTableName = "userTableName"
var affiliationTableName = "affiliationTableName"
var avatarBaseUrl = "https://cdn.example.com/"
var orgName = "orgName"

23
original/cron.go Normal file
View File

@@ -0,0 +1,23 @@
// 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 original
import "github.com/mileusna/crontab"
var ctab *crontab.Crontab
func init() {
ctab = crontab.New()
}

59
original/public_api.go Normal file
View File

@@ -0,0 +1,59 @@
// 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 original
import (
"fmt"
"github.com/casbin/casdoor/object"
)
func isEnabled() bool {
if adapter == nil {
initAdapter()
if adapter == nil {
return false
}
}
return true
}
func AddUserToOriginalDatabase(user *object.User) {
if user.Owner != orgName {
return
}
if !isEnabled() {
return
}
updatedOUser := createOriginalUserFromUser(user)
addUser(updatedOUser)
fmt.Printf("Add from user to oUser: %v\n", updatedOUser)
}
func UpdateUserToOriginalDatabase(user *object.User) {
if user.Owner != orgName {
return
}
if !isEnabled() {
return
}
updatedOUser := createOriginalUserFromUser(user)
updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
}

151
original/sync.go Normal file
View File

@@ -0,0 +1,151 @@
// 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 original
import (
"fmt"
"strconv"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
func getFullAvatarUrl(avatar string) string {
if !strings.HasPrefix(avatar, "https://") {
return fmt.Sprintf("%s%s", avatarBaseUrl, avatar)
}
return avatar
}
func getPartialAvatarUrl(avatar string) string {
if strings.HasPrefix(avatar, avatarBaseUrl) {
return avatar[len(avatarBaseUrl):]
}
return avatar
}
func createUserFromOriginalUser(originalUser *User, affiliationMap map[int]string) *object.User {
affiliation := ""
if originalUser.SchoolId != 0 {
var ok bool
affiliation, ok = affiliationMap[originalUser.SchoolId]
if !ok {
panic(fmt.Sprintf("SchoolId not found: %d", originalUser.SchoolId))
}
}
user := &object.User{
Owner: orgName,
Name: strconv.Itoa(originalUser.Id),
CreatedTime: util.GetCurrentTime(),
Id: strconv.Itoa(originalUser.Id),
Type: "normal-user",
Password: originalUser.Password,
DisplayName: originalUser.Name,
Avatar: getFullAvatarUrl(originalUser.Avatar),
Email: "",
Phone: originalUser.Cellphone,
Address: []string{},
Affiliation: affiliation,
Score: originalUser.SchoolId,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: originalUser.Deleted != 0,
Properties: map[string]string{},
}
return user
}
func createOriginalUserFromUser(user *object.User) *User {
deleted := 0
if user.IsForbidden {
deleted = 1
}
originalUser := &User{
Id: util.ParseInt(user.Id),
Name: user.DisplayName,
Password: user.Password,
Cellphone: user.Phone,
SchoolId: user.Score,
Avatar: getPartialAvatarUrl(user.Avatar),
Deleted: deleted,
}
return originalUser
}
func syncUsers() {
fmt.Printf("Running syncUsers()..\n")
users, userMap := getUserMap()
oUsers, oUserMap := getUserMapOriginal()
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
_, affiliationMap := getAffiliationMap()
newUsers := []*object.User{}
for _, oUser := range oUsers {
id := strconv.Itoa(oUser.Id)
if _, ok := userMap[id]; !ok {
newUser := createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
} else {
user := userMap[id]
oHash := calculateHash(oUser)
if user.Hash == user.PreHash {
if user.Hash != oHash {
updatedUser := createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
object.UpdateUserForOriginalFields(updatedUser)
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
}
} else {
if user.PreHash == oHash {
updatedOUser := createOriginalUserFromUser(user)
updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
// update preHash
user.PreHash = user.Hash
object.SetUserField(user, "pre_hash", user.PreHash)
} else {
if user.Hash == oHash {
// update preHash
user.PreHash = user.Hash
object.SetUserField(user, "pre_hash", user.PreHash)
} else {
updatedUser := createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
object.UpdateUserForOriginalFields(updatedUser)
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
}
}
}
}
}
object.AddUsersSafe(newUsers)
for _, user := range users {
id := user.Id
if _, ok := oUserMap[id]; !ok {
panic(fmt.Sprintf("New original user: cannot create now, user = %v", user))
}
}
}

32
original/user.go Normal file
View File

@@ -0,0 +1,32 @@
// 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 original
import "github.com/casbin/casdoor/object"
func getUsers() []*object.User {
users := object.GetUsers(orgName)
return users
}
func getUserMap() ([]*object.User, map[string]*object.User) {
users := getUsers()
m := map[string]*object.User{}
for _, user := range users {
m[user.Name] = user
}
return users, m
}

79
original/user_original.go Normal file
View File

@@ -0,0 +1,79 @@
// 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 original
import (
"strconv"
"strings"
"github.com/casbin/casdoor/util"
)
type User struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Name string `xorm:"varchar(128)" json:"name"`
Password string `xorm:"varchar(128)" json:"password"`
Cellphone string `xorm:"varchar(128)" json:"cellphone"`
SchoolId int `json:"schoolId"`
Avatar string `xorm:"varchar(128)" json:"avatar"`
Deleted int `xorm:"tinyint(1)" json:"deleted"`
}
func (User) TableName() string {
return userTableName
}
func getUsersOriginal() []*User {
users := []*User{}
err := adapter.Engine.Asc("id").Find(&users)
if err != nil {
panic(err)
}
return users
}
func getUserMapOriginal() ([]*User, map[string]*User) {
users := getUsersOriginal()
m := map[string]*User{}
for _, user := range users {
m[strconv.Itoa(user.Id)] = user
}
return users, m
}
func addUser(user *User) bool {
affected, err := adapter.Engine.Insert(user)
if err != nil {
panic(err)
}
return affected != 0
}
func updateUser(user *User) bool {
affected, err := adapter.Engine.ID(user.Id).Cols("name", "password", "cellphone", "school_id", "avatar", "deleted").Update(user)
if err != nil {
panic(err)
}
return affected != 0
}
func calculateHash(user *User) string {
s := strings.Join([]string{strconv.Itoa(user.Id), user.Password, user.Name, getFullAvatarUrl(user.Avatar), user.Cellphone, strconv.Itoa(user.SchoolId)}, "|")
return util.GetMd5Hash(s)
}

View File

@@ -0,0 +1,49 @@
// 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 original
import (
"fmt"
"testing"
"time"
"github.com/casbin/casdoor/object"
)
func TestGetUsers(t *testing.T) {
initConfig()
initAdapter()
users := getUsersOriginal()
for _, user := range users {
fmt.Printf("%v\n", user)
}
}
func TestSyncUsers(t *testing.T) {
initConfig()
initAdapter()
object.InitAdapter()
syncUsers()
// run at every minute
schedule := "* * * * *"
err := ctab.AddJob(schedule, syncUsers)
if err != nil {
panic(err)
}
time.Sleep(time.Duration(1<<63 - 1))
}

105
payment/paypal.go Normal file
View File

@@ -0,0 +1,105 @@
package payment
import (
"context"
"fmt"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/object"
"github.com/plutov/paypal/v4"
)
var client = GetClient()
func GetClient() *paypal.Client {
c, err := paypal.NewClient(beego.AppConfig.String("paypalClientId"), beego.AppConfig.String("paypalSecret"), paypal.APIBaseSandBox)
if err != nil {
panic(err)
}
return c
}
func Paypal(payItem object.PayItem, clientId string, redirectUri string) string {
application := object.GetApplicationByClientId(clientId)
if application == nil {
return "Invalid client_id"
}
applicationName := fmt.Sprintf("%s/%s", application.Owner, application.Name)
if payItem.Currency == "" {
payItem.Currency = "USD"
}
_, err := client.GetAccessToken(context.Background())
if err != nil {
panic(err)
}
appContext := &paypal.ApplicationContext{
ReturnURL: "http://localhost:7001/pay/success", //回调链接
CancelURL: "https://www.baidu.com",
}
purchaseUnits := make([]paypal.PurchaseUnitRequest, 1)
purchaseUnits[0] = paypal.PurchaseUnitRequest{
Amount: &paypal.PurchaseUnitAmount{
Currency: payItem.Currency, //收款类型
Value: payItem.Price, //收款数量
},
InvoiceID: payItem.Invoice,
Description: payItem.Description,
}
order, err := client.CreateOrder(context.Background(),
paypal.OrderIntentCapture,
purchaseUnits,
&paypal.CreateOrderPayer{},
appContext)
if err != nil {
panic(err)
}
newPay := object.Payment{
Id: order.ID,
Invoice: payItem.Invoice,
PayItem: payItem,
Application: applicationName,
Status: order.Status,
Callback: redirectUri,
}
success := object.AddPayment(&newPay)
if success {
links := order.Links
for _, link := range links {
fmt.Println(link.Rel)
if link.Rel == "approve" {
return link.Href
}
}
}
return "Add Order to Database false"
}
func SuccessPay(token string) string {
_, err := client.GetAccessToken(context.Background())
if err != nil {
panic(err)
}
captureOrder, err := client.CaptureOrder(context.Background(), token, paypal.CaptureOrderRequest{})
if err != nil {
panic(err)
}
pay := object.GetPayment(captureOrder.ID)
pay.Purchase = captureOrder.PurchaseUnits
pay.Payer = captureOrder.Payer
pay.UpdateTime = time.Now().String()
pay.Status = captureOrder.Status
object.UpdatePay(captureOrder.ID, pay)
if captureOrder.Status == "COMPLETED" {
return fmt.Sprintf("%s?paymentId=%s", pay.Callback, token)
}
return ""
}

64
proxy/proxy.go Normal file
View File

@@ -0,0 +1,64 @@
// 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 proxy
import (
"net/http"
"strings"
"github.com/astaxie/beego"
"golang.org/x/net/proxy"
)
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 = DefaultHttpClient
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)
}
func GetHttpClient(url string) *http.Client {
if strings.Contains(url, "githubusercontent.com") {
return ProxyHttpClient
} else {
return DefaultHttpClient
}
}

115
routers/authz_filter.go Normal file
View File

@@ -0,0 +1,115 @@
// 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 routers
import (
"encoding/json"
"fmt"
"net/http"
"github.com/astaxie/beego/context"
"github.com/casbin/casdoor/authz"
"github.com/casbin/casdoor/util"
)
type Object struct {
Owner string `json:"owner"`
Name string `json:"name"`
}
func getUsername(ctx *context.Context) (username string) {
defer func() {
if r := recover(); r != nil {
username = getUsernameByClientIdSecret(ctx)
}
}()
username = ctx.Input.Session("username").(string)
if username == "" {
username = getUsernameByClientIdSecret(ctx)
}
return
}
func getSubject(ctx *context.Context) (string, string) {
username := getUsername(ctx)
if username == "" {
return "anonymous", "anonymous"
}
// username == "built-in/admin"
return util.GetOwnerAndNameFromId(username)
}
func getObject(ctx *context.Context) (string, string) {
method := ctx.Request.Method
if method == http.MethodGet {
// query == "?id=built-in/admin"
id := ctx.Input.Query("id")
if id == "" {
return "", ""
}
return util.GetOwnerAndNameFromId(id)
} else {
body := ctx.Input.RequestBody
if len(body) == 0 {
return "", ""
}
var obj Object
err := json.Unmarshal(body, &obj)
if err != nil {
//panic(err)
return "", ""
}
return obj.Owner, obj.Name
}
}
func willLog(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
if subOwner == "anonymous" && subName == "anonymous" && method == "GET" && (urlPath == "/api/get-account" || urlPath == "/api/get-app-login") && objOwner == "" && objName == "" {
return false
}
return true
}
func AuthzFilter(ctx *context.Context) {
subOwner, subName := getSubject(ctx)
method := ctx.Request.Method
urlPath := ctx.Request.URL.Path
objOwner, objName := getObject(ctx)
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
result := "deny"
if isAllowed {
result = "allow"
}
if willLog(subOwner, subName, method, urlPath, objOwner, objName) {
logLine := fmt.Sprintf("subOwner = %s, subName = %s, method = %s, urlPath = %s, obj.Owner = %s, obj.Name = %s, result = %s",
subOwner, subName, method, urlPath, objOwner, objName, result)
fmt.Println(logLine)
util.LogInfo(ctx, logLine)
}
if !isAllowed {
denyRequest(ctx)
}
}

View File

@@ -0,0 +1,65 @@
// 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 routers
import (
"fmt"
"github.com/astaxie/beego/context"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
func AutoSigninFilter(ctx *context.Context) {
//if getSessionUser(ctx) != "" {
// return
//}
// "/page?access_token=123"
accessToken := ctx.Input.Query("accessToken")
if accessToken != "" {
claims, err := object.ParseJwtToken(accessToken)
if err != nil {
responseError(ctx, "invalid JWT token")
return
}
userId := fmt.Sprintf("%s/%s", claims.User.Owner, claims.User.Name)
setSessionUser(ctx, userId)
return
}
// "/page?clientId=123&clientSecret=456"
userId := getUsernameByClientIdSecret(ctx)
if userId != "" {
setSessionUser(ctx, userId)
return
}
// "/page?username=abc&password=123"
userId = ctx.Input.Query("username")
password := ctx.Input.Query("password")
if userId != "" && password != "" {
owner, name := util.GetOwnerAndNameFromId(userId)
_, msg := object.CheckUserPassword(owner, name, password)
if msg != "" {
responseError(ctx, msg)
return
}
setSessionUser(ctx, userId)
return
}
}

83
routers/base.go Normal file
View File

@@ -0,0 +1,83 @@
// 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 routers
import (
"fmt"
"github.com/astaxie/beego/context"
"github.com/casbin/casdoor/object"
)
type Response struct {
Status string `json:"status"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
Data2 interface{} `json:"data2"`
}
func responseError(ctx *context.Context, error string, data ...interface{}) {
resp := Response{Status: "error", Msg: error}
switch len(data) {
case 2:
resp.Data2 = data[1]
fallthrough
case 1:
resp.Data = data[0]
}
err := ctx.Output.JSON(resp, true, false)
if err != nil {
panic(err)
}
}
func denyRequest(ctx *context.Context) {
responseError(ctx, "Unauthorized operation")
}
func getUsernameByClientIdSecret(ctx *context.Context) string {
clientId := ctx.Input.Query("clientId")
clientSecret := ctx.Input.Query("clientSecret")
if clientId == "" || clientSecret == "" {
return ""
}
application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret {
return ""
}
return fmt.Sprintf("app/%s", application.Name)
}
func getSessionUser(ctx *context.Context) string {
user := ctx.Input.CruSession.Get("username")
if user == nil {
return ""
}
return user.(string)
}
func setSessionUser(ctx *context.Context, user string) {
err := ctx.Input.CruSession.Set("username", user)
if err != nil {
panic(err)
}
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
}

69
routers/record.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 routers
import (
"fmt"
"github.com/astaxie/beego/context"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
func getUser(ctx *context.Context) (username string) {
defer func() {
if r := recover(); r != nil {
username = getUserByClientIdSecret(ctx)
}
}()
username = ctx.Input.Session("username").(string)
if username == "" {
username = getUserByClientIdSecret(ctx)
}
return
}
func getUserByClientIdSecret(ctx *context.Context) string {
clientId := ctx.Input.Query("clientId")
clientSecret := ctx.Input.Query("clientSecret")
if clientId == "" || clientSecret == "" {
return ""
}
application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret {
return ""
}
return fmt.Sprintf("%s/%s", application.Organization, application.Name)
}
func RecordMessage(ctx *context.Context) {
if ctx.Request.URL.Path == "/api/login" {
return
}
record := util.Records(ctx)
userId := getUser(ctx)
if userId != "" {
record.Organization, record.Username = util.GetOwnerAndNameFromId(userId)
}
object.AddRecord(record)
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
// 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.
@@ -12,12 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package routers
// @APIVersion 1.0.0
// @Title Casdoor API
// @Description Documentation of Casdoor API
// @Contact admin@casbin.org
package routers
import (
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/controllers"
"github.com/casbin/casdoor/controllers"
)
func init() {
@@ -27,15 +32,86 @@ func init() {
func initAPI() {
ns :=
beego.NewNamespace("/api",
beego.NSInclude(
&controllers.ApiController{},
beego.NSNamespace("/api",
beego.NSInclude(
&controllers.ApiController{},
),
),
)
beego.AddNamespace(ns)
beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup")
beego.Router("/api/login", &controllers.ApiController{}, "POST:Login")
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout")
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
beego.Router("/api/get-application-clientId", &controllers.ApiController{}, "Get:GetApplicationByClientId")
beego.Router("/api/update-organization", &controllers.ApiController{}, "POST:UpdateOrganization")
beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization")
beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization")
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
beego.Router("/api/get-ldap", &controllers.ApiController{}, "POST:GetLdap")
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
beego.Router("/api/check-ldap-users-exist", &controllers.ApiController{}, "POST:CheckLdapUsersExist")
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
beego.Router("/api/get-user-application", &controllers.ApiController{}, "GET:GetUserApplication")
beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
beego.Router("/api/get-resources", &controllers.ApiController{}, "GET:GetResources")
beego.Router("/api/get-resource", &controllers.ApiController{}, "GET:GetResource")
beego.Router("/api/update-resource", &controllers.ApiController{}, "POST:UpdateResource")
beego.Router("/api/add-resource", &controllers.ApiController{}, "POST:AddResource")
beego.Router("/api/delete-resource", &controllers.ApiController{}, "POST:DeleteResource")
beego.Router("/api/upload-resource", &controllers.ApiController{}, "POST:UploadResource")
beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens")
beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken")
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
beego.Router("/api/paypal", &controllers.ApiController{}, "POST:PaypalPay")
beego.Router("/api/success-pay", &controllers.ApiController{}, "GET:SuccessPay")
beego.Router("/api/get-payments", &controllers.ApiController{}, "Get:GetPayments")
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
}

View File

@@ -1,4 +1,4 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
// 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.
@@ -19,10 +19,10 @@ import (
"strings"
"github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/util"
"github.com/casbin/casdoor/util"
)
func TransparentStatic(ctx *context.Context) {
func StaticFilter(ctx *context.Context) {
urlPath := ctx.Request.URL.Path
if strings.HasPrefix(urlPath, "/api/") {
return

31
storage/aliyun.go Normal file
View File

@@ -0,0 +1,31 @@
// 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 storage
import (
"github.com/qor/oss"
"github.com/qor/oss/aliyun"
)
func NewAliyunStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
sp := aliyun.New(&aliyun.Config{
AccessID: clientId,
AccessKey: clientSecret,
Bucket: bucket,
Endpoint: endpoint,
})
return sp
}

34
storage/aws_s3.go Normal file
View File

@@ -0,0 +1,34 @@
// 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 storage
import (
awss3 "github.com/aws/aws-sdk-go/service/s3"
"github.com/qor/oss"
"github.com/qor/oss/s3"
)
func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
sp := s3.New(&s3.Config{
AccessID: clientId,
AccessKey: clientSecret,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
ACL: awss3.BucketCannedACLPublicRead,
})
return sp
}

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 storage
import (
"io"
"os"
"path/filepath"
"strings"
"github.com/qor/oss"
)
var baseFolder = "files"
// FileSystem file system storage
type FileSystem struct {
Base string
}
// NewFileSystem initialize the local file system storage
func NewFileSystem(base string) *FileSystem {
absBase, err := filepath.Abs(base)
if err != nil {
panic("local file system storage's base folder is not initialized")
}
return &FileSystem{Base: absBase}
}
// GetFullPath get full path from absolute/relative path
func (fileSystem FileSystem) GetFullPath(path string) string {
fullPath := path
if !strings.HasPrefix(path, fileSystem.Base) {
fullPath, _ = filepath.Abs(filepath.Join(fileSystem.Base, path))
}
return fullPath
}
// Get receive file with given path
func (fileSystem FileSystem) Get(path string) (*os.File, error) {
return os.Open(fileSystem.GetFullPath(path))
}
// GetStream get file as stream
func (fileSystem FileSystem) GetStream(path string) (io.ReadCloser, error) {
return os.Open(fileSystem.GetFullPath(path))
}
// Put store a reader into given path
func (fileSystem FileSystem) Put(path string, reader io.Reader) (*oss.Object, error) {
var (
fullPath = fileSystem.GetFullPath(path)
err = os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
)
if err != nil {
return nil, err
}
dst, err := os.Create(fullPath)
if err == nil {
if seeker, ok := reader.(io.ReadSeeker); ok {
seeker.Seek(0, 0)
}
_, err = io.Copy(dst, reader)
}
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: fileSystem}, err
}
// Delete delete file
func (fileSystem FileSystem) Delete(path string) error {
return os.Remove(fileSystem.GetFullPath(path))
}
// List list all objects under current path
func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
var (
objects []*oss.Object
fullPath = fileSystem.GetFullPath(path)
)
filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
if path == fullPath {
return nil
}
if err == nil && !info.IsDir() {
modTime := info.ModTime()
objects = append(objects, &oss.Object{
Path: strings.TrimPrefix(path, fileSystem.Base),
Name: info.Name(),
LastModified: &modTime,
StorageInterface: fileSystem,
})
}
return nil
})
return objects, nil
}
// GetEndpoint get endpoint, FileSystem's endpoint is /
func (fileSystem FileSystem) GetEndpoint() string {
return "/"
}
// GetURL get public accessible URL
func (fileSystem FileSystem) GetURL(path string) (url string, err error) {
return path, nil
}
func NewLocalFileSystemStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
return NewFileSystem(baseFolder)
}

30
storage/storage.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 storage
import "github.com/qor/oss"
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
switch providerType {
case "Local File System":
return NewLocalFileSystemStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Aliyun OSS":
return NewAliyunStorageProvider(clientId, clientSecret, region, bucket, endpoint)
}
return nil
}

BIN
swagger/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

BIN
swagger/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

93
swagger/index.html Normal file
View File

@@ -0,0 +1,93 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js"> </script>
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "swagger.json",
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>

View File

@@ -0,0 +1,53 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var isValid, qp, arr;
qp = (window.location.hash || location.search).substring(1);
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback(oauth2.auth);
} else {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: "Authorization failed: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid});
}
window.close();
}
</script>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;AAu/FA;AA6+FA;;;;;;;;;;;;;;;;;;;;;;;;;;AAyTA;;;;;;AAoIA;AAi7FA;AAmtCA;AAi0IA;AA0oJA;AAgwFA;AAyrGA;AA0lFA;AA4nFA;AA+9CA;AA+gDA;AAwrCA;AA60EA;;;;;AA6oCA;AAsyJA;;;;;;;;;;;;;;AA64EA;AA4mIA;AAquJA;AA2qHA;AA2mGA;AAiiEA;AAq4DA;AAg3DA;AAoPA;;;;;;AAk7FA;AA07FA;;;;;AAi8CA;AAgsFA;AAs2CA;AAglCA;AAu9CA;AAy8EA;AAsiCA;AA+yFA;;;;;;;;;AAgkDA;AA2zIA;AAu7FA;AAmrFA;AAu0EA","sourceRoot":""}

File diff suppressed because one or more lines are too long

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