Compare commits

...

79 Commits

Author SHA1 Message Date
Jan Piechowicz
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
Gucheng Wang
c9849d8b55 Accept more file formats in upload. 2022-05-04 23:16:24 +08:00
xiexianbin
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
Resulte Lee
43b1006f11 fix: sign up without email verification do not work (#736) 2022-05-03 18:05:58 +08:00
leoshine
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
Resulte Lee
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
greenhandatsjtu
4acba2d493 Add entrypoint to docker-compose casdoor service (#727) 2022-05-01 19:26:31 +08:00
greenhandatsjtu
fc0ca4cceb Add Okta OAuth provider (#729) 2022-05-01 18:31:42 +08:00
Resulte Lee
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
Yixiang Zhao
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
Gucheng Wang
9af9ead939 Return invoiceUrl in invoice-payment API. 2022-04-28 15:07:57 +08:00
Gucheng Wang
f5590c42f7 Add payerName to provider. 2022-04-28 14:50:59 +08:00
Gucheng Wang
5597f99e3c Scroll to payment page bottom. 2022-04-27 01:32:36 +08:00
Gucheng Wang
ea005aaf4d Improve InvoicePayment() error handling. 2022-04-27 00:24:48 +08:00
Gucheng Wang
e5c1f560c5 Fix bug in payment. 2022-04-27 00:07:13 +08:00
Gucheng Wang
20fc7d1b58 Add payment modal. 2022-04-26 23:40:33 +08:00
Gucheng Wang
cf3b46130b Add InvoicePayment() API. 2022-04-26 22:17:53 +08:00
halozhy
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
Frank Chang
b867872da4 fix: return right after error response on GetUserInfo (#707) 2022-04-26 14:32:04 +08:00
Gucheng Wang
305867f49a Add checkError() to payment. 2022-04-25 21:39:46 +08:00
Gucheng Wang
3f90c18a19 Add invoiceType to payment. 2022-04-25 20:58:53 +08:00
Gucheng Wang
9e5a64c021 Add new payment fields 2022-04-25 20:40:50 +08:00
Gucheng Wang
4263af6f2c Fix frontend warnings. 2022-04-25 20:00:57 +08:00
Gucheng Wang
3e92d761b9 Fix i18n translations. 2022-04-25 19:46:45 +08:00
Gucheng Wang
0e41568f62 Add apps to homepage. 2022-04-25 13:51:46 +08:00
Kevin Fu
fb7e2729c6 fix: support Microsoft AD user search (#704) 2022-04-25 12:20:59 +08:00
akkuman
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
niko7g
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
akkuman
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
akkuman
7b78e60265 fix: close the resp in time (#689) 2022-04-21 23:22:50 +08:00
akkuman
7464f9a8ad fix: when req error, read body(nil) will panic (#690) 2022-04-21 22:14:01 +08:00
akkuman
d3a7a062d3 fix #687 (#688)
fix the display bug on the personal binding information page
2022-04-21 21:52:34 +08:00
Yixiang Zhao
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
Dean CN
a89a7f9eb7 bug fix (#674) 2022-04-17 17:01:56 +08:00
halozhy
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
Yixiang Zhao
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
Yang Luo
70a1428972 Improve resource DB column length. 2022-04-16 13:23:05 +08:00
Nekotoxin
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
Yi Zhan
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
Yi Zhan
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
Yi Zhan
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
Yi Zhan
0b546bba5e fix: grantTypes undefined err (#654)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-08 21:54:48 +08:00
Nekotoxin
938cdbccf4 fix: link type error (#653)
* fix: signin button error in signup page

* fix: type error
2022-04-08 20:01:30 +08:00
Yixiang Zhao
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
Gucheng Wang
91602d2b21 Enable extra pages. 2022-04-06 20:36:31 +08:00
Yi Zhan
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
Yi Zhan
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
Yi Zhan
2023795f3c fix: token endpoint supports json format (#641)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-03 21:32:00 +08:00
halozhy
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
Yi Zhan
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
Gucheng Wang
7a95b9c1d5 Init DB only when necessary. 2022-03-31 12:28:45 +08:00
Yixiang Zhao
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
Minh Ha
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
Yi Zhan
879ca6a488 fix: refresh_token api return old token (#623)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-27 23:10:05 +08:00
Yang Luo
544cd40a08 Disable the new syncer by default. 2022-03-27 23:06:52 +08:00
Yang Luo
99f7883c7d Fix null bug in getCountryRegionData(). 2022-03-27 16:03:25 +08:00
Yang Luo
88b0fb6e52 Add getPrice(). 2022-03-26 16:42:25 +08:00
Yi Zhan
fa9b49e25b fix: some idp error messages return unclear (#620)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-26 15:15:56 +08:00
Yi Zhan
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
chenghonour
04b9e05244 fix: WeComInternalIdProvider GetUserInfo method could not get the correct user id (#616) 2022-03-24 17:53:05 +08:00
Sagilio
a78b2de7b2 fix: panic when not select one provider (#614)
Signed-off-by: Sagilio <Sagilio@outlook.com>
2022-03-24 12:15:10 +08:00
Ryao
d0952ae908 fix: docker-compose up can't work on linux (#606) 2022-03-22 18:43:02 +08:00
Nekotoxin
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
github-actions[bot]
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
Yixiang Zhao
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
Yixiang Zhao
86b5d72e5d fix: concatChar assignment logic (#595)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 11:54:14 +08:00
Yixiang Zhao
6bc4e646e5 fix: oAuthParams may not exist (#594)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 10:33:50 +08:00
Yang Luo
0841eb5c30 Fix !skipCi directive. 2022-03-19 23:15:19 +08:00
github-actions[bot]
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
Yixiang Zhao
dcd6328498 fix: callback url param missing (#583)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-19 20:01:44 +08:00
145 changed files with 4605 additions and 608 deletions

View File

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

View File

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

View File

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

11
build.sh Executable file
View File

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

View File

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

98
conf/conf_test.go Normal file
View File

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

View File

@@ -29,6 +29,8 @@ const (
ResponseTypeCode = "code" ResponseTypeCode = "code"
ResponseTypeToken = "token" ResponseTypeToken = "token"
ResponseTypeIdToken = "id_token" ResponseTypeIdToken = "id_token"
ResponseTypeSaml = "saml"
ResponseTypeCas = "cas"
) )
type RequestForm struct { type RequestForm struct {
@@ -60,6 +62,7 @@ type RequestForm struct {
AutoSignin bool `json:"autoSignin"` AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"` RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"` SamlResponse string `json:"samlResponse"`
} }
@@ -113,7 +116,7 @@ func (c *ApiController) Signup() {
return return
} }
if application.IsSignupItemVisible("Email") && form.Email != "" { if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode) checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 { if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email: %s", checkResult)) c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
@@ -207,7 +210,7 @@ func (c *ApiController) Signup() {
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
go object.AddRecord(record) util.SafeGoroutine(func() { object.AddRecord(record) })
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name) userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId) util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
@@ -282,6 +285,7 @@ func (c *ApiController) GetUserinfo() {
resp, err := object.GetUserInfo(userId, scope, aud, host) resp, err := object.GetUserInfo(userId, scope, aud, host)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return
} }
c.Data["json"] = resp c.Data["json"] = resp
c.ServeJSON() c.ServeJSON()

View File

@@ -23,7 +23,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
@@ -83,8 +83,32 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = tokenToResponse(token) resp = tokenToResponse(token)
} }
} else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl}
} else if form.Type == ResponseTypeCas {
//not oauth but CAS SSO protocol
service := c.Input().Get("service")
resp = wrapErrorResponse(nil)
if service != "" {
st, err := object.GenerateCasToken(userId, service)
if err != nil {
resp = wrapErrorResponse(err)
} else {
resp.Data = st
}
}
if application.EnableSigninSession || application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
} else { } else {
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)} resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
} }
// if user did not check auto signin // if user did not check auto signin
@@ -109,7 +133,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// @Param scope query string true "scope" // @Param scope query string true "scope"
// @Param state query string true "state" // @Param state query string true "state"
// @Success 200 {object} controllers.api_controller.Response The Response object // @Success 200 {object} controllers.api_controller.Response The Response object
// @router /update-application [get] // @router /get-app-login [get]
func (c *ApiController) GetApplicationLogin() { func (c *ApiController) GetApplicationLogin() {
clientId := c.Input().Get("clientId") clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType") responseType := c.Input().Get("responseType")
@@ -224,7 +248,7 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
go object.AddRecord(record) util.SafeGoroutine(func() {object.AddRecord(record)})
} }
} else if form.Provider != "" { } else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
@@ -259,7 +283,7 @@ func (c *ApiController) Login() {
clientSecret = provider.ClientSecret2 clientSecret = provider.ClientSecret2
} }
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain) idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
if idProvider == nil { if idProvider == nil {
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type)) c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
return return
@@ -267,8 +291,8 @@ func (c *ApiController) Login() {
setHttpClient(idProvider, provider.Type) setHttpClient(idProvider, provider.Type)
if form.State != beego.AppConfig.String("authState") && form.State != application.Name { if form.State != conf.GetConfigString("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State)) c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
return return
} }
@@ -317,7 +341,7 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
go object.AddRecord(record) util.SafeGoroutine(func() {object.AddRecord(record)})
} else if provider.Category == "OAuth" { } else if provider.Category == "OAuth" {
// Sign up via OAuth // Sign up via OAuth
if !application.EnableSignUp { if !application.EnableSignUp {
@@ -366,7 +390,7 @@ func (c *ApiController) Login() {
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name
go object.AddRecord(record) util.SafeGoroutine(func() {object.AddRecord(record)})
} else if provider.Category == "SAML" { } else if provider.Category == "SAML" {
resp = &Response{Status: "error", Msg: "The account does not exist"} resp = &Response{Status: "error", Msg: "The account does not exist"}
} }

271
controllers/cas.go Normal file
View File

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

View File

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

View File

@@ -158,3 +158,20 @@ func (c *ApiController) NotifyPayment() {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok)) 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)
}

33
controllers/saml.go Normal file
View File

@@ -0,0 +1,33 @@
// 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))
}
metadata, _ := object.GetSamlMeta(application, host)
c.Data["xml"] = metadata
c.ServeXML()
}

View File

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

View File

@@ -175,13 +175,31 @@ func (c *ApiController) GetOAuthToken() {
scope := c.Input().Get("scope") scope := c.Input().Get("scope")
username := c.Input().Get("username") username := c.Input().Get("username")
password := c.Input().Get("password") password := c.Input().Get("password")
tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar")
if clientId == "" && clientSecret == "" { if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth() clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
} }
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
code = tokenRequest.Code
verifier = tokenRequest.Verifier
scope = tokenRequest.Scope
username = tokenRequest.Username
password = tokenRequest.Password
tag = tokenRequest.Tag
avatar = tokenRequest.Avatar
}
}
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host) c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
c.ServeJSON() c.ServeJSON()
} }
@@ -204,6 +222,18 @@ func (c *ApiController) RefreshToken() {
clientSecret := c.Input().Get("client_secret") clientSecret := c.Input().Get("client_secret")
host := c.Ctx.Request.Host host := c.Ctx.Request.Host
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
scope = tokenRequest.Scope
refreshToken = tokenRequest.RefreshToken
}
}
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host) c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.ServeJSON() c.ServeJSON()
} }
@@ -245,21 +275,20 @@ func (c *ApiController) IntrospectToken() {
tokenValue := c.Input().Get("token") tokenValue := c.Input().Get("token")
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth() clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
if !ok { if !ok {
util.LogWarning(c.Ctx, "Basic Authorization parses failed") clientId = c.Input().Get("client_id")
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"} clientSecret = c.Input().Get("client_secret")
c.ServeJSON() if clientId == "" || clientSecret == "" {
return c.ResponseError("empty clientId or clientSecret")
return
}
} }
application := object.GetApplicationByClientId(clientId) application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret { if application == nil || application.ClientSecret != clientSecret {
util.LogWarning(c.Ctx, "Basic Authorization failed") c.ResponseError("invalid application or wrong clientSecret")
c.Data["json"] = Response{Status: "error", Msg: "Unauthorized operation"}
c.ServeJSON()
return return
} }
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name) token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
if token == nil { if token == nil {
util.LogWarning(c.Ctx, "application: %s can not find token", application.Name)
c.Data["json"] = &object.IntrospectionResponse{Active: false} c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON() c.ServeJSON()
return return
@@ -269,7 +298,6 @@ func (c *ApiController) IntrospectToken() {
// and token revoked case. but we not implement // and token revoked case. but we not implement
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs. // TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
// refs: https://tools.ietf.org/html/rfc7009 // refs: https://tools.ietf.org/html/rfc7009
util.LogWarning(c.Ctx, "token invalid")
c.Data["json"] = &object.IntrospectionResponse{Active: false} c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON() c.ServeJSON()
return 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") id := c.Input().Get("id")
owner := c.Input().Get("owner") owner := c.Input().Get("owner")
email := c.Input().Get("email") email := c.Input().Get("email")
userOwner, _ := util.GetOwnerAndNameFromId(id)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
if !hasPermission {
c.ResponseError(err.Error())
return
}
}
var user *object.User var user *object.User
if email == "" { if email == "" {
@@ -111,6 +122,10 @@ func (c *ApiController) UpdateUser() {
id := c.Input().Get("id") id := c.Input().Get("id")
columnsStr := c.Input().Get("columns") columnsStr := c.Input().Get("columns")
if id == "" {
id = c.GetSessionUsername()
}
var user object.User var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user) err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil { if err != nil {
@@ -229,39 +244,15 @@ func (c *ApiController) SetPassword() {
newPassword := c.Ctx.Request.Form.Get("newPassword") newPassword := c.Ctx.Request.Form.Get("newPassword")
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
if requestUserId == "" {
c.ResponseError("Please login first")
return
}
userId := fmt.Sprintf("%s/%s", userOwner, userName) userId := fmt.Sprintf("%s/%s", userOwner, userName)
targetUser := object.GetUser(userId)
if targetUser == nil { hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId)) if !hasPermission {
c.ResponseError(err.Error())
return return
} }
hasPermission := false targetUser := object.GetUser(userId)
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := object.GetUser(requestUserId)
if requestUser == nil {
c.ResponseError("Session outdated. Please login again.")
return
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
hasPermission = true
}
}
if !hasPermission {
c.ResponseError("You don't have the permission to do this.")
return
}
if oldPassword != "" { if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword) msg := object.CheckPassword(targetUser, oldPassword)

View File

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

38
cred/argon2id.go Normal file
View File

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

View File

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

View File

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

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

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

View File

@@ -5,12 +5,15 @@ services:
build: build:
context: ./ context: ./
dockerfile: Dockerfile dockerfile: Dockerfile
entrypoint: /bin/sh -c './server --createDatabase=true'
ports: ports:
- "8000:8000" - "8000:8000"
depends_on: depends_on:
- db - db
environment: environment:
RUNNING_IN_DOCKER: "true" RUNNING_IN_DOCKER: "true"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes: volumes:
- ./conf:/conf/ - ./conf:/conf/
db: db:

23
go.mod
View File

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

92
go.sum
View File

@@ -35,6 +35,21 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U=
github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
@@ -44,22 +59,26 @@ github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8L
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b h1:EgJ6N2S0h1WfFIjU5/VVHWbMSVYXAluop97Qxpr/lfQ=
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b/go.mod h1:3SAoF0F5EbcOuBD5WT9nYkbIJieBS84cUQXADbXeBsU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I=
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4= github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible h1:9gWa46nstkJ9miBReJcN8Gq34cBFbzSpQZVVT9N09TM=
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ= github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA= github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0= github.com/aws/aws-sdk-go v1.44.4 h1:ePN0CVJMdiz2vYUcJH96eyxRrtKGSDMgyhP6rah2OgE=
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.44.4/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
@@ -79,10 +98,12 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM= github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54= github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.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 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk= github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/casdoor/oss v1.2.0 h1:ozLAE+nnNdFQBWbzH8U9spzaO8h8NrB57lBcdyMUUQ8=
github.com/casdoor/oss v1.2.0/go.mod h1:qii35VBuxnR/uEuYSKpS0aJ8htQFOcCVsZ4FHgHLuss=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -111,6 +132,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw= github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
@@ -128,6 +151,12 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw= github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8= github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@@ -135,10 +164,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -237,6 +264,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@@ -250,6 +279,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ= github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM= github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -258,10 +288,10 @@ github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0= github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 h1:JibQrkJapVsb0pweJ5T14jZuuYZZTjll0PZBw4XfSCI=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
@@ -278,6 +308,8 @@ github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
@@ -313,8 +345,9 @@ github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFB
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs= github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA= github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4= github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0= github.com/qiniu/go-sdk/v7 v7.12.1/go.mod h1:btsaOc8CA3hdVloULfFdDgDc+g4f3TDZEFsDY0BLE+w=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -383,6 +416,11 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-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 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220208233918-bba287dce954/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -431,6 +469,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -447,9 +486,11 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -466,6 +507,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -480,6 +522,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -499,24 +542,28 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE= golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -692,5 +739,6 @@ xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw= xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM= xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4= xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.0.4 h1:UBXA4I3NhiyjXfPqxXUkS2t5hMta9SSPATeMMaZg9oA=
xorm.io/xorm v1.0.4/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=

View File

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

292
idp/alipay.go Normal file
View File

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

View File

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

View File

@@ -17,7 +17,6 @@ package idp
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
@@ -132,8 +131,9 @@ func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

109
idp/custom.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

200
idp/okta.go Normal file
View File

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

View File

@@ -35,7 +35,7 @@ type IdProvider interface {
GetUserInfo(token *oauth2.Token) (*UserInfo, error) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
} }
func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string) IdProvider { func GetIdProvider(typ string, subType string, clientId string, clientSecret string, appId string, redirectUrl string, hostUrl string, authUrl string, tokenUrl string, userInfoUrl string) IdProvider {
if typ == "GitHub" { if typ == "GitHub" {
return NewGithubIdProvider(clientId, clientSecret, redirectUrl) return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Google" { } else if typ == "Google" {
@@ -70,6 +70,10 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl) return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Baidu" { } else if typ == "Baidu" {
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl) return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Alipay" {
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Custom" {
return NewCustomIdProvider(clientId, clientSecret, redirectUrl, authUrl, tokenUrl, userInfoUrl)
} else if typ == "Infoflow" { } else if typ == "Infoflow" {
if subType == "Internal" { if subType == "Internal" {
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl) return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
@@ -80,6 +84,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
} }
} else if typ == "Casdoor" { } else if typ == "Casdoor" {
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl) return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Okta" {
return NewOktaIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if isGothSupport(typ) { } else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl) return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
} }

View File

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

82
idp/wechat_miniprogram.go Normal file
View File

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

View File

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

View File

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

View File

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

11
main.go
View File

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

View File

@@ -41,7 +41,7 @@ func InitConfig() {
func InitAdapter(createDatabase bool) { func InitAdapter(createDatabase bool) {
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName")) adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase { if createDatabase {
adapter.CreateDatabase() adapter.CreateDatabase()
} }
@@ -111,10 +111,10 @@ func (a *Adapter) close() {
} }
func (a *Adapter) createTable() { func (a *Adapter) createTable() {
showSql, _ := beego.AppConfig.Bool("showSql") showSql, _ := conf.GetConfigBool("showSql")
a.Engine.ShowSQL(showSql) a.Engine.ShowSQL(showSql)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix) tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper) a.Engine.SetTableMapper(tbMapper)

View File

@@ -229,7 +229,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***" application.OrganizationObj.PasswordSalt = "***"
} }
} }
return application return application
} }
func GetMaskedApplications(applications []*Application, userId string) []*Application { func GetMaskedApplications(applications []*Application, userId string) []*Application {
@@ -300,7 +300,6 @@ func (application *Application) GetId() string {
func CheckRedirectUriValid(application *Application, redirectUri string) bool { func CheckRedirectUriValid(application *Application, redirectUri string) bool {
var validUri = false var validUri = false
for _, tmpUri := range application.RedirectUris { for _, tmpUri := range application.RedirectUris {
fmt.Println(tmpUri, redirectUri)
if strings.Contains(redirectUri, tmpUri) { if strings.Contains(redirectUri, tmpUri) {
validUri = true validUri = true
break break

View File

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

View File

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

View File

@@ -15,29 +15,25 @@
package object package object
import ( import (
_ "embed" "io/ioutil"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
//go:embed token_jwt_key.pem
var tokenJwtPublicKey string
//go:embed token_jwt_key.key
var tokenJwtPrivateKey string
func InitDb() { func InitDb() {
initBuiltInOrganization() existed := initBuiltInOrganization()
initBuiltInUser() if !existed {
initBuiltInApplication() initBuiltInUser()
initBuiltInCert() initBuiltInApplication()
initBuiltInLdap() initBuiltInCert()
initBuiltInLdap()
}
} }
func initBuiltInOrganization() { func initBuiltInOrganization() bool {
organization := getOrganization("admin", "built-in") organization := getOrganization("admin", "built-in")
if organization != nil { if organization != nil {
return return true
} }
organization = &Organization{ organization = &Organization{
@@ -53,6 +49,7 @@ func initBuiltInOrganization() {
Tags: []string{}, Tags: []string{},
} }
AddOrganization(organization) AddOrganization(organization)
return false
} }
func initBuiltInUser() { func initBuiltInUser() {
@@ -112,7 +109,7 @@ func initBuiltInApplication() {
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "Normal"},
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"}, {Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
}, },
@@ -122,7 +119,22 @@ func initBuiltInApplication() {
AddApplication(application) AddApplication(application)
} }
func readTokenFromFile() (string, string) {
pemPath := "./object/token_jwt_key.pem"
keyPath := "./object/token_jwt_key.key"
pem, err := ioutil.ReadFile(pemPath)
if err != nil {
return "", ""
}
key, err := ioutil.ReadFile(keyPath)
if err != nil {
return "", ""
}
return string(pem), string(key)
}
func initBuiltInCert() { func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in") cert := getCert("admin", "cert-built-in")
if cert != nil { if cert != nil {
return return

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
) )
@@ -58,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
func GetOidcDiscovery(host string) OidcDiscovery { func GetOidcDiscovery(host string) OidcDiscovery {
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
if origin != "" { if origin != "" {
originFrontend = origin originFrontend = origin
originBackend = origin originBackend = origin
@@ -105,6 +105,8 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
jwk.Key = x509Cert.PublicKey jwk.Key = x509Cert.PublicKey
jwk.Certificates = []*x509.Certificate{x509Cert} jwk.Certificates = []*x509.Certificate{x509Cert}
jwk.KeyID = cert.Name jwk.KeyID = cert.Name
jwk.Algorithm = cert.CryptoAlgorithm
jwk.Use = "sig"
jwks.Keys = append(jwks.Keys, jwk) jwks.Keys = append(jwks.Keys, jwk)
} }

View File

@@ -35,6 +35,7 @@ type Organization struct {
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"` MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"` EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
} }
func GetOrganizationCount(owner, field, value string) int { func GetOrganizationCount(owner, field, value string) int {

View File

@@ -44,6 +44,16 @@ type Payment struct {
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"` ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"` State string `xorm:"varchar(100)" json:"state"`
Message string `xorm:"varchar(1000)" json:"message"` Message string `xorm:"varchar(1000)" json:"message"`
PersonName string `xorm:"varchar(100)" json:"personName"`
PersonIdCard string `xorm:"varchar(100)" json:"personIdCard"`
PersonEmail string `xorm:"varchar(100)" json:"personEmail"`
PersonPhone string `xorm:"varchar(100)" json:"personPhone"`
InvoiceType string `xorm:"varchar(100)" json:"invoiceType"`
InvoiceTitle string `xorm:"varchar(100)" json:"invoiceTitle"`
InvoiceTaxId string `xorm:"varchar(100)" json:"invoiceTaxId"`
InvoiceRemark string `xorm:"varchar(100)" json:"invoiceRemark"`
InvoiceUrl string `xorm:"varchar(255)" json:"invoiceUrl"`
} }
func GetPaymentCount(owner, field, value string) int { func GetPaymentCount(owner, field, value string) int {
@@ -197,6 +207,44 @@ func NotifyPayment(request *http.Request, body []byte, owner string, providerNam
return ok return ok
} }
func invoicePayment(payment *Payment) (string, error) {
provider := getProvider(payment.Owner, payment.Provider)
if provider == nil {
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
}
pProvider, _, err := provider.getPaymentProvider()
if err != nil {
return "", err
}
invoiceUrl, err := pProvider.GetInvoice(payment.Name, payment.PersonName, payment.PersonIdCard, payment.PersonEmail, payment.PersonPhone, payment.InvoiceType, payment.InvoiceTitle, payment.InvoiceTaxId)
if err != nil {
return "", err
}
return invoiceUrl, nil
}
func InvoicePayment(payment *Payment) (string, error) {
if payment.State != "Paid" {
return "", fmt.Errorf("the payment state is supposed to be: \"%s\", got: \"%s\"", "Paid", payment.State)
}
invoiceUrl, err := invoicePayment(payment)
if err != nil {
return "", err
}
payment.InvoiceUrl = invoiceUrl
affected := UpdatePayment(payment.GetId(), payment)
if !affected {
return "", fmt.Errorf("failed to update the payment: %s", payment.Name)
}
return invoiceUrl, nil
}
func (payment *Payment) GetId() string { func (payment *Payment) GetId() string {
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name) return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
} }

View File

@@ -170,6 +170,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
owner := product.Owner owner := product.Owner
productName := product.Name productName := product.Name
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
paymentName := util.GenerateTimeId() paymentName := util.GenerateTimeId()
productDisplayName := product.DisplayName productDisplayName := product.DisplayName
@@ -177,7 +178,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName) 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) notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
payUrl, err := pProvider.Pay(providerName, productName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl) payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@@ -13,6 +13,7 @@
// limitations under the License. // limitations under the License.
//go:build !skipCi //go:build !skipCi
// +build !skipCi
package object package object

View File

@@ -27,16 +27,21 @@ type Provider struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"` Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
SubType string `xorm:"varchar(100)" json:"subType"` SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"` Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"` ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
Cert string `xorm:"varchar(100)" json:"cert"` Cert string `xorm:"varchar(100)" json:"cert"`
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
CustomScope string `xorm:"varchar(200)" json:"customScope"`
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
Host string `xorm:"varchar(100)" json:"host"` Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"` Port int `json:"port"`
@@ -151,6 +156,16 @@ func GetDefaultHumanCheckProvider() *Provider {
return &provider return &provider
} }
func GetWechatMiniProgramProvider(application *Application) *Provider {
providers := application.Providers
for _, provider := range providers {
if provider.Provider.Type == "WeChatMiniProgram" {
return provider.Provider
}
}
return nil
}
func UpdateProvider(id string, provider *Provider) bool { func UpdateProvider(id string, provider *Provider) bool {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if getProvider(owner, name) == nil { if getProvider(owner, name) == nil {

View File

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

View File

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

View File

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

352
object/saml_idp.go Normal file
View File

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

View File

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

View File

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

View File

@@ -206,3 +206,8 @@ func (syncer *Syncer) getTable() string {
return syncer.Table return syncer.Table
} }
} }
func RunSyncer(syncer *Syncer) {
syncer.initAdapter()
syncer.syncUsers()
}

View File

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

View File

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

View File

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

View File

@@ -22,10 +22,15 @@ import (
"strings" "strings"
"time" "time"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
const (
hourSeconds = 3600
)
type Code struct { type Code struct {
Message string `xorm:"varchar(100)" json:"message"` Message string `xorm:"varchar(100)" json:"message"`
Code string `xorm:"varchar(100)" json:"code"` Code string `xorm:"varchar(100)" json:"code"`
@@ -58,6 +63,7 @@ type TokenWrapper struct {
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"` ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"` Scope string `json:"scope"`
Error string `json:"error,omitempty"`
} }
type IntrospectionResponse struct { type IntrospectionResponse struct {
@@ -290,7 +296,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeChallenge: challenge, CodeChallenge: challenge,
@@ -305,24 +311,31 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
} }
} }
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string) *TokenWrapper {
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, username string, password string, host string, tag string, avatar string) *TokenWrapper {
var errString string
application := GetApplicationByClientId(clientId) application := GetApplicationByClientId(clientId)
if application == nil { if application == nil {
errString = "error: invalid client_id"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid client_id", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
//Check if grantType is allowed in the current application //Check if grantType is allowed in the current application
if !IsGrantTypeValid(grantType, application.GrantTypes) {
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
errString = fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType)
return &TokenWrapper{ return &TokenWrapper{
AccessToken: fmt.Sprintf("error: grant_type: %s is not supported in this application", grantType), AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
@@ -337,12 +350,19 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
token, err = GetClientCredentialsToken(application, clientSecret, scope, host) token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
} }
if tag == "wechat_miniprogram" {
// Wechat Mini Program
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar)
}
if err != nil { if err != nil {
errString = err.Error()
return &TokenWrapper{ return &TokenWrapper{
AccessToken: err.Error(), AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
@@ -361,62 +381,75 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
} }
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper { func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper {
var errString string
// check parameters // check parameters
if grantType != "refresh_token" { if grantType != "refresh_token" {
errString = "error: grant_type should be \"refresh_token\""
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: grant_type should be \"refresh_token\"", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
application := GetApplicationByClientId(clientId) application := GetApplicationByClientId(clientId)
if application == nil { if application == nil {
errString = "error: invalid client_id"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid client_id", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
if clientSecret != "" && application.ClientSecret != clientSecret { if clientSecret != "" && application.ClientSecret != clientSecret {
errString = "error: invalid client_secret"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid client_secret", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
// check whether the refresh token is valid, and has not expired. // check whether the refresh token is valid, and has not expired.
token := Token{RefreshToken: refreshToken} token := Token{RefreshToken: refreshToken}
existed, err := adapter.Engine.Get(&token) existed, err := adapter.Engine.Get(&token)
if err != nil || !existed { if err != nil || !existed {
errString = "error: invalid refresh_token"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: invalid refresh_token", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
cert := getCertByApplication(application) cert := getCertByApplication(application)
_, err = ParseJwtToken(refreshToken, cert) _, err = ParseJwtToken(refreshToken, cert)
if err != nil { if err != nil {
errString := fmt.Sprintf("error: %s", err.Error())
return &TokenWrapper{ return &TokenWrapper{
AccessToken: fmt.Sprintf("error: %s", err.Error()), AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
// generate a new token // generate a new token
user := getUser(application.Organization, token.User) user := getUser(application.Organization, token.User)
if user.IsForbidden { if user.IsForbidden {
errString = "error: the user is forbidden to sign in, please contact the administrator"
return &TokenWrapper{ return &TokenWrapper{
AccessToken: "error: the user is forbidden to sign in, please contact the administrator", AccessToken: errString,
TokenType: "", TokenType: "",
ExpiresIn: 0, ExpiresIn: 0,
Scope: "", Scope: "",
Error: errString,
} }
} }
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host) newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
@@ -434,19 +467,20 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: newAccessToken, AccessToken: newAccessToken,
RefreshToken: newRefreshToken, RefreshToken: newRefreshToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
} }
AddToken(newToken) AddToken(newToken)
DeleteToken(&token)
tokenWrapper := &TokenWrapper{ tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken, AccessToken: newToken.AccessToken,
IdToken: token.AccessToken, IdToken: newToken.AccessToken,
RefreshToken: token.RefreshToken, RefreshToken: newToken.RefreshToken,
TokenType: token.TokenType, TokenType: newToken.TokenType,
ExpiresIn: token.ExpiresIn, ExpiresIn: newToken.ExpiresIn,
Scope: token.Scope, Scope: newToken.Scope,
} }
return tokenWrapper return tokenWrapper
@@ -521,7 +555,8 @@ func GetPasswordToken(application *Application, username string, password string
if user == nil { if user == nil {
return nil, errors.New("error: the user does not exist") return nil, errors.New("error: the user does not exist")
} }
if user.Password != password { msg := CheckPassword(user, password)
if msg != "" {
return nil, errors.New("error: invalid username or password") return nil, errors.New("error: invalid username or password")
} }
if user.IsForbidden { if user.IsForbidden {
@@ -541,7 +576,7 @@ func GetPasswordToken(application *Application, username string, password string
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,
@@ -573,7 +608,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
User: nullUser.Name, User: nullUser.Name,
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,
@@ -598,7 +633,7 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
Code: util.GenerateClientId(), Code: util.GenerateClientId(),
AccessToken: accessToken, AccessToken: accessToken,
RefreshToken: refreshToken, RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60, ExpiresIn: application.ExpireInHours * hourSeconds,
Scope: scope, Scope: scope,
TokenType: "Bearer", TokenType: "Bearer",
CodeIsUsed: true, CodeIsUsed: true,
@@ -606,3 +641,74 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
AddToken(token) AddToken(token)
return token, nil return token, nil
} }
// Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) {
mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil {
return nil, errors.New("error: the application does not support wechat mini program")
}
provider := GetProvider(util.GetId(mpProvider.Name))
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
session, err := mpIdp.GetSessionByCode(code)
if err != nil {
return nil, err
}
openId, unionId := session.Openid, session.Unionid
if openId == "" && unionId == "" {
return nil, errors.New("err: WeChat's openid and unionid are empty")
}
user := getUserByWechatId(openId, unionId)
if user == nil {
if !application.EnableSignUp {
return nil, errors.New("err: the application does not allow to sign up new account")
}
//Add new user
var name string
if username != "" {
name = username
} else {
name = fmt.Sprintf("wechat-%s", openId)
}
user = &User{
Owner: application.Organization,
Id: util.GenerateId(),
Name: name,
Avatar: avatar,
SignupApplication: application.Name,
WeChat: openId,
WeChatUnionId: unionId,
Type: "normal-user",
CreatedTime: util.GetCurrentTime(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
}
AddUser(user)
}
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
if err != nil {
return nil, err
}
token := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: session.SessionKey, //a trick, because miniprogram does not use the code, so use the code field to save the session_key
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: "",
TokenType: "Bearer",
CodeIsUsed: true,
}
AddToken(token)
return token, nil
}

327
object/token_cas.go Normal file
View File

@@ -0,0 +1,327 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"crypto"
"encoding/base64"
"encoding/json"
"encoding/pem"
"encoding/xml"
"fmt"
"math/rand"
"sync"
"time"
"github.com/beevik/etree"
"github.com/casdoor/casdoor/util"
dsig "github.com/russellhaering/goxmldsig"
)
type CasServiceResponse struct {
XMLName xml.Name `xml:"cas:serviceResponse" json:"-"`
Xmlns string `xml:"xmlns:cas,attr"`
Failure *CasAuthenticationFailure
Success *CasAuthenticationSuccess
ProxySuccess *CasProxySuccess
ProxyFailure *CasProxyFailure
}
type CasAuthenticationFailure struct {
XMLName xml.Name `xml:"cas:authenticationFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
type CasAuthenticationSuccess struct {
XMLName xml.Name `xml:"cas:authenticationSuccess" json:"-"`
User string `xml:"cas:user"`
ProxyGrantingTicket string `xml:"cas:proxyGrantingTicket,omitempty"`
Proxies *CasProxies `xml:"cas:proxies"`
Attributes *CasAttributes `xml:"cas:attributes"`
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasProxies struct {
XMLName xml.Name `xml:"cas:proxies" json:"-"`
Proxies []string `xml:"cas:proxy"`
}
type CasAttributes struct {
XMLName xml.Name `xml:"cas:attributes" json:"-"`
AuthenticationDate time.Time `xml:"cas:authenticationDate"`
LongTermAuthenticationRequestTokenUsed bool `xml:"cas:longTermAuthenticationRequestTokenUsed"`
IsFromNewLogin bool `xml:"cas:isFromNewLogin"`
MemberOf []string `xml:"cas:memberOf"`
UserAttributes *CasUserAttributes
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasUserAttributes struct {
XMLName xml.Name `xml:"cas:userAttributes" json:"-"`
Attributes []*CasNamedAttribute `xml:"cas:attribute"`
AnyAttributes []*CasAnyAttribute `xml:",any"`
}
type CasNamedAttribute struct {
XMLName xml.Name `xml:"cas:attribute" json:"-"`
Name string `xml:"name,attr,omitempty"`
Value string `xml:",innerxml"`
}
type CasAnyAttribute struct {
XMLName xml.Name
Value string `xml:",chardata"`
}
type CasAuthenticationSuccessWrapper struct {
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
Service string //to which service this token is issued
UserId string
}
type CasProxySuccess struct {
XMLName xml.Name `xml:"cas:proxySuccess" json:"-"`
ProxyTicket string `xml:"cas:proxyTicket"`
}
type CasProxyFailure struct {
XMLName xml.Name `xml:"cas:proxyFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
type Saml11Request struct {
XMLName xml.Name `xml:"Request"`
SAMLP string `xml:"samlp,attr"`
MajorVersion string `xml:"MajorVersion,attr"`
MinorVersion string `xml:"MinorVersion,attr"`
RequestID string `xml:"RequestID,attr"`
IssueInstant string `xml:"IssueInstance,attr"`
AssertionArtifact Saml11AssertionArtifact
}
type Saml11AssertionArtifact struct {
XMLName xml.Name `xml:"AssertionArtifact"`
InnerXML string `xml:",innerxml"`
}
//st is short for service ticket
var stToServiceResponse sync.Map
//pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service, userId string) string {
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: service,
UserId: userId,
})
return pgt
}
func GenerateId() {
panic("unimplemented")
}
/**
@ret1: whether a token is found
@ret2: token, nil if not found
@ret3: the service URL who requested to issue this token
@ret4: userIf of user who requested to issue this token
*/
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string, string) {
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
}
return false, nil, "", ""
}
/**
@ret1: whether a token is found
@ret2: token, nil if not found
@ret3: the service URL who requested to issue this token
@ret4: userIf of user who requested to issue this token
*/
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string, string) {
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service, responseWrapperTypeCast.UserId
}
return false, nil, "", ""
}
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService, userId string) string {
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: targetService,
UserId: userId,
})
return proxyTicket
}
func GenerateCasToken(userId string, service string) (string, error) {
if user := GetUser(userId); user != nil {
authenticationSuccess := CasAuthenticationSuccess{
User: user.Name,
Attributes: &CasAttributes{
AuthenticationDate: time.Now(),
UserAttributes: &CasUserAttributes{},
},
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
}
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
for k, v := range tmp {
if v != "" {
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: v,
})
}
}
st := fmt.Sprintf("ST-%d", rand.Int())
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: &authenticationSuccess,
Service: service,
UserId: userId,
})
return st, nil
} else {
return "", fmt.Errorf("invalid user Id")
}
}
/**
@ret1: saml response
@ret2: the service URL who requested to issue this token
@ret3: error
*/
func GetValidationBySaml(samlRequest string, host string) (string, string, error) {
var request Saml11Request
err := xml.Unmarshal([]byte(samlRequest), &request)
if err != nil {
return "", "", err
}
ticket := request.AssertionArtifact.InnerXML
if ticket == "" {
return "", "", fmt.Errorf("samlp:AssertionArtifact field not found")
}
ok, _, service, userId := GetCasTokenByTicket(ticket)
if !ok {
return "", "", fmt.Errorf("ticket %s found", ticket)
}
user := GetUser(userId)
if user == nil {
return "", "", fmt.Errorf("user %s found", userId)
}
application := GetApplicationByUser(user)
if application == nil {
return "", "", fmt.Errorf("application for user %s found", userId)
}
samlResponse := NewSamlResponse11(user, request.RequestID, host)
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
}
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1
signedXML, err := ctx.SignEnveloped(samlResponse)
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
doc := etree.NewDocument()
doc.SetRoot(signedXML)
xmlStr, err := doc.WriteToString()
if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error())
}
return xmlStr, service, nil
}
func (c *CasAuthenticationSuccess) DeepCopy() CasAuthenticationSuccess {
res := *c
//copy proxy
if c.Proxies != nil {
tmp := c.Proxies.DeepCopy()
res.Proxies = &tmp
}
if c.Attributes != nil {
tmp := c.Attributes.DeepCopy()
res.Attributes = &tmp
}
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasProxies) DeepCopy() CasProxies {
res := CasProxies{
Proxies: make([]string, len(c.Proxies)),
}
copy(res.Proxies, c.Proxies)
return res
}
func (c *CasAttributes) DeepCopy() CasAttributes {
res := *c
if c.MemberOf != nil {
res.MemberOf = make([]string, len(c.MemberOf))
copy(res.MemberOf, c.MemberOf)
}
tmp := c.UserAttributes.DeepCopy()
res.UserAttributes = &tmp
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
res := CasUserAttributes{
AnyAttributes: make([]*CasAnyAttribute, len(c.AnyAttributes)),
Attributes: make([]*CasNamedAttribute, len(c.Attributes)),
}
for i, a := range c.AnyAttributes {
var tmp = *a
res.AnyAttributes[i] = &tmp
}
for i, a := range c.Attributes {
var tmp = *a
res.Attributes[i] = &tmp
}
return res
}

View File

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

View File

@@ -18,7 +18,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
@@ -39,6 +39,7 @@ type User struct {
Avatar string `xorm:"varchar(500)" json:"avatar"` Avatar string `xorm:"varchar(500)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"` PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"` Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(100) index" json:"phone"` Phone string `xorm:"varchar(100) index" json:"phone"`
Location string `xorm:"varchar(100)" json:"location"` Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"` Address []string `json:"address"`
@@ -71,26 +72,30 @@ type User struct {
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"` LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"` LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"` Github string `xorm:"varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"` Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"` QQ string `xorm:"qq varchar(100)" json:"qq"`
WeChat string `xorm:"wechat varchar(100)" json:"wechat"` WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
Facebook string `xorm:"facebook varchar(100)" json:"facebook"` WeChatUnionId string `xorm:"varchar(100)" json:"unionId"`
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"` Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
Weibo string `xorm:"weibo varchar(100)" json:"weibo"` DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
Gitee string `xorm:"gitee varchar(100)" json:"gitee"` Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"` Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
Wecom string `xorm:"wecom varchar(100)" json:"wecom"` LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
Lark string `xorm:"lark varchar(100)" json:"lark"` Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"` Lark string `xorm:"lark varchar(100)" json:"lark"`
Adfs string `xorm:"adfs varchar(100)" json:"adfs"` Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"` Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"` Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"` Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
Apple string `xorm:"apple varchar(100)" json:"apple"` Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"` Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
Slack string `xorm:"slack varchar(100)" json:"slack"` Apple string `xorm:"apple varchar(100)" json:"apple"`
Steam string `xorm:"steam varchar(100)" json:"steam"` AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
Slack string `xorm:"slack varchar(100)" json:"slack"`
Steam string `xorm:"steam varchar(100)" json:"steam"`
Okta string `xorm:"okta varchar(100)" json:"okta"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"` Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"` Properties map[string]string `json:"properties"`
@@ -225,6 +230,23 @@ func getUserById(owner string, id string) *User {
} }
} }
func getUserByWechatId(wechatOpenId string, wechatUnionId string) *User {
if wechatUnionId == "" {
wechatUnionId = wechatOpenId
}
user := &User{}
existed, err := adapter.Engine.Where("wechat = ? OR wechat = ? OR unionid = ?", wechatOpenId, wechatUnionId, wechatUnionId).Get(user)
if err != nil {
panic(err)
}
if existed {
return user
} else {
return nil
}
}
func GetUserByEmail(owner string, email string) *User { func GetUserByEmail(owner string, email string) *User {
if owner == "" || email == "" { if owner == "" || email == "" {
return nil return nil
@@ -304,7 +326,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"} "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
} }
if isGlobalAdmin { if isGlobalAdmin {
columns = append(columns, "name") columns = append(columns, "name", "email", "phone")
} }
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user) affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
@@ -429,7 +451,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
if user == nil { if user == nil {
return nil, fmt.Errorf("the user: %s doesn't exist", userId) return nil, fmt.Errorf("the user: %s doesn't exist", userId)
} }
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
if origin != "" { if origin != "" {
originBackend = origin originBackend = origin

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ import (
"math/rand" "math/rand"
"time" "time"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
@@ -129,7 +129,7 @@ func CheckVerificationCode(dest, code string) string {
return "Code has not been sent yet!" return "Code has not been sent yet!"
} }
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout") timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -45,7 +45,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
return pp return pp
} }
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
//pp.Client.DebugSwitch = gopay.DebugOn //pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
@@ -90,3 +90,7 @@ func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, auth
return productDisplayName, paymentName, price, productName, providerName, nil return productDisplayName, paymentName, price, productName, providerName, nil
} }
func (pp *AlipayPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
return "", nil
}

112
pp/gc.go
View File

@@ -38,11 +38,14 @@ type GcPayReqInfo struct {
OrderDate string `json:"orderdate"` OrderDate string `json:"orderdate"`
OrderNo string `json:"orderno"` OrderNo string `json:"orderno"`
Amount string `json:"amount"` Amount string `json:"amount"`
PayerId string `json:"payerid"`
PayerName string `json:"payername"`
Xmpch string `json:"xmpch"` Xmpch string `json:"xmpch"`
Body string `json:"body"`
ReturnUrl string `json:"return_url"` ReturnUrl string `json:"return_url"`
NotifyUrl string `json:"notify_url"` NotifyUrl string `json:"notify_url"`
PayerId string `json:"payerid"`
PayerName string `json:"payername"`
Remark1 string `json:"remark1"`
Remark2 string `json:"remark2"`
} }
type GcPayRespInfo struct { type GcPayRespInfo struct {
@@ -87,6 +90,27 @@ type GcResponseBody struct {
Sign string `json:"sign"` Sign string `json:"sign"`
} }
type GcInvoiceReqInfo struct {
BusNo string `json:"busno"`
PayerName string `json:"payername"`
IdNum string `json:"idnum"`
PayerType string `json:"payertype"`
InvoiceTitle string `json:"invoicetitle"`
Tin string `json:"tin"`
Phone string `json:"phone"`
Email string `json:"email"`
}
type GcInvoiceRespInfo struct {
BusNo string `json:"busno"`
State string `json:"state"`
EbillCode string `json:"ebillcode"`
EbillNo string `json:"ebillno"`
CheckCode string `json:"checkcode"`
Url string `json:"url"`
Content string `json:"content"`
}
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider { func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
pp := &GcPaymentProvider{} pp := &GcPaymentProvider{}
@@ -130,16 +154,17 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
return respBytes, nil return respBytes, nil
} }
func (pp *GcPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) { func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
payReqInfo := GcPayReqInfo{ payReqInfo := GcPayReqInfo{
OrderDate: util.GenerateSimpleTimeId(), OrderDate: util.GenerateSimpleTimeId(),
OrderNo: util.GenerateTimeId(), OrderNo: paymentName,
Amount: getPriceString(price), Amount: getPriceString(price),
PayerId: "",
PayerName: "",
Xmpch: pp.Xmpch, Xmpch: pp.Xmpch,
Body: productDisplayName,
ReturnUrl: returnUrl, ReturnUrl: returnUrl,
NotifyUrl: notifyUrl, NotifyUrl: notifyUrl,
Remark1: payerName,
Remark2: productName,
} }
b, err := json.Marshal(payReqInfo) b, err := json.Marshal(payReqInfo)
@@ -230,3 +255,78 @@ func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorit
return productDisplayName, paymentName, price, productName, providerName, nil return productDisplayName, paymentName, price, productName, providerName, nil
} }
func (pp *GcPaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
payerType := "0"
if invoiceType == "Organization" {
payerType = "1"
}
invoiceReqInfo := GcInvoiceReqInfo{
BusNo: paymentName,
PayerName: personName,
IdNum: personIdCard,
PayerType: payerType,
InvoiceTitle: invoiceTitle,
Tin: invoiceTaxId,
Phone: personPhone,
Email: personEmail,
}
b, err := json.Marshal(invoiceReqInfo)
if err != nil {
return "", err
}
body := GcRequestBody{
Op: "InvoiceEBillByOrder",
Xmpch: pp.Xmpch,
Version: "1.4",
Data: base64.StdEncoding.EncodeToString(b),
RequestTime: util.GenerateSimpleTimeId(),
}
params := fmt.Sprintf("data=%s&op=%s&requesttime=%s&version=%s&xmpch=%s%s", body.Data, body.Op, body.RequestTime, body.Version, body.Xmpch, pp.SecretKey)
body.Sign = strings.ToUpper(util.GetMd5Hash(params))
bodyBytes, err := json.Marshal(body)
if err != nil {
return "", err
}
respBytes, err := pp.doPost(bodyBytes)
if err != nil {
return "", err
}
var respBody GcResponseBody
err = json.Unmarshal(respBytes, &respBody)
if err != nil {
return "", err
}
if respBody.ReturnCode != "SUCCESS" {
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
}
invoiceRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
if err != nil {
return "", err
}
var invoiceRespInfo GcInvoiceRespInfo
err = json.Unmarshal(invoiceRespInfoBytes, &invoiceRespInfo)
if err != nil {
return "", err
}
if invoiceRespInfo.State == "0" {
return "", fmt.Errorf("申请成功,开票中")
}
if invoiceRespInfo.Url == "" {
return "", fmt.Errorf("invoice URL is empty")
}
return invoiceRespInfo.Url, nil
}

View File

@@ -17,8 +17,9 @@ package pp
import "net/http" import "net/http"
type PaymentProvider interface { type PaymentProvider interface {
Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
} }
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider { func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {

View File

@@ -21,7 +21,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
) )
@@ -54,7 +54,7 @@ func isAddressOpen(address string) bool {
} }
func getProxyHttpClient() *http.Client { func getProxyHttpClient() *http.Client {
sock5Proxy := beego.AppConfig.String("sock5Proxy") sock5Proxy := conf.GetConfigString("sock5Proxy")
if sock5Proxy == "" { if sock5Proxy == "" {
return &http.Client{} return &http.Client{}
} }

View File

@@ -100,10 +100,22 @@ func willLog(subOwner string, subName string, method string, urlPath string, obj
return true return true
} }
func getUrlPath(urlPath string) string {
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
return "/cas"
}
if strings.HasPrefix(urlPath, "/api/login/oauth") {
return "/api/login/oauth"
}
return urlPath
}
func AuthzFilter(ctx *context.Context) { func AuthzFilter(ctx *context.Context) {
subOwner, subName := getSubject(ctx) subOwner, subName := getSubject(ctx)
method := ctx.Request.Method method := ctx.Request.Method
urlPath := ctx.Request.URL.Path urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := getObject(ctx) objOwner, objName := getObject(ctx)
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName) isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)

View File

@@ -65,5 +65,5 @@ func RecordMessage(ctx *context.Context) {
record.Organization, record.User = util.GetOwnerAndNameFromId(userId) record.Organization, record.User = util.GetOwnerAndNameFromId(userId)
} }
go object.AddRecord(record) util.SafeGoroutine(func() {object.AddRecord(record)})
} }

View File

@@ -54,6 +54,7 @@ func initAPI() {
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink") beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin") beego.Router("/api/get-saml-login", &controllers.ApiController{}, "GET:GetSamlLogin")
beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin") beego.Router("/api/acs", &controllers.ApiController{}, "POST:HandleSamlLogin")
beego.Router("/api/saml/metadata", &controllers.ApiController{}, "GET:GetSamlMeta")
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations") beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization") beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
@@ -144,6 +145,7 @@ func initAPI() {
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer") beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer") beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer") beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts") beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert") beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
@@ -165,10 +167,21 @@ func initAPI() {
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment") beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment") beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment") beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
beego.Router("/api/invoice-payment", &controllers.ApiController{}, "POST:InvoicePayment")
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail") beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms") beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery") beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks") beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
} }

View File

@@ -27,6 +27,9 @@ func StaticFilter(ctx *context.Context) {
if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") { if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") {
return return
} }
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
return
}
path := "web/build" path := "web/build"
if urlPath == "/" { if urlPath == "/" {

View File

@@ -15,8 +15,8 @@
package storage package storage
import ( import (
"github.com/qor/oss" "github.com/casdoor/oss"
"github.com/qor/oss/aliyun" "github.com/casdoor/oss/aliyun"
) )
func NewAliyunOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface { func NewAliyunOssStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {

View File

@@ -16,8 +16,8 @@ package storage
import ( import (
awss3 "github.com/aws/aws-sdk-go/service/s3" awss3 "github.com/aws/aws-sdk-go/service/s3"
"github.com/qor/oss" "github.com/casdoor/oss"
"github.com/qor/oss/s3" "github.com/casdoor/oss/s3"
) )
func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface { func NewAwsS3StorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {

31
storage/azure.go Normal file
View File

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

View File

@@ -20,7 +20,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/qor/oss" "github.com/casdoor/oss"
) )
var baseFolder = "files" var baseFolder = "files"

View File

@@ -14,7 +14,7 @@
package storage package storage
import "github.com/qor/oss" import "github.com/casdoor/oss"
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface { func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
switch providerType { switch providerType {
@@ -26,6 +26,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint) return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Tencent Cloud COS": case "Tencent Cloud COS":
return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint) return NewTencentCloudCosStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Azure Blob":
return NewAzureBlobStorageProvider(clientId, clientSecret, region, bucket, endpoint)
} }
return nil return nil

View File

@@ -15,8 +15,8 @@
package storage package storage
import ( import (
"github.com/qor/oss" "github.com/casdoor/oss"
"github.com/qor/oss/tencent" "github.com/casdoor/oss/tencent"
) )
func NewTencentCloudCosStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface { func NewTencentCloudCosStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {

View File

@@ -2797,11 +2797,11 @@
} }
}, },
"definitions": { "definitions": {
"2026.0xc000380de0.false": { "2127.0xc00036c600.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
"2060.0xc000380e10.false": { "2161.0xc00036c630.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
@@ -2818,10 +2818,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2026.0xc000380de0.false" "$ref": "#/definitions/2127.0xc00036c600.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2060.0xc000380e10.false" "$ref": "#/definitions/2161.0xc00036c630.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@@ -2842,10 +2842,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2026.0xc000380de0.false" "$ref": "#/definitions/2127.0xc00036c600.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2060.0xc000380e10.false" "$ref": "#/definitions/2161.0xc00036c630.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@@ -3648,6 +3648,9 @@
"access_token": { "access_token": {
"type": "string" "type": "string"
}, },
"error": {
"type": "string"
},
"expires_in": { "expires_in": {
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
@@ -3682,6 +3685,9 @@
"affiliation": { "affiliation": {
"type": "string" "type": "string"
}, },
"alipay": {
"type": "string"
},
"apple": { "apple": {
"type": "string" "type": "string"
}, },
@@ -3721,6 +3727,9 @@
"email": { "email": {
"type": "string" "type": "string"
}, },
"emailVerified": {
"type": "boolean"
},
"facebook": { "facebook": {
"type": "string" "type": "string"
}, },

View File

@@ -1831,10 +1831,10 @@ paths:
schema: schema:
$ref: '#/definitions/object.Userinfo' $ref: '#/definitions/object.Userinfo'
definitions: definitions:
2026.0xc000380de0.false: 2127.0xc00036c600.false:
title: "false" title: "false"
type: object type: object
2060.0xc000380e10.false: 2161.0xc00036c630.false:
title: "false" title: "false"
type: object type: object
RequestForm: RequestForm:
@@ -1848,9 +1848,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2026.0xc000380de0.false' $ref: '#/definitions/2127.0xc00036c600.false'
data2: data2:
$ref: '#/definitions/2060.0xc000380e10.false' $ref: '#/definitions/2161.0xc00036c630.false'
msg: msg:
type: string type: string
name: name:
@@ -1864,9 +1864,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2026.0xc000380de0.false' $ref: '#/definitions/2127.0xc00036c600.false'
data2: data2:
$ref: '#/definitions/2060.0xc000380e10.false' $ref: '#/definitions/2161.0xc00036c630.false'
msg: msg:
type: string type: string
name: name:
@@ -2407,6 +2407,8 @@ definitions:
properties: properties:
access_token: access_token:
type: string type: string
error:
type: string
expires_in: expires_in:
type: integer type: integer
format: int64 format: int64
@@ -2430,6 +2432,8 @@ definitions:
type: string type: string
affiliation: affiliation:
type: string type: string
alipay:
type: string
apple: apple:
type: string type: string
avatar: avatar:
@@ -2456,6 +2460,8 @@ definitions:
type: string type: string
email: email:
type: string type: string
emailVerified:
type: boolean
facebook: facebook:
type: string type: string
firstName: firstName:

View File

@@ -39,4 +39,4 @@ func IsPhoneCnValid(phone string) bool {
func getMaskedPhone(phone string) string { func getMaskedPhone(phone string) string {
return rePhone.ReplaceAllString(phone, "$1****$2") return rePhone.ReplaceAllString(phone, "$1****$2")
} }

View File

@@ -20,7 +20,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"os" "io/ioutil"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -52,6 +52,10 @@ func ParseFloat(s string) float64 {
} }
func ParseBool(s string) bool { func ParseBool(s string) bool {
if s == "\x01" {
return true
}
i := ParseInt(s) i := ParseInt(s)
return i != 0 return i != 0
} }
@@ -162,7 +166,7 @@ func GetMinLenStr(strs ...string) string {
} }
func ReadStringFromPath(path string) string { func ReadStringFromPath(path string) string {
data, err := os.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -171,7 +175,7 @@ func ReadStringFromPath(path string) string {
} }
func WriteStringToPath(s string, path string) { func WriteStringToPath(s string, path string) {
err := os.WriteFile(path, []byte(s), 0644) err := ioutil.WriteFile(path, []byte(s), 0644)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -220,7 +224,7 @@ func GetMaskedEmail(email string) string {
username := maskString(tokens[0]) username := maskString(tokens[0])
domain := tokens[1] domain := tokens[1]
domainTokens := strings.Split(domain, ".") domainTokens := strings.Split(domain, ".")
domainTokens[len(domainTokens) - 2] = maskString(domainTokens[len(domainTokens) - 2]) domainTokens[len(domainTokens)-2] = maskString(domainTokens[len(domainTokens)-2])
return fmt.Sprintf("%s@%s", username, strings.Join(domainTokens, ".")) return fmt.Sprintf("%s@%s", username, strings.Join(domainTokens, "."))
} }
@@ -228,6 +232,6 @@ func maskString(str string) string {
if len(str) <= 2 { if len(str) <= 2 {
return str return str
} else { } else {
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str) - 2), str[len(str) - 1]) return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
} }
} }

View File

@@ -245,3 +245,4 @@ func TestSnakeString(t *testing.T) {
}) })
} }
} }

38
util/util.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 util
import (
"fmt"
"github.com/astaxie/beego/logs"
)
func SafeGoroutine(fn func()) {
var err error
go func() {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("%v", r)
}
logs.Error("goroutine panic: %v", err)
}
}()
fn()
}()
}

View File

@@ -18,6 +18,22 @@ module.exports = {
'/.well-known/openid-configuration': { '/.well-known/openid-configuration': {
target: 'http://localhost:8000', target: 'http://localhost:8000',
changeOrigin: true, changeOrigin: true,
},
'/cas/serviceValidate': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/proxyValidate': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/proxy': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/validate': {
target: 'http://localhost:8000',
changeOrigin: true,
} }
}, },
}, },

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