Compare commits

..

213 Commits

Author SHA1 Message Date
cf4e76f9dc feat: add footer to door pages (#868) 2022-07-08 20:36:49 +08:00
81f2d01dc1 fix: fix dockerfile (#866) 2022-07-07 16:10:15 +08:00
61773d3173 fix: support user-defined clientId&Secret (#862) 2022-07-06 19:27:59 +08:00
ec29621547 feat: init from configuration file (#858)
* feat: init from configuration file

* Update init_data.json.template

* Update init_data.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-05 00:36:22 +08:00
b8e324cadf fix: azurad provider (#855) 2022-07-04 16:40:23 +08:00
f37fd6ba87 Fix empty arg bug in getPermanentAvatarUrl(). 2022-07-03 19:31:12 +08:00
b4bf734fe8 fix: fix cors filter (#847)
* fix: fix cors filter

* Update cors_filter.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-02 13:45:18 +08:00
f0431701c9 fix: fix OAuth error response (#835)
* fix: fix OAuth error response

* fix: provide more detailed error messages for TokenError
2022-07-01 14:53:34 +08:00
aa5078de15 fix: crowdin kept deleting translations (#843)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-01 10:51:40 +08:00
9a324b2cca fix: Update Crowdin link (#841) 2022-06-30 22:05:20 +08:00
919eaf1df4 fix: fix CORS error after sucessful OPTION (#838) 2022-06-30 21:29:02 +08:00
cd902a21ba fix: some minor bugs and make Dockerfile more productive. (#831)
* fix: some minor bugs and make Dockerfile more productive.

* fix: make GitHub CI configuration support build image with STANDARD target.

* fix: Naming the base stage in multi-stage builds with lowercase letters to support various operating systems.

* fix: copy swagger to the image as well.
2022-06-29 23:21:18 +08:00
fe0ab0aa6f Fix downloadFile()'s google proxy. 2022-06-29 22:01:38 +08:00
a0e11cc8a0 feat: add aliyun captcha (#833)
* feat: add aliyun captcha provider

* Rename App key

* fix typo

* Rename HMACSHA1 & Reused clientId2 and clientSecret2

* Update ProviderEditPage.js

* Delete unused import

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-29 11:31:32 +08:00
8a66448365 feat: support casdoor as saml idp to connect keycloak (#832)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-06-28 22:05:02 +08:00
477d386f3c fix: captcha preview panic when clientId or clientSecret is empty (#824)
* fix: captcha preview panic when clientId or clientSecret is empty

* return original errors from captcha
2022-06-26 22:09:57 +08:00
339c6c2dd0 Fix null bug in getTermsofuseContent(). 2022-06-26 09:34:01 +08:00
7c9370ef90 feat: add CORS filter to fix OPTION request failure (#826) 2022-06-26 01:28:33 +08:00
31b586e391 feat: Add email config test on provider edit page (#819)
* feat: Add email config test on provider edit page

* Re-use send-email API

* Optimize code

Optimize code

* Update service.go

* Update service.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-24 01:47:10 +08:00
249f83e764 Fix TestProduct() compile error. 2022-06-23 00:54:31 +08:00
16f5569e50 fix: encryption without salt (#821)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-06-22 22:30:27 +08:00
f99c1f44e8 fix: don't trigger countdown if failed to send verification code (#815)
* feat: add countdown when no captcha provider found

* fix: add countdown when sent code successfully
2022-06-22 22:22:40 +08:00
c8c4dfbfb8 Fix bug and i18n issue in captcha provider edit page. 2022-06-22 21:54:25 +08:00
d9c6ff2507 fix: captcha widget JS warnings (#820) 2022-06-22 18:31:18 +08:00
e1664f2f60 Fix newApplication() to add provider. 2022-06-22 00:08:46 +08:00
460a4d4969 fix: init default captcha provider (#810)
* feat: init built in provider

* Update built-in provider in application

* Delete unnecessary judge

* Update init.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-22 00:03:55 +08:00
376bac15dc fix: improve swagger Api docunment (#812) 2022-06-21 23:11:29 +08:00
8d0e92edef Fix missing items in renderAccountItem(). 2022-06-21 17:08:08 +08:00
0075b7af52 Fix JS warnings. 2022-06-21 15:26:58 +08:00
2c57bece39 feat: fix stuck error when no captcha provider found (#808) 2022-06-21 12:22:46 +08:00
2e42511bc4 feat: support configurable captcha(reCaptcha & hCaptcha) (#765)
* feat: support configurable captcha(layered architecture)

* refactor & add captcha logo

* rename captcha

* Update authz.go

* Update hcaptcha.go

* Update default.go

* Update recaptcha.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-18 16:00:31 +08:00
ae4ab9902b Add accountTable. 2022-06-18 01:41:21 +08:00
065b235dc5 Fix signupTable i18n. 2022-06-17 23:26:02 +08:00
63c09a879f fix: disable jsx-a11y/anchor-is-valid (#800)
* fix: disable jsx-a11y/anchor-is-valid

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

* Update LoginPage.js

* Update SignupPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-17 19:57:11 +08:00
61c80e790f Fix Authentication failure! invalid_ticket: OneLogin::RubySaml::ValidationError #798 (#799) 2022-06-17 18:35:44 +08:00
be91ff47aa Fix logo columns. 2022-06-17 00:07:16 +08:00
b4c18eb7a4 Use codemirror for samlMetadata. 2022-06-16 23:59:18 +08:00
0f483fb65b Improve preview buttons to copy link. 2022-06-16 22:01:09 +08:00
ebe9889d58 Improve i18n 2022-06-16 21:35:52 +08:00
ee42fcac8e Remove signup_item.go 2022-06-16 20:52:54 +08:00
6187b48f61 fix: show alert when user clicks on application edit page's preview window (#794)
* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* Update ApplicationEditPage.js

* fix: show alert when user clicks on application edit page's preview window

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-15 22:11:37 +08:00
2020955270 Fix cannot support old Docker version bug, revert PR: https://github.com/casdoor/casdoor/pull/606 2022-06-15 01:20:00 +08:00
1b5a8f8e57 Fix missing i18n text. 2022-06-15 00:55:06 +08:00
ff94e5164a feat: fix incorrect CAS url concatenation (#795)
* fix: fix incorrect cas url concatenation

* Update LoginPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-14 21:51:40 +08:00
15a6fd2b52 feat: show alert when user clicks on application edit page's preview wi… (#791)
* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* fix:Show alert when user clicks on application edit page's preview window

* fix: Show alert when user clicks on application edit page's preview window in preview

* Update ApplicationEditPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-13 12:18:18 +08:00
37b6b50751 fix: remove redundant query for OAuth user (#788) 2022-06-10 15:58:22 +08:00
efe5431f54 fix: OAuth user id confusion caused by username (#785) 2022-06-10 00:08:26 +08:00
e9159902eb fix: fix the web compiled warnings (#778)
* fix: fix the web compiled warnings

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

* fix: disable changeMomentLanguage

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

* Update SyncerEditPage.js

* Update UserEditPage.js

* Update ResourceListPage.js

* Update ProviderEditPage.js

* Update ProductBuyPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-06-05 20:56:31 +08:00
604e2757c8 fix: fix the problem that user owner is not updated when updating organization name (#775)
* fix: use openid or unionid as username rather than nickname when logging with WeChat
FIX #762

* fix: fix the problem that user owner is not updated when updating organization name

* Update wechat.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-03 00:37:22 +08:00
88c5aae9e9 Fix meta desc info. 2022-06-01 22:22:00 +08:00
3d0cf8788b fix: trigger missing webhook (#770)
* fix: trigger missing webhook

* Update auth.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-06-01 09:34:56 +08:00
e78ea2546f fix: bilibili name and avatar (#772)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-31 21:54:00 +08:00
f7705931f7 fix: handle WeChat username conflicts (#771)
* handle username conflicts

* Update auth.go

Co-authored-by: roobtyan <roobtyan@qq.com>
Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-05-31 21:51:41 +08:00
5d8b710bf7 fix: use openid or unionid as username rather than nickname when logging with WeChat (#763)
FIX #762
2022-05-31 21:22:10 +08:00
b85ad896bf fix: saml endpoint crash (#773)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-31 21:10:35 +08:00
42c2210178 fix: set phone prefix when disable verification code (#769)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-30 18:26:42 +08:00
d52caed3a9 feat: add model page (#757)
* feat: add model page

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

* feat: support config model for permission

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

* translation and indentation

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-24 18:27:47 +08:00
27d8cd758d Simplify README 2022-05-23 21:45:31 +08:00
98f77960de feat: add Douyin OAuth provider (#753) 2022-05-15 20:59:21 +08:00
e5b71a08ae feat: support "+" in syncer column name (#752)
* feat: support + in syncer column name

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

* feat: trim

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-05-13 20:24:46 +08:00
3ad4b7a43c feat: add Bilibili OAuth (#720)
* implemented bilibili oauth

* add bilibili oauth

* add document address

* add frontend page

* uncheck
2022-05-12 10:07:52 +08:00
c5c3a08aa9 feat: add saml metadata in application edit page (#750)
* feat: add saml metadata in application edit page

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

* Update ApplicationEditPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-05-11 20:23:36 +08:00
8efd964835 fix: unchanged masked client_secret/password updated to *** (#749) 2022-05-10 17:37:12 +08:00
5dac87a4c3 feat: hide proxy-test output (#746)
* feat: hide proxy-test output

* Update build.sh

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-05-07 20:56:12 +08:00
49c3266400 Fix missing OIDC response_types_supported. 2022-05-07 09:36:20 +08:00
39548d5d72 Change cert default algorithm to RS256. 2022-05-06 09:34:42 +08:00
1c949e415e Add refresh_token to app grantTypes. 2022-05-06 09:31:22 +08:00
1b840a2e9f feat: support argon2id pass manager (#744)
* support for argon2id

* Update argon2id.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-05-06 09:25:42 +08:00
c9849d8b55 Accept more file formats in upload. 2022-05-04 23:16:24 +08:00
b747f5e27c fix: mistake GetApplicationLogin swagger router (casdoor#739) (#740)
Signed-off-by: xiexianbin <me@xiexianbin.cn>
2022-05-04 19:15:13 +08:00
8b340105c1 fix: fix missing OpenLDAP uid in ldap.go 2022-05-04 10:23:17 +08:00
43b1006f11 fix: sign up without email verification do not work (#736) 2022-05-03 18:05:58 +08:00
78efc9c2d0 feat: add azure storage support (#735)
* feat: add zure storage support

* Update local_file_system.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-05-03 17:59:07 +08:00
c4089eacb7 feat: Allow to sign up with Email without verification (#728)
* feat: Allow to sign up with Email without verification by rule

* Update account.go

* Update SignupTable.js

* Update SignupPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-05-02 17:19:40 +08:00
4acba2d493 Add entrypoint to docker-compose casdoor service (#727) 2022-05-01 19:26:31 +08:00
fc0ca4cceb Add Okta OAuth provider (#729) 2022-05-01 18:31:42 +08:00
912d9d0c01 feat: DingTalk provider value case unsensitive (#724) 2022-04-30 16:20:20 +08:00
zc
8e48bddf5f remove extra parentheses showing account numbers (#726) 2022-04-30 15:20:08 +08:00
c05fb77224 fix: set sync ldap user default attributes (#721)
* fix: set the password of the sync ldap user to empty

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

* fix: set sync ldap user default attributes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-04-29 21:44:13 +08:00
9af9ead939 Return invoiceUrl in invoice-payment API. 2022-04-28 15:07:57 +08:00
f5590c42f7 Add payerName to provider. 2022-04-28 14:50:59 +08:00
5597f99e3c Scroll to payment page bottom. 2022-04-27 01:32:36 +08:00
ea005aaf4d Improve InvoicePayment() error handling. 2022-04-27 00:24:48 +08:00
e5c1f560c5 Fix bug in payment. 2022-04-27 00:07:13 +08:00
20fc7d1b58 Add payment modal. 2022-04-26 23:40:33 +08:00
cf3b46130b Add InvoicePayment() API. 2022-04-26 22:17:53 +08:00
cab51fae9c fix: add 'use' and 'alg' in .well-known/jwks (#708)
* fix: add 'use' and 'alg' in .well-known/jwks

* fix: dynamically assign value to 'alg' param
2022-04-26 21:53:05 +08:00
b867872da4 fix: return right after error response on GetUserInfo (#707) 2022-04-26 14:32:04 +08:00
305867f49a Add checkError() to payment. 2022-04-25 21:39:46 +08:00
3f90c18a19 Add invoiceType to payment. 2022-04-25 20:58:53 +08:00
9e5a64c021 Add new payment fields 2022-04-25 20:40:50 +08:00
4263af6f2c Fix frontend warnings. 2022-04-25 20:00:57 +08:00
3e92d761b9 Fix i18n translations. 2022-04-25 19:46:45 +08:00
0e41568f62 Add apps to homepage. 2022-04-25 13:51:46 +08:00
fb7e2729c6 fix: support Microsoft AD user search (#704) 2022-04-25 12:20:59 +08:00
28b9154d7e fix: fix #693 token error (#695) 2022-04-23 01:12:06 +08:00
b0b3eb0805 fix: fix failure of introspection (#682)
* fix: fix failure of introspection

* Update token.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-04-22 22:45:52 +08:00
73bd9dd517 bugfix #664 Casdoor fails to start when there is already a database (#681)
Signed-off-by: niko7g <niko7.g@gmail.com>
2022-04-22 22:17:03 +08:00
0bc8c2d15f fix: recover when goroutine panic that will kill main program (#692)
* fix #684

recover when goroutine panic that will kill main program

* Update util.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-04-22 21:59:06 +08:00
7b78e60265 fix: close the resp in time (#689) 2022-04-21 23:22:50 +08:00
7464f9a8ad fix: when req error, read body(nil) will panic (#690) 2022-04-21 22:14:01 +08:00
d3a7a062d3 fix #687 (#688)
fix the display bug on the personal binding information page
2022-04-21 21:52:34 +08:00
67a0264411 feat: add sync button to execute syncer once (#668)
* feat: add sync button to execute syncer once

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

* fix: requested changes

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

* fix: requested changes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-04-18 16:27:34 +08:00
a6a055cc83 Fix: ExpiresIn of token should be seconds. (#676)
Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com>
2022-04-18 10:57:51 +08:00
a89a7f9eb7 bug fix (#674) 2022-04-17 17:01:56 +08:00
287f60353c feat: try to support custom OAuth provider (#667)
* feat: try to support private provider

* fix: modify code according to code review

* feat: set example values for custom params
2022-04-16 17:17:45 +08:00
530330bd66 feat: add isProfilePublic setting for accessing user info (#656)
* feat: add isProfilePublic setting for accessing user info

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

* fix: requested changes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-04-16 15:10:03 +08:00
70a1428972 Improve resource DB column length. 2022-04-16 13:23:05 +08:00
1d183decea fix: cicd error (#671)
* fix: ci/cd error

* fix: ci/cd error

* fix: ci/cd error
2022-04-16 00:09:23 +08:00
b92d03e2bb feat: add wechat mini program support (#658)
* feat: add wechat mini program support

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

* fix: accept suggestions.

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

* fix: error message and code level modification

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

* fix: simplify the use process

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-15 11:49:56 +08:00
9877174780 fix: add independent error message in token endpoint (#662)
* fix: add independent error message in token endpoint

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

* fix: reduced use of variables

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

* fix: error messages use the same variable

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-14 10:22:56 +08:00
b178be9aef feat: implement proxy (#661) 2022-04-13 14:04:40 +08:00
7236cca8cf feat: implement CAS 3.0 (#659) 2022-04-11 21:11:31 +08:00
15daf5dbfe feat: add casdoor as saml idp support (#571)
* feat: add casdoor as saml idp support

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

* fix: merge code

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

* fix: modify response value

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

* fix: modify samlResponse generation method

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

* fix: generating a response using etree

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

* fix: change metadata url

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

* fix: modify front-end adaptation

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

* fix: recovering an incorrect override

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

* fix: change the samlResponse location

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

* fix: add relayState support

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-08 23:06:48 +08:00
0b546bba5e fix: grantTypes undefined err (#654)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-08 21:54:48 +08:00
938cdbccf4 fix: link type error (#653)
* fix: signin button error in signup page

* fix: type error
2022-04-08 20:01:30 +08:00
801302c6e7 feat: support user migration from Keycloak using syncer (#645)
* feat: support user migration from Keycloak using syncer

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

* feat: add more Keycloak columns

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

* fix: requested changes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-04-06 20:38:14 +08:00
91602d2b21 Enable extra pages. 2022-04-06 20:36:31 +08:00
86b3a078ef fix: sign In button in the result page has broken (#646)
* fix: sign In button in the result page has broken

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

* fix: code format

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-05 08:49:11 +08:00
abc15b88c8 fix: change goth version (#644)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-04 15:58:51 +08:00
3cf1b990be feat: support CAS with organizations and applications (#621) 2022-04-04 00:09:04 +08:00
2023795f3c fix: token endpoint supports json format (#641)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-03 21:32:00 +08:00
8d13bf7e27 feat: add Alipay support as idp (#638)
* feat: add alipay support as idp

* fix: rename a static svg icon

* fix: sort imports

* fix: no longer use pkcs8 package
2022-04-02 22:37:13 +08:00
29aa379fb2 fix: qq idp missing username (#636)
* fix: qq idp missing username

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

* fix: api uses the latest fields

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-01 11:46:33 +08:00
7a95b9c1d5 Init DB only when necessary. 2022-03-31 12:28:45 +08:00
0fc0ba0c76 feat: support global admin to modify the email and phone of other users (#633)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-30 20:27:23 +08:00
24459d852e fix: comparing hashed password with plain text password during password grant (#627)
* fix: use object.CheckPassword for password grant

* Apply suggestions from code review

fix: remove log per change request
2022-03-30 00:37:38 +08:00
e3f5bf93b2 fix: adjust the password check logic for ldap user (#597)
* fix: the password check logic for ldap user.
LDAP user should only use the ldap connection to check the password.

* fix: code format
2022-03-28 17:19:58 +08:00
879ca6a488 fix: refresh_token api return old token (#623)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-27 23:10:05 +08:00
544cd40a08 Disable the new syncer by default. 2022-03-27 23:06:52 +08:00
99f7883c7d Fix null bug in getCountryRegionData(). 2022-03-27 16:03:25 +08:00
88b0fb6e52 Add getPrice(). 2022-03-26 16:42:25 +08:00
fa9b49e25b fix: some idp error messages return unclear (#620)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-26 15:15:56 +08:00
cd76e9372e feat: delete the old token when refreshing token (#617)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-24 19:58:12 +08:00
04b9e05244 fix: WeComInternalIdProvider GetUserInfo method could not get the correct user id (#616) 2022-03-24 17:53:05 +08:00
a78b2de7b2 fix: panic when not select one provider (#614)
Signed-off-by: Sagilio <Sagilio@outlook.com>
2022-03-24 12:15:10 +08:00
d0952ae908 fix: docker-compose up can't work on linux (#606) 2022-03-22 18:43:02 +08:00
ade64693e4 fix: support lower go version(1.15) (#599)
* fix: support lower go version(1.15)

* fix: support lower go version(1.15)

* fix: support lower go version(1.15)
2022-03-21 21:55:16 +08:00
5f8924ed4e feat: support overriding configuration with env (#590) 2022-03-20 23:21:09 +08:00
1a6d98d029 refactor: New Crowdin translations by Github Action (#592)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-20 22:30:29 +08:00
447dd1c534 feat: update the uploaded user field and provide demo xlsx file (#596)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 22:28:22 +08:00
86b5d72e5d fix: concatChar assignment logic (#595)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 11:54:14 +08:00
6bc4e646e5 fix: oAuthParams may not exist (#594)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 10:33:50 +08:00
0841eb5c30 Fix !skipCi directive. 2022-03-19 23:15:19 +08:00
4015c221f7 refactor: New Crowdin translations by Github Action (#588)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-19 22:01:20 +08:00
dcd6328498 fix: callback url param missing (#583)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-19 20:01:44 +08:00
8080927890 fix: redirect for non-built-in app logout (#587)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-19 19:50:05 +08:00
a95c5b05a9 Remove GitHub provider hacking code. 2022-03-19 19:43:54 +08:00
865a65d399 fix: fix the params problem in code signin (#577)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-18 20:12:29 +08:00
e8b9c67671 feat: add casdoor as itself idp support (#578)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-18 18:28:46 +08:00
e5ff49f7a7 fix: UI bug after switching to English (#570) 2022-03-15 21:02:54 +08:00
9f7924a6e0 fix: mask email and phone number on the backend (#563)
* fix: mask email and phone number on the backend

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

* fix: login with masked email or phone

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

* fix: improve regex

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-15 12:54:57 +08:00
377e200837 fix: repair the problem that AutoSigninFilter middleware doesn't recognize the access_token request parameter (#569)
AutoSigninFilter method only checks for `accessToken` request parameters or `Authorization` request header, doesn't recognize `access_token` request parameters, now added, use `utils.GetMaxLenStr()` method to get the maximum length characters
2022-03-15 12:52:44 +08:00
93a76de044 fix: fix compile error in low go version (#568) 2022-03-15 12:49:12 +08:00
35bef969fd feat: support Huawei Cloud SMS (#565)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-14 20:49:03 +08:00
4dca3bd3f7 Add Notify() to payment provider. 2022-03-14 02:56:04 +08:00
5de417ecf7 Add gc provider. 2022-03-14 00:32:36 +08:00
bf24594fb4 Make resource name longer. 2022-03-13 21:20:00 +08:00
4a87b4790e Avoid panic in AddUsers(). 2022-03-13 20:53:05 +08:00
fde8c4b5f6 Fix NotifyPayment(). 2022-03-13 19:57:23 +08:00
55a84644e1 Add PaymentResultPage. 2022-03-13 18:05:16 +08:00
ca87dd7dea Add returnUrl to product. 2022-03-13 16:25:54 +08:00
32af4a766e Add GetUserPayments() API. 2022-03-13 14:56:21 +08:00
4d035bf66d Add tags to organization. 2022-03-13 00:35:49 +08:00
743dcc9725 Fix translation. 2022-03-12 23:37:58 +08:00
d43d7d1ae9 feat: support master password for ldap user (#561)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-12 21:06:38 +08:00
c906f1e5d2 Add user and state to payment pages. 2022-03-12 20:03:58 +08:00
37a26e2a91 Fix delete-resource authz check. 2022-03-11 11:27:52 +08:00
e7018e3de4 docs: add a tip to create db for the first time (#550)
* add a tip to create db schema ahead of time

* add a tip to create db schema ahead of time

* docs: add a tip to create db schema ahead of time
2022-03-10 11:03:52 +08:00
3a64e4dcd8 docs: add a tip to create db schema ahead of time (#547) 2022-03-10 09:58:00 +08:00
380cdc5f7e fix: The top-right logout button sometimes disappears for small screen size (#544) 2022-03-08 21:14:04 +08:00
3602d9b9a7 fix: improve error messages 2022-03-07 15:16:09 +08:00
8a9cc2eb8f fix: change client_secret in refresh_token API as optional (#540)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-07 13:52:51 +08:00
4f9a13f18a fix: comment TestReadSheet() 2022-03-07 13:50:08 +08:00
a4fc04474e Add NotifyPayment API. 2022-03-07 00:33:45 +08:00
bf5d4eea48 Add alipay provider. 2022-03-06 22:46:02 +08:00
0e40a1d922 Check application existence in login(). 2022-03-06 00:09:57 +08:00
ab777c1d73 Add Conf.EnableExtraPages 2022-03-05 23:51:55 +08:00
ca0fa5fc40 fix: fix missing parameters when signup (#533) 2022-03-05 16:47:08 +08:00
cfbce79e32 fix: add ie support (ie >= 9) (#538)
* fix: add ie support (ie > 9)

* fix: add support for IE11

* fix: small fix

* fix: fix
2022-03-05 16:32:37 +08:00
efc07f0919 Improve translation. 2022-03-05 00:53:59 +08:00
fuh
a783315fa2 fix: Returns a valid userId when form.Username is empty (#523)
* fix: Returns a valid userId when form.Username is empty

* fix: format code
2022-03-04 23:39:12 +08:00
1d0af9cf7b fix: client_credentials' token miss some claims (#536)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-04 22:57:31 +08:00
4d48517be9 fix: fix the No.0 bug(for all sign up methods) (#535) 2022-03-04 13:06:21 +08:00
178cf7945d feat: improve token introspection endpoint (#534)
* feat: add introspection endpoint to oidc discovery endpoint

* fix: let introspect endpoint handle formData as spec define.

Signed-off-by: Leon <leondevlifelog@gmail.com>
2022-03-04 08:54:33 +08:00
ab5af979c8 feat: add Oauth 2.0 Token Introspection(rfc7662) endpoint support (#532)
Signed-off-by: Leon <leondevlifelog@gmail.com>
2022-03-03 17:48:47 +08:00
e31aaf5657 Rename httpProxy. 2022-03-03 08:59:38 +08:00
eaf5cb66f3 fix: update authz rule list (#528)
* fix: update authz rule list

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

* fix: resolve conflicts.

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-03 00:52:28 +08:00
83a6b757a4 fix: password leakage vulnerability caused by pagination (#527)
* fix: password leakage vulnerability caused by pagination

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

* fix: unsafe get-app-login response fields

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-02 20:58:16 +08:00
2a0dcd746f feat: add token logout endpoint (#526)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-02 20:37:31 +08:00
22f5ad06ec fix: Make secret optional when using PKCE (#525)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-02 13:15:14 +08:00
18aa70dfb2 Fix delete-resource authz failure. 2022-03-01 22:37:23 +08:00
697b3e4998 feat: add implicit flow support (#520)
* feat: add implicit flow support

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

* fix: idp support in implicit flow

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-01 19:09:59 +08:00
d48d515c36 fix: Missing extendedUser in signup webhook (#522)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-01 18:25:48 +08:00
a5d166c35f Support language param. 2022-02-28 21:33:10 +08:00
4915963c52 fix: member No.0 bug (#516)
* fix: member No.0 bug

* Update account.go

* fix: member No.0 bug

* fix: member No.0 bug

* Update account.go
2022-02-28 19:42:11 +08:00
759a1421e5 feat: add the 'karma' prop to table User (#518)
* feature: feat : add the 'karma' prop to table User

* feat: add the 'karma' prop to table User
2022-02-28 16:25:09 +08:00
c14bf9fdab Fix bug in first name, last name checking 2022-02-28 13:17:05 +08:00
e19f07c521 Add product detail page. 2022-02-27 23:50:35 +08:00
39ab71c5db Add product pages. 2022-02-27 20:09:19 +08:00
2c97f8a8b7 feat: add two authentication flow types (#512)
* feat: add two authentication flow types

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

* fix: delete implicit method

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

* fix: use a more appropriate name

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

* fix: apply suggestion

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

* fix: remove redundant code

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-02-27 14:05:07 +08:00
21392dcc14 Support user's first name and last name. 2022-02-27 14:02:52 +08:00
953d3d5bc5 Change personal to real name. 2022-02-27 13:44:44 +08:00
ddee97f544 fix: this.props.location undefined (#513)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-02-26 18:39:24 +08:00
c58a6d8725 Set enableSigninSession to false by default. 2022-02-25 23:58:13 +08:00
a5ff9549c1 Remove useless menu item. 2022-02-25 22:35:24 +08:00
fe57dcbff4 Improve translation. 2022-02-25 21:31:15 +08:00
f8c4ca0f00 feat: add cancel buttons on the edit page (#509)
* feat: add cancel buttons on the edit page

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

* fix: remove warning and primary type of cancel buttons

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-02-25 18:16:02 +08:00
e738c42bd8 fix: facebook login exceptions (#508)
* Fix the exception caused by "Username" being empty when logging in with facebook

* fix: facebook login missing "Username" exception
2022-02-23 23:58:17 +08:00
cbc8c58e85 fix: oidc jwks endpoint only return default cert (#506)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-02-21 23:17:16 +08:00
07c90e048f Update personal name. 2022-02-21 16:01:39 +08:00
a33076ada4 feat: add AD-FS support (#505)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-02-20 15:01:48 +08:00
9cabc4035f fix: docker-compose.yml has duplicated label (#502)
the casdoor service config in docker-compose.yml has duplicated restart label
2022-02-20 14:15:57 +08:00
217 changed files with 14697 additions and 1792 deletions

0
$env Normal file
View File

View File

@ -70,7 +70,7 @@ jobs:
- name: Fetch Previous version
id: get-previous-tag
uses: actions-ecosystem/action-get-latest-tag@v1
uses: actions-ecosystem/action-get-latest-tag@v1.6.0
- name: Release
run: yarn global add semantic-release@17.4.4 && semantic-release
@ -79,7 +79,7 @@ jobs:
- name: Fetch Current version
id: get-current-tag
uses: actions-ecosystem/action-get-latest-tag@v1
uses: actions-ecosystem/action-get-latest-tag@v1.6.0
- name: Decide Should_Push Or Not
id: should_push
@ -101,7 +101,7 @@ jobs:
echo ::set-output name=push::'false'
fi
- name: Log in to Docker Hub
uses: docker/login-action@v1
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
@ -109,14 +109,15 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push to Docker Hub
uses: docker/build-push-action@v2
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
target: STANDARD
push: true
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
- name: Push All In One Version to Docker Hub
uses: docker/build-push-action@v2
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'

View File

@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v2
- name: crowdin action
uses: crowdin/github-action@1.2.0
uses: crowdin/github-action@1.4.8
with:
upload_translations: true
@ -32,4 +32,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: '463556'
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}

View File

@ -1,9 +1,3 @@
FROM golang:1.17.5 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:16.13.0 AS FRONT
WORKDIR /web
COPY ./web .
@ -11,28 +5,42 @@ RUN yarn config set registry https://registry.npmmirror.com
RUN yarn install && yarn run build
FROM debian:latest AS ALLINONE
RUN apt update
RUN apt install -y ca-certificates && update-ca-certificates
RUN apt install -y mariadb-server mariadb-client && mkdir -p web/build && chmod 777 /tmp
LABEL MAINTAINER="https://casdoor.org/"
COPY --from=BACK /go/src/casdoor/ ./
COPY --from=BACK /usr/bin/wait-for-it ./
COPY --from=FRONT /web/build /web/build
CMD chmod 777 /tmp && service mariadb start&&\
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ; fi&&\
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD} &&\
./wait-for-it localhost:3306 -- ./server --createDatabase=true
FROM golang:1.17.5 AS BACK
WORKDIR /go/src/casdoor
COPY . .
RUN ./build.sh
FROM alpine:latest
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl
RUN apk add ca-certificates && update-ca-certificates
FROM alpine:latest AS STANDARD
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 ./server
WORKDIR /
COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=FRONT /web/build ./web/build
ENTRYPOINT ["/server"]
FROM debian:latest AS db
RUN apt update \
&& apt install -y \
mariadb-server \
mariadb-client \
&& rm -rf /var/lib/apt/lists/*
FROM db AS ALLINONE
LABEL MAINTAINER="https://casdoor.org/"
ENV MYSQL_ROOT_PASSWORD=123456
WORKDIR /
COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=FRONT /web/build ./web/build
ENTRYPOINT ["/bin/bash"]
CMD ["/docker-entrypoint.sh"]

146
README.md
View File

@ -42,158 +42,66 @@
</a>
</p>
## Online demo
Deployed site: https://door.casdoor.com/
- International: https://door.casdoor.org (read-only)
- Asian mirror: https://door.casdoor.com (read-only)
- Asian mirror: https://demo.casdoor.com (read-write, will restore for every 5 minutes)
## Quick Start
Run your own casdoor program in a few minutes.
### Download
There are two methods, get code via go subcommand `get`:
## Documentation
```shell
go get github.com/casdoor/casdoor
```
- International: https://casdoor.org
- Asian mirror: https://docs.casdoor.cn
or `git`:
```bash
git clone https://github.com/casdoor/casdoor
```
Finally, change directory:
## Install
```bash
cd casdoor/
```
- By source code: https://casdoor.org/docs/basic/server-installation
- By Docker: https://casdoor.org/docs/basic/try-with-docker
We provide two start up methods for all kinds of users.
### Manual
#### Simple configuration
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
## How to connect to Casdoor?
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
https://casdoor.org/docs/how-to-connect/overview
```bash
username:password@tcp(database_ip:database_port)/
```
#### Run
Casdoor provides two run modes, the difference is binary size and user prompt.
## Casdoor Public API
##### Dev Mode
- Docs: https://casdoor.org/docs/basic/public-api
- Swagger: https://door.casdoor.com/swagger
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
```bash
cd web/ && yarn && yarn run start
```
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
Then build back-end binary file, change directory to root(Relative to casdoor):
## Integrations
```bash
go run main.go
```
https://casdoor.org/docs/integration/apisix
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
## How to contact?
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
- Gitter: https://gitter.im/casbin/casdoor
- Forum: https://forum.casbin.com
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
```bash
cd web/ && yarn && yarn 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
Casdoor provide 2 kinds of image:
- casbin/casdoor-all-in-one, in which casdoor binary, a mysql database and all necessary configurations are packed up. This image is for new user to have a trial on casdoor quickly. **With this image you can start a casdoor immediately with one single command (or two) without any complex configuration**. **Note: we DO NOT recommend you to use this image in productive environment**
- casbin/casdoor: normal & graceful casdoor image with only casdoor and environment installed.
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
### Start casdoor with casbin/casdoor-all-in-one
if the image is not pulled, pull it from dockerhub
```shell
docker pull casbin/casdoor-all-in-one
```
Start it with
```shell
docker run -p 8000:8000 casbin/casdoor-all-in-one
```
Now you can visit http://localhost:8000 and have a try. Default account and password is 'admin' and '123'. Go for it!
### Start casdoor with casbin/casdoor
#### modify the configurations
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
```bash
dataSourceName = root:123456@tcp(db:3306)/
```
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
#### Run
```bash
docker-compose up
```
### K8S
You could use helm to deploy casdoor in k8s. At first, you should modify the [configmap](./manifests/casdoor/templates/configmap.yaml) for your application.
And then run bellow command to deploy it.
```bash
IMG_TAG=latest make deploy
```
And undeploy it with:
```bash
make undeploy
```
That's it! Try to visit http://localhost:8000/. :small_airplane:
## 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, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
### I18n notice
### I18n translation
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) 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.
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/casdoor/casdoor/blob/master/LICENSE)
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)

View File

@ -15,7 +15,6 @@
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"
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
func InitAuthz() {
var err error
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}
@ -54,7 +53,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
(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)
(r.subOwner == r.objOwner && r.subName == r.objName)
`
m, err := model.NewModelFromString(modelText)
@ -81,26 +80,31 @@ p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, POST, /api/login/oauth/access_token, *, *
p, *, *, POST, /api/login/oauth/refresh_token, *, *
p, *, *, *, /api/login/oauth, *, *
p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-users, *, *
p, *, *, GET, /api/get-applications, *, *
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, *, *, GET, /api/get-product, *, *
p, *, *, POST, /api/buy-product, *, *
p, *, *, GET, /api/get-payment, *, *
p, *, *, POST, /api/update-payment, *, *
p, *, *, POST, /api/invoice-payment, *, *
p, *, *, GET, /api/get-providers, *, *
p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *
p, *, *, GET, /api/get-human-check, *, *
p, *, *, GET, /api/get-captcha, *, *
p, *, *, POST, /api/verify-captcha, *, *
p, *, *, POST, /api/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, *
p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, *, /api/certs, *, *
p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, *
`
sa := stringadapter.NewAdapter(ruleText)

11
build.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
#try to connect to google to determine whether user need to use proxy
curl www.google.com -o /dev/null --connect-timeout 5 2 > /dev/null
if [ $? == 0 ]
then
echo "Successfully connected to Google, no need to use Go proxy"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
else
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server .
fi

105
captcha/aliyun.go Normal file
View File

@ -0,0 +1,105 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package captcha
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/casdoor/casdoor/util"
)
const AliyunCaptchaVerifyUrl = "http://afs.aliyuncs.com"
type AliyunCaptchaProvider struct {
}
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
captcha := &AliyunCaptchaProvider{}
return captcha
}
func contentEscape(str string) string {
str = strings.Replace(str, " ", "%20", -1)
str = url.QueryEscape(str)
return str
}
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
pathData, err := url.ParseQuery(token)
if err != nil {
return false, err
}
pathData["Action"] = []string{"AuthenticateSig"}
pathData["Format"] = []string{"json"}
pathData["SignatureMethod"] = []string{"HMAC-SHA1"}
pathData["SignatureNonce"] = []string{strconv.FormatInt(time.Now().UnixNano(), 10)}
pathData["SignatureVersion"] = []string{"1.0"}
pathData["Timestamp"] = []string{time.Now().UTC().Format("2006-01-02T15:04:05Z")}
pathData["Version"] = []string{"2018-01-12"}
var keys []string
for k := range pathData {
keys = append(keys, k)
}
sort.Strings(keys)
sortQuery := ""
for _, k := range keys {
sortQuery += k + "=" + contentEscape(pathData[k][0]) + "&"
}
sortQuery = strings.TrimSuffix(sortQuery, "&")
stringToSign := fmt.Sprintf("GET&%s&%s", url.QueryEscape("/"), url.QueryEscape(sortQuery))
signature := util.GetHmacSha1(clientSecret+"&", stringToSign)
resp, err := http.Get(fmt.Sprintf("%s?%s&Signature=%s", AliyunCaptchaVerifyUrl, sortQuery, url.QueryEscape(signature)))
if err != nil {
return false, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Code int `json:"Code"`
Msg string `json:"Msg"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if captchaResp.Code != 100 {
return false, errors.New(captchaResp.Msg)
}
return true, nil
}

29
captcha/default.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package captcha
import "github.com/casdoor/casdoor/object"
type DefaultCaptchaProvider struct {
}
func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
captcha := &DefaultCaptchaProvider{}
return captcha
}
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
return object.VerifyCaptcha(clientSecret, token), nil
}

67
captcha/hcaptcha.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package captcha
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
const HCaptchaVerifyUrl = "https://hcaptcha.com/siteverify"
type HCaptchaProvider struct {
}
func NewHCaptchaProvider() *HCaptchaProvider {
captcha := &HCaptchaProvider{}
return captcha
}
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
reqData := url.Values{
"secret": {clientSecret},
"response": {token},
}
resp, err := http.PostForm(HCaptchaVerifyUrl, reqData)
if err != nil {
return false, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Success bool `json:"success"`
ErrorCodes []string `json:"error-codes"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if len(captchaResp.ErrorCodes) > 0 {
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
}
return captchaResp.Success, nil
}

32
captcha/provider.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package captcha
type CaptchaProvider interface {
VerifyCaptcha(token, clientSecret string) (bool, error)
}
func GetCaptchaProvider(captchaType string) CaptchaProvider {
if captchaType == "Default" {
return NewDefaultCaptchaProvider()
} else if captchaType == "reCAPTCHA" {
return NewReCaptchaProvider()
} else if captchaType == "hCaptcha" {
return NewHCaptchaProvider()
} else if captchaType == "Aliyun Captcha" {
return NewAliyunCaptchaProvider()
}
return nil
}

67
captcha/recaptcha.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package captcha
import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
const ReCaptchaVerifyUrl = "https://recaptcha.net/recaptcha/api/siteverify"
type ReCaptchaProvider struct {
}
func NewReCaptchaProvider() *ReCaptchaProvider {
captcha := &ReCaptchaProvider{}
return captcha
}
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
reqData := url.Values{
"secret": {clientSecret},
"response": {token},
}
resp, err := http.PostForm(ReCaptchaVerifyUrl, reqData)
if err != nil {
return false, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Success bool `json:"success"`
ErrorCodes []string `json:"error-codes"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if len(captchaResp.ErrorCodes) > 0 {
return false, errors.New(strings.Join(captchaResp.ErrorCodes, ","))
}
return captchaResp.Success, nil
}

View File

@ -12,7 +12,7 @@ redisEndpoint =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
httpProxy = "127.0.0.1:10808"
sock5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
logPostOnly = true

View File

@ -15,18 +15,59 @@
package conf
import (
"fmt"
"os"
"runtime"
"strconv"
"strings"
"github.com/astaxie/beego"
)
func GetConfigString(key string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return beego.AppConfig.String(key)
}
func GetConfigBool(key string) (bool, error) {
value := GetConfigString(key)
if value == "true" {
return true, nil
} else if value == "false" {
return false, nil
}
return false, fmt.Errorf("value %s cannot be converted into bool", value)
}
func GetConfigInt64(key string) (int64, error) {
value := GetConfigString(key)
num, err := strconv.ParseInt(value, 10, 64)
return num, err
}
func init() {
//this array contains the beego configuration items that may be modified via env
var presetConfigItems = []string{"httpport", "appname"}
for _, key := range presetConfigItems {
if value, ok := os.LookupEnv(key); ok {
beego.AppConfig.Set(key, value)
}
}
}
func GetBeegoConfDataSourceName() string {
dataSourceName := beego.AppConfig.String("dataSourceName")
dataSourceName := GetConfigString("dataSourceName")
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" {
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
if runtime.GOOS == "linux" {
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "172.17.0.1")
} else {
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
}
}
return dataSourceName

98
conf/conf_test.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conf
import (
"os"
"testing"
"github.com/astaxie/beego"
"github.com/stretchr/testify/assert"
)
func TestGetConfString(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return casbin", "appname", "casbin"},
{"Should be return 8000", "httpport", "8000"},
{"Should be return value", "key", "value"},
}
//do some set up job
os.Setenv("appname", "casbin")
os.Setenv("key", "value")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual := GetConfigString(scenery.input)
assert.Equal(t, scenery.expected, actual)
})
}
}
func TestGetConfInt(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return 8000", "httpport", 8001},
{"Should be return 8000", "verificationCodeTimeout", 10},
}
//do some set up job
os.Setenv("httpport", "8001")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := GetConfigInt64(scenery.input)
assert.Nil(t, err)
assert.Equal(t, scenery.expected, int(actual))
})
}
}
func TestGetConfBool(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return false", "SessionOn", false},
{"Should be return false", "copyrequestbody", true},
}
//do some set up job
os.Setenv("SessionOn", "false")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := GetConfigBool(scenery.input)
assert.Nil(t, err)
assert.Equal(t, scenery.expected, actual)
})
}
}

View File

@ -18,14 +18,19 @@ import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
const (
ResponseTypeLogin = "login"
ResponseTypeCode = "code"
ResponseTypeLogin = "login"
ResponseTypeCode = "code"
ResponseTypeToken = "token"
ResponseTypeIdToken = "id_token"
ResponseTypeSaml = "saml"
ResponseTypeCas = "cas"
)
type RequestForm struct {
@ -35,6 +40,8 @@ type RequestForm struct {
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
@ -55,6 +62,7 @@ type RequestForm struct {
AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"`
}
@ -67,12 +75,17 @@ type Response struct {
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"`
type Captcha struct {
Type string `json:"type"`
AppKey string `json:"appKey"`
Scene string `json:"scene"`
CaptchaId string `json:"captchaId"`
CaptchaImage []byte `json:"captchaImage"`
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
ClientId2 string `json:"clientId2"`
ClientSecret2 string `json:"clientSecret2"`
SubType string `json:"subType"`
}
// Signup
@ -102,13 +115,13 @@ func (c *ApiController) Signup() {
}
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)
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.Affiliation)
if msg != "" {
c.ResponseError(msg)
return
}
if application.IsSignupItemVisible("Email") && form.Email != "" {
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
@ -126,8 +139,6 @@ func (c *ApiController) Signup() {
}
}
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization)
@ -167,6 +178,22 @@ func (c *ApiController) Signup() {
IsDeleted: false,
SignupApplication: application.Name,
Properties: map[string]string{},
Karma: 0,
}
if len(organization.Tags) > 0 {
tokens := strings.Split(organization.Tags[0], "|")
if len(tokens) > 0 {
user.Tag = tokens[0]
}
}
if application.GetSignupItemRule("Display name") == "First, last" {
if form.FirstName != "" || form.LastName != "" {
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
user.FirstName = form.FirstName
user.LastName = form.LastName
}
}
affected := object.AddUser(user)
@ -185,6 +212,12 @@ func (c *ApiController) Signup() {
object.DisableVerificationCode(form.Email)
object.DisableVerificationCode(checkPhone)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId)
@ -200,10 +233,15 @@ func (c *ApiController) Logout() {
user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ResponseOk(user)
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user)
return
}
c.ResponseOk(user, application.HomepageUrl)
}
// GetAccount
@ -252,25 +290,43 @@ func (c *ApiController) GetUserinfo() {
resp, err := object.GetUserInfo(userId, scope, aud, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = resp
c.ServeJSON()
}
// GetHumanCheck ...
// GetCaptcha ...
// @Tag Login API
// @Title GetHumancheck
// @router /api/get-human-check [get]
func (c *ApiController) GetHumanCheck() {
c.Data["json"] = HumanCheck{Type: "none"}
// @Title GetCaptcha
// @router /api/get-captcha [get]
func (c *ApiController) GetCaptcha() {
applicationId := c.Input().Get("applicationId")
isCurrentProvider := c.Input().Get("isCurrentProvider")
provider := object.GetDefaultHumanCheckProvider()
if provider == nil {
id, img := object.GetCaptcha()
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
c.ServeJSON()
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ServeJSON()
if captchaProvider != nil {
if captchaProvider.Type == "Default" {
id, img := object.GetCaptcha()
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
return
} else if captchaProvider.Type != "" {
c.ResponseOk(Captcha{
Type: captchaProvider.Type,
SubType: captchaProvider.SubType,
ClientId: captchaProvider.ClientId,
ClientSecret: captchaProvider.ClientSecret,
ClientId2: captchaProvider.ClientId2,
ClientSecret2: captchaProvider.ClientSecret2,
})
return
}
}
c.ResponseOk(Captcha{Type: "none"})
}

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -85,7 +86,7 @@ func (c *ApiController) GetUserApplication() {
id := c.Input().Get("id")
user := object.GetUser(id)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", id))
return
}

View File

@ -23,11 +23,12 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid"
)
func codeToResponse(code *object.Code) *Response {
@ -38,6 +39,14 @@ func codeToResponse(code *object.Code) *Response {
return &Response{Status: "ok", Msg: "", Data: code.Code}
}
func tokenToResponse(token *object.Token) *Response {
if token.AccessToken == "" {
return &Response{Status: "error", Msg: "fail to get accessToken", Data: token.AccessToken}
}
return &Response{Status: "ok", Msg: "", Data: token.AccessToken}
}
// HandleLoggedIn ...
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
userId := user.GetId()
@ -66,8 +75,41 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
} else if form.Type == ResponseTypeToken || form.Type == ResponseTypeIdToken { //implicit flow
if !object.IsGrantTypeValid(form.Type, application.GrantTypes) {
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
} else {
scope := c.Input().Get("scope")
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
resp = tokenToResponse(token)
}
} else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl}
} else if form.Type == ResponseTypeCas {
//not oauth but CAS SSO protocol
service := c.Input().Get("service")
resp = wrapErrorResponse(nil)
if service != "" {
st, err := object.GenerateCasToken(userId, service)
if err != nil {
resp = wrapErrorResponse(err)
} else {
resp.Data = st
}
}
if application.EnableSigninSession || 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)}
resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
}
// if user did not check auto signin
@ -91,8 +133,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// @Param redirectUri query string true "redirect uri"
// @Param scope query string true "scope"
// @Param state query string true "state"
// @Success 200 {object} controllers.api_controller.Response The Response object
// @router /update-application [get]
// @Success 200 {object} Response The Response object
// @router /get-app-login [get]
func (c *ApiController) GetApplicationLogin() {
clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType")
@ -101,6 +143,7 @@ func (c *ApiController) GetApplicationLogin() {
state := c.Input().Get("state")
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
application = object.GetMaskedApplication(application, "")
if msg != "" {
c.ResponseError(msg, application)
} else {
@ -120,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
// @Title Login
// @Tag Login API
// @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
// @Param clientId query string true clientId
// @Param responseType query string true responseType
// @Param redirectUri query string true redirectUri
// @Param scope query string false scope
// @Param state query string false state
// @Param nonce query string false nonce
// @Param code_challenge_method query string false code_challenge_method
// @Param code_challenge query string false code_challenge
// @Param form body controllers.RequestForm true "Login information"
// @Success 200 {object} Response The Response object
// @router /login [post]
func (c *ApiController) Login() {
resp := &Response{}
@ -149,9 +199,16 @@ func (c *ApiController) Login() {
var verificationCodeType string
var checkResult string
if form.Name != "" {
user = object.GetUserByFields(form.Organization, form.Name)
}
// check result through Email or Phone
if strings.Contains(form.Username, "@") {
verificationCodeType = "email"
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
form.Username = user.Email
}
checkResult = object.CheckVerificationCode(form.Username, form.Code)
} else {
verificationCodeType = "phone"
@ -160,6 +217,9 @@ func (c *ApiController) Login() {
c.ResponseError(responseText)
return
}
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
}
@ -170,11 +230,15 @@ func (c *ApiController) Login() {
}
// disable the verification code
object.DisableVerificationCode(form.Username)
if strings.Contains(form.Username, "@") {
object.DisableVerificationCode(form.Username)
} else {
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
}
user = object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
return
}
} else {
@ -186,15 +250,25 @@ func (c *ApiController) Login() {
resp = &Response{Status: "error", Msg: msg}
} else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
util.SafeGoroutine(func() { object.AddRecord(record) })
}
} else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
providerItem := application.GetProviderItem(provider.Name)
@ -221,7 +295,7 @@ func (c *ApiController) Login() {
clientSecret = provider.ClientSecret2
}
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri)
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
if idProvider == nil {
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
return
@ -229,8 +303,8 @@ func (c *ApiController) Login() {
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))
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
return
}
@ -259,12 +333,6 @@ func (c *ApiController) Login() {
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
} else if provider.Category == "OAuth" {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if user == nil {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if user == nil {
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
}
}
if user != nil && user.IsDeleted == false {
@ -279,7 +347,7 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
util.SafeGoroutine(func() { object.AddRecord(record) })
} else if provider.Category == "OAuth" {
// Sign up via OAuth
if !application.EnableSignUp {
@ -292,6 +360,19 @@ func (c *ApiController) Login() {
return
}
// Handle username conflicts
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
if tmpUser != nil {
uid, err := uuid.NewRandom()
if err != nil {
c.ResponseError(err.Error())
return
}
uidStr := strings.Split(uid.String(), "-")
userInfo.Username = fmt.Sprintf("%s_%s", userInfo.Username, uidStr[1])
}
properties := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
user = &object.User{
@ -328,7 +409,13 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
util.SafeGoroutine(func() { object.AddRecord(record) })
record2 := object.NewRecord(c.Ctx)
record2.Action = "signup"
record2.Organization = application.Organization
record2.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record2) })
} else if provider.Category == "SAML" {
resp = &Response{Status: "error", Msg: "The account does not exist"}
}
@ -341,9 +428,6 @@ func (c *ApiController) Login() {
}
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
@ -365,8 +449,18 @@ func (c *ApiController) Login() {
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
user := c.getCurrentUser()
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
} else {
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
return

View File

@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
func (c *ApiController) GetSessionApplication() *object.Application {
clientId := c.GetSession("aud")
if clientId == nil {
return nil
}
application := object.GetApplicationByClientId(clientId.(string))
return application
}
func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData()
if sessionData != nil &&
@ -132,3 +141,11 @@ func wrapActionResponse(affected bool) *Response {
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
}
}
func wrapErrorResponse(err error) *Response {
if err == nil {
return &Response{Status: "ok", Msg: ""}
} else {
return &Response{Status: "error", Msg: err.Error()}
}
}

271
controllers/cas.go Normal file
View File

@ -0,0 +1,271 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/xml"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/casdoor/casdoor/object"
)
const (
InvalidRequest string = "INVALID_REQUEST"
InvalidTicketSpec string = "INVALID_TICKET_SPEC"
UnauthorizedServiceProxy string = "UNAUTHORIZED_SERVICE_PROXY"
InvalidProxyCallback string = "INVALID_PROXY_CALLBACK"
InvalidTicket string = "INVALID_TICKET"
InvalidService string = "INVALID_SERVICE"
InteralError string = "INTERNAL_ERROR"
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
)
func (c *RootController) CasValidate() {
ticket := c.Input().Get("ticket")
service := c.Input().Get("service")
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
if service == "" || ticket == "" {
c.Ctx.Output.Body([]byte("no\n"))
return
}
if ok, response, issuedService, _ := object.GetCasTokenByTicket(ticket); ok {
//check whether service is the one for which we previously issued token
if issuedService == service {
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
return
}
}
//token not found
c.Ctx.Output.Body([]byte("no\n"))
}
func (c *RootController) CasServiceValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
if !strings.HasPrefix(ticket, "ST") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ServiceAndProxyValidate()
}
func (c *RootController) CasProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
if !strings.HasPrefix(ticket, "PT") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ServiceAndProxyValidate()
}
func (c *RootController) CasP3ServiceAndProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
service := c.Input().Get("service")
pgtUrl := c.Input().Get("pgtUrl")
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
}
//check whether all required parameters are met
if service == "" || ticket == "" {
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
return
}
ok, response, issuedService, userId := object.GetCasTokenByTicket(ticket)
//find the token
if ok {
//check whether service is the one for which we previously issued token
if strings.HasPrefix(service, issuedService) {
serviceResponse.Success = response
} else {
//service not match
c.sendCasAuthenticationResponseErr(InvalidService, fmt.Sprintf("service %s and %s does not match", service, issuedService), format)
return
}
} else {
//token not found
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
return
}
if pgtUrl != "" && serviceResponse.Failure == nil {
//that means we are in proxy web flow
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service, userId)
pgtiou := serviceResponse.Success.ProxyGrantingTicket
//todo: check whether it is https
pgtUrlObj, err := url.Parse(pgtUrl)
if pgtUrlObj.Scheme != "https" {
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
return
}
//make a request to pgturl passing pgt and pgtiou
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
return
}
param := pgtUrlObj.Query()
param.Add("pgtId", pgt)
param.Add("pgtIou", pgtiou)
pgtUrlObj.RawQuery = param.Encode()
request, err := http.NewRequest("GET", pgtUrlObj.String(), nil)
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
return
}
resp, err := http.DefaultClient.Do(request)
if err != nil || !(resp.StatusCode >= 200 && resp.StatusCode < 400) {
//failed to send request
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
return
}
}
// everything is ok, send the response
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) CasProxy() {
pgt := c.Input().Get("pgt")
targetService := c.Input().Get("targetService")
format := c.Input().Get("format")
if pgt == "" || targetService == "" {
c.sendCasProxyResponseErr(InvalidRequest, "pgt and targetService must exist", format)
return
}
ok, authenticationSuccess, issuedService, userId := object.GetCasTokenByPgt(pgt)
if !ok {
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
return
}
newAuthenticationSuccess := authenticationSuccess.DeepCopy()
if newAuthenticationSuccess.Proxies == nil {
newAuthenticationSuccess.Proxies = &object.CasProxies{}
}
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService, userId)
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
ProxySuccess: &object.CasProxySuccess{
ProxyTicket: proxyTicket,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) SamlValidate() {
c.Ctx.Output.Header("Content-Type", "text/xml; charset=utf-8")
target := c.Input().Get("TARGET")
body := c.Ctx.Input.RequestBody
envelopRequest := struct {
XMLName xml.Name `xml:"Envelope"`
Body struct {
XMLName xml.Name `xml:"Body"`
Content string `xml:",innerxml"`
}
}{}
err := xml.Unmarshal(body, &envelopRequest)
if err != nil {
c.ResponseError(err.Error())
return
}
response, service, err := object.GetValidationBySaml(envelopRequest.Body.Content, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
if !strings.HasPrefix(target, service) {
c.ResponseError(fmt.Sprintf("service %s and %s do not match", target, service))
return
}
envelopReponse := struct {
XMLName xml.Name `xml:"SOAP-ENV:Envelope"`
Xmlns string `xml:"xmlns:SOAP-ENV"`
Body struct {
XMLName xml.Name `xml:"SOAP-ENV:Body"`
Content string `xml:",innerxml"`
}
}{}
envelopReponse.Xmlns = "http://schemas.xmlsoap.org/soap/envelope/"
envelopReponse.Body.Content = response
data, err := xml.Marshal(envelopReponse)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Ctx.Output.Body([]byte(data))
}
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
ProxyFailure: &object.CasProxyFailure{
Code: code,
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format string) {
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
Failure: &object.CasAuthenticationFailure{
Code: code,
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}

View File

@ -178,7 +178,7 @@ func (c *ApiController) UpdateLdap() {
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0{
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
}
@ -215,7 +215,7 @@ func (c *ApiController) SyncLdapUsers() {
object.UpdateLdapSyncTime(ldapId)
exist, failed := object.SyncLdapUsers(owner, users)
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
Exist: *exist,
Failed: *failed,

120
controllers/model.go Normal file
View File

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

View File

@ -18,6 +18,8 @@ import "github.com/casdoor/casdoor/object"
// @Title GetOidcDiscovery
// @Tag OIDC API
// @Description Get Oidc Discovery
// @Success 200 {object} object.OidcDiscovery
// @router /.well-known/openid-configuration [get]
func (c *RootController) GetOidcDiscovery() {
host := c.Ctx.Request.Host
@ -25,10 +27,11 @@ func (c *RootController) GetOidcDiscovery() {
c.ServeJSON()
}
// @Title GetOidcCert
// @Title GetJwks
// @Tag OIDC API
// @router /api/certs [get]
func (c *RootController) GetOidcCert() {
// @Success 200 {object} jose.JSONWebKey
// @router /.well-known/jwks [get]
func (c *RootController) GetJwks() {
jwks, err := object.GetJsonWebKeySet()
if err != nil {
c.ResponseError(err.Error())

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -48,6 +49,24 @@ func (c *ApiController) GetPayments() {
}
}
// GetUserPayments
// @Title GetUserPayments
// @Tag Payment API
// @Description get payments for a user
// @Param owner query string true "The owner of payments"
// @Param organization query string true "The organization of the user"
// @Param user query string true "The username of the user"
// @Success 200 {array} object.Payment The Response object
// @router /get-user-payments [get]
func (c *ApiController) GetUserPayments() {
owner := c.Input().Get("owner")
organization := c.Input().Get("organization")
user := c.Input().Get("user")
payments := object.GetUserPayments(owner, organization, user)
c.ResponseOk(payments)
}
// @Title GetPayment
// @Tag Payment API
// @Description get payment
@ -114,3 +133,45 @@ func (c *ApiController) DeletePayment() {
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
c.ServeJSON()
}
// @Title NotifyPayment
// @Tag Payment API
// @Description notify payment
// @Param body body object.Payment true "The details of the payment"
// @Success 200 {object} controllers.Response The Response object
// @router /notify-payment [post]
func (c *ApiController) NotifyPayment() {
owner := c.Ctx.Input.Param(":owner")
providerName := c.Ctx.Input.Param(":provider")
productName := c.Ctx.Input.Param(":product")
paymentName := c.Ctx.Input.Param(":payment")
body := c.Ctx.Input.RequestBody
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
if ok {
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
if err != nil {
panic(err)
}
} else {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
}
}
// @Title InvoicePayment
// @Tag Payment API
// @Description invoice payment
// @Param id query string true "The id of the payment"
// @Success 200 {object} controllers.Response The Response object
// @router /invoice-payment [post]
func (c *ApiController) InvoicePayment() {
id := c.Input().Get("id")
payment := object.GetPayment(id)
invoiceUrl, err := object.InvoicePayment(payment)
if err != nil {
c.ResponseError(err.Error())
}
c.ResponseOk(invoiceUrl)
}

150
controllers/product.go Normal file
View File

@ -0,0 +1,150 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetProducts
// @Title GetProducts
// @Tag Product API
// @Description get products
// @Param owner query string true "The owner of products"
// @Success 200 {array} object.Product The Response object
// @router /get-products [get]
func (c *ApiController) GetProducts() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetProducts(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProductCount(owner, field, value)))
products := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(products, paginator.Nums())
}
}
// @Title GetProduct
// @Tag Product API
// @Description get product
// @Param id query string true "The id of the product"
// @Success 200 {object} object.Product The Response object
// @router /get-product [get]
func (c *ApiController) GetProduct() {
id := c.Input().Get("id")
c.Data["json"] = object.GetProduct(id)
c.ServeJSON()
}
// @Title UpdateProduct
// @Tag Product API
// @Description update product
// @Param id query string true "The id of the product"
// @Param body body object.Product true "The details of the product"
// @Success 200 {object} controllers.Response The Response object
// @router /update-product [post]
func (c *ApiController) UpdateProduct() {
id := c.Input().Get("id")
var product object.Product
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateProduct(id, &product))
c.ServeJSON()
}
// @Title AddProduct
// @Tag Product API
// @Description add product
// @Param body body object.Product true "The details of the product"
// @Success 200 {object} controllers.Response The Response object
// @router /add-product [post]
func (c *ApiController) AddProduct() {
var product object.Product
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddProduct(&product))
c.ServeJSON()
}
// @Title DeleteProduct
// @Tag Product API
// @Description delete product
// @Param body body object.Product true "The details of the product"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-product [post]
func (c *ApiController) DeleteProduct() {
var product object.Product
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
c.ServeJSON()
}
// @Title BuyProduct
// @Tag Product API
// @Description buy product
// @Param id query string true "The id of the product"
// @Param providerName query string true "The name of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /buy-product [post]
func (c *ApiController) BuyProduct() {
id := c.Input().Get("id")
providerName := c.Input().Get("providerName")
host := c.Ctx.Request.Host
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please login first")
return
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
payUrl, err := object.BuyProduct(id, providerName, user, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payUrl)
}

View File

@ -26,7 +26,7 @@ import (
// @Description get all records
// @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the page"
// @Success 200 {array} object.Records The Response object
// @Success 200 {object} object.Record The Response object
// @router /get-records [get]
func (c *ApiController) GetRecords() {
limit := c.Input().Get("pageSize")
@ -50,8 +50,8 @@ func (c *ApiController) GetRecords() {
// @Tag Record API
// @Title GetRecordsByFilter
// @Description get records by filter
// @Param body body object.Records true "filter Record message"
// @Success 200 {array} object.Records The Response object
// @Param filter body string true "filter Record message"
// @Success 200 {object} object.Record The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
body := string(c.Ctx.Input.RequestBody)

34
controllers/saml.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"fmt"
"github.com/casdoor/casdoor/object"
)
func (c *ApiController) GetSamlMeta() {
host := c.Ctx.Request.Host
paramApp := c.Input().Get("application")
application := object.GetApplication(paramApp)
if application == nil {
c.ResponseError(fmt.Sprintf("err: application %s not found", paramApp))
return
}
metadata, _ := object.GetSamlMeta(application, host)
c.Data["xml"] = metadata
c.ServeXML()
}

View File

@ -25,33 +25,61 @@ import (
"github.com/casdoor/casdoor/util"
)
type EmailForm struct {
Title string `json:"title"`
Content string `json:"content"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
Provider string `json:"provider"`
}
type SmsForm struct {
Content string `json:"content"`
Receivers []string `json:"receivers"`
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
}
// SendEmail
// @Title SendEmail
// @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param body body emailForm true "Details of the email request"
// @Param from body controllers.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 EmailForm
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
}
var provider *object.Provider
if emailForm.Provider != "" {
// called by frontend's TestEmailWidget, provider name is set by frontend
provider = object.GetProvider(fmt.Sprintf("admin/%s", emailForm.Provider))
} else {
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
var ok bool
provider, _, ok = c.GetProviderFromContext("Email")
if !ok {
return
}
}
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
err := object.DailSmtpServer(provider)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
return
@ -86,7 +114,7 @@ func (c *ApiController) 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 smsForm true "Details of the sms request"
// @Param from body controllers.SmsForm true "Details of the sms request"
// @Success 200 {object} Response object
// @router /api/send-sms [post]
func (c *ApiController) SendSms() {
@ -95,11 +123,7 @@ func (c *ApiController) SendSms() {
return
}
var smsForm struct {
Content string `json:"content"`
Receivers []string `json:"receivers"`
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
}
var smsForm SmsForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil {
c.ResponseError(err.Error())

View File

@ -16,7 +16,6 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -114,3 +113,18 @@ func (c *ApiController) DeleteSyncer() {
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
c.ServeJSON()
}
// @Title RunSyncer
// @Tag Syncer API
// @Description run syncer
// @Param body body object.Syncer true "The details of the syncer"
// @Success 200 {object} controllers.Response The Response object
// @router /run-syncer [get]
func (c *ApiController) RunSyncer() {
id := c.Input().Get("id")
syncer := object.GetSyncer(id)
object.RunSyncer(syncer)
c.ResponseOk()
}

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"net/http"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -164,6 +165,8 @@ func (c *ApiController) GetOAuthCode() {
// @Param client_secret query string true "OAuth client secret"
// @Param code query string true "OAuth code"
// @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
@ -171,24 +174,50 @@ func (c *ApiController) GetOAuthToken() {
clientSecret := c.Input().Get("client_secret")
code := c.Input().Get("code")
verifier := c.Input().Get("code_verifier")
scope := c.Input().Get("scope")
username := c.Input().Get("username")
password := c.Input().Get("password")
tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
code = tokenRequest.Code
verifier = tokenRequest.Verifier
scope = tokenRequest.Scope
username = tokenRequest.Username
password = tokenRequest.Password
tag = tokenRequest.Tag
avatar = tokenRequest.Avatar
}
}
host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier)
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
// RefreshToken
// @Title RefreshToken
// @Tag Token API
// @Description refresh OAuth access token
// @Param grant_type query string true "OAuth grant type"
// @Param refresh_token query string true "OAuth refresh token"
// @Param scope query string true "OAuth scope"
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string true "OAuth client secret"
// @Param client_secret query string false "OAuth client secret"
// @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() {
grantType := c.Input().Get("grant_type")
@ -198,6 +227,112 @@ func (c *ApiController) RefreshToken() {
clientSecret := c.Input().Get("client_secret")
host := c.Ctx.Request.Host
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
scope = tokenRequest.Scope
refreshToken = tokenRequest.RefreshToken
}
}
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
// TokenLogout
// @Title TokenLogout
// @Tag Token API
// @Description delete token by AccessToken
// @Param id_token_hint query string true "id_token_hint"
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
// @Param state query string true "state"
// @Success 200 {object} controllers.Response The Response object
// @router /login/oauth/logout [get]
func (c *ApiController) TokenLogout() {
token := c.Input().Get("id_token_hint")
flag, application := object.DeleteTokenByAceessToken(token)
redirectUri := c.Input().Get("post_logout_redirect_uri")
state := c.Input().Get("state")
if application != nil && object.CheckRedirectUriValid(application, redirectUri) {
c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state)
return
}
c.Data["json"] = wrapActionResponse(flag)
c.ServeJSON()
}
// IntrospectToken
// @Title IntrospectToken
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
// parameter representing an OAuth 2.0 token and returns a JSON document
// representing the meta information surrounding the
// token, including whether this token is currently active.
// This endpoint only support Basic Authorization.
// @Param token formData string true "access_token's value or refresh_token's value"
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
// @Success 200 {object} object.IntrospectionResponse The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/introspect [post]
func (c *ApiController) IntrospectToken() {
tokenValue := c.Input().Get("token")
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
if !ok {
clientId = c.Input().Get("client_id")
clientSecret = c.Input().Get("client_secret")
if clientId == "" || clientSecret == "" {
c.ResponseError("empty clientId or clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_REQUEST,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
return
}
}
application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret {
c.ResponseError("invalid application or wrong clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_CLIENT,
}
c.SetTokenErrorHttpStatus()
return
}
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
if token == nil {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
if err != nil || jwtToken.Valid() != nil {
// and token revoked case. but we not implement
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
// refs: https://tools.ietf.org/html/rfc7009
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
c.Data["json"] = &object.IntrospectionResponse{
Active: true,
Scope: jwtToken.Scope,
ClientId: clientId,
Username: token.User,
TokenType: token.TokenType,
Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(),
Sub: jwtToken.Subject,
Aud: jwtToken.Audience,
Iss: jwtToken.Issuer,
Jti: jwtToken.Id,
}
c.ServeJSON()
}

29
controllers/types.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
type TokenRequest struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Verifier string `json:"code_verifier"`
Scope string `json:"scope"`
Username string `json:"username"`
Password string `json:"password"`
Tag string `json:"tag"`
Avatar string `json:"avatar"`
RefreshToken string `json:"refresh_token"`
}

View File

@ -44,6 +44,7 @@ func (c *ApiController) GetGlobalUsers() {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
users = object.GetMaskedUsers(users)
c.ResponseOk(users, paginator.Nums())
}
}
@ -70,6 +71,7 @@ func (c *ApiController) GetUsers() {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
users = object.GetMaskedUsers(users)
c.ResponseOk(users, paginator.Nums())
}
}
@ -85,6 +87,17 @@ func (c *ApiController) GetUser() {
id := c.Input().Get("id")
owner := c.Input().Get("owner")
email := c.Input().Get("email")
userOwner, _ := util.GetOwnerAndNameFromId(id)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
if !hasPermission {
c.ResponseError(err.Error())
return
}
}
var user *object.User
if email == "" {
@ -109,6 +122,10 @@ func (c *ApiController) UpdateUser() {
id := c.Input().Get("id")
columnsStr := c.Input().Get("columns")
if id == "" {
id = c.GetSessionUsername()
}
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
@ -188,19 +205,23 @@ func (c *ApiController) GetEmailAndPhone() {
user := object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
return
}
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
respUser := object.User{Name: user.Name}
var contentType string
switch form.Username {
case user.Email:
contentType = "email"
respUser.Email = user.Email
case user.Phone:
contentType = "phone"
respUser.Phone = user.Phone
case user.Name:
contentType = "username"
respUser.Email = util.GetMaskedEmail(user.Email)
respUser.Phone = util.GetMaskedPhone(user.Phone)
}
c.ResponseOk(respUser, contentType)
@ -223,39 +244,15 @@ func (c *ApiController) SetPassword() {
newPassword := c.Ctx.Request.Form.Get("newPassword")
requestUserId := c.GetSessionUsername()
if requestUserId == "" {
c.ResponseError("Please login first.")
return
}
userId := fmt.Sprintf("%s/%s", userOwner, userName)
targetUser := object.GetUser(userId)
if targetUser == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
if !hasPermission {
c.ResponseError(err.Error())
return
}
hasPermission := false
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := object.GetUser(requestUserId)
if requestUser == nil {
c.ResponseError("Session outdated. Please login again.")
return
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
hasPermission = true
}
}
if !hasPermission {
c.ResponseError("You don't have the permission to do this.")
return
}
targetUser := object.GetUser(userId)
if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword)

View File

@ -18,7 +18,7 @@ import (
"fmt"
"strconv"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -51,6 +51,23 @@ func (c *ApiController) ResponseError(error string, data ...interface{}) {
c.ServeJSON()
}
// SetTokenErrorHttpStatus ...
func (c *ApiController) SetTokenErrorHttpStatus() {
_, ok := c.Data["json"].(*object.TokenError)
if ok {
if c.Data["json"].(*object.TokenError).Error == object.INVALID_CLIENT {
c.Ctx.Output.SetStatus(401)
c.Ctx.Output.Header("WWW-Authenticate", "Basic realm=\"OAuth2\"")
} else {
c.Ctx.Output.SetStatus(400)
}
}
_, ok = c.Data["json"].(*object.TokenWrapper)
if ok {
c.Ctx.Output.SetStatus(200)
}
}
// RequireSignedIn ...
func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername()
@ -62,7 +79,7 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
}
func getInitScore() int {
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
score, err := strconv.Atoi(conf.GetConfigString("initScore"))
if err != nil {
panic(err)
}

View File

@ -19,6 +19,7 @@ import (
"fmt"
"strings"
"github.com/casdoor/casdoor/captcha"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -48,35 +49,50 @@ func (c *ApiController) SendVerificationCode() {
checkUser := c.Ctx.Request.Form.Get("checkUser")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 {
c.ResponseError("Missing parameter.")
return
}
isHuman := false
captchaProvider := object.GetDefaultHumanCheckProvider()
if captchaProvider == nil {
isHuman = object.VerifyCaptcha(checkId, checkKey)
}
captchaProvider := captcha.GetCaptchaProvider(checkType)
if !isHuman {
c.ResponseError("Turing test failed.")
return
if captchaProvider != nil {
if checkKey == "" {
c.ResponseError("Missing parameter: checkKey.")
return
}
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
if err != nil {
c.ResponseError(err.Error())
return
}
if !isHuman {
c.ResponseError("Turing test failed.")
return
}
}
user := c.getCurrentUser()
organization := object.GetOrganization(orgId)
application := object.GetApplicationByOrganizationName(organization.Name)
if checkUser == "true" && user == nil &&
object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("No such user.")
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("Please login first")
return
}
sendResp := errors.New("Invalid dest type.")
sendResp := errors.New("Invalid dest type")
if user == nil && checkUser != "" && checkUser != "true" {
_, name := util.GetOwnerAndNameFromId(orgId)
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
}
switch destType {
case "email":
if user != nil && util.GetMaskedEmail(user.Email) == dest {
dest = user.Email
}
if !util.IsEmailValid(dest) {
c.ResponseError("Invalid Email address")
return
@ -85,6 +101,9 @@ func (c *ApiController) SendVerificationCode() {
provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone":
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
dest = user.Phone
}
if !util.IsPhoneCnValid(dest) {
c.ResponseError("Invalid phone number")
return
@ -121,7 +140,7 @@ func (c *ApiController) ResetEmailOrPhone() {
user := object.GetUser(userId)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
@ -163,3 +182,36 @@ func (c *ApiController) ResetEmailOrPhone() {
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
}
// VerifyCaptcha ...
// @Title VerifyCaptcha
// @Tag Verification API
// @router /verify-captcha [post]
func (c *ApiController) VerifyCaptcha() {
captchaType := c.Ctx.Request.Form.Get("captchaType")
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
if captchaToken == "" {
c.ResponseError("Missing parameter: captchaToken.")
return
}
if clientSecret == "" {
c.ResponseError("Missing parameter: clientSecret.")
return
}
provider := captcha.GetCaptchaProvider(captchaType)
if provider == nil {
c.ResponseError("Invalid captcha provider.")
return
}
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(isValid)
}

38
cred/argon2id.go Normal file
View File

@ -0,0 +1,38 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cred
import "github.com/alexedwards/argon2id"
type Argon2idCredManager struct{}
func NewArgon2idCredManager() *Argon2idCredManager {
cm := &Argon2idCredManager{}
return cm
}
func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
return ""
}
return hash
}
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
return match
}

View File

@ -28,6 +28,10 @@ func GetCredManager(passwordType string) CredManager {
return NewMd5UserSaltCredManager()
} else if passwordType == "bcrypt" {
return NewBcryptCredManager()
} else if passwordType == "pbkdf2-salt" {
return NewPbkdf2SaltCredManager()
} else if passwordType == "argon2id" {
return NewArgon2idCredManager()
}
return nil
}

View File

@ -32,14 +32,16 @@ func getMd5HexDigest(s string) string {
return res
}
func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
cm := &Sha256SaltCredManager{}
func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
cm := &Md5UserSaltCredManager{}
return cm
}
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
hash := getMd5HexDigest(password)
res := getMd5HexDigest(hash + userSalt)
res := getMd5HexDigest(password)
if userSalt != "" {
res = getMd5HexDigest(res + userSalt)
}
return res
}

39
cred/pbkdf2-salt.go Normal file
View File

@ -0,0 +1,39 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cred
import (
"crypto/sha256"
"encoding/base64"
"golang.org/x/crypto/pbkdf2"
)
type Pbkdf2SaltCredManager struct{}
func NewPbkdf2SaltCredManager() *Pbkdf2SaltCredManager {
cm := &Pbkdf2SaltCredManager{}
return cm
}
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
decodedSalt, _ := base64.StdEncoding.DecodeString(userSalt)
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
return base64.StdEncoding.EncodeToString(res)
}
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
}

View File

@ -38,8 +38,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
}
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
hash := getSha256HexDigest(password)
res := getSha256HexDigest(hash + organizationSalt)
res := getSha256HexDigest(password)
if organizationSalt != "" {
res = getSha256HexDigest(res + organizationSalt)
}
return res
}

View File

@ -25,3 +25,10 @@ func TestGetSaltedPassword(t *testing.T) {
cm := NewSha256SaltCredManager()
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
}
func TestGetPassword(t *testing.T) {
password := "123456"
cm := NewSha256SaltCredManager()
// https://passwordsgenerator.net/sha256-hash-generator/
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, "", ""))
}

View File

@ -5,6 +5,8 @@ services:
build:
context: ./
dockerfile: Dockerfile
target: STANDARD
entrypoint: /bin/sh -c './server --createDatabase=true'
ports:
- "8000:8000"
depends_on:
@ -13,7 +15,6 @@ services:
RUNNING_IN_DOCKER: "true"
volumes:
- ./conf:/conf/
restart: always
db:
restart: always
image: mysql:8.0.25
@ -23,4 +24,4 @@ services:
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- /usr/local/docker/mysql:/var/lib/mysql
- /usr/local/docker/mysql:/var/lib/mysql

7
docker-entrypoint.sh Normal file
View File

@ -0,0 +1,7 @@
#!/bin/bash
service mariadb start
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD}
exec /server --createDatabase=true

31
go.mod
View File

@ -3,40 +3,43 @@ module github.com/casdoor/casdoor
go 1.16
require (
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
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/aws/aws-sdk-go v1.44.4
github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.0.5
github.com/casdoor/go-sms-sender v0.2.0
github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.5.0
github.com/golang-jwt/jwt/v4 v4.1.0
github.com/golang-jwt/jwt/v4 v4.2.0
github.com/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v0.9.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.6.0
github.com/russellhaering/goxmldsig v1.1.1
github.com/satori/go.uuid v1.2.0 // indirect
github.com/satori/go.uuid v1.2.0
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.3.0 // indirect
xorm.io/core v0.7.2
xorm.io/xorm v1.0.3
xorm.io/xorm v1.0.4
)

202
go.sum
View File

@ -20,64 +20,68 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
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 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
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 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
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 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
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 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
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 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=
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/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
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 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
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/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 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b h1:EgJ6N2S0h1WfFIjU5/VVHWbMSVYXAluop97Qxpr/lfQ=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b/go.mod h1:3SAoF0F5EbcOuBD5WT9nYkbIJieBS84cUQXADbXeBsU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
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 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
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 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+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 h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
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/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
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 h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
@ -85,7 +89,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
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 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
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=
@ -95,96 +98,78 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs=
github.com/casdoor/goth v1.69.0-FIX2/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss=
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 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
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 h1:OMrhQqj1QCyDT2sxHCDjE+k8aMdn2ngTCGG7g4wrdLo=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 h1:8s2l8TVUwMXl6tZMe3+hPCRJ25nQXiA3d1x622JtOqc=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
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/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 h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+niDAlj5M8TK8=
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 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
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/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c h1:iRTj5SRYwbvsygdwVp+y9kZT145Y1s6xOPpeOEIeGc4=
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 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
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 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
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 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
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 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
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 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d h1:lBXNCxVENCipq4D1Is42JVOP4eQjlB8TQ6H69Yx5J9Q=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
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 h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
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=
@ -215,7 +200,6 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
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 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
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=
@ -226,11 +210,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
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=
@ -239,15 +220,12 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7 h1:k+KkMRk8mGOu1xG38StS7dQ+Z6oW1i9n3dgrAVU9Q/E=
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
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/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
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=
@ -257,9 +235,7 @@ github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
@ -267,9 +243,7 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
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 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk=
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
@ -286,30 +260,26 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
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 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
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 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
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 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6 h1:wxyqOzKxsRJ6vVRL9sXQ64Z45wmBuQ+OTH9sLsC5rKc=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -318,10 +288,10 @@ github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 h1:JibQrkJapVsb0pweJ5T14jZuuYZZTjll0PZBw4XfSCI=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
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=
@ -336,10 +306,10 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
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=
@ -348,11 +318,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233 h1:jmJndGFBPjNWW+MAYarU/Nl8QrQVzbw4B/AYE0LzETo=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -378,8 +345,9 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
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/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.12.1/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -395,31 +363,26 @@ 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 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400 h1:091wFNQB3PXcL5+me0joH7EiyqQaI0wGMpEjVCkK04U=
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
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 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
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 h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
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 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
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.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/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=
@ -430,26 +393,20 @@ github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL
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 h1:9AUN7+NK4IV+A11igqjQM5i8obiOAQo4SXgjaxe+orI=
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 h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
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/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973 h1:iCnkJ/qjKZGdZnlcj1N55AxPDan814kpc3s1cDpQKd8=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
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 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
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=
@ -459,8 +416,13 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
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/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -470,10 +432,8 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0
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 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
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 h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
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=
@ -484,17 +444,14 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
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 h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
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 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
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 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
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=
@ -512,6 +469,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
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-20191112182307-2180aed22343/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=
@ -528,9 +486,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
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=
@ -546,8 +506,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
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 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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=
@ -562,6 +522,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
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-20191112214154-59a1497f0cea/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=
@ -581,25 +542,28 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
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/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/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=
@ -642,7 +606,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f h1:18s2P7JILnVhIF2+ZtGJQ9czV5bvTsb13/UGtNPDbjA=
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -665,7 +628,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts=
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -705,7 +667,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe h1:6SgESkjJknFUnsfQ2yxQbmTAi37BxhwS/riq+VdLo9c=
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@ -720,7 +681,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -733,7 +693,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
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 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
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=
@ -743,7 +702,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
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=
@ -752,7 +710,6 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkp
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 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@ -774,17 +731,14 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
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 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
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 v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.0.4 h1:UBXA4I3NhiyjXfPqxXUkS2t5hMta9SSPATeMMaZg9oA=
xorm.io/xorm v1.0.4/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=

136
idp/adfs.go Normal file
View File

@ -0,0 +1,136 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jwt"
"golang.org/x/oauth2"
)
type AdfsIdProvider struct {
Client *http.Client
Config *oauth2.Config
Host string
}
func NewAdfsIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *AdfsIdProvider {
idp := &AdfsIdProvider{}
config := idp.getConfig(hostUrl)
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
idp.Host = hostUrl
return idp
}
func (idp *AdfsIdProvider) SetHttpClient(client *http.Client) {
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
}
idp.Client = client
idp.Client.Transport = tr
}
func (idp *AdfsIdProvider) getConfig(hostUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
AuthURL: fmt.Sprintf("%s/adfs/oauth2/authorize", hostUrl),
TokenURL: fmt.Sprintf("%s/adfs/oauth2/token", hostUrl),
}
var config = &oauth2.Config{
Endpoint: endpoint,
}
return config
}
type AdfsToken struct {
IdToken string `json:"id_token"`
ExpiresIn int `json:"expires_in"`
ErrMsg string `json:"error_description"`
}
// get more detail via: https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/overview/ad-fs-openid-connect-oauth-flows-scenarios#request-an-access-token
func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
payload := url.Values{}
payload.Set("code", code)
payload.Set("grant_type", "authorization_code")
payload.Set("client_id", idp.Config.ClientID)
payload.Set("redirect_uri", idp.Config.RedirectURL)
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
pToken := &AdfsToken{}
err = json.Unmarshal(data, pToken)
if err != nil {
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
}
if pToken.ErrMsg != "" {
return nil, fmt.Errorf("pToken.Errmsg = %s", pToken.ErrMsg)
}
token := &oauth2.Token{
AccessToken: pToken.IdToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
return token, nil
}
// Since the userinfo endpoint of ADFS only returns sub,
// the id_token is used to resolve the userinfo
func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
resp, err := idp.Client.Get(fmt.Sprintf("%s/adfs/discovery/keys", idp.Host))
if err != nil {
return nil, err
}
keyset, err := jwk.Parse(resp.Body)
if err != nil {
return nil, err
}
tokenSrc := []byte(token.AccessToken)
publicKey, _ := keyset.Keys[0].Materialize()
id_token, _ := jwt.Parse(bytes.NewReader(tokenSrc), jwt.WithVerify(jwa.RS256, publicKey))
sid, _ := id_token.Get("sid")
upn, _ := id_token.Get("upn")
name, _ := id_token.Get("unique_name")
userinfo := &UserInfo{
Id: sid.(string),
Username: name.(string),
DisplayName: name.(string),
Email: upn.(string),
}
return userinfo, nil
}

292
idp/alipay.go Normal file
View File

@ -0,0 +1,292 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"io"
"io/ioutil"
"net/http"
"net/url"
"sort"
"strings"
"time"
"golang.org/x/oauth2"
)
type AlipayIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
// NewAlipayIdProvider ...
func NewAlipayIdProvider(clientId string, clientSecret string, redirectUrl string) *AlipayIdProvider {
idp := &AlipayIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
// SetHttpClient ...
func (idp *AlipayIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *AlipayIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
AuthURL: "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm",
TokenURL: "https://openapi.alipay.com/gateway.do",
}
var config = &oauth2.Config{
Scopes: []string{"", ""},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type AlipayAccessToken struct {
Response AlipaySystemOauthTokenResponse `json:"alipay_system_oauth_token_response"`
Sign string `json:"sign"`
}
type AlipaySystemOauthTokenResponse struct {
AccessToken string `json:"access_token"`
AlipayUserId string `json:"alipay_user_id"`
ExpiresIn int `json:"expires_in"`
ReExpiresIn int `json:"re_expires_in"`
RefreshToken string `json:"refresh_token"`
UserId string `json:"user_id"`
}
// GetToken use code to get access_token
func (idp *AlipayIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
ClientId string `json:"app_id"`
CharSet string `json:"charset"`
Code string `json:"code"`
GrantType string `json:"grant_type"`
Method string `json:"method"`
SignType string `json:"sign_type"`
TimeStamp string `json:"timestamp"`
Version string `json:"version"`
}{idp.Config.ClientID, "utf-8", code, "authorization_code", "alipay.system.oauth.token", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
pToken := &AlipayAccessToken{}
err = json.Unmarshal(data, pToken)
if err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: pToken.Response.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.Response.ExpiresIn), 0),
}
return token, nil
}
/*
{
"alipay_user_info_share_response":{
"code":"10000",
"msg":"Success",
"avatar":"https:\/\/tfs.alipayobjects.com\/images\/partner\/T1.QxFXk4aXXXXXXXX",
"nick_name":"zhangsan",
"user_id":"2099222233334444"
},
"sign":"m8rWJeqfoa5tDQRRVnPhRHcpX7NZEgjIPTPF1QBxos6XXXXXXXXXXXXXXXXXXXXXXXXXX"
}
*/
type AlipayUserResponse struct {
AlipayUserInfoShareResponse AlipayUserInfoShareResponse `json:"alipay_user_info_share_response"`
Sign string `json:"sign"`
}
type AlipayUserInfoShareResponse struct {
Code string `json:"code"`
Msg string `json:"msg"`
Avatar string `json:"avatar"`
NickName string `json:"nick_name"`
UserId string `json:"user_id"`
}
// GetUserInfo Use access_token to get UserInfo
func (idp *AlipayIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
atUserInfo := &AlipayUserResponse{}
accessToken := token.AccessToken
pTokenParams := &struct {
ClientId string `json:"app_id"`
CharSet string `json:"charset"`
AuthToken string `json:"auth_token"`
Method string `json:"method"`
SignType string `json:"sign_type"`
TimeStamp string `json:"timestamp"`
Version string `json:"version"`
}{idp.Config.ClientID, "utf-8", accessToken, "alipay.user.info.share", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, atUserInfo)
if err != nil {
return nil, err
}
userInfo := UserInfo{
Id: atUserInfo.AlipayUserInfoShareResponse.UserId,
Username: atUserInfo.AlipayUserInfoShareResponse.NickName,
DisplayName: atUserInfo.AlipayUserInfoShareResponse.NickName,
AvatarUrl: atUserInfo.AlipayUserInfoShareResponse.Avatar,
}
return &userInfo, nil
}
func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyJson := make(map[string]interface{})
err = json.Unmarshal(bs, &bodyJson)
if err != nil {
return nil, err
}
formData := url.Values{}
for k := range bodyJson {
formData.Set(k, bodyJson[k].(string))
}
sign, err := rsaSignWithRSA256(getStringToSign(formData), idp.Config.ClientSecret)
if err != nil {
return nil, err
}
formData.Set("sign", sign)
resp, err := idp.Client.PostForm(targetUrl, formData)
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
}
// get the string to sign, see https://opendocs.alipay.com/common/02kf5q
func getStringToSign(formData url.Values) string {
keys := make([]string, 0, len(formData))
for k := range formData {
keys = append(keys, k)
}
sort.Strings(keys)
str := ""
for _, k := range keys {
if k == "sign" || formData[k][0] == "" {
continue
} else {
str += "&" + k + "=" + formData[k][0]
}
}
str = strings.Trim(str, "&")
return str
}
// use privateKey to sign the content
func rsaSignWithRSA256(signContent string, privateKey string) (string, error) {
privateKey = formatPrivateKey(privateKey)
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
panic("fail to parse privateKey")
}
h := sha256.New()
h.Write([]byte(signContent))
hashed := h.Sum(nil)
privateKeyRSA, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", err
}
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKeyRSA.(*rsa.PrivateKey), crypto.SHA256, hashed)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// privateKey in database is a string, format it to PEM style
func formatPrivateKey(privateKey string) string {
// each line length is 64
preFmtPrivateKey := ""
for i := 0; ; {
if i+64 <= len(privateKey) {
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:i+64] + "\n"
i += 64
} else {
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:]
break
}
}
privateKey = strings.Trim(preFmtPrivateKey, "\n")
// add pkcs#8 BEGIN and END
PemBegin := "-----BEGIN PRIVATE KEY-----\n"
PemEnd := "\n-----END PRIVATE KEY-----"
if !strings.HasPrefix(privateKey, PemBegin) {
privateKey = PemBegin + privateKey
}
if !strings.HasSuffix(privateKey, PemEnd) {
privateKey = privateKey + PemEnd
}
return privateKey
}

View File

@ -18,7 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

221
idp/bilibili.go Normal file
View File

@ -0,0 +1,221 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
"golang.org/x/oauth2"
)
type BilibiliIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewBilibiliIdProvider(clientId string, clientSecret string, redirectUrl string) *BilibiliIdProvider {
idp := &BilibiliIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *BilibiliIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *BilibiliIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://api.bilibili.com/x/account-oauth2/v1/token",
AuthURL: "http://member.bilibili.com/arcopen/fn/user/account/info",
}
var config = &oauth2.Config{
Scopes: []string{"", ""},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type BilibiliProviderToken struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
}
type BilibiliIdProviderTokenResponse struct {
Code int `json:"code"`
Message string `json:"message"`
TTL int `json:"ttl"`
Data BilibiliProviderToken `json:"data"`
}
/*
{
"code": 0,
"message": "0",
"ttl": 1,
"data": {
"access_token": "d30bedaa4d8eb3128cf35ddc1030e27d",
"expires_in": 1630220614,
"refresh_token": "WxFDKwqScZIQDm4iWmKDvetyFugM6HkX"
}
}
*/
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://openhome.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c
func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
GrantType string `json:"grant_type"`
Code string `json:"code"`
}{
idp.Config.ClientID,
idp.Config.ClientSecret,
"authorization_code",
code,
}
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
response := &BilibiliIdProviderTokenResponse{}
err = json.Unmarshal(data, response)
if err != nil {
return nil, err
}
if response.Code != 0 {
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", response.Code, response.Message)
}
token := &oauth2.Token{
AccessToken: response.Data.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(response.Data.ExpiresIn), 0),
RefreshToken: response.Data.RefreshToken,
}
return token, nil
}
/*
{
"code": 0,
"message": "0",
"ttl": 1,
"data": {
"name":"bilibili",
"face":"http://i0.hdslb.com/bfs/face/e1c99895a9f9df4f260a70dc7e227bcb46cf319c.jpg",
"openid":"9205eeaa1879skxys969ed47874f225c3"
}
}
*/
type BilibiliUserInfo struct {
Name string `json:"name"`
Face string `json:"face"`
OpenId string `json:"openid`
}
type BilibiliUserInfoResponse struct {
Code int `json:"code"`
Message string `json:"message"`
TTL int `json:"ttl"`
Data BilibiliUserInfo `json:"data"`
}
// GetUserInfo Use access_token to get UserInfo
// get more detail via: https://openhome.bilibili.com/doc/4/feb66f99-7d87-c206-00e7-d84164cd701c
func (idp *BilibiliIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
accessToken := token.AccessToken
clientId := idp.Config.ClientID
params := url.Values{}
params.Add("client_id", clientId)
params.Add("access_token", accessToken)
userInfoUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.AuthURL, params.Encode())
resp, err := idp.Client.Get(userInfoUrl)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
bUserInfoResponse := &BilibiliUserInfoResponse{}
if err = json.Unmarshal(data, bUserInfoResponse); err != nil {
return nil, err
}
if bUserInfoResponse.Code != 0 {
return nil, fmt.Errorf("userinfo.Errcode = %d, userinfo.Errmsg = %s", bUserInfoResponse.Code, bUserInfoResponse.Message)
}
userInfo := &UserInfo{
Id: bUserInfoResponse.Data.OpenId,
Username: bUserInfoResponse.Data.Name,
DisplayName: bUserInfoResponse.Data.Name,
AvatarUrl: bUserInfoResponse.Data.Face,
}
return userInfo, nil
}
func (idp *BilibiliIdProvider) 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
}

159
idp/casdoor.go Normal file
View File

@ -0,0 +1,159 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"golang.org/x/oauth2"
)
type CasdoorIdProvider struct {
Client *http.Client
Config *oauth2.Config
Host string
}
func NewCasdoorIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *CasdoorIdProvider {
idp := &CasdoorIdProvider{}
config := idp.getConfig(hostUrl)
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
idp.Host = hostUrl
return idp
}
func (idp *CasdoorIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *CasdoorIdProvider) getConfig(hostUrl string) *oauth2.Config {
return &oauth2.Config{
Endpoint: oauth2.Endpoint{
TokenURL: hostUrl + "/api/login/oauth/access_token",
},
Scopes: []string{"openid email profile"},
}
}
type CasdoorToken struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
resp, err := http.PostForm(idp.Config.Endpoint.TokenURL, url.Values{
"client_id": {idp.Config.ClientID},
"client_secret": {idp.Config.ClientSecret},
"code": {code},
"grant_type": {"authorization_code"},
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
pToken := &CasdoorToken{}
err = json.Unmarshal(body, pToken)
if err != nil {
return nil, err
}
//check if token is expired
if pToken.ExpiresIn <= 0 {
return nil, fmt.Errorf("%s", pToken.AccessToken)
}
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
return token, nil
}
/*
{
"sub": "2f80c349-4beb-407f-b1f0-528aac0f1acd",
"iss": "https://door.casbin.com",
"aud": "7a11****0fa2172",
"name": "admin",
"preferred_username": "Admin",
"email": "admin@example.com",
"picture": "https://casbin.org/img/casbin.svg",
"address": "Guangdong",
"phone": "12345678910"
}
*/
type CasdoorUserInfo struct {
Id string `json:"sub"`
Name string `json:"name"`
DisplayName string `json:"preferred_username"`
Email string `json:"email"`
AvatarUrl string `json:"picture"`
Status string `json:"status"`
Msg string `json:"msg"`
}
func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
cdUserinfo := &CasdoorUserInfo{}
accessToken := token.AccessToken
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/userinfo", idp.Host), nil)
if err != nil {
return nil, err
}
//add accesstoken to bearer token
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
resp, err := idp.Client.Do(request)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, cdUserinfo)
if err != nil {
return nil, err
}
if cdUserinfo.Status != "" {
return nil, fmt.Errorf("err: %s", cdUserinfo.Msg)
}
userInfo := &UserInfo{
Id: cdUserinfo.Id,
Username: cdUserinfo.Name,
DisplayName: cdUserinfo.DisplayName,
Email: cdUserinfo.Email,
AvatarUrl: cdUserinfo.AvatarUrl,
}
return userInfo, nil
}

109
idp/custom.go Normal file
View File

@ -0,0 +1,109 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
_ "net/url"
_ "time"
"golang.org/x/oauth2"
)
type CustomIdProvider struct {
Client *http.Client
Config *oauth2.Config
UserInfoUrl string
}
func NewCustomIdProvider(clientId string, clientSecret string, redirectUrl string, authUrl string, tokenUrl string, userInfoUrl string) *CustomIdProvider {
idp := &CustomIdProvider{}
idp.UserInfoUrl = userInfoUrl
var config = &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
Endpoint: oauth2.Endpoint{
AuthURL: authUrl,
TokenURL: tokenUrl,
},
}
idp.Config = config
return idp
}
func (idp *CustomIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *CustomIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
type CustomUserInfo struct {
Id string `json:"sub"`
Name string `json:"name"`
DisplayName string `json:"preferred_username"`
Email string `json:"email"`
AvatarUrl string `json:"picture"`
Status string `json:"status"`
Msg string `json:"msg"`
}
func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
ctUserinfo := &CustomUserInfo{}
accessToken := token.AccessToken
request, err := http.NewRequest("GET", idp.UserInfoUrl, nil)
if err != nil {
return nil, err
}
//add accessToken to request header
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
resp, err := idp.Client.Do(request)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, ctUserinfo)
if err != nil {
return nil, err
}
if ctUserinfo.Status != "" {
return nil, fmt.Errorf("err: %s", ctUserinfo.Msg)
}
userInfo := &UserInfo{
Id: ctUserinfo.Id,
Username: ctUserinfo.Name,
DisplayName: ctUserinfo.DisplayName,
Email: ctUserinfo.Email,
AvatarUrl: ctUserinfo.AvatarUrl,
}
return userInfo, nil
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -142,8 +143,9 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -178,7 +180,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

198
idp/douyin.go Normal file
View File

@ -0,0 +1,198 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"golang.org/x/oauth2"
)
type DouyinIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewDouyinIdProvider(clientId string, clientSecret string, redirectUrl string) *DouyinIdProvider {
idp := &DouyinIdProvider{}
idp.Config = idp.getConfig(clientId, clientSecret, redirectUrl)
return idp
}
func (idp *DouyinIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *DouyinIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://open.douyin.com/oauth/access_token",
AuthURL: "https://open.douyin.com/platform/oauth/connect",
}
var config = &oauth2.Config{
Scopes: []string{"user_info"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
/*
{
"data": {
"access_token": "access_token",
"description": "",
"error_code": "0",
"expires_in": "86400",
"open_id": "aaa-bbb-ccc",
"refresh_expires_in": "86400",
"refresh_token": "refresh_token",
"scope": "user_info"
},
"message": "<nil>"
}
*/
type DouyinTokenResp struct {
Data struct {
AccessToken string `json:"access_token"`
ExpiresIn int64 `json:"expires_in"`
OpenId string `json:"open_id"`
RefreshToken string `json:"refresh_token"`
Scope string `json:"scope"`
} `json:"data"`
Message string `json:"message"`
}
// GetToken use code to get access_token
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-permission/get-access-token
func (idp *DouyinIdProvider) GetToken(code string) (*oauth2.Token, error) {
payload := url.Values{}
payload.Set("code", code)
payload.Set("grant_type", "authorization_code")
payload.Set("client_key", idp.Config.ClientID)
payload.Set("client_secret", idp.Config.ClientSecret)
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
tokenResp := &DouyinTokenResp{}
err = json.Unmarshal(data, tokenResp)
if err != nil {
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
}
token := &oauth2.Token{
AccessToken: tokenResp.Data.AccessToken,
RefreshToken: tokenResp.Data.RefreshToken,
Expiry: time.Unix(time.Now().Unix()+tokenResp.Data.ExpiresIn, 0),
}
raw := make(map[string]interface{})
raw["open_id"] = tokenResp.Data.OpenId
token = token.WithExtra(raw)
return token, nil
}
// get more details via: https://open.douyin.com/platform/doc?doc=docs/openapi/account-management/get-account-open-info
/*
{
"data": {
"avatar": "https://example.com/x.jpeg",
"city": "上海",
"country": "中国",
"description": "",
"e_account_role": "<nil>",
"error_code": "0",
"gender": "<nil>",
"nickname": "张伟",
"open_id": "0da22181-d833-447f-995f-1beefea5bef3",
"province": "上海",
"union_id": "1ad4e099-4a0c-47d1-a410-bffb4f2f64a4"
}
}
*/
type DouyinUserInfo struct {
Data struct {
Avatar string `json:"avatar"`
City string `json:"city"`
Country string `json:"country"`
// 0->unknown, 1->male, 2->female
Gender int64 `json:"gender"`
Nickname string `json:"nickname"`
OpenId string `json:"open_id"`
Province string `json:"province"`
} `json:"data"`
}
// GetUserInfo use token to get user profile
func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
body := &struct {
AccessToken string `json:"access_token"`
OpenId string `json:"open_id"`
}{token.AccessToken, token.Extra("open_id").(string)}
data, err := json.Marshal(body)
if err != nil {
return nil, err
}
req, err := http.NewRequest("GET", "https://open.douyin.com/oauth/userinfo/", bytes.NewReader(data))
if err != nil {
return nil, err
}
req.Header.Add("access-token", token.AccessToken)
req.Header.Add("Accept", "application/json")
req.Header.Add("Content-Type", "application/json")
resp, err := idp.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var douyinUserInfo DouyinUserInfo
err = json.Unmarshal(respBody, &douyinUserInfo)
if err != nil {
return nil, err
}
userInfo := UserInfo{
Id: douyinUserInfo.Data.OpenId,
Username: douyinUserInfo.Data.Nickname,
DisplayName: douyinUserInfo.Data.Nickname,
AvatarUrl: douyinUserInfo.Data.Avatar,
}
return &userInfo, nil
}

View File

@ -122,9 +122,9 @@ func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
//}
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.
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"`
@ -164,6 +164,7 @@ func (idp *FacebookIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
userInfo := UserInfo{
Id: facebookUserInfo.Id,
Username: facebookUserInfo.Name,
DisplayName: facebookUserInfo.Name,
Email: facebookUserInfo.Email,
AvatarUrl: facebookUserInfo.Picture.Data.Url,

View File

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

View File

@ -15,11 +15,13 @@
package idp
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
"golang.org/x/oauth2"
@ -60,9 +62,38 @@ func (idp *GithubIdProvider) getConfig() *oauth2.Config {
return config
}
type GithubToken struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
Error string `json:"error"`
}
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
params := &struct {
Code string `json:"code"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
}{code, idp.Config.ClientID, idp.Config.ClientSecret}
data, err := idp.postWithBody(params, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
pToken := &GithubToken{}
if err = json.Unmarshal(data, pToken); err != nil {
return nil, err
}
if pToken.Error != "" {
return nil, fmt.Errorf("err: %s", pToken.Error)
}
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
TokenType: "Bearer",
}
return token, nil
}
//{
@ -172,7 +203,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -192,3 +223,30 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
return &userInfo, nil
}
func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
r := strings.NewReader(string(bs))
req, _ := http.NewRequest("POST", url, r)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
resp, err := idp.Client.Do(req)
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
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

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

View File

@ -22,35 +22,35 @@ import (
"time"
"github.com/casdoor/casdoor/util"
"github.com/markbates/goth"
"github.com/markbates/goth/providers/amazon"
"github.com/markbates/goth/providers/apple"
"github.com/markbates/goth/providers/azuread"
"github.com/markbates/goth/providers/bitbucket"
"github.com/markbates/goth/providers/digitalocean"
"github.com/markbates/goth/providers/discord"
"github.com/markbates/goth/providers/dropbox"
"github.com/markbates/goth/providers/facebook"
"github.com/markbates/goth/providers/gitea"
"github.com/markbates/goth/providers/github"
"github.com/markbates/goth/providers/gitlab"
"github.com/markbates/goth/providers/google"
"github.com/markbates/goth/providers/heroku"
"github.com/markbates/goth/providers/instagram"
"github.com/markbates/goth/providers/kakao"
"github.com/markbates/goth/providers/line"
"github.com/markbates/goth/providers/linkedin"
"github.com/markbates/goth/providers/microsoftonline"
"github.com/markbates/goth/providers/paypal"
"github.com/markbates/goth/providers/salesforce"
"github.com/markbates/goth/providers/shopify"
"github.com/markbates/goth/providers/slack"
"github.com/markbates/goth/providers/steam"
"github.com/markbates/goth/providers/tumblr"
"github.com/markbates/goth/providers/twitter"
"github.com/markbates/goth/providers/yahoo"
"github.com/markbates/goth/providers/yandex"
"github.com/markbates/goth/providers/zoom"
"github.com/casdoor/goth"
"github.com/casdoor/goth/providers/amazon"
"github.com/casdoor/goth/providers/apple"
"github.com/casdoor/goth/providers/azuread"
"github.com/casdoor/goth/providers/bitbucket"
"github.com/casdoor/goth/providers/digitalocean"
"github.com/casdoor/goth/providers/discord"
"github.com/casdoor/goth/providers/dropbox"
"github.com/casdoor/goth/providers/facebook"
"github.com/casdoor/goth/providers/gitea"
"github.com/casdoor/goth/providers/github"
"github.com/casdoor/goth/providers/gitlab"
"github.com/casdoor/goth/providers/google"
"github.com/casdoor/goth/providers/heroku"
"github.com/casdoor/goth/providers/instagram"
"github.com/casdoor/goth/providers/kakao"
"github.com/casdoor/goth/providers/line"
"github.com/casdoor/goth/providers/linkedin"
"github.com/casdoor/goth/providers/microsoftonline"
"github.com/casdoor/goth/providers/paypal"
"github.com/casdoor/goth/providers/salesforce"
"github.com/casdoor/goth/providers/shopify"
"github.com/casdoor/goth/providers/slack"
"github.com/casdoor/goth/providers/steam"
"github.com/casdoor/goth/providers/tumblr"
"github.com/casdoor/goth/providers/twitter"
"github.com/casdoor/goth/providers/yahoo"
"github.com/casdoor/goth/providers/yandex"
"github.com/casdoor/goth/providers/zoom"
"golang.org/x/oauth2"
)
@ -231,6 +231,10 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
value.Add("code", code)
}
accessToken, err := idp.Session.Authorize(idp.Provider, value)
if err != nil {
return nil, err
}
//Get ExpiresAt's value
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
if valueOfExpire.IsValid() {
@ -240,7 +244,8 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
AccessToken: accessToken,
Expiry: expireAt,
}
return &token, err
return &token, nil
}
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -69,7 +69,7 @@ func (idp *InfoflowInternalIdProvider) GetToken(code string) (*oauth2.Token, err
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -147,7 +147,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -165,7 +165,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -143,7 +144,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -161,7 +162,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -196,7 +197,7 @@ func (idp *InfoflowIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

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

View File

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

200
idp/okta.go Normal file
View File

@ -0,0 +1,200 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"golang.org/x/oauth2"
)
type OktaIdProvider struct {
Client *http.Client
Config *oauth2.Config
Host string
}
func NewOktaIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *OktaIdProvider {
idp := &OktaIdProvider{}
config := idp.getConfig(hostUrl, clientId, clientSecret, redirectUrl)
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
idp.Host = hostUrl
return idp
}
func (idp *OktaIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *OktaIdProvider) getConfig(hostUrl string, clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: fmt.Sprintf("%s/v1/token", hostUrl),
AuthURL: fmt.Sprintf("%s/v1/authorize", hostUrl),
}
var config = &oauth2.Config{
// openid is required for authentication requests
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#reserved-scopes
Scopes: []string{"openid", "profile", "email"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#token
/*
{
"access_token" : "eyJhbGciOiJSUzI1NiJ9.eyJ2ZXIiOjEsImlzcyI6Imh0dHA6Ly9yYWluLm9rdGExLmNvbToxODAyIiwiaWF0IjoxNDQ5Nj
I0MDI2LCJleHAiOjE0NDk2Mjc2MjYsImp0aSI6IlVmU0lURzZCVVNfdHA3N21BTjJxIiwic2NvcGVzIjpbIm9wZW5pZCIsI
mVtYWlsIl0sImNsaWVudF9pZCI6InVBYXVub2ZXa2FESnh1a0NGZUJ4IiwidXNlcl9pZCI6IjAwdWlkNEJ4WHc2STZUVjRt
MGczIn0.HaBu5oQxdVCIvea88HPgr2O5evqZlCT4UXH4UKhJnZ5px-ArNRqwhxXWhHJisslswjPpMkx1IgrudQIjzGYbtLF
jrrg2ueiU5-YfmKuJuD6O2yPWGTsV7X6i7ABT6P-t8PRz_RNbk-U1GXWIEkNnEWbPqYDAm_Ofh7iW0Y8WDA5ez1jbtMvd-o
XMvJLctRiACrTMLJQ2e5HkbUFxgXQ_rFPNHJbNSUBDLqdi2rg_ND64DLRlXRY7hupNsvWGo0gF4WEUk8IZeaLjKw8UoIs-E
TEwJlAMcvkhoVVOsN5dPAaEKvbyvPC1hUGXb4uuThlwdD3ECJrtwgKqLqcWonNtiw",
"token_type" : "Bearer",
"expires_in" : 3600,
"scope" : "openid email",
"refresh_token" : "a9VpZDRCeFh3Nkk2VdY",
"id_token" : "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMHVpZDRCeFh3Nkk2VFY0bTBnMyIsImVtYWlsIjoid2VibWFzdGVyQGNsb3VkaXR1ZG
UubmV0IiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInZlciI6MSwiaXNzIjoiaHR0cDovL3JhaW4ub2t0YTEuY29tOjE4MDIiLCJsb
2dpbiI6ImFkbWluaXN0cmF0b3IxQGNsb3VkaXR1ZGUubmV0IiwiYXVkIjoidUFhdW5vZldrYURKeHVrQ0ZlQngiLCJpYXQiOjE0
NDk2MjQwMjYsImV4cCI6MTQ0OTYyNzYyNiwiYW1yIjpbInB3ZCJdLCJqdGkiOiI0ZUFXSk9DTUIzU1g4WGV3RGZWUiIsImF1dGh
fdGltZSI6MTQ0OTYyNDAyNiwiYXRfaGFzaCI6ImNwcUtmZFFBNWVIODkxRmY1b0pyX1EifQ.Btw6bUbZhRa89DsBb8KmL9rfhku
--_mbNC2pgC8yu8obJnwO12nFBepui9KzbpJhGM91PqJwi_AylE6rp-ehamfnUAO4JL14PkemF45Pn3u_6KKwxJnxcWxLvMuuis
nvIs7NScKpOAab6ayZU0VL8W6XAijQmnYTtMWQfSuaaR8rYOaWHrffh3OypvDdrQuYacbkT0csxdrayXfBG3UF5-ZAlhfch1fhF
T3yZFdWwzkSDc0BGygfiFyNhCezfyT454wbciSZgrA9ROeHkfPCaX7KCFO8GgQEkGRoQntFBNjluFhNLJIUkEFovEDlfuB4tv_M
8BM75celdy3jkpOurg"
}
*/
type OktaToken struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
RefreshToken string `json:"refresh_token"`
IdToken string `json:"id_token"`
}
// GetToken use code to get access_token
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#token
func (idp *OktaIdProvider) GetToken(code string) (*oauth2.Token, error) {
payload := url.Values{}
payload.Set("code", code)
payload.Set("grant_type", "authorization_code")
payload.Set("client_id", idp.Config.ClientID)
payload.Set("client_secret", idp.Config.ClientSecret)
payload.Set("redirect_uri", idp.Config.RedirectURL)
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
pToken := &OktaToken{}
err = json.Unmarshal(data, pToken)
if err != nil {
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
}
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
TokenType: "Bearer",
RefreshToken: pToken.RefreshToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
return token, nil
}
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#userinfo
/*
{
"sub": "00uid4BxXw6I6TV4m0g3",
"name" :"John Doe",
"nickname":"Jimmy",
"given_name":"John",
"middle_name":"James",
"family_name":"Doe",
"profile":"https://example.com/john.doe",
"zoneinfo":"America/Los_Angeles",
"locale":"en-US",
"updated_at":1311280970,
"email":"john.doe@example.com",
"email_verified":true,
"address" : { "street_address":"123 Hollywood Blvd.", "locality":"Los Angeles", "region":"CA", "postal_code":"90210", "country":"US" },
"phone_number":"+1 (425) 555-1212"
}
*/
type OktaUserInfo struct {
Email string `json:"email"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Picture string `json:"picture"`
Sub string `json:"sub"`
}
// GetUserInfo use token to get user profile
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#userinfo
func (idp *OktaIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/userinfo", idp.Host), nil)
if err != nil {
return nil, err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token.AccessToken))
req.Header.Add("Accept", "application/json")
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 oktaUserInfo OktaUserInfo
err = json.Unmarshal(body, &oktaUserInfo)
if err != nil {
return nil, err
}
userInfo := UserInfo{
Id: oktaUserInfo.Sub,
Username: oktaUserInfo.PreferredUsername,
DisplayName: oktaUserInfo.Name,
Email: oktaUserInfo.Email,
AvatarUrl: oktaUserInfo.Picture,
}
return &userInfo, nil
}

View File

@ -35,7 +35,7 @@ type IdProvider interface {
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string) IdProvider {
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string, authUrl string, tokenUrl string, userInfoUrl string) IdProvider {
if typ == "GitHub" {
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Google" {
@ -66,8 +66,14 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "GitLab" {
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Adfs" {
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Baidu" {
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Alipay" {
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Custom" {
return NewCustomIdProvider(clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userInfoUrl)
} else if typ == "Infoflow" {
if subType == "Internal" {
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
@ -76,8 +82,16 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
} else {
return nil
}
} else if typ == "Casdoor" {
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Okta" {
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Douyin" {
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
} else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
} else if typ == "Bilibili" {
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
}
return nil

View File

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

82
idp/wechat_miniprogram.go Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
)
type WeChatMiniProgramIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewWeChatMiniProgramIdProvider(clientId string, clientSecret string) *WeChatMiniProgramIdProvider {
idp := &WeChatMiniProgramIdProvider{}
config := idp.getConfig(clientId, clientSecret)
idp.Config = config
idp.Client = &http.Client{}
return idp
}
func (idp *WeChatMiniProgramIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *WeChatMiniProgramIdProvider) getConfig(clientId string, clientSecret string) *oauth2.Config {
var config = &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
}
return config
}
type WeChatMiniProgramSessionResponse struct {
Openid string `json:"openid"`
SessionKey string `json:"session_key"`
Unionid string `json:"unionid"`
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
}
func (idp *WeChatMiniProgramIdProvider) GetSessionByCode(code string) (*WeChatMiniProgramSessionResponse, error) {
sessionUri := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", idp.Config.ClientID, idp.Config.ClientSecret, code)
sessionResponse, err := idp.Client.Get(sessionUri)
if err != nil {
return nil, err
}
defer sessionResponse.Body.Close()
data, err := ioutil.ReadAll(sessionResponse.Body)
if err != nil {
return nil, err
}
var session WeChatMiniProgramSessionResponse
err = json.Unmarshal(data, &session)
if err != nil {
return nil, err
}
if session.Errcode != 0 {
return nil, fmt.Errorf("err: %s", session.Errmsg)
}
return &session, nil
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
@ -72,7 +72,7 @@ func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -111,6 +111,7 @@ type WecomInternalUserInfo struct {
Email string `json:"email"`
Avatar string `json:"avatar"`
OpenId string `json:"open_userid"`
UserId string `json:"userid"`
}
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
@ -122,7 +123,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -143,7 +144,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -156,7 +157,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
}
userInfo := UserInfo{
Id: infoResp.OpenId,
Id: infoResp.UserId,
Username: infoResp.Name,
DisplayName: infoResp.Name,
Email: infoResp.Email,

View File

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

View File

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

160
init_data.json.template Normal file
View File

@ -0,0 +1,160 @@
{
"organizations": [
{
"owner": "",
"name": "",
"displayName": "",
"websiteUrl": "",
"favicon": "",
"passwordType": "",
"phonePrefix": "",
"defaultAvatar": "",
"tags": [""]
}
],
"applications": [
{
"owner": "",
"name": "",
"displayName": "",
"logo": "",
"homepageUrl": "",
"organization": "",
"cert": "",
"enablePassword": true,
"enableSignUp": true,
"clientId": "",
"clientSecret": "",
"providers": [
{
"name": "",
"canSignUp": true,
"canSignIn": true,
"canUnlink": false,
"prompted": false,
"alertType": "None"
}
],
"signupItems": [
{
"name": "ID",
"visible": false,
"required": true,
"prompted": false,
"rule": "Random"
},
{
"name": "Username",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Display name",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Password",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Confirm password",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Email",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Phone",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
},
{
"name": "Agreement",
"visible": true,
"required": true,
"prompted": false,
"rule": "None"
}
],
"redirectUris": [""],
"expireInHours": 168
}
],
"users": [
{
"owner": "",
"name": "",
"type": "normal-user",
"password": "",
"displayName": "",
"avatar": "",
"email": "",
"phone": "",
"address": [],
"affiliation": "",
"tag": "",
"score": 2000,
"ranking": 1,
"isAdmin": true,
"isGlobalAdmin": true,
"isForbidden": false,
"isDeleted": false,
"signupApplication": "",
"createdIp": ""
}
],
"providers": [
{
"owner": "",
"name": "",
"displayName": "",
"category": "",
"type": ""
}
],
"certs": [
{
"owner": "",
"name": "",
"displayName": "",
"scope": "JWT",
"type": "x509",
"cryptoAlgorithm": "RS256",
"bitSize": 4096,
"expireInYears": 20,
"publicKey": "",
"privateKey": ""
}
],
"ldaps": [
{
"id": "",
"owner": "",
"serverName": "",
"host": "",
"port": 389,
"admin": "",
"passwd": "",
"baseDn": "",
"autoSync": 0,
"lastSync": ""
}
]
}

13
main.go
View File

@ -22,23 +22,27 @@ import (
"github.com/astaxie/beego/logs"
_ "github.com/astaxie/beego/session/redis"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/routers"
_ "github.com/casdoor/casdoor/routers"
"github.com/casdoor/casdoor/util"
)
func main() {
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
createDatabase := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
flag.Parse()
object.InitAdapter(*createDatabase)
object.InitDb()
object.InitFromFile()
object.InitDefaultStorageProvider()
object.InitLdapAutoSynchronizer()
proxy.InitHttpClient()
authz.InitAuthz()
go object.RunSyncUsersJob()
util.SafeGoroutine(func() {object.RunSyncUsersJob()})
//beego.DelStaticPath("/static")
beego.SetStaticPath("/static", "web/build/static")
@ -48,16 +52,17 @@ func main() {
// https://studygolang.com/articles/2303
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
if beego.AppConfig.String("redisEndpoint") == "" {
if conf.GetConfigString("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.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode

View File

@ -16,7 +16,7 @@ data:
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
httpProxy = "127.0.0.1:10808"
sock5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
logPostOnly = true

View File

@ -17,7 +17,6 @@ package object
import (
"fmt"
"runtime"
"xorm.io/core"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
@ -25,6 +24,7 @@ import (
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
"xorm.io/core"
"xorm.io/xorm"
)
@ -41,7 +41,7 @@ func InitConfig() {
func InitAdapter(createDatabase bool) {
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase {
adapter.CreateDatabase()
}
@ -111,10 +111,10 @@ func (a *Adapter) close() {
}
func (a *Adapter) createTable() {
showSql, _ := beego.AppConfig.Bool("showSql")
showSql, _ := conf.GetConfigBool("showSql")
a.Engine.ShowSQL(showSql)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper)
@ -138,6 +138,11 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Model))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)
@ -183,6 +188,11 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Product))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Payment))
if err != nil {
panic(err)

View File

@ -16,11 +16,21 @@ package object
import (
"fmt"
"net/url"
"strings"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
Rule string `json:"rule"`
}
type Application struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -36,8 +46,10 @@ type Application struct {
EnableSignUp bool `json:"enableSignUp"`
EnableSigninSession bool `json:"enableSigninSession"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
@ -215,6 +227,18 @@ func GetMaskedApplication(application *Application, userId string) *Application
if application.ClientSecret != "" {
application.ClientSecret = "***"
}
if application.OrganizationObj != nil {
if application.OrganizationObj.MasterPassword != "" {
application.OrganizationObj.MasterPassword = "***"
}
if application.OrganizationObj.PasswordType != "" {
application.OrganizationObj.PasswordType = "***"
}
if application.OrganizationObj.PasswordSalt != "" {
application.OrganizationObj.PasswordSalt = "***"
}
}
return application
}
@ -243,7 +267,11 @@ func UpdateApplication(id string, application *Application) bool {
providerItem.Provider = nil
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(application)
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if application.ClientSecret == "***" {
session.Omit("client_secret")
}
affected, err := session.Update(application)
if err != nil {
panic(err)
}
@ -252,8 +280,12 @@ func UpdateApplication(id string, application *Application) bool {
}
func AddApplication(application *Application) bool {
application.ClientId = util.GenerateClientId()
application.ClientSecret = util.GenerateClientSecret()
if application.ClientId == "" {
application.ClientId = util.GenerateClientId()
}
if application.ClientSecret == "" {
application.ClientSecret = util.GenerateClientSecret()
}
for _, providerItem := range application.Providers {
providerItem.Provider = nil
}
@ -282,3 +314,50 @@ func DeleteApplication(application *Application) bool {
func (application *Application) GetId() string {
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
}
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
var validUri = false
for _, tmpUri := range application.RedirectUris {
if strings.Contains(redirectUri, tmpUri) {
validUri = true
break
}
}
return validUri
}
func IsAllowOrigin(origin string) bool {
allowOrigin := false
originUrl, err := url.Parse(origin)
if err != nil {
return false
}
rows, err := adapter.Engine.Cols("redirect_uris").Rows(&Application{})
if err != nil {
panic(err)
}
application := Application{}
for rows.Next() {
err := rows.Scan(&application)
if err != nil {
panic(err)
}
for _, tmpRedirectUri := range application.RedirectUris {
u1, err := url.Parse(tmpRedirectUri)
if err != nil {
continue
}
if u1.Scheme == originUrl.Scheme && u1.Host == originUrl.Host {
allowOrigin = true
break
}
}
if allowOrigin {
break
}
}
return allowOrigin
}

View File

@ -19,14 +19,14 @@ import (
"fmt"
"io"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy"
)
var defaultStorageProvider *Provider = nil
func InitDefaultStorageProvider() {
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
defaultStorageProviderStr := conf.GetConfigString("defaultStorageProvider")
if defaultStorageProviderStr != "" {
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
}
@ -51,6 +51,10 @@ func downloadFile(url string) (*bytes.Buffer, error) {
}
func getPermanentAvatarUrl(organization string, username string, url string) string {
if url == "" {
return ""
}
if defaultStorageProvider == nil {
return ""
}

View File

@ -33,8 +33,10 @@ type Cert struct {
BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"`
PublicKey string `xorm:"mediumtext" json:"publicKey"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
PublicKey string `xorm:"mediumtext" json:"publicKey"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
}
func GetMaskedCert(cert *Cert) *Cert {

View File

@ -17,6 +17,7 @@ package object
import (
"fmt"
"regexp"
"strings"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util"
@ -33,7 +34,7 @@ func init() {
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
}
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string) string {
if organization == nil {
return "organization does not exist"
}
@ -85,11 +86,19 @@ func CheckUserSignup(application *Application, organization *Organization, usern
}
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.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") {
if firstName == "" {
return "firstName cannot be blank"
} else if lastName == "" {
return "lastName cannot be blank"
}
} else {
if displayName == "" {
return "displayName cannot be blank"
} else if application.GetSignupItemRule("Display name") == "Real name" {
if !isValidRealName(displayName) {
return "displayName is not valid real name"
}
}
}
}
@ -171,19 +180,53 @@ func CheckUserPassword(organization string, username string, password string) (*
if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator"
}
//for ldap users
if user.Ldap != "" {
//ONLY for ldap users
return checkLdapUserPassword(user, password)
} else {
msg := CheckPassword(user, password)
if msg != "" {
return nil, msg
}
}
msg := CheckPassword(user, password)
if msg != "" {
return nil, msg
}
return user, ""
}
func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf("please login first")
}
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
}
hasPermission := false
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := GetUser(requestUserId)
if requestUser == nil {
return false, fmt.Errorf("session outdated, please login again")
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner {
if strict {
hasPermission = requestUser.IsAdmin
} else {
hasPermission = true
}
}
}
return hasPermission, fmt.Errorf("you don't have the permission to do this")
}

View File

@ -16,16 +16,16 @@ package object
import "regexp"
var rePersonalName *regexp.Regexp
var reRealName *regexp.Regexp
func init() {
var err error
rePersonalName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
reRealName, 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)
func isValidRealName(s string) bool {
return reRealName.MatchString(s)
}

View File

@ -29,3 +29,16 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
return dialer.DialAndSend(message)
}
// DailSmtpServer Dail Smtp server
func DailSmtpServer(provider *Provider) error {
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
sender, err := dialer.Dial()
if err != nil {
return err
}
defer sender.Close()
return nil
}

View File

@ -15,29 +15,26 @@
package object
import (
_ "embed"
"io/ioutil"
"github.com/casdoor/casdoor/util"
)
//go:embed token_jwt_key.pem
var tokenJwtPublicKey string
//go:embed token_jwt_key.key
var tokenJwtPrivateKey string
func InitDb() {
initBuiltInOrganization()
initBuiltInUser()
initBuiltInApplication()
initBuiltInCert()
initBuiltInLdap()
existed := initBuiltInOrganization()
if !existed {
initBuiltInProvider()
initBuiltInUser()
initBuiltInApplication()
initBuiltInCert()
initBuiltInLdap()
}
}
func initBuiltInOrganization() {
func initBuiltInOrganization() bool {
organization := getOrganization("admin", "built-in")
if organization != nil {
return
return true
}
organization = &Organization{
@ -47,11 +44,38 @@ func initBuiltInOrganization() {
DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com",
Favicon: "https://cdn.casbin.com/static/favicon.ico",
PasswordType: "plain",
PhonePrefix: "86",
DefaultAvatar: "https://casbin.org/img/casbin.svg",
PasswordType: "plain",
Tags: []string{},
AccountItems: []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
},
}
AddOrganization(organization)
return false
}
func initBuiltInUser() {
@ -80,7 +104,7 @@ func initBuiltInUser() {
IsGlobalAdmin: true,
IsForbidden: false,
IsDeleted: false,
SignupApplication: "built-in-app",
SignupApplication: "app-built-in",
CreatedIp: "127.0.0.1",
Properties: make(map[string]string),
}
@ -104,14 +128,16 @@ func initBuiltInApplication() {
Cert: "cert-built-in",
EnablePassword: true,
EnableSignUp: true,
Providers: []*ProviderItem{},
Providers: []*ProviderItem{
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Provider: nil},
},
SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "Normal"},
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
},
@ -121,7 +147,22 @@ func initBuiltInApplication() {
AddApplication(application)
}
func readTokenFromFile() (string, string) {
pemPath := "./object/token_jwt_key.pem"
keyPath := "./object/token_jwt_key.key"
pem, err := ioutil.ReadFile(pemPath)
if err != nil {
return "", ""
}
key, err := ioutil.ReadFile(keyPath)
if err != nil {
return "", ""
}
return string(pem), string(key)
}
func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in")
if cert != nil {
return
@ -134,7 +175,7 @@ func initBuiltInCert() {
DisplayName: "Built-in Cert",
Scope: "JWT",
Type: "x509",
CryptoAlgorithm: "RSA",
CryptoAlgorithm: "RS256",
BitSize: 4096,
ExpireInYears: 20,
PublicKey: tokenJwtPublicKey,
@ -163,3 +204,20 @@ func initBuiltInLdap() {
}
AddLdap(ldap)
}
func initBuiltInProvider() {
provider := GetProvider("admin/provider_captcha_default")
if provider != nil {
return
}
provider = &Provider{
Owner: "admin",
Name: "provider_captcha_default",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Captcha Default",
Category: "Captcha",
Type: "Default",
}
AddProvider(provider)
}

146
object/init_data.go Normal file
View File

@ -0,0 +1,146 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import "github.com/casdoor/casdoor/util"
type InitData struct {
Organizations []*Organization `json:"organizations"`
Applications []*Application `json:"applications"`
Users []*User `json:"users"`
Certs []*Cert `json:"certs"`
Providers []*Provider `json:"providers"`
Ldaps []*Ldap `json:"ldaps"`
}
func InitFromFile() {
initData := readInitDataFromFile("./init_data.json")
if initData != nil {
for _, organization := range initData.Organizations {
initDefinedOrganization(organization)
}
for _, provider := range initData.Providers {
initDefinedProvider(provider)
}
for _, user := range initData.Users {
initDefinedUser(user)
}
for _, application := range initData.Applications {
initDefinedApplication(application)
}
for _, cert := range initData.Certs {
initDefinedCert(cert)
}
for _, ldap := range initData.Ldaps {
initDefinedLdap(ldap)
}
}
}
func readInitDataFromFile(filePath string) *InitData {
if !util.FileExist(filePath) {
return nil
}
s := util.ReadStringFromPath(filePath)
data := &InitData{}
err := util.JsonToStruct(s, data)
if err != nil {
panic(err)
}
return data
}
func initDefinedOrganization(organization *Organization) {
existed := getOrganization(organization.Owner, organization.Name)
if existed != nil {
return
}
organization.CreatedTime = util.GetCurrentTime()
organization.AccountItems = []*AccountItem{
{Name: "Organization", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "ID", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Name", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Display name", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Avatar", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "User type", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Password", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Email", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Phone", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Country/Region", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Location", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Affiliation", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Title", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Homepage", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
}
AddOrganization(organization)
}
func initDefinedApplication(application *Application) {
existed := getApplication(application.Owner, application.Name)
if existed != nil {
return
}
application.CreatedTime = util.GetCurrentTime()
AddApplication(application)
}
func initDefinedUser(user *User) {
existed := getUser(user.Owner, user.Name)
if existed != nil {
return
}
user.CreatedTime = util.GetCurrentTime()
user.Id = util.GenerateId()
user.Properties = make(map[string]string)
AddUser(user)
}
func initDefinedCert(cert *Cert) {
existed := getCert(cert.Owner, cert.Name)
if existed != nil {
return
}
cert.CreatedTime = util.GetCurrentTime()
AddCert(cert)
}
func initDefinedLdap(ldap *Ldap) {
existed := GetLdap(ldap.Id)
if existed != nil {
return
}
AddLdap(ldap)
}
func initDefinedProvider(provider *Provider) {
existed := GetProvider(provider.GetId())
if existed != nil {
return
}
AddProvider(provider)
}

View File

@ -19,6 +19,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
@ -42,6 +43,7 @@ type Ldap struct {
type ldapConn struct {
Conn *goldap.Conn
IsAD bool
}
//type ldapGroup struct {
@ -78,6 +80,13 @@ type LdapRespUser struct {
Address string `json:"address"`
}
type ldapServerType struct {
Vendorname string
Vendorversion string
IsGlobalCatalogReady string
ForestFunctionality string
}
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
returnAnyNotEmpty := func(strs ...string) string {
for _, str := range strs {
@ -104,6 +113,45 @@ func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
return res
}
func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
SearchFilter := "(objectclass=*)"
SearchAttributes := []string{"vendorname", "vendorversion", "isGlobalCatalogReady", "forestFunctionality"}
searchReq := goldap.NewSearchRequest("",
goldap.ScopeBaseObject, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, SearchAttributes, nil)
searchResult, err := Conn.Search(searchReq)
if err != nil {
return false, err
}
if len(searchResult.Entries) == 0 {
return false, errors.New("no result")
}
isMicrosoft := false
var ldapServerType ldapServerType
for _, entry := range searchResult.Entries {
for _, attribute := range entry.Attributes {
switch attribute.Name {
case "vendorname":
ldapServerType.Vendorname = attribute.Values[0]
case "vendorversion":
ldapServerType.Vendorversion = attribute.Values[0]
case "isGlobalCatalogReady":
ldapServerType.IsGlobalCatalogReady = attribute.Values[0]
case "forestFunctionality":
ldapServerType.ForestFunctionality = attribute.Values[0]
}
}
}
if ldapServerType.Vendorname == "" &&
ldapServerType.Vendorversion == "" &&
ldapServerType.IsGlobalCatalogReady == "TRUE" &&
ldapServerType.ForestFunctionality != "" {
isMicrosoft = true
}
return isMicrosoft, err
}
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 {
@ -115,7 +163,11 @@ func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
}
return &ldapConn{Conn: conn}, nil
isAD, err := isMicrosoftAD(conn)
if err != nil {
return nil, fmt.Errorf("fail to get Ldap server type [%s]", adminUser)
}
return &ldapConn{Conn: conn, IsAD: isAD}, nil
}
//FIXME: The Base DN does not necessarily contain the Group
@ -158,10 +210,19 @@ 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)
SearchFilterMsAD := "(objectClass=user)"
SearchAttributesMsAD := []string{"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
var searchReq *goldap.SearchRequest
if l.IsAD {
searchReq = goldap.NewSearchRequest(baseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilterMsAD, SearchAttributesMsAD, nil)
} else {
searchReq = goldap.NewSearchRequest(baseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, SearchAttributes, nil)
}
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
if err != nil {
return nil, err
@ -181,12 +242,16 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
ldapUserItem.UidNumber = attribute.Values[0]
case "uid":
ldapUserItem.Uid = attribute.Values[0]
case "sAMAccountName":
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 "objectGUID":
ldapUserItem.Uuid = attribute.Values[0]
case "mail":
ldapUserItem.Mail = attribute.Values[0]
case "email":
@ -300,7 +365,7 @@ func DeleteLdap(ldap *Ldap) bool {
return affected != 0
}
func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]LdapRespUser) {
func SyncLdapUsers(owner string, users []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
var existUsers []LdapRespUser
var failedUsers []LdapRespUser
var uuids []string
@ -311,6 +376,25 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
existUuids := CheckLdapUuidExist(owner, uuids)
organization := getOrganization("admin", owner)
ldap := GetLdap(ldapId)
var dc []string
for _, basedn := range strings.Split(ldap.BaseDn, ",") {
if strings.Contains(basedn, "dc=") {
dc = append(dc, basedn[3:])
}
}
affiliation := strings.Join(dc, ".")
var ou []string
for _, admin := range strings.Split(ldap.Admin, ",") {
if strings.Contains(admin, "ou=") {
ou = append(ou, admin[3:])
}
}
tag := strings.Join(ou, ".")
for _, user := range users {
found := false
if len(existUuids) > 0 {
@ -325,15 +409,14 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
Owner: owner,
Name: buildLdapUserName(user.Uid, user.UidNumber),
CreatedTime: util.GetCurrentTime(),
Password: "123",
DisplayName: user.Cn,
Avatar: "https://casbin.org/img/casbin.svg",
Avatar: organization.DefaultAvatar,
Email: user.Email,
Phone: user.Phone,
Address: []string{user.Address},
Affiliation: "Example Inc.",
Tag: "staff",
Score: 2000,
Affiliation: affiliation,
Tag: tag,
Score: beego.AppConfig.DefaultInt("initScore", 2000),
Ldap: user.Uuid,
}) {
failedUsers = append(failedUsers, user)

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/astaxie/beego/logs"
"github.com/casdoor/casdoor/util"
)
type LdapAutoSynchronizer struct {
@ -47,7 +48,7 @@ func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
stopChan := make(chan struct{})
l.ldapIdToStopChan[ldapId] = stopChan
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
go l.syncRoutine(ldap, stopChan)
util.SafeGoroutine(func() {l.syncRoutine(ldap, stopChan)})
return nil
}
@ -85,7 +86,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users))
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
if len(*failed) != 0 {
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
} else {

122
object/model.go Normal file
View File

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

View File

@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"gopkg.in/square/go-jose.v2"
)
@ -30,6 +30,7 @@ type OidcDiscovery struct {
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
JwksUri string `json:"jwks_uri"`
IntrospectionEndpoint string `json:"introspection_endpoint"`
ResponseTypesSupported []string `json:"response_types_supported"`
ResponseModesSupported []string `json:"response_modes_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
@ -57,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
func GetOidcDiscovery(host string) OidcDiscovery {
originFrontend, originBackend := getOriginFromHost(host)
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
if origin != "" {
originFrontend = origin
originBackend = origin
@ -73,8 +74,9 @@ func GetOidcDiscovery(host string) OidcDiscovery {
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
JwksUri: fmt.Sprintf("%s/api/certs", originBackend),
ResponseTypesSupported: []string{"id_token"},
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
ResponseModesSupported: []string{"login", "code", "link"},
GrantTypesSupported: []string{"password", "authorization_code"},
SubjectTypesSupported: []string{"public"},
@ -89,21 +91,24 @@ func GetOidcDiscovery(host string) OidcDiscovery {
}
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
cert := GetDefaultCert()
certs := GetCerts("admin")
jwks := jose.JSONWebKeySet{}
//follows the protocol rfc 7517(draft)
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
certPemBlock := []byte(cert.PublicKey)
certDerBlock, _ := pem.Decode(certPemBlock)
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
for _, cert := range certs {
certPemBlock := []byte(cert.PublicKey)
certDerBlock, _ := pem.Decode(certPemBlock)
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
var jwk jose.JSONWebKey
jwk.Key = x509Cert.PublicKey
jwk.Certificates = []*x509.Certificate{x509Cert}
jwk.KeyID = cert.Name
var jwk jose.JSONWebKey
jwk.Key = x509Cert.PublicKey
jwk.Certificates = []*x509.Certificate{x509Cert}
jwk.KeyID = cert.Name
jwk.Algorithm = cert.CryptoAlgorithm
jwk.Use = "sig"
jwks.Keys = append(jwks.Keys, jwk)
}
var jwks jose.JSONWebKeySet
jwks.Keys = []jose.JSONWebKey{jwk}
return jwks, nil
}

View File

@ -20,20 +20,31 @@ import (
"xorm.io/core"
)
type AccountItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
ViewRule string `json:"viewRule"`
ModifyRule string `json:"modifyRule"`
}
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"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
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"`
Tags []string `xorm:"mediumtext" json:"tags"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
AccountItems []*AccountItem `xorm:"varchar(2000)" json:"accountItems"`
}
func GetOrganizationCount(owner, field, value string) int {
@ -119,14 +130,18 @@ func UpdateOrganization(id string, organization *Organization) bool {
}
if name != organization.Name {
applications := GetApplicationsByOrganizationName("admin", name)
for _, application := range applications {
go func() {
application := new(Application)
application.Organization = organization.Name
UpdateApplication(application.GetId(), application)
}
_, _ = adapter.Engine.Where("organization=?", name).Update(application)
user := new(User)
user.Owner = organization.Name
_, _ = adapter.Engine.Where("owner=?", name).Update(user)
}()
}
if organization.MasterPassword != "" {
if organization.MasterPassword != "" && organization.MasterPassword != "***" {
credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil {
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt)
@ -134,7 +149,11 @@ func UpdateOrganization(id string, organization *Organization) bool {
}
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if organization.MasterPassword == "***" {
session.Omit("master_password")
}
affected, err := session.Update(organization)
if err != nil {
panic(err)
}

View File

@ -16,6 +16,7 @@ package object
import (
"fmt"
"net/http"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
@ -27,15 +28,32 @@ type Payment struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
Good string `xorm:"varchar(100)" json:"good"`
Amount string `xorm:"varchar(100)" json:"amount"`
Currency string `xorm:"varchar(100)" json:"currency"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
ProductName string `xorm:"varchar(100)" json:"productName"`
ProductDisplayName string `xorm:"varchar(100)" json:"productDisplayName"`
State string `xorm:"varchar(100)" json:"state"`
Detail string `xorm:"varchar(100)" json:"detail"`
Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"`
Price float64 `json:"price"`
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"`
Message string `xorm:"varchar(1000)" json:"message"`
PersonName string `xorm:"varchar(100)" json:"personName"`
PersonIdCard string `xorm:"varchar(100)" json:"personIdCard"`
PersonEmail string `xorm:"varchar(100)" json:"personEmail"`
PersonPhone string `xorm:"varchar(100)" json:"personPhone"`
InvoiceType string `xorm:"varchar(100)" json:"invoiceType"`
InvoiceTitle string `xorm:"varchar(100)" json:"invoiceTitle"`
InvoiceTaxId string `xorm:"varchar(100)" json:"invoiceTaxId"`
InvoiceRemark string `xorm:"varchar(100)" json:"invoiceRemark"`
InvoiceUrl string `xorm:"varchar(255)" json:"invoiceUrl"`
}
func GetPaymentCount(owner, field, value string) int {
@ -58,6 +76,16 @@ func GetPayments(owner string) []*Payment {
return payments
}
func GetUserPayments(owner string, organization string, user string) []*Payment {
payments := []*Payment{}
err := adapter.Engine.Desc("created_time").Find(&payments, &Payment{Owner: owner, Organization: organization, User: user})
if err != nil {
panic(err)
}
return payments
}
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
payments := []*Payment{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
@ -124,6 +152,99 @@ func DeletePayment(payment *Payment) bool {
return affected != 0
}
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error) {
payment := getPayment(owner, paymentName)
if payment == nil {
return nil, fmt.Errorf("the payment: %s does not exist", paymentName)
}
product := getProduct(owner, productName)
if product == nil {
return nil, fmt.Errorf("the product: %s does not exist", productName)
}
provider, err := product.getProvider(providerName)
if err != nil {
return payment, err
}
pProvider, cert, err := provider.getPaymentProvider()
if err != nil {
return payment, err
}
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
if err != nil {
return payment, err
}
if productDisplayName != "" && productDisplayName != product.DisplayName {
return nil, fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName)
}
if price != product.Price {
return nil, fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price)
}
return payment, nil
}
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) bool {
payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName)
if payment != nil {
if err != nil {
payment.State = "Error"
payment.Message = err.Error()
} else {
payment.State = "Paid"
}
UpdatePayment(payment.GetId(), payment)
}
ok := err == nil
return ok
}
func invoicePayment(payment *Payment) (string, error) {
provider := getProvider(payment.Owner, payment.Provider)
if provider == nil {
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
}
pProvider, _, err := provider.getPaymentProvider()
if err != nil {
return "", err
}
invoiceUrl, err := pProvider.GetInvoice(payment.Name, payment.PersonName, payment.PersonIdCard, payment.PersonEmail, payment.PersonPhone, payment.InvoiceType, payment.InvoiceTitle, payment.InvoiceTaxId)
if err != nil {
return "", err
}
return invoiceUrl, nil
}
func InvoicePayment(payment *Payment) (string, error) {
if payment.State != "Paid" {
return "", fmt.Errorf("the payment state is supposed to be: \"%s\", got: \"%s\"", "Paid", payment.State)
}
invoiceUrl, err := invoicePayment(payment)
if err != nil {
return "", err
}
payment.InvoiceUrl = invoiceUrl
affected := UpdatePayment(payment.GetId(), payment)
if !affected {
return "", fmt.Errorf("failed to update the payment: %s", payment.Name)
}
return invoiceUrl, nil
}
func (payment *Payment) GetId() string {
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
}

View File

@ -30,6 +30,7 @@ type Permission struct {
Users []string `xorm:"mediumtext" json:"users"`
Roles []string `xorm:"mediumtext" json:"roles"`
Model string `xorm:"varchar(100)" json:"model"`
ResourceType string `xorm:"varchar(100)" json:"resourceType"`
Resources []string `xorm:"mediumtext" json:"resources"`
Actions []string `xorm:"mediumtext" json:"actions"`

211
object/product.go Normal file
View File

@ -0,0 +1,211 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type Product 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"`
Image string `xorm:"varchar(100)" json:"image"`
Detail string `xorm:"varchar(100)" json:"detail"`
Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"`
Price float64 `json:"price"`
Quantity int `json:"quantity"`
Sold int `json:"sold"`
Providers []string `xorm:"varchar(100)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"`
}
func GetProductCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Product{})
if err != nil {
panic(err)
}
return int(count)
}
func GetProducts(owner string) []*Product {
products := []*Product{}
err := adapter.Engine.Desc("created_time").Find(&products, &Product{Owner: owner})
if err != nil {
panic(err)
}
return products
}
func GetPaginationProducts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Product {
products := []*Product{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&products)
if err != nil {
panic(err)
}
return products
}
func getProduct(owner string, name string) *Product {
if owner == "" || name == "" {
return nil
}
product := Product{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&product)
if err != nil {
panic(err)
}
if existed {
return &product
} else {
return nil
}
}
func GetProduct(id string) *Product {
owner, name := util.GetOwnerAndNameFromId(id)
return getProduct(owner, name)
}
func UpdateProduct(id string, product *Product) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getProduct(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(product)
if err != nil {
panic(err)
}
return affected != 0
}
func AddProduct(product *Product) bool {
affected, err := adapter.Engine.Insert(product)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteProduct(product *Product) bool {
affected, err := adapter.Engine.ID(core.PK{product.Owner, product.Name}).Delete(&Product{})
if err != nil {
panic(err)
}
return affected != 0
}
func (product *Product) GetId() string {
return fmt.Sprintf("%s/%s", product.Owner, product.Name)
}
func (product *Product) isValidProvider(provider *Provider) bool {
for _, providerName := range product.Providers {
if providerName == provider.Name {
return true
}
}
return false
}
func (product *Product) getProvider(providerId string) (*Provider, error) {
provider := getProvider(product.Owner, providerId)
if provider == nil {
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
}
if !product.isValidProvider(provider) {
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
}
return provider, nil
}
func BuyProduct(id string, providerName string, user *User, host string) (string, error) {
product := GetProduct(id)
if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id)
}
provider, err := product.getProvider(providerName)
if err != nil {
return "", err
}
pProvider, _, err := provider.getPaymentProvider()
if err != nil {
return "", err
}
owner := product.Owner
productName := product.Name
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
paymentName := util.GenerateTimeId()
productDisplayName := product.DisplayName
originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
if err != nil {
return "", err
}
payment := Payment{
Owner: product.Owner,
Name: paymentName,
CreatedTime: util.GetCurrentTime(),
DisplayName: paymentName,
Provider: provider.Name,
Type: provider.Type,
Organization: user.Owner,
User: user.Name,
ProductName: productName,
ProductDisplayName: productDisplayName,
Detail: product.Detail,
Tag: product.Tag,
Currency: product.Currency,
Price: product.Price,
PayUrl: payUrl,
ReturnUrl: product.ReturnUrl,
State: "Created",
}
affected := AddPayment(&payment)
if !affected {
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
}
return payUrl, err
}

44
object/product_test.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !skipCi
// +build !skipCi
package object
import (
"testing"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
)
func TestProduct(t *testing.T) {
InitConfig()
product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
paymentName := util.GenerateTimeId()
returnUrl := ""
notifyUrl := ""
payUrl, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, returnUrl, notifyUrl)
if err != nil {
panic(err)
}
println(payUrl)
}

View File

@ -17,6 +17,7 @@ package object
import (
"fmt"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -26,15 +27,21 @@ type Provider struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"`
SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"`
SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
Cert string `xorm:"varchar(100)" json:"cert"`
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
CustomScope string `xorm:"varchar(200)" json:"customScope"`
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
@ -135,8 +142,8 @@ func GetProvider(id string) *Provider {
return getProvider(owner, name)
}
func GetDefaultHumanCheckProvider() *Provider {
provider := Provider{Owner: "admin", Category: "HumanCheck"}
func GetDefaultCaptchaProvider() *Provider {
provider := Provider{Owner: "admin", Category: "Captcha"}
existed, err := adapter.Engine.Get(&provider)
if err != nil {
panic(err)
@ -149,13 +156,30 @@ func GetDefaultHumanCheckProvider() *Provider {
return &provider
}
func GetWechatMiniProgramProvider(application *Application) *Provider {
providers := application.Providers
for _, provider := range providers {
if provider.Provider.Type == "WeChatMiniProgram" {
return provider.Provider
}
}
return nil
}
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)
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if provider.ClientSecret == "***" {
session = session.Omit("client_secret")
}
if provider.ClientSecret2 == "***" {
session = session.Omit("client_secret2")
}
affected, err := session.Update(provider)
if err != nil {
panic(err)
}
@ -181,6 +205,57 @@ func DeleteProvider(provider *Provider) bool {
return affected != 0
}
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
cert := &Cert{}
if p.Cert != "" {
cert = getCert(p.Owner, p.Cert)
if cert == nil {
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
}
}
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
if pProvider == nil {
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
}
return pProvider, cert, nil
}
func (p *Provider) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}
func GetCaptchaProviderByOwnerName(applicationId string) (*Provider, error) {
owner, name := util.GetOwnerAndNameFromId(applicationId)
provider := Provider{Owner: owner, Name: name, Category: "Captcha"}
existed, err := adapter.Engine.Get(&provider)
if err != nil {
return nil, err
}
if !existed {
return nil, fmt.Errorf("the provider: %s does not exist", applicationId)
}
return &provider, nil
}
func GetCaptchaProviderByApplication(applicationId, isCurrentProvider string) (*Provider, error) {
if isCurrentProvider == "true" {
return GetCaptchaProviderByOwnerName(applicationId)
}
application := GetApplication(applicationId)
if application == nil || len(application.Providers) == 0 {
return nil, fmt.Errorf("invalid application id")
}
for _, provider := range application.Providers {
if provider.Provider == nil {
continue
}
if provider.Provider.Category == "Captcha" {
return GetCaptchaProviderByOwnerName(fmt.Sprintf("%s/%s", provider.Provider.Owner, provider.Provider.Name))
}
}
return nil, nil
}

View File

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

View File

@ -18,8 +18,8 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
)
@ -27,7 +27,7 @@ var logPostOnly bool
func init() {
var err error
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
logPostOnly, err = conf.GetConfigBool("logPostOnly")
if err != nil {
//panic(err)
}

View File

@ -23,7 +23,7 @@ import (
type Resource struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
Name string `xorm:"varchar(250) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
User string `xorm:"varchar(100)" json:"user"`
@ -31,7 +31,7 @@ type Resource struct {
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"`
FileName string `xorm:"varchar(1000)" json:"fileName"`
FileType string `xorm:"varchar(100)" json:"fileType"`
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
FileSize int `json:"fileSize"`

369
object/saml_idp.go Normal file
View File

@ -0,0 +1,369 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"bytes"
"compress/flate"
"crypto"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"encoding/pem"
"encoding/xml"
"fmt"
"io"
"time"
"github.com/RobotsAndPencils/go-saml"
"github.com/astaxie/beego"
"github.com/beevik/etree"
"github.com/golang-jwt/jwt/v4"
dsig "github.com/russellhaering/goxmldsig"
uuid "github.com/satori/go.uuid"
)
//returns a saml2 response
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
}
now := time.Now().UTC().Format(time.RFC3339)
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
samlResponse.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
arId := uuid.NewV4()
samlResponse.CreateAttr("ID", fmt.Sprintf("_%s", arId))
samlResponse.CreateAttr("Version", "2.0")
samlResponse.CreateAttr("IssueInstant", now)
samlResponse.CreateAttr("Destination", destination)
samlResponse.CreateAttr("InResponseTo", requestId)
samlResponse.CreateElement("saml:Issuer").SetText(host)
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "urn:oasis:names:tc:SAML:2.0:status:Success")
assertion := samlResponse.CreateElement("saml:Assertion")
assertion.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
assertion.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
assertion.CreateAttr("ID", fmt.Sprintf("_%s", uuid.NewV4()))
assertion.CreateAttr("Version", "2.0")
assertion.CreateAttr("IssueInstant", now)
assertion.CreateElement("saml:Issuer").SetText(host)
subject := assertion.CreateElement("saml:Subject")
subject.CreateElement("saml:NameID").SetText(user.Email)
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
subjectConfirmation.CreateAttr("Method", "urn:oasis:names:tc:SAML:2.0:cm:bearer")
subjectConfirmationData := subjectConfirmation.CreateElement("saml:SubjectConfirmationData")
subjectConfirmationData.CreateAttr("InResponseTo", requestId)
subjectConfirmationData.CreateAttr("Recipient", destination)
subjectConfirmationData.CreateAttr("NotOnOrAfter", expireTime)
condition := assertion.CreateElement("saml:Conditions")
condition.CreateAttr("NotBefore", now)
condition.CreateAttr("NotOnOrAfter", expireTime)
audience := condition.CreateElement("saml:AudienceRestriction")
audience.CreateElement("saml:Audience").SetText(iss)
for _, value := range redirectUri {
audience.CreateElement("saml:Audience").SetText(value)
}
authnStatement := assertion.CreateElement("saml:AuthnStatement")
authnStatement.CreateAttr("AuthnInstant", now)
authnStatement.CreateAttr("SessionIndex", fmt.Sprintf("_%s", uuid.NewV4()))
authnStatement.CreateAttr("SessionNotOnOrAfter", expireTime)
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
attributes := assertion.CreateElement("saml:AttributeStatement")
email := attributes.CreateElement("saml:Attribute")
email.CreateAttr("Name", "Email")
email.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
email.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Email)
name := attributes.CreateElement("saml:Attribute")
name.CreateAttr("Name", "Name")
name.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
name.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.Name)
displayName := attributes.CreateElement("saml:Attribute")
displayName.CreateAttr("Name", "DisplayName")
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
return samlResponse, nil
}
type X509Key struct {
X509Certificate string
PrivateKey string
}
func (x X509Key) GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err error) {
cert, _ = base64.StdEncoding.DecodeString(x.X509Certificate)
privateKey, err = jwt.ParseRSAPrivateKeyFromPEM([]byte(x.PrivateKey))
return privateKey, cert, err
}
//SAML METADATA
type IdpEntityDescriptor struct {
XMLName xml.Name `xml:"EntityDescriptor"`
DS string `xml:"xmlns:ds,attr"`
XMLNS string `xml:"xmlns,attr"`
MD string `xml:"xmlns:md,attr"`
EntityId string `xml:"entityID,attr"`
IdpSSODescriptor IdpSSODescriptor `xml:"IDPSSODescriptor"`
}
type KeyInfo struct {
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# KeyInfo"`
X509Data X509Data `xml:",innerxml"`
}
type X509Data struct {
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Data"`
X509Certificate X509Certificate `xml:",innerxml"`
}
type X509Certificate struct {
XMLName xml.Name `xml:"http://www.w3.org/2000/09/xmldsig# X509Certificate"`
Cert string `xml:",innerxml"`
}
type KeyDescriptor struct {
XMLName xml.Name `xml:"KeyDescriptor"`
Use string `xml:"use,attr"`
KeyInfo KeyInfo `xml:"KeyInfo"`
}
type IdpSSODescriptor struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:SAML:2.0:metadata IDPSSODescriptor"`
ProtocolSupportEnumeration string `xml:"protocolSupportEnumeration,attr"`
SigningKeyDescriptor KeyDescriptor
NameIDFormats []NameIDFormat `xml:"NameIDFormat"`
SingleSignOnService SingleSignOnService `xml:"SingleSignOnService"`
Attribute []Attribute `xml:"Attribute"`
}
type NameIDFormat struct {
XMLName xml.Name
Value string `xml:",innerxml"`
}
type SingleSignOnService struct {
XMLName xml.Name
Binding string `xml:"Binding,attr"`
Location string `xml:"Location,attr"`
}
type Attribute struct {
XMLName xml.Name
Name string `xml:"Name,attr"`
NameFormat string `xml:"NameFormat,attr"`
FriendlyName string `xml:"FriendlyName,attr"`
Xmlns string `xml:"xmlns,attr"`
}
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
//_, originBackend := getOriginFromHost(host)
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
origin := beego.AppConfig.String("origin")
originFrontend, originBackend := getOriginFromHost(host)
if origin != "" {
originBackend = origin
}
d := IdpEntityDescriptor{
XMLName: xml.Name{
Local: "md:EntityDescriptor",
},
DS: "http://www.w3.org/2000/09/xmldsig#",
XMLNS: "urn:oasis:names:tc:SAML:2.0:metadata",
MD: "urn:oasis:names:tc:SAML:2.0:metadata",
EntityId: originBackend,
IdpSSODescriptor: IdpSSODescriptor{
SigningKeyDescriptor: KeyDescriptor{
Use: "signing",
KeyInfo: KeyInfo{
X509Data: X509Data{
X509Certificate: X509Certificate{
Cert: publicKey,
},
},
},
},
NameIDFormats: []NameIDFormat{
{Value: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"},
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"},
{Value: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"},
},
Attribute: []Attribute{
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Email", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "E-Mail"},
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "DisplayName", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "displayName"},
{Xmlns: "urn:oasis:names:tc:SAML:2.0:assertion", Name: "Name", NameFormat: "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", FriendlyName: "Name"},
},
SingleSignOnService: SingleSignOnService{
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
Location: fmt.Sprintf("%s/login/saml/authorize/%s/%s", originFrontend, application.Owner, application.Name),
},
ProtocolSupportEnumeration: "urn:oasis:names:tc:SAML:2.0:protocol",
},
}
return &d, nil
}
// GetSamlResponse generates a SAML2.0 response
// parameter samlRequest is saml request in base64 format
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, error) {
// base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
// decompress
var buffer bytes.Buffer
rdr := flate.NewReader(bytes.NewReader(defated))
io.Copy(&buffer, rdr)
var authnRequest saml.AuthnRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
// verify samlRequest
if valid := CheckRedirectUriValid(application, authnRequest.Issuer.Url); !valid {
return "", "", fmt.Errorf("err: invalid issuer url")
}
// get public key string
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
_, originBackend := getOriginFromHost(host)
// build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
}
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
//if err != nil {
// return "", "", fmt.Errorf("err: %s", err.Error())
//}
sig, err := ctx.ConstructSignature(samlResponse, true)
samlResponse.InsertChildAt(1, sig)
doc := etree.NewDocument()
doc.SetRoot(samlResponse)
xmlBytes, err := doc.WriteToBytes()
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
// compress
if application.EnableSamlCompress {
flated := bytes.NewBuffer(nil)
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
writer.Write(xmlBytes)
writer.Close()
xmlBytes = flated.Bytes()
}
// base64 encode
res := base64.StdEncoding.EncodeToString(xmlBytes)
return res, authnRequest.AssertionConsumerServiceURL, nil
}
// NewSamlResponse11 return a saml1.1 response(not 2.0)
func NewSamlResponse11(user *User, requestID string, host string) *etree.Element {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
}
//create samlresponse
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
samlResponse.CreateAttr("MajorVersion", "1")
samlResponse.CreateAttr("MinorVersion", "1")
responseID := uuid.NewV4()
samlResponse.CreateAttr("ResponseID", fmt.Sprintf("_%s", responseID))
samlResponse.CreateAttr("InResponseTo", requestID)
now := time.Now().UTC().Format(time.RFC3339)
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
samlResponse.CreateAttr("IssueInstant", now)
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "samlp:Success")
//create assertion which is inside the response
assertion := samlResponse.CreateElement("saml:Assertion")
assertion.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:1.0:assertion")
assertion.CreateAttr("MajorVersion", "1")
assertion.CreateAttr("MinorVersion", "1")
assertion.CreateAttr("AssertionID", uuid.NewV4().String())
assertion.CreateAttr("Issuer", host)
assertion.CreateAttr("IssueInstant", now)
condition := assertion.CreateElement("saml:Conditions")
condition.CreateAttr("NotBefore", now)
condition.CreateAttr("NotOnOrAfter", expireTime)
//AuthenticationStatement inside assertion
authenticationStatement := assertion.CreateElement("saml:AuthenticationStatement")
authenticationStatement.CreateAttr("AuthenticationMethod", "urn:oasis:names:tc:SAML:1.0:am:password")
authenticationStatement.CreateAttr("AuthenticationInstant", now)
//subject inside AuthenticationStatement
subject := assertion.CreateElement("saml:Subject")
//nameIdentifier inside subject
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
//nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
nameIdentifier.SetText(user.Name)
//subjectConfirmation inside subject
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
subjectConfirmation.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
attributeStatement := assertion.CreateElement("saml:AttributeStatement")
subjectInAttribute := attributeStatement.CreateElement("saml:Subject")
nameIdentifierInAttribute := subjectInAttribute.CreateElement("saml:NameIdentifier")
nameIdentifierInAttribute.SetText(user.Name)
subjectConfirmationInAttribute := subjectInAttribute.CreateElement("saml:SubjectConfirmation")
subjectConfirmationInAttribute.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
for k, v := range tmp {
if v != "" {
attr := attributeStatement.CreateElement("saml:Attribute")
attr.CreateAttr("saml:AttributeName", k)
attr.CreateAttr("saml:AttributeNamespace", "http://www.ja-sig.org/products/cas/")
attr.CreateElement("saml:AttributeValue").SetText(v)
}
}
return samlResponse
}

View File

@ -23,7 +23,7 @@ import (
"regexp"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
@ -73,7 +73,7 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
certEncodedData := ""
if samlResponse != "" {
certEncodedData = parseSamlResponse(samlResponse, provider.Type)

View File

@ -18,6 +18,9 @@ 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.TemplateCode, provider.AppId)
if provider.Type == go_sms_sender.HuaweiCloud {
client, err = go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
}
if err != nil {
return err
}

View File

@ -19,7 +19,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/storage"
"github.com/casdoor/casdoor/util"
)
@ -28,7 +28,7 @@ var isCloudIntranet bool
func init() {
var err error
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
isCloudIntranet, err = conf.GetConfigBool("isCloudIntranet")
if err != nil {
//panic(err)
}
@ -56,6 +56,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
host = util.UrlJoin(provider.Domain, "/files")
}
if provider.Type == "Azure Blob" {
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
}
fileUrl := util.UrlJoin(host, objectKey)
if hasTimestamp {

View File

@ -133,7 +133,11 @@ func UpdateSyncer(id string, syncer *Syncer) bool {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(syncer)
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if syncer.Password == "***" {
session.Omit("password")
}
affected, err := session.Update(syncer)
if err != nil {
panic(err)
}
@ -206,3 +210,8 @@ func (syncer *Syncer) getTable() string {
return syncer.Table
}
}
func RunSyncer(syncer *Syncer) {
syncer.initAdapter()
syncer.syncUsers()
}

View File

@ -25,6 +25,11 @@ import (
type OriginalUser = User
type Credential struct {
Value string `json:"value"`
Salt string `json:"salt"`
}
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
sql := fmt.Sprintf("select * from %s", syncer.getTable())
results, err := syncer.Adapter.Engine.QueryString(sql)

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