Compare commits

..

191 Commits

Author SHA1 Message Date
f7243f879b Fix some JS warnings. 2022-07-12 20:47:11 +08:00
7f3b2500b3 feat: support webauthn (#407)
* feat: support webauthn

* Update init.go

* Update user_webauthn.go

* Update UserEditPage.js

* Update WebauthnCredentialTable.js

* Update LoginPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-07-12 20:06:01 +08:00
208dc11d25 fix: set SessionOn always true (#877)
* fix: set SessionOn always true

* Update adapter.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-07-11 12:36:20 +08:00
503d244166 feat(web): add lint (#875)
* feat: add lint

* feat: fix lint error

* chore: add ignore file

* chore: close indent
2022-07-10 15:45:55 +08:00
475b6da35a Rename session storage item to signinUrl. 2022-07-10 11:50:48 +08:00
b9404f14dc feat: fix bug of using email provider from wrong application (#869) 2022-07-10 00:40:52 +08:00
0baae87390 feat: fix oauth unknown authority in docker (#871) 2022-07-09 17:36:56 +08:00
06759041a8 Fix socks5Proxy config typo. 2022-07-08 23:24:54 +08:00
cf4e76f9dc feat: add footer to door pages (#868) 2022-07-08 20:36:49 +08:00
81f2d01dc1 fix: fix dockerfile (#866) 2022-07-07 16:10:15 +08:00
61773d3173 fix: support user-defined clientId&Secret (#862) 2022-07-06 19:27:59 +08:00
ec29621547 feat: init from configuration file (#858)
* feat: init from configuration file

* Update init_data.json.template

* Update init_data.go

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

* Update cors_filter.go

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

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

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

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

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

* Rename App key

* fix typo

* Rename HMACSHA1 & Reused clientId2 and clientSecret2

* Update ProviderEditPage.js

* Delete unused import

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

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

* Re-use send-email API

* Optimize code

Optimize code

* Update service.go

* Update service.go

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

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

* Update built-in provider in application

* Delete unnecessary judge

* Update init.go

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

* refactor & add captcha logo

* rename captcha

* Update authz.go

* Update hcaptcha.go

* Update default.go

* Update recaptcha.go

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

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

* Update LoginPage.js

* Update SignupPage.js

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

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

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

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

* Update ApplicationEditPage.js

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

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

* Update LoginPage.js

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

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

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

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

* Update ApplicationEditPage.js

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

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

* fix: disable changeMomentLanguage

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

* Update SyncerEditPage.js

* Update UserEditPage.js

* Update ResourceListPage.js

* Update ProviderEditPage.js

* Update ProductBuyPage.js

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

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

* Update wechat.go

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

* Update auth.go

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

* Update auth.go

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

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

* feat: support config model for permission

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

* translation and indentation

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

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

* feat: trim

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

* add bilibili oauth

* add document address

* add frontend page

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

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

* Update ApplicationEditPage.js

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

* Update build.sh

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

* Update argon2id.go

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

* Update local_file_system.go

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

* Update account.go

* Update SignupTable.js

* Update SignupPage.js

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

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

* fix: set sync ldap user default attributes

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

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

* Update token.go

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

recover when goroutine panic that will kill main program

* Update util.go

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

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

* fix: requested changes

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

* fix: requested changes

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

* fix: modify code according to code review

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

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

* fix: requested changes

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

* fix: ci/cd error

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

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

* fix: accept suggestions.

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

* fix: error message and code level modification

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

* fix: simplify the use process

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

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

* fix: reduced use of variables

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

* fix: error messages use the same variable

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

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

* fix: merge code

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

* fix: modify response value

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

* fix: modify samlResponse generation method

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

* fix: generating a response using etree

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

* fix: change metadata url

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

* fix: modify front-end adaptation

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

* fix: recovering an incorrect override

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

* fix: change the samlResponse location

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

* fix: add relayState support

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

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

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

* feat: add more Keycloak columns

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

* fix: requested changes

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

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

* fix: code format

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

* fix: rename a static svg icon

* fix: sort imports

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

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

* fix: api uses the latest fields

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

* Apply suggestions from code review

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

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

* fix: support lower go version(1.15)

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

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

* fix: login with masked email or phone

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

* fix: improve regex

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

* add a tip to create db schema ahead of time

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

* fix: add support for IE11

* fix: small fix

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

* fix: format code
2022-03-04 23:39:12 +08:00
1d0af9cf7b fix: client_credentials' token miss some claims (#536)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-04 22:57:31 +08:00
269 changed files with 28831 additions and 17695 deletions

0
$env Normal file
View File

View File

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

View File

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

View File

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

146
README.md
View File

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

View File

@ -15,7 +15,6 @@
package authz
import (
"github.com/astaxie/beego"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
func InitAuthz() {
var err error
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}
@ -81,23 +80,32 @@ p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, POST, /api/login/oauth/access_token, *, *
p, *, *, POST, /api/login/oauth/refresh_token, *, *
p, *, *, GET, /api/login/oauth/logout, *, *
p, *, *, *, /api/login/oauth, *, *
p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-applications, *, *
p, *, *, GET, /api/get-user, *, *
p, *, *, GET, /api/get-user-application, *, *
p, *, *, GET, /api/get-resources, *, *
p, *, *, GET, /api/get-product, *, *
p, *, *, POST, /api/buy-product, *, *
p, *, *, GET, /api/get-payment, *, *
p, *, *, POST, /api/update-payment, *, *
p, *, *, POST, /api/invoice-payment, *, *
p, *, *, GET, /api/get-providers, *, *
p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *
p, *, *, GET, /api/get-human-check, *, *
p, *, *, GET, /api/get-captcha, *, *
p, *, *, POST, /api/verify-captcha, *, *
p, *, *, POST, /api/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, *
p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, *
p, *, *, *, /api/webauthn, *, *
`
sa := stringadapter.NewAdapter(ruleText)

11
build.sh Executable file
View File

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

105
captcha/aliyun.go Normal file
View File

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

29
captcha/default.go Normal file
View File

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

67
captcha/hcaptcha.go Normal file
View File

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

32
captcha/provider.go Normal file
View File

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

67
captcha/recaptcha.go Normal file
View File

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

View File

@ -1,7 +1,6 @@
appname = casdoor
httpport = 8000
runmode = dev
SessionOn = true
copyrequestbody = true
driverName = mysql
dataSourceName = root:123456@tcp(localhost:3306)/
@ -12,7 +11,7 @@ redisEndpoint =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
sock5Proxy = "127.0.0.1:10808"
socks5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
logPostOnly = true

View File

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

94
conf/conf_test.go Normal file
View File

@ -0,0 +1,94 @@
// 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", "copyrequestbody", true},
}
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,6 +18,7 @@ import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -28,6 +29,8 @@ const (
ResponseTypeCode = "code"
ResponseTypeToken = "token"
ResponseTypeIdToken = "id_token"
ResponseTypeSaml = "saml"
ResponseTypeCas = "cas"
)
type RequestForm struct {
@ -59,6 +62,7 @@ type RequestForm struct {
AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"`
}
@ -71,12 +75,17 @@ type Response struct {
Data2 interface{} `json:"data2"`
}
type HumanCheck struct {
Type string `json:"type"`
AppKey string `json:"appKey"`
Scene string `json:"scene"`
CaptchaId string `json:"captchaId"`
CaptchaImage interface{} `json:"captchaImage"`
type Captcha struct {
Type string `json:"type"`
AppKey string `json:"appKey"`
Scene string `json:"scene"`
CaptchaId string `json:"captchaId"`
CaptchaImage []byte `json:"captchaImage"`
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
ClientId2 string `json:"clientId2"`
ClientSecret2 string `json:"clientSecret2"`
SubType string `json:"subType"`
}
// Signup
@ -112,7 +121,7 @@ func (c *ApiController) Signup() {
return
}
if application.IsSignupItemVisible("Email") && form.Email != "" {
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
@ -130,8 +139,6 @@ func (c *ApiController) Signup() {
}
}
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization)
@ -174,6 +181,13 @@ func (c *ApiController) Signup() {
Karma: 0,
}
if len(organization.Tags) > 0 {
tokens := strings.Split(organization.Tags[0], "|")
if len(tokens) > 0 {
user.Tag = tokens[0]
}
}
if application.GetSignupItemRule("Display name") == "First, last" {
if form.FirstName != "" || form.LastName != "" {
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
@ -201,8 +215,9 @@ func (c *ApiController) Signup() {
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
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)
c.ResponseOk(userId)
@ -218,10 +233,15 @@ func (c *ApiController) Logout() {
user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ResponseOk(user)
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user)
return
}
c.ResponseOk(user, application.HomepageUrl)
}
// GetAccount
@ -270,25 +290,43 @@ func (c *ApiController) GetUserinfo() {
resp, err := object.GetUserInfo(userId, scope, aud, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = resp
c.ServeJSON()
}
// GetHumanCheck ...
// GetCaptcha ...
// @Tag Login API
// @Title GetHumancheck
// @router /api/get-human-check [get]
func (c *ApiController) GetHumanCheck() {
c.Data["json"] = HumanCheck{Type: "none"}
// @Title GetCaptcha
// @router /api/get-captcha [get]
func (c *ApiController) GetCaptcha() {
applicationId := c.Input().Get("applicationId")
isCurrentProvider := c.Input().Get("isCurrentProvider")
provider := object.GetDefaultHumanCheckProvider()
if provider == nil {
id, img := object.GetCaptcha()
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
c.ServeJSON()
captchaProvider, err := object.GetCaptchaProviderByApplication(applicationId, isCurrentProvider)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ServeJSON()
if captchaProvider != nil {
if captchaProvider.Type == "Default" {
id, img := object.GetCaptcha()
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
return
} else if captchaProvider.Type != "" {
c.ResponseOk(Captcha{
Type: captchaProvider.Type,
SubType: captchaProvider.SubType,
ClientId: captchaProvider.ClientId,
ClientSecret: captchaProvider.ClientSecret,
ClientId2: captchaProvider.ClientId2,
ClientSecret2: captchaProvider.ClientSecret2,
})
return
}
}
c.ResponseOk(Captcha{Type: "none"})
}

View File

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

View File

@ -23,11 +23,12 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid"
)
func codeToResponse(code *object.Code) *Response {
@ -83,8 +84,32 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = tokenToResponse(token)
}
} else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl}
} else if form.Type == ResponseTypeCas {
//not oauth but CAS SSO protocol
service := c.Input().Get("service")
resp = wrapErrorResponse(nil)
if service != "" {
st, err := object.GenerateCasToken(userId, service)
if err != nil {
resp = wrapErrorResponse(err)
} else {
resp.Data = st
}
}
if application.EnableSigninSession || application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
} else {
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
}
// if user did not check auto signin
@ -108,8 +133,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// @Param redirectUri query string true "redirect uri"
// @Param scope query string true "scope"
// @Param state query string true "state"
// @Success 200 {object} controllers.api_controller.Response The Response object
// @router /update-application [get]
// @Success 200 {object} Response The Response object
// @router /get-app-login [get]
func (c *ApiController) GetApplicationLogin() {
clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType")
@ -138,9 +163,16 @@ func setHttpClient(idProvider idp.IdProvider, providerType string) {
// @Title Login
// @Tag Login API
// @Description login
// @Param oAuthParams query string true "oAuth parameters"
// @Param body body RequestForm true "Login information"
// @Success 200 {object} controllers.api_controller.Response The Response object
// @Param clientId query string true clientId
// @Param responseType query string true responseType
// @Param redirectUri query string true redirectUri
// @Param scope query string false scope
// @Param state query string false state
// @Param nonce query string false nonce
// @Param code_challenge_method query string false code_challenge_method
// @Param code_challenge query string false code_challenge
// @Param form body controllers.RequestForm true "Login information"
// @Success 200 {object} Response The Response object
// @router /login [post]
func (c *ApiController) Login() {
resp := &Response{}
@ -167,9 +199,16 @@ func (c *ApiController) Login() {
var verificationCodeType string
var checkResult string
if form.Name != "" {
user = object.GetUserByFields(form.Organization, form.Name)
}
// check result through Email or Phone
if strings.Contains(form.Username, "@") {
verificationCodeType = "email"
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
form.Username = user.Email
}
checkResult = object.CheckVerificationCode(form.Username, form.Code)
} else {
verificationCodeType = "phone"
@ -178,6 +217,9 @@ func (c *ApiController) Login() {
c.ResponseError(responseText)
return
}
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
}
@ -188,11 +230,15 @@ func (c *ApiController) Login() {
}
// disable the verification code
object.DisableVerificationCode(form.Username)
if strings.Contains(form.Username, "@") {
object.DisableVerificationCode(form.Username)
} else {
object.DisableVerificationCode(fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username))
}
user = object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError("No such user.")
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
return
}
} else {
@ -204,15 +250,25 @@ func (c *ApiController) Login() {
resp = &Response{Status: "error", Msg: msg}
} else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
util.SafeGoroutine(func() { object.AddRecord(record) })
}
} else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
providerItem := application.GetProviderItem(provider.Name)
@ -239,7 +295,7 @@ func (c *ApiController) Login() {
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 {
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
return
@ -247,8 +303,8 @@ func (c *ApiController) Login() {
setHttpClient(idProvider, provider.Type)
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
return
}
@ -277,12 +333,6 @@ func (c *ApiController) Login() {
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
} else if provider.Category == "OAuth" {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if user == nil {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if user == nil {
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
}
}
if user != nil && user.IsDeleted == false {
@ -297,7 +347,7 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
util.SafeGoroutine(func() { object.AddRecord(record) })
} else if provider.Category == "OAuth" {
// Sign up via OAuth
if !application.EnableSignUp {
@ -310,6 +360,19 @@ func (c *ApiController) Login() {
return
}
// Handle username conflicts
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
if tmpUser != nil {
uid, err := uuid.NewRandom()
if err != nil {
c.ResponseError(err.Error())
return
}
uidStr := strings.Split(uid.String(), "-")
userInfo.Username = fmt.Sprintf("%s_%s", userInfo.Username, uidStr[1])
}
properties := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
user = &object.User{
@ -346,7 +409,13 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
util.SafeGoroutine(func() { object.AddRecord(record) })
record2 := object.NewRecord(c.Ctx)
record2.Action = "signup"
record2.Organization = application.Organization
record2.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record2) })
} else if provider.Category == "SAML" {
resp = &Response{Status: "error", Msg: "The account does not exist"}
}
@ -359,9 +428,6 @@ func (c *ApiController) Login() {
}
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if oldUser == nil {
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if oldUser != nil {
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
return
@ -383,8 +449,18 @@ func (c *ApiController) Login() {
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
if application == nil {
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
return
}
user := c.getCurrentUser()
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
} else {
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
return

View File

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

271
controllers/cas.go Normal file
View File

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

View File

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

120
controllers/model.go Normal file
View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -114,3 +115,36 @@ func (c *ApiController) DeleteProduct() {
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
c.ServeJSON()
}
// @Title BuyProduct
// @Tag Product API
// @Description buy product
// @Param id query string true "The id of the product"
// @Param providerName query string true "The name of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /buy-product [post]
func (c *ApiController) BuyProduct() {
id := c.Input().Get("id")
providerName := c.Input().Get("providerName")
host := c.Ctx.Request.Host
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please login first")
return
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
payUrl, err := object.BuyProduct(id, providerName, user, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payUrl)
}

View File

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

34
controllers/saml.go Normal file
View File

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

View File

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

View File

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

View File

@ -165,6 +165,8 @@ func (c *ApiController) GetOAuthCode() {
// @Param client_secret query string true "OAuth client secret"
// @Param code query string true "OAuth code"
// @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
@ -175,13 +177,32 @@ func (c *ApiController) GetOAuthToken() {
scope := c.Input().Get("scope")
username := c.Input().Get("username")
password := c.Input().Get("password")
tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
code = tokenRequest.Code
verifier = tokenRequest.Verifier
scope = tokenRequest.Scope
username = tokenRequest.Username
password = tokenRequest.Password
tag = tokenRequest.Tag
avatar = tokenRequest.Avatar
}
}
host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
@ -193,8 +214,10 @@ func (c *ApiController) GetOAuthToken() {
// @Param refresh_token query string true "OAuth refresh token"
// @Param scope query string true "OAuth scope"
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string true "OAuth client secret"
// @Param client_secret query string false "OAuth client secret"
// @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() {
grantType := c.Input().Get("grant_type")
@ -204,7 +227,20 @@ func (c *ApiController) RefreshToken() {
clientSecret := c.Input().Get("client_secret")
host := c.Ctx.Request.Host
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
scope = tokenRequest.Scope
refreshToken = tokenRequest.RefreshToken
}
}
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
@ -240,26 +276,36 @@ func (c *ApiController) TokenLogout() {
// @Param token formData string true "access_token's value or refresh_token's value"
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
// @Success 200 {object} object.IntrospectionResponse The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/introspect [post]
func (c *ApiController) IntrospectToken() {
tokenValue := c.Input().Get("token")
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
if !ok {
util.LogWarning(c.Ctx, "Basic Authorization parses failed")
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
c.ServeJSON()
return
clientId = c.Input().Get("client_id")
clientSecret = c.Input().Get("client_secret")
if clientId == "" || clientSecret == "" {
c.ResponseError("empty clientId or clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_REQUEST,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
return
}
}
application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret {
util.LogWarning(c.Ctx, "Basic Authorization failed")
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
c.ServeJSON()
c.ResponseError("invalid application or wrong clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_CLIENT,
}
c.SetTokenErrorHttpStatus()
return
}
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
if token == nil {
util.LogWarning(c.Ctx, "application: %s can not find token", application.Name)
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
@ -269,7 +315,6 @@ func (c *ApiController) IntrospectToken() {
// 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
util.LogWarning(c.Ctx, "token invalid")
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return

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

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

View File

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

View File

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

138
controllers/webauthn.go Normal file
View File

@ -0,0 +1,138 @@
// 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 (
"bytes"
"io/ioutil"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
)
// @Title WebAuthnSignupBegin
// @Tag User API
// @Description WebAuthn Registration Flow 1st stage
// @Success 200 {object} protocol.CredentialCreation The CredentialCreationOptions object
// @router /webauthn/signup/begin [get]
func (c *ApiController) WebAuthnSignupBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
user := c.getCurrentUser()
if user == nil {
c.ResponseError("Please login first.")
return
}
registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
}
options, sessionData, err := webauthnObj.BeginRegistration(
user,
registerOptions,
)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSession("registration", *sessionData)
c.Data["json"] = options
c.ServeJSON()
}
// @Title WebAuthnSignupFinish
// @Tag User API
// @Description WebAuthn Registration Flow 2nd stage
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
// @Success 200 {object} Response "The Response object"
// @router /webauthn/signup/finish [post]
func (c *ApiController) WebAuthnSignupFinish() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
user := c.getCurrentUser()
if user == nil {
c.ResponseError("Please login first.")
return
}
sessionObj := c.GetSession("registration")
sessionData, ok := sessionObj.(webauthn.SessionData)
if !ok {
c.ResponseError("Please call WebAuthnSignupBegin first")
return
}
c.Ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
credential, err := webauthnObj.FinishRegistration(user, sessionData, c.Ctx.Request)
if err != nil {
c.ResponseError(err.Error())
return
}
isGlobalAdmin := c.IsGlobalAdmin()
user.AddCredentials(*credential, isGlobalAdmin)
c.ResponseOk()
}
// @Title WebAuthnSigninBegin
// @Tag Login API
// @Description WebAuthn Login Flow 1st stage
// @Param owner query string true "owner"
// @Param name query string true "name"
// @Success 200 {object} protocol.CredentialAssertion The CredentialAssertion object
// @router /webauthn/signin/begin [get]
func (c *ApiController) WebAuthnSigninBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
userOwner := c.Input().Get("owner")
userName := c.Input().Get("name")
user := object.GetUserByFields(userOwner, userName)
if user == nil {
c.ResponseError("Please Giveout Owner and Username.")
return
}
options, sessionData, err := webauthnObj.BeginLogin(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSession("authentication", *sessionData)
c.Data["json"] = options
c.ServeJSON()
}
// @Title WebAuthnSigninBegin
// @Tag Login API
// @Description WebAuthn Login Flow 2nd stage
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
// @Success 200 {object} Response "The Response object"
// @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
sessionObj := c.GetSession("authentication")
sessionData, ok := sessionObj.(webauthn.SessionData)
if !ok {
c.ResponseError("Please call WebAuthnSigninBegin first")
return
}
c.Ctx.Request.Body = ioutil.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
userId := string(sessionData.UserID)
user := object.GetUser(userId)
_, err := webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
c.ResponseOk(userId)
}

38
cred/argon2id.go Normal file
View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

8
docker-entrypoint.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
if [ "${MYSQL_ROOT_PASSWORD}" = "" ] ;then MYSQL_ROOT_PASSWORD=123456 ;fi
service mariadb start
mysqladmin -u root password ${MYSQL_ROOT_PASSWORD}
exec /server --createDatabase=true

29
go.mod
View File

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

112
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=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
@ -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/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b h1:EgJ6N2S0h1WfFIjU5/VVHWbMSVYXAluop97Qxpr/lfQ=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b/go.mod h1:3SAoF0F5EbcOuBD5WT9nYkbIJieBS84cUQXADbXeBsU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4=
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/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/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX2 h1:RgfIMkL9kekylgxHHK2ZY8ASAwOGns2HVlaBwLu7Bcs=
github.com/casdoor/goth v1.69.0-FIX2/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -88,6 +111,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1CUzYdAZXijuvLuRMirgiXdf3zsM2Ig=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
@ -101,6 +126,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b h1:L63RATZFZuFMXy6ixnKmv3eNAXwYQF6HW1vd4IYsQqQ=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
@ -109,7 +136,11 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/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/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
@ -124,6 +155,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-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-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@ -131,10 +170,9 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -170,6 +208,8 @@ github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNu
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -233,6 +273,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/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
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/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=
@ -246,6 +288,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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@ -254,16 +297,18 @@ github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 h1:JibQrkJapVsb0pweJ5T14jZuuYZZTjll0PZBw4XfSCI=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -274,6 +319,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/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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
@ -309,8 +356,9 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.12.1/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@ -360,6 +408,8 @@ github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnD
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -376,11 +426,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -427,6 +483,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -443,9 +500,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -462,6 +521,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-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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -476,6 +536,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -495,24 +556,28 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -688,5 +753,6 @@ xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.0.4 h1:UBXA4I3NhiyjXfPqxXUkS2t5hMta9SSPATeMMaZg9oA=
xorm.io/xorm v1.0.4/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=

View File

@ -19,7 +19,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
@ -88,7 +88,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
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"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

221
idp/bilibili.go Normal file
View File

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

159
idp/casdoor.go Normal file
View File

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

109
idp/custom.go Normal file
View File

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

View File

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

198
idp/douyin.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

200
idp/okta.go Normal file
View File

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

View File

@ -35,7 +35,7 @@ type IdProvider interface {
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, 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" {
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Google" {
@ -70,6 +70,10 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Baidu" {
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Alipay" {
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Custom" {
return NewCustomIdProvider(clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userInfoUrl)
} else if typ == "Infoflow" {
if subType == "Internal" {
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
@ -78,8 +82,16 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
} else {
return nil
}
} else if typ == "Casdoor" {
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Okta" {
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Douyin" {
return NewDouyinIdProvider(clientId, clientSecret, redirectUrl)
} else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
} else if typ == "Bilibili" {
return NewBilibiliIdProvider(clientId, clientSecret, redirectUrl)
}
return nil

View File

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

82
idp/wechat_miniprogram.go Normal file
View File

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

View File

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

View File

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

View File

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

160
init_data.json.template Normal file
View File

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

14
main.go
View File

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

View File

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

View File

@ -22,7 +22,7 @@ import (
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
"xorm.io/core"
"xorm.io/xorm"
@ -36,12 +36,13 @@ func InitConfig() {
panic(err)
}
beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter(true)
}
func InitAdapter(createDatabase bool) {
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase {
adapter.CreateDatabase()
}
@ -111,10 +112,10 @@ func (a *Adapter) close() {
}
func (a *Adapter) createTable() {
showSql, _ := beego.AppConfig.Bool("showSql")
showSql, _ := conf.GetConfigBool("showSql")
a.Engine.ShowSQL(showSql)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper)
@ -138,6 +139,11 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Model))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)

View File

@ -16,12 +16,21 @@ package object
import (
"fmt"
"net/url"
"strings"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
Rule string `json:"rule"`
}
type Application struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -37,6 +46,8 @@ type Application struct {
EnableSignUp bool `json:"enableSignUp"`
EnableSigninSession bool `json:"enableSigninSession"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableWebAuthn bool `json:"enableWebAuthn"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
@ -229,7 +240,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***"
}
}
return application
return application
}
func GetMaskedApplications(applications []*Application, userId string) []*Application {
@ -257,7 +268,11 @@ func UpdateApplication(id string, application *Application) bool {
providerItem.Provider = nil
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(application)
session := adapter.Engine.ID(core.PK{owner, name}).AllCols()
if application.ClientSecret == "***" {
session.Omit("client_secret")
}
affected, err := session.Update(application)
if err != nil {
panic(err)
}
@ -266,8 +281,12 @@ func UpdateApplication(id string, application *Application) bool {
}
func AddApplication(application *Application) bool {
application.ClientId = util.GenerateClientId()
application.ClientSecret = util.GenerateClientSecret()
if application.ClientId == "" {
application.ClientId = util.GenerateClientId()
}
if application.ClientSecret == "" {
application.ClientSecret = util.GenerateClientSecret()
}
for _, providerItem := range application.Providers {
providerItem.Provider = nil
}
@ -300,7 +319,6 @@ func (application *Application) GetId() string {
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
var validUri = false
for _, tmpUri := range application.RedirectUris {
fmt.Println(tmpUri, redirectUri)
if strings.Contains(redirectUri, tmpUri) {
validUri = true
break
@ -308,3 +326,39 @@ func CheckRedirectUriValid(application *Application, redirectUri string) bool {
}
return validUri
}
func IsAllowOrigin(origin string) bool {
allowOrigin := false
originUrl, err := url.Parse(origin)
if err != nil {
return false
}
rows, err := adapter.Engine.Cols("redirect_uris").Rows(&Application{})
if err != nil {
panic(err)
}
application := Application{}
for rows.Next() {
err := rows.Scan(&application)
if err != nil {
panic(err)
}
for _, tmpRedirectUri := range application.RedirectUris {
u1, err := url.Parse(tmpRedirectUri)
if err != nil {
continue
}
if u1.Scheme == originUrl.Scheme && u1.Host == originUrl.Host {
allowOrigin = true
break
}
}
if allowOrigin {
break
}
}
return allowOrigin
}

View File

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

View File

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

View File

@ -17,6 +17,7 @@ package object
import (
"fmt"
"regexp"
"strings"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util"
@ -179,19 +180,53 @@ func CheckUserPassword(organization string, username string, password string) (*
if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator"
}
//for ldap users
if user.Ldap != "" {
//ONLY for ldap users
return checkLdapUserPassword(user, password)
} else {
msg := CheckPassword(user, password)
if msg != "" {
return nil, msg
}
}
msg := CheckPassword(user, password)
if msg != "" {
return nil, msg
}
return user, ""
}
func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf("please login first")
}
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
}
hasPermission := false
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := GetUser(requestUserId)
if requestUser == nil {
return false, fmt.Errorf("session outdated, please login again")
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner {
if strict {
hasPermission = requestUser.IsAdmin
} else {
hasPermission = true
}
}
}
return hasPermission, fmt.Errorf("you don't have the permission to do this")
}

View File

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

View File

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

146
object/init_data.go Normal file
View File

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

View File

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

View File

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

122
object/model.go Normal file
View File

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

View File

@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"gopkg.in/square/go-jose.v2"
)
@ -58,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
func GetOidcDiscovery(host string) OidcDiscovery {
originFrontend, originBackend := getOriginFromHost(host)
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
if origin != "" {
originFrontend = origin
originBackend = origin
@ -76,7 +76,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
ResponseTypesSupported: []string{"id_token"},
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
ResponseModesSupported: []string{"login", "code", "link"},
GrantTypesSupported: []string{"password", "authorization_code"},
SubjectTypesSupported: []string{"public"},
@ -105,6 +105,8 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
jwk.Key = x509Cert.PublicKey
jwk.Certificates = []*x509.Certificate{x509Cert}
jwk.KeyID = cert.Name
jwk.Algorithm = cert.CryptoAlgorithm
jwk.Use = "sig"
jwks.Keys = append(jwks.Keys, jwk)
}

View File

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

View File

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

View File

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

View File

@ -31,10 +31,11 @@ type Product struct {
Detail string `xorm:"varchar(100)" json:"detail"`
Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"`
Price int `json:"price"`
Price float64 `json:"price"`
Quantity int `json:"quantity"`
Sold int `json:"sold"`
Providers []string `xorm:"varchar(100)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"`
}
@ -128,3 +129,83 @@ func DeleteProduct(product *Product) bool {
func (product *Product) GetId() string {
return fmt.Sprintf("%s/%s", product.Owner, product.Name)
}
func (product *Product) isValidProvider(provider *Provider) bool {
for _, providerName := range product.Providers {
if providerName == provider.Name {
return true
}
}
return false
}
func (product *Product) getProvider(providerId string) (*Provider, error) {
provider := getProvider(product.Owner, providerId)
if provider == nil {
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
}
if !product.isValidProvider(provider) {
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
}
return provider, nil
}
func BuyProduct(id string, providerName string, user *User, host string) (string, error) {
product := GetProduct(id)
if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id)
}
provider, err := product.getProvider(providerName)
if err != nil {
return "", err
}
pProvider, _, err := provider.getPaymentProvider()
if err != nil {
return "", err
}
owner := product.Owner
productName := product.Name
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
paymentName := util.GenerateTimeId()
productDisplayName := product.DisplayName
originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
if err != nil {
return "", err
}
payment := Payment{
Owner: product.Owner,
Name: paymentName,
CreatedTime: util.GetCurrentTime(),
DisplayName: paymentName,
Provider: provider.Name,
Type: provider.Type,
Organization: user.Owner,
User: user.Name,
ProductName: productName,
ProductDisplayName: productDisplayName,
Detail: product.Detail,
Tag: product.Tag,
Currency: product.Currency,
Price: product.Price,
PayUrl: payUrl,
ReturnUrl: product.ReturnUrl,
State: "Created",
}
affected := AddPayment(&payment)
if !affected {
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
}
return payUrl, err
}

44
object/product_test.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

369
object/saml_idp.go Normal file
View File

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

View File

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

View File

@ -18,6 +18,9 @@ import "github.com/casdoor/go-sms-sender"
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
if provider.Type == go_sms_sender.HuaweiCloud {
client, err = go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
}
if err != nil {
return err
}

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@
// 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

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