Compare commits

...

170 Commits

Author SHA1 Message Date
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
198 changed files with 11426 additions and 1477 deletions

0
$env Normal file
View File

View File

@ -70,7 +70,7 @@ jobs:
- name: Fetch Previous version - name: Fetch Previous version
id: get-previous-tag id: get-previous-tag
uses: actions-ecosystem/action-get-latest-tag@v1 uses: actions-ecosystem/action-get-latest-tag@v1.6.0
- name: Release - name: Release
run: yarn global add semantic-release@17.4.4 && semantic-release run: yarn global add semantic-release@17.4.4 && semantic-release
@ -79,7 +79,7 @@ jobs:
- name: Fetch Current version - name: Fetch Current version
id: get-current-tag 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 - name: Decide Should_Push Or Not
id: should_push id: should_push

View File

@ -14,7 +14,7 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: crowdin action - name: crowdin action
uses: crowdin/github-action@1.2.0 uses: crowdin/github-action@1.4.8
with: with:
upload_translations: true upload_translations: true

View File

@ -1,8 +1,7 @@
FROM golang:1.17.5 AS BACK FROM golang:1.17.5 AS BACK
WORKDIR /go/src/casdoor WORKDIR /go/src/casdoor
COPY . . COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . \ RUN ./build.sh && apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
&& apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
FROM node:16.13.0 AS FRONT FROM node:16.13.0 AS FRONT
WORKDIR /web WORKDIR /web

144
README.md
View File

@ -42,158 +42,66 @@
</a> </a>
</p> </p>
## Online demo ## 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 - International: https://casdoor.org
go get github.com/casdoor/casdoor - Asian mirror: https://docs.casdoor.cn
```
or `git`:
```bash
git clone https://github.com/casdoor/casdoor
```
Finally, change directory: ## Install
```bash - By source code: https://casdoor.org/docs/basic/server-installation
cd casdoor/ - By Docker: https://casdoor.org/docs/basic/try-with-docker
```
We provide two start up methods for all kinds of users.
### Manual
#### Simple configuration ## How to connect to Casdoor?
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format: 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 https://casdoor.org/docs/integration/apisix
go run main.go
```
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
**But make sure you always request the backend port 8000 when you are using SDKs.**
##### Production Mode ## 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 ## 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). 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-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. 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 ## 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 package authz
import ( import (
"github.com/astaxie/beego"
"github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model" "github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2" xormadapter "github.com/casbin/xorm-adapter/v2"
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
func InitAuthz() { func InitAuthz() {
var err error var err error
tableNamePrefix := beego.AppConfig.String("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true) a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -54,7 +53,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
(r.urlPath == p.urlPath || p.urlPath == "*") && \ (r.urlPath == p.urlPath || p.urlPath == "*") && \
(r.objOwner == p.objOwner || p.objOwner == "*") && \ (r.objOwner == p.objOwner || p.objOwner == "*") && \
(r.objName == p.objName || p.objName == "*") || \ (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) m, err := model.NewModelFromString(modelText)
@ -81,26 +80,31 @@ p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, * p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/get-account, *, * p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, * p, *, *, GET, /api/userinfo, *, *
p, *, *, POST, /api/login/oauth/access_token, *, * p, *, *, *, /api/login/oauth, *, *
p, *, *, POST, /api/login/oauth/refresh_token, *, *
p, *, *, GET, /api/get-application, *, * p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-users, *, * p, *, *, GET, /api/get-applications, *, *
p, *, *, GET, /api/get-user, *, * p, *, *, GET, /api/get-user, *, *
p, *, *, GET, /api/get-organizations, *, *
p, *, *, GET, /api/get-user-application, *, * p, *, *, GET, /api/get-user-application, *, *
p, *, *, GET, /api/get-default-providers, *, *
p, *, *, GET, /api/get-resources, *, * 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/unlink, *, *
p, *, *, POST, /api/set-password, *, * p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, * 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/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, * p, *, *, POST, /api/upload-resource, *, *
p, *, *, GET, /.well-known/openid-configuration, *, * p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, *, /.well-known/jwks, *, * p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, * p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, * p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, *
` `
sa := stringadapter.NewAdapter(ruleText) 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

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
}

60
captcha/hcaptcha.go Normal file
View File

@ -0,0 +1,60 @@
// 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"
"io/ioutil"
"net/http"
"net/url"
)
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"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
return captchaResp.Success, nil
}

30
captcha/provider.go Normal file
View File

@ -0,0 +1,30 @@
// 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()
}
return nil
}

60
captcha/recaptcha.go Normal file
View File

@ -0,0 +1,60 @@
// 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"
"io/ioutil"
"net/http"
"net/url"
)
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"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
return captchaResp.Success, nil
}

View File

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

View File

@ -15,18 +15,59 @@
package conf package conf
import ( import (
"fmt"
"os" "os"
"runtime"
"strconv"
"strings" "strings"
"github.com/astaxie/beego" "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 { func GetBeegoConfDataSourceName() string {
dataSourceName := beego.AppConfig.String("dataSourceName") dataSourceName := GetConfigString("dataSourceName")
runningInDocker := os.Getenv("RUNNING_IN_DOCKER") runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" { 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 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" "encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"strings"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
const ( const (
ResponseTypeLogin = "login" ResponseTypeLogin = "login"
ResponseTypeCode = "code" ResponseTypeCode = "code"
ResponseTypeToken = "token"
ResponseTypeIdToken = "id_token"
ResponseTypeSaml = "saml"
ResponseTypeCas = "cas"
) )
type RequestForm struct { type RequestForm struct {
@ -57,6 +62,7 @@ type RequestForm struct {
AutoSignin bool `json:"autoSignin"` AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"` RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"` SamlResponse string `json:"samlResponse"`
} }
@ -69,12 +75,14 @@ type Response struct {
Data2 interface{} `json:"data2"` Data2 interface{} `json:"data2"`
} }
type HumanCheck struct { type Captcha struct {
Type string `json:"type"` Type string `json:"type"`
AppKey string `json:"appKey"` AppKey string `json:"appKey"`
Scene string `json:"scene"` Scene string `json:"scene"`
CaptchaId string `json:"captchaId"` CaptchaId string `json:"captchaId"`
CaptchaImage interface{} `json:"captchaImage"` CaptchaImage []byte `json:"captchaImage"`
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
} }
// Signup // Signup
@ -110,7 +118,7 @@ func (c *ApiController) Signup() {
return 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) checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 { if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email: %s", checkResult)) c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
@ -128,8 +136,6 @@ func (c *ApiController) Signup() {
} }
} }
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
id := util.GenerateId() id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" { if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization) lastUser := object.GetLastUser(form.Organization)
@ -147,8 +153,6 @@ func (c *ApiController) Signup() {
username = id username = id
} }
userCount := object.GetUserCount(form.Organization, "", "") + 1
user := &object.User{ user := &object.User{
Owner: form.Organization, Owner: form.Organization,
Name: username, Name: username,
@ -171,10 +175,16 @@ func (c *ApiController) Signup() {
IsDeleted: false, IsDeleted: false,
SignupApplication: application.Name, SignupApplication: application.Name,
Properties: map[string]string{}, Properties: map[string]string{},
Ranking: userCount + 1,
Karma: 0, 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 application.GetSignupItemRule("Display name") == "First, last" {
if form.FirstName != "" || form.LastName != "" { if form.FirstName != "" || form.LastName != "" {
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName) user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
@ -202,8 +212,9 @@ func (c *ApiController) Signup() {
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
go object.AddRecord(record) 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) util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId) c.ResponseOk(userId)
@ -219,10 +230,15 @@ func (c *ApiController) Logout() {
user := c.GetSessionUsername() user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
c.SetSessionUsername("") c.SetSessionUsername("")
c.SetSessionData(nil) 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 // GetAccount
@ -271,25 +287,36 @@ func (c *ApiController) GetUserinfo() {
resp, err := object.GetUserInfo(userId, scope, aud, host) resp, err := object.GetUserInfo(userId, scope, aud, host)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return
} }
c.Data["json"] = resp c.Data["json"] = resp
c.ServeJSON() c.ServeJSON()
} }
// GetHumanCheck ... // GetCaptcha ...
// @Tag Login API // @Tag Login API
// @Title GetHumancheck // @Title GetCaptcha
// @router /api/get-human-check [get] // @router /api/get-captcha [get]
func (c *ApiController) GetHumanCheck() { func (c *ApiController) GetCaptcha() {
c.Data["json"] = HumanCheck{Type: "none"} applicationId := c.Input().Get("applicationId")
isCurrentProvider := c.Input().Get("isCurrentProvider")
provider := object.GetDefaultHumanCheckProvider() captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
if provider == nil { if err != nil {
id, img := object.GetCaptcha() c.ResponseError(err.Error())
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
c.ServeJSON()
return 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, ClientId: captchaProvider.ClientId, ClientSecret: captchaProvider.ClientSecret})
return
}
}
c.ResponseOk(Captcha{Type: "none"})
} }

View File

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

View File

@ -23,11 +23,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/google/uuid"
) )
func codeToResponse(code *object.Code) *Response { func codeToResponse(code *object.Code) *Response {
@ -38,6 +39,14 @@ func codeToResponse(code *object.Code) *Response {
return &Response{Status: "ok", Msg: "", Data: code.Code} 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 ... // HandleLoggedIn ...
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) { func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
userId := user.GetId() 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 // The prompt page needs the user to be signed in
c.SetSessionUsername(userId) 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 { } 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 // 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 redirectUri query string true "redirect uri"
// @Param scope query string true "scope" // @Param scope query string true "scope"
// @Param state query string true "state" // @Param state query string true "state"
// @Success 200 {object} controllers.api_controller.Response The Response object // @Success 200 {object} Response The Response object
// @router /update-application [get] // @router /get-app-login [get]
func (c *ApiController) GetApplicationLogin() { func (c *ApiController) GetApplicationLogin() {
clientId := c.Input().Get("clientId") clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType") responseType := c.Input().Get("responseType")
@ -101,6 +143,7 @@ func (c *ApiController) GetApplicationLogin() {
state := c.Input().Get("state") state := c.Input().Get("state")
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state) msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
application = object.GetMaskedApplication(application, "")
if msg != "" { if msg != "" {
c.ResponseError(msg, application) c.ResponseError(msg, application)
} else { } else {
@ -120,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
// @Title Login // @Title Login
// @Tag Login API // @Tag Login API
// @Description login // @Description login
// @Param oAuthParams query string true "oAuth parameters" // @Param clientId query string true clientId
// @Param body body RequestForm true "Login information" // @Param responseType query string true responseType
// @Success 200 {object} controllers.api_controller.Response The Response object // @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] // @router /login [post]
func (c *ApiController) Login() { func (c *ApiController) Login() {
resp := &Response{} resp := &Response{}
@ -149,9 +199,16 @@ func (c *ApiController) Login() {
var verificationCodeType string var verificationCodeType string
var checkResult string var checkResult string
if form.Name != "" {
user = object.GetUserByFields(form.Organization, form.Name)
}
// check result through Email or Phone // check result through Email or Phone
if strings.Contains(form.Username, "@") { if strings.Contains(form.Username, "@") {
verificationCodeType = "email" verificationCodeType = "email"
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
form.Username = user.Email
}
checkResult = object.CheckVerificationCode(form.Username, form.Code) checkResult = object.CheckVerificationCode(form.Username, form.Code)
} else { } else {
verificationCodeType = "phone" verificationCodeType = "phone"
@ -160,6 +217,9 @@ func (c *ApiController) Login() {
c.ResponseError(responseText) c.ResponseError(responseText)
return return
} }
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username) checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code) checkResult = object.CheckVerificationCode(checkPhone, form.Code)
} }
@ -170,11 +230,15 @@ func (c *ApiController) Login() {
} }
// disable the verification code // 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) user = object.GetUserByFields(form.Organization, form.Username)
if user == nil { if user == nil {
c.ResponseError("No such user.") c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
return return
} }
} else { } else {
@ -186,15 +250,25 @@ func (c *ApiController) Login() {
resp = &Response{Status: "error", Msg: msg} resp = &Response{Status: "error", Msg: msg}
} else { } else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) 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) resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
go object.AddRecord(record) util.SafeGoroutine(func() { object.AddRecord(record) })
} }
} else if form.Provider != "" { } else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) 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)) organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider)) provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
providerItem := application.GetProviderItem(provider.Name) providerItem := application.GetProviderItem(provider.Name)
@ -221,7 +295,7 @@ func (c *ApiController) Login() {
clientSecret = provider.ClientSecret2 clientSecret = provider.ClientSecret2
} }
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain) idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
if idProvider == nil { if idProvider == nil {
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type)) c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
return return
@ -229,8 +303,8 @@ func (c *ApiController) Login() {
setHttpClient(idProvider, provider.Type) setHttpClient(idProvider, provider.Type)
if form.State != beego.AppConfig.String("authState") && form.State != application.Name { if form.State != conf.GetConfigString("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State)) c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
return return
} }
@ -259,12 +333,6 @@ func (c *ApiController) Login() {
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id)) user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
} else if provider.Category == "OAuth" { } else if provider.Category == "OAuth" {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id) 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 { if user != nil && user.IsDeleted == false {
@ -279,7 +347,7 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
go object.AddRecord(record) util.SafeGoroutine(func() { object.AddRecord(record) })
} else if provider.Category == "OAuth" { } else if provider.Category == "OAuth" {
// Sign up via OAuth // Sign up via OAuth
if !application.EnableSignUp { if !application.EnableSignUp {
@ -292,6 +360,19 @@ func (c *ApiController) Login() {
return 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 := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2) properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
user = &object.User{ user = &object.User{
@ -328,7 +409,13 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name 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" { } else if provider.Category == "SAML" {
resp = &Response{Status: "error", Msg: "The account does not exist"} 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) oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if oldUser == nil {
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if oldUser != nil { 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)) 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 return
@ -365,8 +449,18 @@ func (c *ApiController) Login() {
if c.GetSessionUsername() != "" { if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in // user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) 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() user := c.getCurrentUser()
resp = c.HandleLoggedIn(application, user, &form) 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 { } else {
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form))) c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
return return

View File

@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
return user.(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) { func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData() sessionData := c.GetSessionData()
if sessionData != nil && if sessionData != nil &&
@ -132,3 +141,11 @@ func wrapActionResponse(affected bool) *Response {
return &Response{Status: "ok", Msg: "", Data: "Unaffected"} 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 { if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id) 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) object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
} }
@ -215,7 +215,7 @@ func (c *ApiController) SyncLdapUsers() {
object.UpdateLdapSyncTime(ldapId) object.UpdateLdapSyncTime(ldapId)
exist, failed := object.SyncLdapUsers(owner, users) exist, failed := object.SyncLdapUsers(owner, users, ldapId)
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{ c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
Exist: *exist, Exist: *exist,
Failed: *failed, 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 // @Title GetOidcDiscovery
// @Tag OIDC API // @Tag OIDC API
// @Description Get Oidc Discovery
// @Success 200 {object} object.OidcDiscovery
// @router /.well-known/openid-configuration [get] // @router /.well-known/openid-configuration [get]
func (c *RootController) GetOidcDiscovery() { func (c *RootController) GetOidcDiscovery() {
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
@ -27,6 +29,7 @@ func (c *RootController) GetOidcDiscovery() {
// @Title GetJwks // @Title GetJwks
// @Tag OIDC API // @Tag OIDC API
// @Success 200 {object} jose.JSONWebKey
// @router /.well-known/jwks [get] // @router /.well-known/jwks [get]
func (c *RootController) GetJwks() { func (c *RootController) GetJwks() {
jwks, err := object.GetJsonWebKeySet() jwks, err := object.GetJsonWebKeySet()

View File

@ -16,6 +16,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination" "github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "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 // @Title GetPayment
// @Tag Payment API // @Tag Payment API
// @Description get payment // @Description get payment
@ -114,3 +133,45 @@ func (c *ApiController) DeletePayment() {
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment)) c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
c.ServeJSON() 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)
}

View File

@ -16,6 +16,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination" "github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
@ -114,3 +115,36 @@ func (c *ApiController) DeleteProduct() {
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product)) c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
c.ServeJSON() 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 // @Description get all records
// @Param pageSize query string true "The size of each page" // @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the 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] // @router /get-records [get]
func (c *ApiController) GetRecords() { func (c *ApiController) GetRecords() {
limit := c.Input().Get("pageSize") limit := c.Input().Get("pageSize")
@ -50,8 +50,8 @@ func (c *ApiController) GetRecords() {
// @Tag Record API // @Tag Record API
// @Title GetRecordsByFilter // @Title GetRecordsByFilter
// @Description get records by filter // @Description get records by filter
// @Param body body object.Records true "filter Record message" // @Param filter body string true "filter Record message"
// @Success 200 {array} object.Records The Response object // @Success 200 {object} object.Record The Response object
// @router /get-records-filter [post] // @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() { func (c *ApiController) GetRecordsByFilter() {
body := string(c.Ctx.Input.RequestBody) 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,13 +25,26 @@ import (
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
type EmailForm struct {
Title string `json:"title"`
Content string `json:"content"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
}
type SmsForm struct {
Content string `json:"content"`
Receivers []string `json:"receivers"`
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
}
// SendEmail // SendEmail
// @Title SendEmail // @Title SendEmail
// @Tag Service API // @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs. // @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application" // @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application" // @Param clientSecret query string true "The clientSecret of the application"
// @Param 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 // @Success 200 {object} Response object
// @router /api/send-email [post] // @router /api/send-email [post]
func (c *ApiController) SendEmail() { func (c *ApiController) SendEmail() {
@ -40,12 +53,8 @@ func (c *ApiController) SendEmail() {
return return
} }
var emailForm struct { var emailForm EmailForm
Title string `json:"title"`
Content string `json:"content"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm) err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
@ -86,7 +95,7 @@ func (c *ApiController) SendEmail() {
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs. // @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application" // @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application" // @Param clientSecret query string true "The clientSecret of the application"
// @Param 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 // @Success 200 {object} Response object
// @router /api/send-sms [post] // @router /api/send-sms [post]
func (c *ApiController) SendSms() { func (c *ApiController) SendSms() {
@ -95,11 +104,7 @@ func (c *ApiController) SendSms() {
return return
} }
var smsForm struct { var smsForm SmsForm
Content string `json:"content"`
Receivers []string `json:"receivers"`
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm) err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())

View File

@ -16,7 +16,6 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"github.com/astaxie/beego/utils/pagination" "github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -114,3 +113,18 @@ func (c *ApiController) DeleteSyncer() {
c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer)) c.Data["json"] = wrapActionResponse(object.DeleteSyncer(&syncer))
c.ServeJSON() 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 ( import (
"encoding/json" "encoding/json"
"net/http"
"github.com/astaxie/beego/utils/pagination" "github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
@ -174,24 +175,43 @@ func (c *ApiController) GetOAuthToken() {
scope := c.Input().Get("scope") scope := c.Input().Get("scope")
username := c.Input().Get("username") username := c.Input().Get("username")
password := c.Input().Get("password") password := c.Input().Get("password")
tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar")
if clientId == "" && clientSecret == "" { if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth() clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
} }
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
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 host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host) c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
c.ServeJSON() c.ServeJSON()
} }
// RefreshToken // RefreshToken
// @Title RefreshToken // @Title RefreshToken
// @Tag Token API
// @Description refresh OAuth access token // @Description refresh OAuth access token
// @Param grant_type query string true "OAuth grant type" // @Param grant_type query string true "OAuth grant type"
// @Param refresh_token query string true "OAuth refresh token" // @Param refresh_token query string true "OAuth refresh token"
// @Param scope query string true "OAuth scope" // @Param scope query string true "OAuth scope"
// @Param client_id query string true "OAuth client id" // @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 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/refresh_token [post] // @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() { func (c *ApiController) RefreshToken() {
@ -202,6 +222,100 @@ func (c *ApiController) RefreshToken() {
clientSecret := c.Input().Get("client_secret") clientSecret := c.Input().Get("client_secret")
host := c.Ctx.Request.Host 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.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.ServeJSON() 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
// @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")
return
}
}
application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret {
c.ResponseError("invalid application or wrong clientSecret")
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) limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value))) paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder) users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
users = object.GetMaskedUsers(users)
c.ResponseOk(users, paginator.Nums()) c.ResponseOk(users, paginator.Nums())
} }
} }
@ -70,6 +71,7 @@ func (c *ApiController) GetUsers() {
limit := util.ParseInt(limit) limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value))) 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.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
users = object.GetMaskedUsers(users)
c.ResponseOk(users, paginator.Nums()) c.ResponseOk(users, paginator.Nums())
} }
} }
@ -85,6 +87,17 @@ func (c *ApiController) GetUser() {
id := c.Input().Get("id") id := c.Input().Get("id")
owner := c.Input().Get("owner") owner := c.Input().Get("owner")
email := c.Input().Get("email") 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 var user *object.User
if email == "" { if email == "" {
@ -109,6 +122,10 @@ func (c *ApiController) UpdateUser() {
id := c.Input().Get("id") id := c.Input().Get("id")
columnsStr := c.Input().Get("columns") columnsStr := c.Input().Get("columns")
if id == "" {
id = c.GetSessionUsername()
}
var user object.User var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user) err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil { if err != nil {
@ -188,19 +205,23 @@ func (c *ApiController) GetEmailAndPhone() {
user := object.GetUserByFields(form.Organization, form.Username) user := object.GetUserByFields(form.Organization, form.Username)
if user == nil { if user == nil {
c.ResponseError("No such user.") c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
return return
} }
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name} respUser := object.User{Name: user.Name}
var contentType string var contentType string
switch form.Username { switch form.Username {
case user.Email: case user.Email:
contentType = "email" contentType = "email"
respUser.Email = user.Email
case user.Phone: case user.Phone:
contentType = "phone" contentType = "phone"
respUser.Phone = user.Phone
case user.Name: case user.Name:
contentType = "username" contentType = "username"
respUser.Email = util.GetMaskedEmail(user.Email)
respUser.Phone = util.GetMaskedPhone(user.Phone)
} }
c.ResponseOk(respUser, contentType) c.ResponseOk(respUser, contentType)
@ -223,39 +244,15 @@ func (c *ApiController) SetPassword() {
newPassword := c.Ctx.Request.Form.Get("newPassword") newPassword := c.Ctx.Request.Form.Get("newPassword")
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
if requestUserId == "" {
c.ResponseError("Please login first.")
return
}
userId := fmt.Sprintf("%s/%s", userOwner, userName) userId := fmt.Sprintf("%s/%s", userOwner, userName)
targetUser := object.GetUser(userId)
if targetUser == nil { hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId)) if !hasPermission {
c.ResponseError(err.Error())
return return
} }
hasPermission := false targetUser := object.GetUser(userId)
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
}
if oldPassword != "" { if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword) msg := object.CheckPassword(targetUser, oldPassword)

View File

@ -18,7 +18,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@ -62,7 +62,7 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
} }
func getInitScore() int { func getInitScore() int {
score, err := strconv.Atoi(beego.AppConfig.String("initScore")) score, err := strconv.Atoi(conf.GetConfigString("initScore"))
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/casdoor/casdoor/captcha"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@ -48,35 +49,50 @@ func (c *ApiController) SendVerificationCode() {
checkUser := c.Ctx.Request.Form.Get("checkUser") checkUser := c.Ctx.Request.Form.Get("checkUser")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request) 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.") c.ResponseError("Missing parameter.")
return return
} }
isHuman := false captchaProvider := captcha.GetCaptchaProvider(checkType)
captchaProvider := object.GetDefaultHumanCheckProvider()
if captchaProvider == nil {
isHuman = object.VerifyCaptcha(checkId, checkKey)
}
if !isHuman { if captchaProvider != nil {
c.ResponseError("Turing test failed.") if checkKey == "" {
return c.ResponseError("Missing parameter: checkKey.")
return
}
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
if err != nil {
c.ResponseError("Failed to verify captcha: %v", err)
return
}
if !isHuman {
c.ResponseError("Turing test failed.")
return
}
} }
user := c.getCurrentUser() user := c.getCurrentUser()
organization := object.GetOrganization(orgId) organization := object.GetOrganization(orgId)
application := object.GetApplicationByOrganizationName(organization.Name) application := object.GetApplicationByOrganizationName(organization.Name)
if checkUser == "true" && user == nil && if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
object.GetUserByFields(organization.Name, dest) == nil { c.ResponseError("Please login first")
c.ResponseError("No such user.")
return 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 { switch destType {
case "email": case "email":
if user != nil && util.GetMaskedEmail(user.Email) == dest {
dest = user.Email
}
if !util.IsEmailValid(dest) { if !util.IsEmailValid(dest) {
c.ResponseError("Invalid Email address") c.ResponseError("Invalid Email address")
return return
@ -85,6 +101,9 @@ func (c *ApiController) SendVerificationCode() {
provider := application.GetEmailProvider() provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest) sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone": case "phone":
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
dest = user.Phone
}
if !util.IsPhoneCnValid(dest) { if !util.IsPhoneCnValid(dest) {
c.ResponseError("Invalid phone number") c.ResponseError("Invalid phone number")
return return
@ -121,7 +140,7 @@ func (c *ApiController) ResetEmailOrPhone() {
user := object.GetUser(userId) user := object.GetUser(userId)
if user == nil { if user == nil {
c.ResponseError("No such user.") c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return return
} }
@ -163,3 +182,36 @@ func (c *ApiController) ResetEmailOrPhone() {
c.Data["json"] = Response{Status: "ok"} c.Data["json"] = Response{Status: "ok"}
c.ServeJSON() 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("Failed to verify captcha: %v", err)
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() return NewMd5UserSaltCredManager()
} else if passwordType == "bcrypt" { } else if passwordType == "bcrypt" {
return NewBcryptCredManager() return NewBcryptCredManager()
} else if passwordType == "pbkdf2-salt" {
return NewPbkdf2SaltCredManager()
} else if passwordType == "argon2id" {
return NewArgon2idCredManager()
} }
return nil return nil
} }

View File

@ -32,8 +32,8 @@ func getMd5HexDigest(s string) string {
return res return res
} }
func NewMd5UserSaltCredManager() *Sha256SaltCredManager { func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
cm := &Sha256SaltCredManager{} cm := &Md5UserSaltCredManager{}
return cm return cm
} }

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

@ -5,6 +5,7 @@ services:
build: build:
context: ./ context: ./
dockerfile: Dockerfile dockerfile: Dockerfile
entrypoint: /bin/sh -c './server --createDatabase=true'
ports: ports:
- "8000:8000" - "8000:8000"
depends_on: depends_on:

28
go.mod
View File

@ -3,41 +3,43 @@ module github.com/casdoor/casdoor
go 1.16 go 1.16
require ( 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/astaxie/beego v1.12.3
github.com/aws/aws-sdk-go v1.37.30 github.com/aws/aws-sdk-go v1.44.4
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect github.com/beevik/etree v1.1.0
github.com/casbin/casbin/v2 v2.30.1 github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.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-FIX1
github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0 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/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/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v0.9.0 github.com/lestrrat-go/jwx v0.9.0
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/qiangmzsx/string-adapter/v2 v2.1.0 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/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.6.0 github.com/russellhaering/gosaml2 v0.6.0
github.com/russellhaering/goxmldsig v1.1.1 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/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tealeg/xlsx v1.0.5 github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4 github.com/thanhpk/randstr v1.0.4
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 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/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.3.0 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect
xorm.io/core v0.7.2 xorm.io/core v0.7.2
xorm.io/xorm v1.0.3 xorm.io/xorm v1.0.4
) )

100
go.sum
View File

@ -35,6 +35,21 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 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 h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= 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 h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= 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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
@ -44,22 +59,26 @@ github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8L
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= 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/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/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-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
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/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/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 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= 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.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= 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/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 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA= github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/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.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= 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 h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
@ -79,8 +98,12 @@ 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/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 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54= 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.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M= github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
github.com/casdoor/goth v1.69.0-FIX1/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/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 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -109,6 +132,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= github.com/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 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
@ -124,6 +149,14 @@ 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-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.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
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-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.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 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@ -131,10 +164,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/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/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/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-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -233,6 +264,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -246,6 +279,7 @@ 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= github.com/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 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -254,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/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 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= 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 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= 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.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
@ -274,6 +308,8 @@ github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 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.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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
@ -309,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/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 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA= 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/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0= 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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -379,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-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-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-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-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -427,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-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-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-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-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-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -443,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-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-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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= 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-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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -462,6 +507,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/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-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -476,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-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-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-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-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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -495,24 +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-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-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-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-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-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/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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-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.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.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.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.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.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.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-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-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-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-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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-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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -688,5 +739,6 @@ xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= 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 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= 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.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=

View File

@ -19,7 +19,7 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
@ -88,7 +88,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

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" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err 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" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -142,8 +143,9 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -178,7 +180,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err 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

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

View File

@ -15,11 +15,13 @@
package idp package idp
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -60,9 +62,38 @@ func (idp *GithubIdProvider) getConfig() *oauth2.Config {
return 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) { func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client) params := &struct {
return idp.Config.Exchange(ctx, code) 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() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -192,3 +223,30 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
} }
return &userInfo, nil 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 ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -84,7 +85,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
rbs, err := io.ReadAll(resp.Body) rbs, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -322,7 +323,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
} }
}(resp.Body) }(resp.Body)
bs, err := io.ReadAll(resp.Body) bs, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err 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) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
} }
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl 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" { if typ == "GitHub" {
return NewGithubIdProvider(clientId, clientSecret, redirectUrl) return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Google" { } else if typ == "Google" {
@ -70,6 +70,10 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl) return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Baidu" { } else if typ == "Baidu" {
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl) 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" { } else if typ == "Infoflow" {
if subType == "Internal" { if subType == "Internal" {
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl) return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
@ -78,8 +82,16 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
} else { } else {
return nil 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) { } else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl) return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
} else if typ == "Bilibili" {
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
} }
return nil return nil

View File

@ -18,7 +18,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"regexp" "regexp"
@ -75,7 +75,10 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
} }
defer resp.Body.Close() 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=(.*?)&") re := regexp.MustCompile("token=(.*?)&")
matched := re.FindAllStringSubmatch(string(tokenContent), -1) matched := re.FindAllStringSubmatch(string(tokenContent), -1)
@ -145,7 +148,10 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
} }
defer resp.Body.Close() 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\":\"(.*?)\"}") re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
matched := re.FindAllStringSubmatch(string(openIdBody), -1) matched := re.FindAllStringSubmatch(string(openIdBody), -1)
@ -161,7 +167,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
userInfoBody, err := io.ReadAll(resp.Body) userInfoBody, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -178,6 +184,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
userInfo := UserInfo{ userInfo := UserInfo{
Id: openId, Id: openId,
Username: qqUserInfo.Nickname,
DisplayName: qqUserInfo.Nickname, DisplayName: qqUserInfo.Nickname,
AvatarUrl: qqUserInfo.FigureurlQq1, 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 ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"time" "time"
@ -72,7 +72,7 @@ func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error)
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -111,6 +111,7 @@ type WecomInternalUserInfo struct {
Email string `json:"email"` Email string `json:"email"`
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
OpenId string `json:"open_userid"` OpenId string `json:"open_userid"`
UserId string `json:"userid"`
} }
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
@ -122,7 +123,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -143,7 +144,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err return nil, err
} }
data, err = io.ReadAll(resp.Body) data, err = ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err 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) return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
} }
userInfo := UserInfo{ userInfo := UserInfo{
Id: infoResp.OpenId, Id: infoResp.UserId,
Username: infoResp.Name, Username: infoResp.Name,
DisplayName: infoResp.Name, DisplayName: infoResp.Name,
Email: infoResp.Email, Email: infoResp.Email,

View File

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

View File

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

11
main.go
View File

@ -22,15 +22,18 @@ import (
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
_ "github.com/astaxie/beego/session/redis" _ "github.com/astaxie/beego/session/redis"
"github.com/casdoor/casdoor/authz" "github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/routers" "github.com/casdoor/casdoor/routers"
_ "github.com/casdoor/casdoor/routers" _ "github.com/casdoor/casdoor/routers"
"github.com/casdoor/casdoor/util"
) )
func main() { 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() flag.Parse()
object.InitAdapter(*createDatabase) object.InitAdapter(*createDatabase)
object.InitDb() object.InitDb()
object.InitDefaultStorageProvider() object.InitDefaultStorageProvider()
@ -38,7 +41,7 @@ func main() {
proxy.InitHttpClient() proxy.InitHttpClient()
authz.InitAuthz() authz.InitAuthz()
go object.RunSyncUsersJob() util.SafeGoroutine(func() {object.RunSyncUsersJob()})
//beego.DelStaticPath("/static") //beego.DelStaticPath("/static")
beego.SetStaticPath("/static", "web/build/static") beego.SetStaticPath("/static", "web/build/static")
@ -52,12 +55,12 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage) beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id" 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.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp" beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
} else { } else {
beego.BConfig.WebConfig.Session.SessionProvider = "redis" 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.SessionCookieLifeTime = 3600 * 24 * 30
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode //beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode

View File

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

View File

@ -41,7 +41,7 @@ func InitConfig() {
func InitAdapter(createDatabase bool) { 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 { if createDatabase {
adapter.CreateDatabase() adapter.CreateDatabase()
} }
@ -111,10 +111,10 @@ func (a *Adapter) close() {
} }
func (a *Adapter) createTable() { func (a *Adapter) createTable() {
showSql, _ := beego.AppConfig.Bool("showSql") showSql, _ := conf.GetConfigBool("showSql")
a.Engine.ShowSQL(showSql) a.Engine.ShowSQL(showSql)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix) tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper) a.Engine.SetTableMapper(tbMapper)
@ -138,6 +138,11 @@ func (a *Adapter) createTable() {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(Model))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider)) err = a.Engine.Sync2(new(Provider))
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -16,11 +16,20 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "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 { type Application struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -216,6 +225,18 @@ func GetMaskedApplication(application *Application, userId string) *Application
if application.ClientSecret != "" { if application.ClientSecret != "" {
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 return application
} }
@ -244,7 +265,11 @@ func UpdateApplication(id string, application *Application) bool {
providerItem.Provider = nil 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 { if err != nil {
panic(err) panic(err)
} }
@ -283,3 +308,14 @@ func DeleteApplication(application *Application) bool {
func (application *Application) GetId() string { func (application *Application) GetId() string {
return fmt.Sprintf("%s/%s", application.Owner, application.Name) 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
}

View File

@ -19,14 +19,14 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
) )
var defaultStorageProvider *Provider = nil var defaultStorageProvider *Provider = nil
func InitDefaultStorageProvider() { func InitDefaultStorageProvider() {
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider") defaultStorageProviderStr := conf.GetConfigString("defaultStorageProvider")
if defaultStorageProviderStr != "" { if defaultStorageProviderStr != "" {
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr) defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
} }

View File

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

View File

@ -17,6 +17,7 @@ package object
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
"github.com/casdoor/casdoor/cred" "github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -179,19 +180,53 @@ func CheckUserPassword(organization string, username string, password string) (*
if user.IsForbidden { if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator" return nil, "the user is forbidden to sign in, please contact the administrator"
} }
//for ldap users
if user.Ldap != "" { if user.Ldap != "" {
//ONLY for ldap users
return checkLdapUserPassword(user, password) 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, "" return user, ""
} }
func filterField(field string) bool { func filterField(field string) bool {
return reFieldWhiteList.MatchString(field) 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

@ -15,29 +15,26 @@
package object package object
import ( import (
_ "embed" "io/ioutil"
"github.com/casdoor/casdoor/util" "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() { func InitDb() {
initBuiltInOrganization() existed := initBuiltInOrganization()
initBuiltInUser() if !existed {
initBuiltInApplication() initBuiltInProvider()
initBuiltInCert() initBuiltInUser()
initBuiltInLdap() initBuiltInApplication()
initBuiltInCert()
initBuiltInLdap()
}
} }
func initBuiltInOrganization() { func initBuiltInOrganization() bool {
organization := getOrganization("admin", "built-in") organization := getOrganization("admin", "built-in")
if organization != nil { if organization != nil {
return return true
} }
organization = &Organization{ organization = &Organization{
@ -47,11 +44,38 @@ func initBuiltInOrganization() {
DisplayName: "Built-in Organization", DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com", WebsiteUrl: "https://example.com",
Favicon: "https://cdn.casbin.com/static/favicon.ico", Favicon: "https://cdn.casbin.com/static/favicon.ico",
PasswordType: "plain",
PhonePrefix: "86", PhonePrefix: "86",
DefaultAvatar: "https://casbin.org/img/casbin.svg", 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) AddOrganization(organization)
return false
} }
func initBuiltInUser() { func initBuiltInUser() {
@ -104,14 +128,16 @@ func initBuiltInApplication() {
Cert: "cert-built-in", Cert: "cert-built-in",
EnablePassword: true, EnablePassword: true,
EnableSignUp: true, EnableSignUp: true,
Providers: []*ProviderItem{}, Providers: []*ProviderItem{
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Provider: nil},
},
SignupItems: []*SignupItem{ SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"}, {Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Display name", 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: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Confirm 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: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", 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) 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() { func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in") cert := getCert("admin", "cert-built-in")
if cert != nil { if cert != nil {
return return
@ -134,7 +175,7 @@ func initBuiltInCert() {
DisplayName: "Built-in Cert", DisplayName: "Built-in Cert",
Scope: "JWT", Scope: "JWT",
Type: "x509", Type: "x509",
CryptoAlgorithm: "RSA", CryptoAlgorithm: "RS256",
BitSize: 4096, BitSize: 4096,
ExpireInYears: 20, ExpireInYears: 20,
PublicKey: tokenJwtPublicKey, PublicKey: tokenJwtPublicKey,
@ -163,3 +204,20 @@ func initBuiltInLdap() {
} }
AddLdap(ldap) 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)
}

View File

@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3" goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr" "github.com/thanhpk/randstr"
@ -42,6 +43,7 @@ type Ldap struct {
type ldapConn struct { type ldapConn struct {
Conn *goldap.Conn Conn *goldap.Conn
IsAD bool
} }
//type ldapGroup struct { //type ldapGroup struct {
@ -78,6 +80,13 @@ type LdapRespUser struct {
Address string `json:"address"` Address string `json:"address"`
} }
type ldapServerType struct {
Vendorname string
Vendorversion string
IsGlobalCatalogReady string
ForestFunctionality string
}
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser { func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
returnAnyNotEmpty := func(strs ...string) string { returnAnyNotEmpty := func(strs ...string) string {
for _, str := range strs { for _, str := range strs {
@ -104,6 +113,45 @@ func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
return res 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) { func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port)) conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil { 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 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 //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)" SearchFilter := "(objectClass=posixAccount)"
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email", SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"} "emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
SearchFilterMsAD := "(objectClass=user)"
searchReq := goldap.NewSearchRequest(baseDn, SearchAttributesMsAD := []string{"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false, "emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
SearchFilter, SearchAttributes, nil) 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) searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
if err != nil { if err != nil {
return nil, err return nil, err
@ -181,12 +242,16 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
ldapUserItem.UidNumber = attribute.Values[0] ldapUserItem.UidNumber = attribute.Values[0]
case "uid": case "uid":
ldapUserItem.Uid = attribute.Values[0] ldapUserItem.Uid = attribute.Values[0]
case "sAMAccountName":
ldapUserItem.Uid = attribute.Values[0]
case "cn": case "cn":
ldapUserItem.Cn = attribute.Values[0] ldapUserItem.Cn = attribute.Values[0]
case "gidNumber": case "gidNumber":
ldapUserItem.GidNumber = attribute.Values[0] ldapUserItem.GidNumber = attribute.Values[0]
case "entryUUID": case "entryUUID":
ldapUserItem.Uuid = attribute.Values[0] ldapUserItem.Uuid = attribute.Values[0]
case "objectGUID":
ldapUserItem.Uuid = attribute.Values[0]
case "mail": case "mail":
ldapUserItem.Mail = attribute.Values[0] ldapUserItem.Mail = attribute.Values[0]
case "email": case "email":
@ -300,7 +365,7 @@ func DeleteLdap(ldap *Ldap) bool {
return affected != 0 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 existUsers []LdapRespUser
var failedUsers []LdapRespUser var failedUsers []LdapRespUser
var uuids []string var uuids []string
@ -311,6 +376,25 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
existUuids := CheckLdapUuidExist(owner, uuids) 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 { for _, user := range users {
found := false found := false
if len(existUuids) > 0 { if len(existUuids) > 0 {
@ -325,15 +409,14 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
Owner: owner, Owner: owner,
Name: buildLdapUserName(user.Uid, user.UidNumber), Name: buildLdapUserName(user.Uid, user.UidNumber),
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
Password: "123",
DisplayName: user.Cn, DisplayName: user.Cn,
Avatar: "https://casbin.org/img/casbin.svg", Avatar: organization.DefaultAvatar,
Email: user.Email, Email: user.Email,
Phone: user.Phone, Phone: user.Phone,
Address: []string{user.Address}, Address: []string{user.Address},
Affiliation: "Example Inc.", Affiliation: affiliation,
Tag: "staff", Tag: tag,
Score: 2000, Score: beego.AppConfig.DefaultInt("initScore", 2000),
Ldap: user.Uuid, Ldap: user.Uuid,
}) { }) {
failedUsers = append(failedUsers, user) failedUsers = append(failedUsers, user)

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
"github.com/casdoor/casdoor/util"
) )
type LdapAutoSynchronizer struct { type LdapAutoSynchronizer struct {
@ -47,7 +48,7 @@ func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
stopChan := make(chan struct{}) stopChan := make(chan struct{})
l.ldapIdToStopChan[ldapId] = stopChan l.ldapIdToStopChan[ldapId] = stopChan
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id)) logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
go l.syncRoutine(ldap, stopChan) util.SafeGoroutine(func() {l.syncRoutine(ldap, stopChan)})
return nil 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)) logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue continue
} }
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users)) existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
if len(*failed) != 0 { 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) logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
} else { } 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" "fmt"
"strings" "strings"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
) )
@ -30,6 +30,7 @@ type OidcDiscovery struct {
TokenEndpoint string `json:"token_endpoint"` TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"` UserinfoEndpoint string `json:"userinfo_endpoint"`
JwksUri string `json:"jwks_uri"` JwksUri string `json:"jwks_uri"`
IntrospectionEndpoint string `json:"introspection_endpoint"`
ResponseTypesSupported []string `json:"response_types_supported"` ResponseTypesSupported []string `json:"response_types_supported"`
ResponseModesSupported []string `json:"response_modes_supported"` ResponseModesSupported []string `json:"response_modes_supported"`
GrantTypesSupported []string `json:"grant_types_supported"` GrantTypesSupported []string `json:"grant_types_supported"`
@ -57,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
func GetOidcDiscovery(host string) OidcDiscovery { func GetOidcDiscovery(host string) OidcDiscovery {
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
if origin != "" { if origin != "" {
originFrontend = origin originFrontend = origin
originBackend = origin originBackend = origin
@ -74,7 +75,8 @@ func GetOidcDiscovery(host string) OidcDiscovery {
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend), TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend), UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend), JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
ResponseTypesSupported: []string{"id_token"}, 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"}, ResponseModesSupported: []string{"login", "code", "link"},
GrantTypesSupported: []string{"password", "authorization_code"}, GrantTypesSupported: []string{"password", "authorization_code"},
SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: []string{"public"},
@ -103,6 +105,8 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
jwk.Key = x509Cert.PublicKey jwk.Key = x509Cert.PublicKey
jwk.Certificates = []*x509.Certificate{x509Cert} jwk.Certificates = []*x509.Certificate{x509Cert}
jwk.KeyID = cert.Name jwk.KeyID = cert.Name
jwk.Algorithm = cert.CryptoAlgorithm
jwk.Use = "sig"
jwks.Keys = append(jwks.Keys, jwk) jwks.Keys = append(jwks.Keys, jwk)
} }

View File

@ -20,20 +20,31 @@ import (
"xorm.io/core" "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 { type Organization struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"` WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"` Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"` PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"` PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"` DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"` Tags []string `xorm:"mediumtext" json:"tags"`
EnableSoftDeletion bool `json:"enableSoftDeletion"` 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 { func GetOrganizationCount(owner, field, value string) int {
@ -119,14 +130,18 @@ func UpdateOrganization(id string, organization *Organization) bool {
} }
if name != organization.Name { if name != organization.Name {
applications := GetApplicationsByOrganizationName("admin", name) go func() {
for _, application := range applications { application := new(Application)
application.Organization = organization.Name 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) credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil { if credManager != nil {
hashedPassword := credManager.GetHashedPassword(organization.MasterPassword, "", organization.PasswordSalt) 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 { if err != nil {
panic(err) panic(err)
} }

View File

@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"net/http"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
@ -27,15 +28,32 @@ type Payment struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Provider string `xorm:"varchar(100)" json:"provider"` Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"` Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"` User string `xorm:"varchar(100)" json:"user"`
Good string `xorm:"varchar(100)" json:"good"` ProductName string `xorm:"varchar(100)" json:"productName"`
Amount string `xorm:"varchar(100)" json:"amount"` ProductDisplayName string `xorm:"varchar(100)" json:"productDisplayName"`
Currency string `xorm:"varchar(100)" json:"currency"`
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 { func GetPaymentCount(owner, field, value string) int {
@ -58,6 +76,16 @@ func GetPayments(owner string) []*Payment {
return payments 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 { func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
payments := []*Payment{} payments := []*Payment{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder) session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
@ -124,6 +152,99 @@ func DeletePayment(payment *Payment) bool {
return affected != 0 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 { func (payment *Payment) GetId() string {
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name) return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
} }

View File

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

View File

@ -31,10 +31,11 @@ type Product struct {
Detail string `xorm:"varchar(100)" json:"detail"` Detail string `xorm:"varchar(100)" json:"detail"`
Tag string `xorm:"varchar(100)" json:"tag"` Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"` Currency string `xorm:"varchar(100)" json:"currency"`
Price int `json:"price"` Price float64 `json:"price"`
Quantity int `json:"quantity"` Quantity int `json:"quantity"`
Sold int `json:"sold"` Sold int `json:"sold"`
Providers []string `xorm:"varchar(100)" json:"providers"` Providers []string `xorm:"varchar(100)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"` State string `xorm:"varchar(100)" json:"state"`
} }
@ -128,3 +129,83 @@ func DeleteProduct(product *Product) bool {
func (product *Product) GetId() string { func (product *Product) GetId() string {
return fmt.Sprintf("%s/%s", product.Owner, product.Name) 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)
paymentId := util.GenerateTimeId()
returnUrl := ""
notifyUrl := ""
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
if err != nil {
panic(err)
}
println(payUrl)
}

View File

@ -17,6 +17,7 @@ package object
import ( import (
"fmt" "fmt"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
@ -26,15 +27,21 @@ type Provider struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"` Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
SubType string `xorm:"varchar(100)" json:"subType"` SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"` Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"` ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` ClientSecret2 string `xorm:"varchar(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"` Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"` Port int `json:"port"`
@ -135,8 +142,8 @@ func GetProvider(id string) *Provider {
return getProvider(owner, name) return getProvider(owner, name)
} }
func GetDefaultHumanCheckProvider() *Provider { func GetDefaultCaptchaProvider() *Provider {
provider := Provider{Owner: "admin", Category: "HumanCheck"} provider := Provider{Owner: "admin", Category: "Captcha"}
existed, err := adapter.Engine.Get(&provider) existed, err := adapter.Engine.Get(&provider)
if err != nil { if err != nil {
panic(err) panic(err)
@ -149,13 +156,30 @@ func GetDefaultHumanCheckProvider() *Provider {
return &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 { func UpdateProvider(id string, provider *Provider) bool {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if getProvider(owner, name) == nil { if getProvider(owner, name) == nil {
return false 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 { if err != nil {
panic(err) panic(err)
} }
@ -181,6 +205,57 @@ func DeleteProvider(provider *Provider) bool {
return affected != 0 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 { func (p *Provider) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name) 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 { func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil {
return false
}
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML" return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
} }

View File

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

View File

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

354
object/saml_idp.go Normal file
View File

@ -0,0 +1,354 @@
// 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, 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", fmt.Sprintf("_%s", arId))
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", fmt.Sprintf("_%s", arId))
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
}
//GenerateSamlResponse 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) {
//decode samlRequest
defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
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 publickey 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, 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)
xmlStr, err := doc.WriteToString()
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
res := base64.StdEncoding.EncodeToString([]byte(xmlStr))
return res, authnRequest.AssertionConsumerServiceURL, nil
}
//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" "regexp"
"strings" "strings"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
saml2 "github.com/russellhaering/gosaml2" saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig" dsig "github.com/russellhaering/goxmldsig"
) )
@ -73,7 +73,7 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
certStore := dsig.MemoryX509CertificateStore{ certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{}, Roots: []*x509.Certificate{},
} }
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
certEncodedData := "" certEncodedData := ""
if samlResponse != "" { if samlResponse != "" {
certEncodedData = parseSamlResponse(samlResponse, provider.Type) 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 { 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) 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 { if err != nil {
return err return err
} }

View File

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

View File

@ -133,7 +133,11 @@ func UpdateSyncer(id string, syncer *Syncer) bool {
return false 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 { if err != nil {
panic(err) panic(err)
} }
@ -206,3 +210,8 @@ func (syncer *Syncer) getTable() string {
return syncer.Table return syncer.Table
} }
} }
func RunSyncer(syncer *Syncer) {
syncer.initAdapter()
syncer.syncUsers()
}

View File

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

View File

@ -10,6 +10,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build !skipCi //go:build !skipCi
// +build !skipCi // +build !skipCi

View File

@ -15,9 +15,11 @@
package object package object
import ( import (
"encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@ -99,12 +101,18 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
user.PasswordSalt = value user.PasswordSalt = value
case "DisplayName": case "DisplayName":
user.DisplayName = value user.DisplayName = value
case "FirstName":
user.FirstName = value
case "LastName":
user.LastName = value
case "Avatar": case "Avatar":
user.Avatar = syncer.getPartialAvatarUrl(value) user.Avatar = syncer.getPartialAvatarUrl(value)
case "PermanentAvatar": case "PermanentAvatar":
user.PermanentAvatar = value user.PermanentAvatar = value
case "Email": case "Email":
user.Email = value user.Email = value
case "EmailVerified":
user.EmailVerified = util.ParseBool(value)
case "Phone": case "Phone":
user.Phone = value user.Phone = value
case "Location": case "Location":
@ -165,8 +173,45 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
} }
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name]) value := ""
if strings.Contains(tableColumn.Name, "+") {
names := strings.Split(tableColumn.Name, "+")
var values []string
for _, name := range names {
values = append(values, result[strings.Trim(name, " ")])
}
value = strings.Join(values, " ")
} else {
value = result[tableColumn.Name]
}
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
} }
if syncer.Type == "Keycloak" {
// query and set password and password salt from credential table
sql := fmt.Sprintf("select * from credential where type = 'password' and user_id = '%s'", originalUser.Id)
credentialResult, _ := syncer.Adapter.Engine.QueryString(sql)
if len(credentialResult) > 0 {
credential := Credential{}
_ = json.Unmarshal([]byte(credentialResult[0]["SECRET_DATA"]), &credential)
originalUser.Password = credential.Value
originalUser.PasswordSalt = credential.Salt
}
// query and set signup application from user group table
sql = fmt.Sprintf("select name from keycloak_group where id = " +
"(select group_id as gid from user_group_membership where user_id = '%s')", originalUser.Id)
groupResult, _ := syncer.Adapter.Engine.QueryString(sql)
if len(groupResult) > 0 {
originalUser.SignupApplication = groupResult[0]["name"]
}
// create time
i, _ := strconv.ParseInt(originalUser.CreatedTime, 10, 64)
tm := time.Unix(i/int64(1000), 0)
originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00")
// enable
originalUser.IsForbidden = !(result["ENABLED"] == "\x01")
}
users = append(users, originalUser) users = append(users, originalUser)
} }
return users return users

View File

@ -22,10 +22,15 @@ import (
"strings" "strings"
"time" "time"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
const (
hourSeconds = 3600
)
type Code struct { type Code struct {
Message string `xorm:"varchar(100)" json:"message"` Message string `xorm:"varchar(100)" json:"message"`
Code string `xorm:"varchar(100)" json:"code"` Code string `xorm:"varchar(100)" json:"code"`
@ -58,6 +63,22 @@ type TokenWrapper struct {
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"` ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"` Scope string `json:"scope"`
Error string `json:"error,omitempty"`
}
type IntrospectionResponse struct {
Active bool `json:"active"`
Scope string `json:"scope,omitempty"`
ClientId string `json:"client_id,omitempty"`
Username string `json:"username,omitempty"`
TokenType string `json:"token_type,omitempty"`
Exp int64 `json:"exp,omitempty"`
Iat int64 `json:"iat,omitempty"`
Nbf int64 `json:"nbf,omitempty"`
Sub string `json:"sub,omitempty"`
Aud []string `json:"aud,omitempty"`
Iss string `json:"iss,omitempty"`
Jti string `json:"jti,omitempty"`
} }
func GetTokenCount(owner, field, value string) int { func GetTokenCount(owner, field, value string) int {
@ -169,6 +190,25 @@ func DeleteToken(token *Token) bool {
return affected != 0 return affected != 0
} }
func DeleteTokenByAceessToken(accessToken string) (bool, *Application) {
token := Token{AccessToken: accessToken}
existed, err := adapter.Engine.Get(&token)
if err != nil {
panic(err)
}
if !existed {
return false, nil
}
application := getApplication(token.Owner, token.Application)
affected, err := adapter.Engine.Where("access_token=?", accessToken).Delete(&Token{})
if err != nil {
panic(err)
}
return affected != 0, application
}
func GetTokenByAccessToken(accessToken string) *Token { func GetTokenByAccessToken(accessToken string) *Token {
//Check if the accessToken is in the database //Check if the accessToken is in the database
token := Token{AccessToken: accessToken} token := Token{AccessToken: accessToken}
@ -179,9 +219,18 @@ func GetTokenByAccessToken(accessToken string) *Token {
return &token return &token
} }
func GetTokenByTokenAndApplication(token string, application string) *Token {
tokenResult := Token{}
existed, err := adapter.Engine.Where("(refresh_token = ? or access_token = ? ) and application = ?", token, token, application).Get(&tokenResult)
if err != nil || !existed {
return nil
}
return &tokenResult
}
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) { func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
if responseType != "code" { if responseType != "code" && responseType != "token" && responseType != "id_token" {
return "response_type should be \"code\"", nil return fmt.Sprintf("error: grant_type: %s is not supported in this application", responseType), nil
} }
application := GetApplicationByClientId(clientId) application := GetApplicationByClientId(clientId)
@ -247,7 +296,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeChallenge: challenge, CodeChallenge: challenge,
@ -262,24 +311,31 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
} }
} }
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string) *TokenWrapper {
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) *TokenWrapper {
var errString string
application := GetApplicationByClientId(clientId) application := GetApplicationByClientId(clientId)
if application == nil { if application == nil {
errString = "error: invalid client_id"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid client_id", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
//Check if grantType is allowed in the current application //Check if grantType is allowed in the current application
if !isGrantTypeValid(grantType, application.GrantTypes) {
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType)
return &TokenWrapper{ return &TokenWrapper{
AccessToken: fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType), AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
@ -294,12 +350,19 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
token, err = GetClientCredentialsToken(application, clientSecret, scope, host) token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
} }
if tag == "wechat_miniprogram" {
// Wechat Mini Program
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar)
}
if err != nil { if err != nil {
errString = err.Error()
return &TokenWrapper{ return &TokenWrapper{
AccessToken: err.Error(), AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
@ -318,62 +381,75 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
} }
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper { func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper {
var errString string
// check parameters // check parameters
if grantType != "refresh_token" { if grantType != "refresh_token" {
errString = "error: grant_type should be \"refresh_token\""
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: grant_type should be \"refresh_token\"", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
application := GetApplicationByClientId(clientId) application := GetApplicationByClientId(clientId)
if application == nil { if application == nil {
errString = "error: invalid client_id"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid client_id", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
if application.ClientSecret != clientSecret { if clientSecret != "" && application.ClientSecret != clientSecret {
errString = "error: invalid client_secret"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid client_secret", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
// check whether the refresh token is valid, and has not expired. // check whether the refresh token is valid, and has not expired.
token := Token{RefreshToken: refreshToken} token := Token{RefreshToken: refreshToken}
existed, err := adapter.Engine.Get(&token) existed, err := adapter.Engine.Get(&token)
if err != nil || !existed { if err != nil || !existed {
errString = "error: invalid refresh_token"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid refresh_token", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
cert := getCertByApplication(application) cert := getCertByApplication(application)
_, err = ParseJwtToken(refreshToken, cert) _, err = ParseJwtToken(refreshToken, cert)
if err != nil { if err != nil {
errString := fmt.Sprintf("error: %s", err.Error())
return &TokenWrapper{ return &TokenWrapper{
AccessToken: fmt.Sprintf("error: %s", err.Error()), AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
// generate a new token // generate a new token
user := getUser(application.Organization, token.User) user := getUser(application.Organization, token.User)
if user.IsForbidden { if user.IsForbidden {
errString = "error: the user is forbidden to sign in, please contact the administrator"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: the user is forbidden to sign in, please contact the administrator", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host) newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
@ -391,19 +467,20 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: newAccessToken, AccessToken: newAccessToken,
RefreshToken: newRefreshToken, RefreshToken: newRefreshToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
} }
AddToken(newToken) AddToken(newToken)
DeleteToken(&token)
tokenWrapper := &TokenWrapper{ tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken, AccessToken: newToken.AccessToken,
IdToken: token.AccessToken, IdToken: newToken.AccessToken,
RefreshToken: token.RefreshToken, RefreshToken: newToken.RefreshToken,
TokenType: token.TokenType, TokenType: newToken.TokenType,
ExpiresIn: token.ExpiresIn, ExpiresIn: newToken.ExpiresIn,
Scope: token.Scope, Scope: newToken.Scope,
} }
return tokenWrapper return tokenWrapper
@ -418,7 +495,7 @@ func pkceChallenge(verifier string) string {
// Check if grantType is allowed in the current application // Check if grantType is allowed in the current application
// authorization_code is allowed by default // authorization_code is allowed by default
func isGrantTypeValid(method string, grantTypes []string) bool { func IsGrantTypeValid(method string, grantTypes []string) bool {
if method == "authorization_code" { if method == "authorization_code" {
return true return true
} }
@ -444,18 +521,27 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
// anti replay attacks // anti replay attacks
return nil, errors.New("error: authorization code has been used") return nil, errors.New("error: authorization code has been used")
} }
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return nil, errors.New("error: incorrect code_verifier")
}
if application.ClientSecret != clientSecret { if application.ClientSecret != clientSecret {
return nil, errors.New("error: invalid client_secret") // when using PKCE, the Client Secret can be empty,
// but if it is provided, it must be accurate.
if token.CodeChallenge == "" {
return nil, errors.New("error: invalid client_secret")
} else {
if clientSecret != "" {
return nil, errors.New("error: invalid client_secret")
}
}
} }
if application.Name != token.Application { if application.Name != token.Application {
return nil, errors.New("error: the token is for wrong application (client_id)") return nil, errors.New("error: the token is for wrong application (client_id)")
} }
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return nil, errors.New("error: incorrect code_verifier")
}
if time.Now().Unix() > token.CodeExpireIn { if time.Now().Unix() > token.CodeExpireIn {
// code must be used within 5 minutes // code must be used within 5 minutes
return nil, errors.New("error: authorization code has expired") return nil, errors.New("error: authorization code has expired")
@ -469,7 +555,8 @@ func GetPasswordToken(application *Application, username string, password string
if user == nil { if user == nil {
return nil, errors.New("error: the user does not exist") return nil, errors.New("error: the user does not exist")
} }
if user.Password != password { msg := CheckPassword(user, password)
if msg != "" {
return nil, errors.New("error: invalid username or password") return nil, errors.New("error: invalid username or password")
} }
if user.IsForbidden { if user.IsForbidden {
@ -489,7 +576,7 @@ func GetPasswordToken(application *Application, username string, password string
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,
@ -504,7 +591,9 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
return nil, errors.New("error: invalid client_secret") return nil, errors.New("error: invalid client_secret")
} }
nullUser := &User{ nullUser := &User{
Name: fmt.Sprintf("app/%s", application.Name), Owner: application.Owner,
Id: application.GetId(),
Name: fmt.Sprintf("app/%s", application.Name),
} }
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host) accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
if err != nil { if err != nil {
@ -519,7 +608,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
User: nullUser.Name, User: nullUser.Name,
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,
@ -527,3 +616,99 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
AddToken(token) AddToken(token)
return token, nil return token, nil
} }
// Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return nil, err
}
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope,
TokenType: "Bearer",
CodeIsUsed: true,
}
AddToken(token)
return token, nil
}
// Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) {
mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil {
return nil, errors.New("error: the application does not support wechat mini program")
}
provider := GetProvider(util.GetId(mpProvider.Name))
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
session, err := mpIdp.GetSessionByCode(code)
if err != nil {
return nil, err
}
openId, unionId := session.Openid, session.Unionid
if openId == "" && unionId == "" {
return nil, errors.New("err: WeChat's openid and unionid are empty")
}
user := getUserByWechatId(openId, unionId)
if user == nil {
if !application.EnableSignUp {
return nil, errors.New("err: the application does not allow to sign up new account")
}
//Add new user
var name string
if username != "" {
name = username
} else {
name = fmt.Sprintf("wechat-%s", openId)
}
user = &User{
Owner: application.Organization,
Id: util.GenerateId(),
Name: name,
Avatar: avatar,
SignupApplication: application.Name,
WeChat: openId,
WeChatUnionId: unionId,
Type: "normal-user",
CreatedTime: util.GetCurrentTime(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
}
AddUser(user)
}
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
if err != nil {
return nil, err
}
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: session.SessionKey, //a trick, because miniprogram does not use the code, so use the code field to save the session_key
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: "",
TokenType: "Bearer",
CodeIsUsed: true,
}
AddToken(token)
return token, nil
}

327
object/token_cas.go Normal file
View File

@ -0,0 +1,327 @@
// 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 (
"crypto"
"encoding/base64"
"encoding/json"
"encoding/pem"
"encoding/xml"
"fmt"
"math/rand"
"sync"
"time"
"github.com/beevik/etree"
"github.com/casdoor/casdoor/util"
dsig "github.com/russellhaering/goxmldsig"
)
type CasServiceResponse struct {
XMLName xml.Name `xml:"cas:serviceResponse" json:"-"`
Xmlns string `xml:"xmlns:cas,attr"`
Failure *CasAuthenticationFailure
Success *CasAuthenticationSuccess
ProxySuccess *CasProxySuccess
ProxyFailure *CasProxyFailure
}
type CasAuthenticationFailure struct {
XMLName xml.Name `xml:"cas:authenticationFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
type CasAuthenticationSuccess struct {
XMLName xml.Name `xml:"cas:authenticationSuccess" json:"-"`
User string `xml:"cas:user"`
ProxyGrantingTicket string `xml:"cas:proxyGrantingTicket,omitempty"`
Proxies *CasProxies `xml:"cas:proxies"`
Attributes *CasAttributes `xml:"cas:attributes"`
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasProxies struct {
XMLName xml.Name `xml:"cas:proxies" json:"-"`
Proxies []string `xml:"cas:proxy"`
}
type CasAttributes struct {
XMLName xml.Name `xml:"cas:attributes" json:"-"`
AuthenticationDate time.Time `xml:"cas:authenticationDate"`
LongTermAuthenticationRequestTokenUsed bool `xml:"cas:longTermAuthenticationRequestTokenUsed"`
IsFromNewLogin bool `xml:"cas:isFromNewLogin"`
MemberOf []string `xml:"cas:memberOf"`
UserAttributes *CasUserAttributes
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasUserAttributes struct {
XMLName xml.Name `xml:"cas:userAttributes" json:"-"`
Attributes []*CasNamedAttribute `xml:"cas:attribute"`
AnyAttributes []*CasAnyAttribute `xml:",any"`
}
type CasNamedAttribute struct {
XMLName xml.Name `xml:"cas:attribute" json:"-"`
Name string `xml:"name,attr,omitempty"`
Value string `xml:",innerxml"`
}
type CasAnyAttribute struct {
XMLName xml.Name
Value string `xml:",chardata"`
}
type CasAuthenticationSuccessWrapper struct {
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
Service string //to which service this token is issued
UserId string
}
type CasProxySuccess struct {
XMLName xml.Name `xml:"cas:proxySuccess" json:"-"`
ProxyTicket string `xml:"cas:proxyTicket"`
}
type CasProxyFailure struct {
XMLName xml.Name `xml:"cas:proxyFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
type Saml11Request struct {
XMLName xml.Name `xml:"Request"`
SAMLP string `xml:"samlp,attr"`
MajorVersion string `xml:"MajorVersion,attr"`
MinorVersion string `xml:"MinorVersion,attr"`
RequestID string `xml:"RequestID,attr"`
IssueInstant string `xml:"IssueInstance,attr"`
AssertionArtifact Saml11AssertionArtifact
}
type Saml11AssertionArtifact struct {
XMLName xml.Name `xml:"AssertionArtifact"`
InnerXML string `xml:",innerxml"`
}
//st is short for service ticket
var stToServiceResponse sync.Map
//pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service, userId string) string {
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: service,
UserId: userId,
})
return pgt
}
func GenerateId() {
panic("unimplemented")
}
/**
@ret1: whether a token is found
@ret2: token, nil if not found
@ret3: the service URL who requested to issue this token
@ret4: userIf of user who requested to issue this token
*/
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string, string) {
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
}
return false, nil, "", ""
}
/**
@ret1: whether a token is found
@ret2: token, nil if not found
@ret3: the service URL who requested to issue this token
@ret4: userIf of user who requested to issue this token
*/
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string, string) {
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
}
return false, nil, "", ""
}
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService, userId string) string {
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: targetService,
UserId: userId,
})
return proxyTicket
}
func GenerateCasToken(userId string, service string) (string, error) {
if user := GetUser(userId); user != nil {
authenticationSuccess := CasAuthenticationSuccess{
User: user.Name,
Attributes: &CasAttributes{
AuthenticationDate: time.Now(),
UserAttributes: &CasUserAttributes{},
},
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
}
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
for k, v := range tmp {
if v != "" {
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: v,
})
}
}
st := fmt.Sprintf("ST-%d", rand.Int())
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: &authenticationSuccess,
Service: service,
UserId: userId,
})
return st, nil
} else {
return "", fmt.Errorf("invalid user Id")
}
}
/**
@ret1: saml response
@ret2: the service URL who requested to issue this token
@ret3: error
*/
func GetValidationBySaml(samlRequest string, host string) (string, string, error) {
var request Saml11Request
err := xml.Unmarshal([]byte(samlRequest), &request)
if err != nil {
return "", "", err
}
ticket := request.AssertionArtifact.InnerXML
if ticket == "" {
return "", "", fmt.Errorf("samlp:AssertionArtifact field not found")
}
ok, _, service, userId := GetCasTokenByTicket(ticket)
if !ok {
return "", "", fmt.Errorf("ticket %s found", ticket)
}
user := GetUser(userId)
if user == nil {
return "", "", fmt.Errorf("user %s found", userId)
}
application := GetApplicationByUser(user)
if application == nil {
return "", "", fmt.Errorf("application for user %s found", userId)
}
samlResponse := NewSamlResponse11(user, request.RequestID, host)
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
}
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1
signedXML, err := ctx.SignEnveloped(samlResponse)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
doc := etree.NewDocument()
doc.SetRoot(signedXML)
xmlStr, err := doc.WriteToString()
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
return xmlStr, service, nil
}
func (c *CasAuthenticationSuccess) DeepCopy() CasAuthenticationSuccess {
res := *c
//copy proxy
if c.Proxies != nil {
tmp := c.Proxies.DeepCopy()
res.Proxies = &tmp
}
if c.Attributes != nil {
tmp := c.Attributes.DeepCopy()
res.Attributes = &tmp
}
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasProxies) DeepCopy() CasProxies {
res := CasProxies{
Proxies: make([]string, len(c.Proxies)),
}
copy(res.Proxies, c.Proxies)
return res
}
func (c *CasAttributes) DeepCopy() CasAttributes {
res := *c
if c.MemberOf != nil {
res.MemberOf = make([]string, len(c.MemberOf))
copy(res.MemberOf, c.MemberOf)
}
tmp := c.UserAttributes.DeepCopy()
res.UserAttributes = &tmp
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
res := CasUserAttributes{
AnyAttributes: make([]*CasAnyAttribute, len(c.AnyAttributes)),
Attributes: make([]*CasNamedAttribute, len(c.Attributes)),
}
for i, a := range c.AnyAttributes {
var tmp = *a
res.AnyAttributes[i] = &tmp
}
for i, a := range c.Attributes {
var tmp = *a
res.Attributes[i] = &tmp
}
return res
}

View File

@ -15,11 +15,10 @@
package object package object
import ( import (
_ "embed"
"fmt" "fmt"
"time" "time"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
) )
@ -67,7 +66,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour) refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
user.Password = "" user.Password = ""
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
if origin != "" { if origin != "" {
originBackend = origin originBackend = origin
@ -147,3 +146,7 @@ func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
return nil, err return nil, err
} }
func ParseJwtTokenByApplication(token string, application *Application) (*Claims, error) {
return ParseJwtToken(token, getCertByApplication(application))
}

View File

@ -18,7 +18,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
@ -39,6 +39,7 @@ type User struct {
Avatar string `xorm:"varchar(500)" json:"avatar"` Avatar string `xorm:"varchar(500)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"` PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"` Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(100) index" json:"phone"` Phone string `xorm:"varchar(100) index" json:"phone"`
Location string `xorm:"varchar(100)" json:"location"` Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"` Address []string `json:"address"`
@ -71,25 +72,32 @@ type User struct {
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"` LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"` LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"` Github string `xorm:"varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"` Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"` QQ string `xorm:"qq varchar(100)" json:"qq"`
WeChat string `xorm:"wechat varchar(100)" json:"wechat"` WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
Facebook string `xorm:"facebook varchar(100)" json:"facebook"` WeChatUnionId string `xorm:"varchar(100)" json:"unionId"`
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"` Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
Weibo string `xorm:"weibo varchar(100)" json:"weibo"` DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
Gitee string `xorm:"gitee varchar(100)" json:"gitee"` Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"` Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
Wecom string `xorm:"wecom varchar(100)" json:"wecom"` LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
Lark string `xorm:"lark varchar(100)" json:"lark"` Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"` Lark string `xorm:"lark varchar(100)" json:"lark"`
Adfs string `xorm:"adfs varchar(100)" json:"adfs"` Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"` Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"` Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
Apple string `xorm:"apple varchar(100)" json:"apple"` Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"` Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
Slack string `xorm:"slack varchar(100)" json:"slack"` Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
Steam string `xorm:"steam varchar(100)" json:"steam"` Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
Slack string `xorm:"slack varchar(100)" json:"slack"`
Steam string `xorm:"steam varchar(100)" json:"steam"`
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"` Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"` Properties map[string]string `json:"properties"`
@ -224,6 +232,23 @@ func getUserById(owner string, id string) *User {
} }
} }
func getUserByWechatId(wechatOpenId string, wechatUnionId string) *User {
if wechatUnionId == "" {
wechatUnionId = wechatOpenId
}
user := &User{}
existed, err := adapter.Engine.Where("wechat = ? OR wechat = ? OR unionid = ?", wechatOpenId, wechatUnionId, wechatUnionId).Get(user)
if err != nil {
panic(err)
}
if existed {
return user
} else {
return nil
}
}
func GetUserByEmail(owner string, email string) *User { func GetUserByEmail(owner string, email string) *User {
if owner == "" || email == "" { if owner == "" || email == "" {
return nil return nil
@ -291,6 +316,9 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
return false return false
} }
if user.Password == "***" {
user.Password = oldUser.Password
}
user.UpdateUserHash() user.UpdateUserHash()
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" { if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
@ -303,7 +331,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"} "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
} }
if isGlobalAdmin { if isGlobalAdmin {
columns = append(columns, "name") columns = append(columns, "name", "email", "phone")
} }
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user) affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
@ -352,6 +380,8 @@ func AddUser(user *User) bool {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar) user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
user.Ranking = GetUserCount(user.Owner, "", "") + 1
affected, err := adapter.Engine.Insert(user) affected, err := adapter.Engine.Insert(user)
if err != nil { if err != nil {
panic(err) panic(err)
@ -378,7 +408,9 @@ func AddUsers(users []*User) bool {
affected, err := adapter.Engine.Insert(users) affected, err := adapter.Engine.Insert(users)
if err != nil { if err != nil {
panic(err) if !strings.Contains(err.Error(), "Duplicate entry") {
panic(err)
}
} }
return affected != 0 return affected != 0
@ -424,7 +456,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
if user == nil { if user == nil {
return nil, fmt.Errorf("the user: %s doesn't exist", userId) return nil, fmt.Errorf("the user: %s doesn't exist", userId)
} }
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
if origin != "" { if origin != "" {
originBackend = origin originBackend = origin

View File

@ -97,3 +97,14 @@ func TestGetMaskedUsers(t *testing.T) {
}) })
} }
} }
func TestGetUserByField(t *testing.T) {
InitConfig()
user := GetUserByField("built-in", "DingTalk", "test")
if user != nil {
t.Logf("%+v", user)
} else {
t.Log("no user found")
}
}

View File

@ -52,8 +52,8 @@ func UploadUsers(owner string, fileId string) bool {
oldUserMap := getUserMap(owner) oldUserMap := getUserMap(owner)
newUsers := []*User{} newUsers := []*User{}
for _, line := range table { for index, line := range table {
if parseLineItem(&line, 0) == "" { if index == 0 || parseLineItem(&line, 0) == "" {
continue continue
} }
@ -67,38 +67,42 @@ func UploadUsers(owner string, fileId string) bool {
Password: parseLineItem(&line, 6), Password: parseLineItem(&line, 6),
PasswordSalt: parseLineItem(&line, 7), PasswordSalt: parseLineItem(&line, 7),
DisplayName: parseLineItem(&line, 8), DisplayName: parseLineItem(&line, 8),
Avatar: parseLineItem(&line, 9), FirstName: parseLineItem(&line, 9),
LastName: parseLineItem(&line, 10),
Avatar: parseLineItem(&line, 11),
PermanentAvatar: "", PermanentAvatar: "",
Email: parseLineItem(&line, 10), Email: parseLineItem(&line, 12),
Phone: parseLineItem(&line, 11), Phone: parseLineItem(&line, 13),
Location: parseLineItem(&line, 12), Location: parseLineItem(&line, 14),
Address: []string{parseLineItem(&line, 13)}, Address: []string{parseLineItem(&line, 15)},
Affiliation: parseLineItem(&line, 14), Affiliation: parseLineItem(&line, 16),
Title: parseLineItem(&line, 15), Title: parseLineItem(&line, 17),
IdCardType: parseLineItem(&line, 16), IdCardType: parseLineItem(&line, 18),
IdCard: parseLineItem(&line, 17), IdCard: parseLineItem(&line, 19),
Homepage: parseLineItem(&line, 18), Homepage: parseLineItem(&line, 20),
Bio: parseLineItem(&line, 19), Bio: parseLineItem(&line, 21),
Tag: parseLineItem(&line, 20), Tag: parseLineItem(&line, 22),
Region: parseLineItem(&line, 21), Region: parseLineItem(&line, 23),
Language: parseLineItem(&line, 22), Language: parseLineItem(&line, 24),
Gender: parseLineItem(&line, 23), Gender: parseLineItem(&line, 25),
Birthday: parseLineItem(&line, 24), Birthday: parseLineItem(&line, 26),
Education: parseLineItem(&line, 25), Education: parseLineItem(&line, 27),
Score: parseLineItemInt(&line, 26), Score: parseLineItemInt(&line, 28),
Ranking: parseLineItemInt(&line, 27), Karma: parseLineItemInt(&line, 29),
Ranking: parseLineItemInt(&line, 30),
IsDefaultAvatar: false, IsDefaultAvatar: false,
IsOnline: parseLineItemBool(&line, 28), IsOnline: parseLineItemBool(&line, 31),
IsAdmin: parseLineItemBool(&line, 29), IsAdmin: parseLineItemBool(&line, 32),
IsGlobalAdmin: parseLineItemBool(&line, 30), IsGlobalAdmin: parseLineItemBool(&line, 33),
IsForbidden: parseLineItemBool(&line, 31), IsForbidden: parseLineItemBool(&line, 34),
IsDeleted: parseLineItemBool(&line, 32), IsDeleted: parseLineItemBool(&line, 35),
SignupApplication: parseLineItem(&line, 33), SignupApplication: parseLineItem(&line, 36),
Hash: "", Hash: "",
PreHash: "", PreHash: "",
CreatedIp: parseLineItem(&line, 34), CreatedIp: parseLineItem(&line, 37),
LastSigninTime: parseLineItem(&line, 35), LastSigninTime: parseLineItem(&line, 38),
LastSigninIp: parseLineItem(&line, 36), LastSigninIp: parseLineItem(&line, 39),
Ldap: "",
Properties: map[string]string{}, Properties: map[string]string{},
} }

View File

@ -29,7 +29,7 @@ func GetUserByField(organizationName string, field string, value string) *User {
} }
user := User{Owner: organizationName} user := User{Owner: organizationName}
existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", field), value).Get(&user) existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", strings.ToLower(field)), value).Get(&user)
if err != nil { if err != nil {
panic(err) panic(err)
} }

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