mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-22 21:33:36 +08:00
Compare commits
139 Commits
Author | SHA1 | Date | |
---|---|---|---|
c05fb77224 | |||
9af9ead939 | |||
f5590c42f7 | |||
5597f99e3c | |||
ea005aaf4d | |||
e5c1f560c5 | |||
20fc7d1b58 | |||
cf3b46130b | |||
cab51fae9c | |||
b867872da4 | |||
305867f49a | |||
3f90c18a19 | |||
9e5a64c021 | |||
4263af6f2c | |||
3e92d761b9 | |||
0e41568f62 | |||
fb7e2729c6 | |||
28b9154d7e | |||
b0b3eb0805 | |||
73bd9dd517 | |||
0bc8c2d15f | |||
7b78e60265 | |||
7464f9a8ad | |||
d3a7a062d3 | |||
67a0264411 | |||
a6a055cc83 | |||
a89a7f9eb7 | |||
287f60353c | |||
530330bd66 | |||
70a1428972 | |||
1d183decea | |||
b92d03e2bb | |||
9877174780 | |||
b178be9aef | |||
7236cca8cf | |||
15daf5dbfe | |||
0b546bba5e | |||
938cdbccf4 | |||
801302c6e7 | |||
91602d2b21 | |||
86b3a078ef | |||
abc15b88c8 | |||
3cf1b990be | |||
2023795f3c | |||
8d13bf7e27 | |||
29aa379fb2 | |||
7a95b9c1d5 | |||
0fc0ba0c76 | |||
24459d852e | |||
e3f5bf93b2 | |||
879ca6a488 | |||
544cd40a08 | |||
99f7883c7d | |||
88b0fb6e52 | |||
fa9b49e25b | |||
cd76e9372e | |||
04b9e05244 | |||
a78b2de7b2 | |||
d0952ae908 | |||
ade64693e4 | |||
5f8924ed4e | |||
1a6d98d029 | |||
447dd1c534 | |||
86b5d72e5d | |||
6bc4e646e5 | |||
0841eb5c30 | |||
4015c221f7 | |||
dcd6328498 | |||
8080927890 | |||
a95c5b05a9 | |||
865a65d399 | |||
e8b9c67671 | |||
e5ff49f7a7 | |||
9f7924a6e0 | |||
377e200837 | |||
93a76de044 | |||
35bef969fd | |||
4dca3bd3f7 | |||
5de417ecf7 | |||
bf24594fb4 | |||
4a87b4790e | |||
fde8c4b5f6 | |||
55a84644e1 | |||
ca87dd7dea | |||
32af4a766e | |||
4d035bf66d | |||
743dcc9725 | |||
d43d7d1ae9 | |||
c906f1e5d2 | |||
37a26e2a91 | |||
e7018e3de4 | |||
3a64e4dcd8 | |||
380cdc5f7e | |||
3602d9b9a7 | |||
8a9cc2eb8f | |||
4f9a13f18a | |||
a4fc04474e | |||
bf5d4eea48 | |||
0e40a1d922 | |||
ab777c1d73 | |||
ca0fa5fc40 | |||
cfbce79e32 | |||
efc07f0919 | |||
a783315fa2 | |||
1d0af9cf7b | |||
4d48517be9 | |||
178cf7945d | |||
ab5af979c8 | |||
e31aaf5657 | |||
eaf5cb66f3 | |||
83a6b757a4 | |||
2a0dcd746f | |||
22f5ad06ec | |||
18aa70dfb2 | |||
697b3e4998 | |||
d48d515c36 | |||
a5d166c35f | |||
4915963c52 | |||
759a1421e5 | |||
c14bf9fdab | |||
e19f07c521 | |||
39ab71c5db | |||
2c97f8a8b7 | |||
21392dcc14 | |||
953d3d5bc5 | |||
ddee97f544 | |||
c58a6d8725 | |||
a5ff9549c1 | |||
fe57dcbff4 | |||
f8c4ca0f00 | |||
e738c42bd8 | |||
cbc8c58e85 | |||
07c90e048f | |||
a33076ada4 | |||
9cabc4035f | |||
274096fe9d | |||
661abd6b6e | |||
4122c94205 | |||
68ef5f8311 |
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@ -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'
|
||||||
|
4
.github/workflows/sync.yml
vendored
4
.github/workflows/sync.yml
vendored
@ -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 }}
|
||||||
|
@ -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
|
||||||
|
@ -82,6 +82,14 @@ Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which fo
|
|||||||
username:password@tcp(database_ip:database_port)/
|
username:password@tcp(database_ip:database_port)/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
|
||||||
|
|
||||||
|
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
|
||||||
|
```
|
||||||
|
|
||||||
#### Run
|
#### Run
|
||||||
|
|
||||||
Casdoor provides two run modes, the difference is binary size and user prompt.
|
Casdoor provides two run modes, the difference is binary size and user prompt.
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package authz
|
package authz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/astaxie/beego"
|
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||||
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
|
|||||||
func InitAuthz() {
|
func InitAuthz() {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
|
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||||
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
|
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -54,7 +53,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
|||||||
(r.urlPath == p.urlPath || p.urlPath == "*") && \
|
(r.urlPath == p.urlPath || p.urlPath == "*") && \
|
||||||
(r.objOwner == p.objOwner || p.objOwner == "*") && \
|
(r.objOwner == p.objOwner || p.objOwner == "*") && \
|
||||||
(r.objName == p.objName || p.objName == "*") || \
|
(r.objName == p.objName || p.objName == "*") || \
|
||||||
(r.urlPath == "/api/update-user" && r.subOwner == r.objOwner && r.subName == r.objName)
|
(r.subOwner == r.objOwner && r.subName == r.objName)
|
||||||
`
|
`
|
||||||
|
|
||||||
m, err := model.NewModelFromString(modelText)
|
m, err := model.NewModelFromString(modelText)
|
||||||
@ -81,16 +80,18 @@ p, *, *, GET, /api/get-app-login, *, *
|
|||||||
p, *, *, POST, /api/logout, *, *
|
p, *, *, POST, /api/logout, *, *
|
||||||
p, *, *, GET, /api/get-account, *, *
|
p, *, *, GET, /api/get-account, *, *
|
||||||
p, *, *, GET, /api/userinfo, *, *
|
p, *, *, GET, /api/userinfo, *, *
|
||||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
p, *, *, *, /api/login/oauth, *, *
|
||||||
p, *, *, POST, /api/login/oauth/refresh_token, *, *
|
|
||||||
p, *, *, GET, /api/get-application, *, *
|
p, *, *, GET, /api/get-application, *, *
|
||||||
p, *, *, GET, /api/get-users, *, *
|
p, *, *, GET, /api/get-applications, *, *
|
||||||
p, *, *, GET, /api/get-user, *, *
|
p, *, *, GET, /api/get-user, *, *
|
||||||
p, *, *, GET, /api/get-organizations, *, *
|
|
||||||
p, *, *, GET, /api/get-user-application, *, *
|
p, *, *, GET, /api/get-user-application, *, *
|
||||||
p, *, *, GET, /api/get-default-providers, *, *
|
|
||||||
p, *, *, GET, /api/get-resources, *, *
|
p, *, *, GET, /api/get-resources, *, *
|
||||||
p, *, *, POST, /api/upload-avatar, *, *
|
p, *, *, GET, /api/get-product, *, *
|
||||||
|
p, *, *, POST, /api/buy-product, *, *
|
||||||
|
p, *, *, GET, /api/get-payment, *, *
|
||||||
|
p, *, *, POST, /api/update-payment, *, *
|
||||||
|
p, *, *, POST, /api/invoice-payment, *, *
|
||||||
|
p, *, *, GET, /api/get-providers, *, *
|
||||||
p, *, *, POST, /api/unlink, *, *
|
p, *, *, POST, /api/unlink, *, *
|
||||||
p, *, *, POST, /api/set-password, *, *
|
p, *, *, POST, /api/set-password, *, *
|
||||||
p, *, *, POST, /api/send-verification-code, *, *
|
p, *, *, POST, /api/send-verification-code, *, *
|
||||||
@ -98,9 +99,11 @@ p, *, *, GET, /api/get-human-check, *, *
|
|||||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||||
p, *, *, POST, /api/upload-resource, *, *
|
p, *, *, POST, /api/upload-resource, *, *
|
||||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||||
p, *, *, *, /api/certs, *, *
|
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
11
build.sh
Executable 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
|
@ -12,7 +12,7 @@ redisEndpoint =
|
|||||||
defaultStorageProvider =
|
defaultStorageProvider =
|
||||||
isCloudIntranet = false
|
isCloudIntranet = false
|
||||||
authState = "casdoor"
|
authState = "casdoor"
|
||||||
httpProxy = "127.0.0.1:10808"
|
sock5Proxy = "127.0.0.1:10808"
|
||||||
verificationCodeTimeout = 10
|
verificationCodeTimeout = 10
|
||||||
initScore = 2000
|
initScore = 2000
|
||||||
logPostOnly = true
|
logPostOnly = true
|
||||||
|
37
conf/conf.go
37
conf/conf.go
@ -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
98
conf/conf_test.go
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -20,14 +20,17 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ResponseTypeLogin = "login"
|
ResponseTypeLogin = "login"
|
||||||
ResponseTypeCode = "code"
|
ResponseTypeCode = "code"
|
||||||
|
ResponseTypeToken = "token"
|
||||||
|
ResponseTypeIdToken = "id_token"
|
||||||
|
ResponseTypeSaml = "saml"
|
||||||
|
ResponseTypeCas = "cas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestForm struct {
|
type RequestForm struct {
|
||||||
@ -37,6 +40,8 @@ type RequestForm struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Phone string `json:"phone"`
|
Phone string `json:"phone"`
|
||||||
Affiliation string `json:"affiliation"`
|
Affiliation string `json:"affiliation"`
|
||||||
@ -57,6 +62,7 @@ type RequestForm struct {
|
|||||||
AutoSignin bool `json:"autoSignin"`
|
AutoSignin bool `json:"autoSignin"`
|
||||||
|
|
||||||
RelayState string `json:"relayState"`
|
RelayState string `json:"relayState"`
|
||||||
|
SamlRequest string `json:"samlRequest"`
|
||||||
SamlResponse string `json:"samlResponse"`
|
SamlResponse string `json:"samlResponse"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,18 +75,6 @@ type Response struct {
|
|||||||
Data2 interface{} `json:"data2"`
|
Data2 interface{} `json:"data2"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Userinfo struct {
|
|
||||||
Sub string `json:"sub"`
|
|
||||||
Iss string `json:"iss"`
|
|
||||||
Aud string `json:"aud"`
|
|
||||||
Name string `json:"name,omitempty"`
|
|
||||||
DisplayName string `json:"preferred_username,omitempty"`
|
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
Avatar string `json:"picture,omitempty"`
|
|
||||||
Address string `json:"address,omitempty"`
|
|
||||||
Phone string `json:"phone,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HumanCheck struct {
|
type HumanCheck struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
@ -116,7 +110,7 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
|
||||||
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.Email, form.Phone, form.Affiliation)
|
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.Affiliation)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
return
|
return
|
||||||
@ -140,8 +134,6 @@ func (c *ApiController) Signup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
|
|
||||||
|
|
||||||
id := util.GenerateId()
|
id := util.GenerateId()
|
||||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||||
lastUser := object.GetLastUser(form.Organization)
|
lastUser := object.GetLastUser(form.Organization)
|
||||||
@ -181,6 +173,22 @@ func (c *ApiController) Signup() {
|
|||||||
IsDeleted: false,
|
IsDeleted: false,
|
||||||
SignupApplication: application.Name,
|
SignupApplication: application.Name,
|
||||||
Properties: map[string]string{},
|
Properties: map[string]string{},
|
||||||
|
Karma: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(organization.Tags) > 0 {
|
||||||
|
tokens := strings.Split(organization.Tags[0], "|")
|
||||||
|
if len(tokens) > 0 {
|
||||||
|
user.Tag = tokens[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if application.GetSignupItemRule("Display name") == "First, last" {
|
||||||
|
if form.FirstName != "" || form.LastName != "" {
|
||||||
|
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
|
||||||
|
user.FirstName = form.FirstName
|
||||||
|
user.LastName = form.LastName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := object.AddUser(user)
|
affected := object.AddUser(user)
|
||||||
@ -199,6 +207,12 @@ func (c *ApiController) Signup() {
|
|||||||
object.DisableVerificationCode(form.Email)
|
object.DisableVerificationCode(form.Email)
|
||||||
object.DisableVerificationCode(checkPhone)
|
object.DisableVerificationCode(checkPhone)
|
||||||
|
|
||||||
|
record := object.NewRecord(c.Ctx)
|
||||||
|
record.Organization = application.Organization
|
||||||
|
record.User = user.Name
|
||||||
|
util.SafeGoroutine(func() { object.AddRecord(record) })
|
||||||
|
|
||||||
|
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||||
|
|
||||||
c.ResponseOk(userId)
|
c.ResponseOk(userId)
|
||||||
@ -214,10 +228,15 @@ func (c *ApiController) Logout() {
|
|||||||
user := c.GetSessionUsername()
|
user := c.GetSessionUsername()
|
||||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||||
|
|
||||||
|
application := c.GetSessionApplication()
|
||||||
c.SetSessionUsername("")
|
c.SetSessionUsername("")
|
||||||
c.SetSessionData(nil)
|
c.SetSessionData(nil)
|
||||||
|
|
||||||
c.ResponseOk(user)
|
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
|
||||||
|
c.ResponseOk(user)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.ResponseOk(user, application.HomepageUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount
|
// GetAccount
|
||||||
@ -254,38 +273,19 @@ func (c *ApiController) GetAccount() {
|
|||||||
// @Title UserInfo
|
// @Title UserInfo
|
||||||
// @Tag Account API
|
// @Tag Account API
|
||||||
// @Description return user information according to OIDC standards
|
// @Description return user information according to OIDC standards
|
||||||
// @Success 200 {object} controllers.Userinfo The Response object
|
// @Success 200 {object} object.Userinfo The Response object
|
||||||
// @router /userinfo [get]
|
// @router /userinfo [get]
|
||||||
func (c *ApiController) GetUserinfo() {
|
func (c *ApiController) GetUserinfo() {
|
||||||
userId, ok := c.RequireSignedIn()
|
userId, ok := c.RequireSignedIn()
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := object.GetUser(userId)
|
|
||||||
if user == nil {
|
|
||||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
scope, aud := c.GetSessionOidc()
|
scope, aud := c.GetSessionOidc()
|
||||||
iss := beego.AppConfig.String("origin")
|
host := c.Ctx.Request.Host
|
||||||
resp := Userinfo{
|
resp, err := object.GetUserInfo(userId, scope, aud, host)
|
||||||
Sub: user.Id,
|
if err != nil {
|
||||||
Iss: iss,
|
c.ResponseError(err.Error())
|
||||||
Aud: aud,
|
return
|
||||||
}
|
|
||||||
if strings.Contains(scope, "profile") {
|
|
||||||
resp.Name = user.Name
|
|
||||||
resp.DisplayName = user.DisplayName
|
|
||||||
resp.Avatar = user.Avatar
|
|
||||||
}
|
|
||||||
if strings.Contains(scope, "email") {
|
|
||||||
resp.Email = user.Email
|
|
||||||
}
|
|
||||||
if strings.Contains(scope, "address") {
|
|
||||||
resp.Address = user.Location
|
|
||||||
}
|
|
||||||
if strings.Contains(scope, "phone") {
|
|
||||||
resp.Phone = user.Phone
|
|
||||||
}
|
}
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -85,7 +86,7 @@ func (c *ApiController) GetUserApplication() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
user := object.GetUser(id)
|
user := object.GetUser(id)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
@ -38,6 +38,14 @@ func codeToResponse(code *object.Code) *Response {
|
|||||||
return &Response{Status: "ok", Msg: "", Data: code.Code}
|
return &Response{Status: "ok", Msg: "", Data: code.Code}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tokenToResponse(token *object.Token) *Response {
|
||||||
|
if token.AccessToken == "" {
|
||||||
|
return &Response{Status: "error", Msg: "fail to get accessToken", Data: token.AccessToken}
|
||||||
|
}
|
||||||
|
return &Response{Status: "ok", Msg: "", Data: token.AccessToken}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// HandleLoggedIn ...
|
// HandleLoggedIn ...
|
||||||
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
|
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
|
||||||
userId := user.GetId()
|
userId := user.GetId()
|
||||||
@ -59,15 +67,48 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
|||||||
c.ResponseError("Challenge method should be S256")
|
c.ResponseError("Challenge method should be S256")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
|
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, c.Ctx.Request.Host)
|
||||||
resp = codeToResponse(code)
|
resp = codeToResponse(code)
|
||||||
|
|
||||||
if application.EnableSigninSession || application.HasPromptPage() {
|
if application.EnableSigninSession || application.HasPromptPage() {
|
||||||
// The prompt page needs the user to be signed in
|
// The prompt page needs the user to be signed in
|
||||||
c.SetSessionUsername(userId)
|
c.SetSessionUsername(userId)
|
||||||
}
|
}
|
||||||
|
} else if form.Type == ResponseTypeToken || form.Type == ResponseTypeIdToken { //implicit flow
|
||||||
|
if !object.IsGrantTypeValid(form.Type, application.GrantTypes) {
|
||||||
|
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
|
||||||
|
} else {
|
||||||
|
scope := c.Input().Get("scope")
|
||||||
|
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
|
||||||
|
resp = tokenToResponse(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if form.Type == ResponseTypeSaml { // saml flow
|
||||||
|
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error(), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl}
|
||||||
|
} else if form.Type == ResponseTypeCas {
|
||||||
|
//not oauth but CAS SSO protocol
|
||||||
|
service := c.Input().Get("service")
|
||||||
|
resp = wrapErrorResponse(nil)
|
||||||
|
if service != "" {
|
||||||
|
st, err := object.GenerateCasToken(userId, service)
|
||||||
|
if err != nil {
|
||||||
|
resp = wrapErrorResponse(err)
|
||||||
|
} else {
|
||||||
|
resp.Data = st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if application.EnableSigninSession || application.HasPromptPage() {
|
||||||
|
// The prompt page needs the user to be signed in
|
||||||
|
c.SetSessionUsername(userId)
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
|
resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
|
||||||
}
|
}
|
||||||
|
|
||||||
// if user did not check auto signin
|
// if user did not check auto signin
|
||||||
@ -101,6 +142,7 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
state := c.Input().Get("state")
|
state := c.Input().Get("state")
|
||||||
|
|
||||||
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||||
|
application = object.GetMaskedApplication(application, "")
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg, application)
|
c.ResponseError(msg, application)
|
||||||
} else {
|
} else {
|
||||||
@ -109,7 +151,7 @@ func (c *ApiController) GetApplicationLogin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
||||||
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" {
|
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" || providerType == "Steam" {
|
||||||
idProvider.SetHttpClient(proxy.ProxyHttpClient)
|
idProvider.SetHttpClient(proxy.ProxyHttpClient)
|
||||||
} else {
|
} else {
|
||||||
idProvider.SetHttpClient(proxy.DefaultHttpClient)
|
idProvider.SetHttpClient(proxy.DefaultHttpClient)
|
||||||
@ -149,9 +191,16 @@ func (c *ApiController) Login() {
|
|||||||
var verificationCodeType string
|
var verificationCodeType string
|
||||||
var checkResult string
|
var checkResult string
|
||||||
|
|
||||||
|
if form.Name != "" {
|
||||||
|
user = object.GetUserByFields(form.Organization, form.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// check result through Email or Phone
|
// check result through Email or Phone
|
||||||
if strings.Contains(form.Username, "@") {
|
if strings.Contains(form.Username, "@") {
|
||||||
verificationCodeType = "email"
|
verificationCodeType = "email"
|
||||||
|
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
|
||||||
|
form.Username = user.Email
|
||||||
|
}
|
||||||
checkResult = object.CheckVerificationCode(form.Username, form.Code)
|
checkResult = object.CheckVerificationCode(form.Username, form.Code)
|
||||||
} else {
|
} else {
|
||||||
verificationCodeType = "phone"
|
verificationCodeType = "phone"
|
||||||
@ -160,6 +209,9 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(responseText)
|
c.ResponseError(responseText)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
|
||||||
|
form.Username = user.Phone
|
||||||
|
}
|
||||||
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
|
||||||
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
|
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
|
||||||
}
|
}
|
||||||
@ -174,7 +226,7 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
user = object.GetUserByFields(form.Organization, form.Username)
|
user = object.GetUserByFields(form.Organization, form.Username)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -186,15 +238,25 @@ func (c *ApiController) Login() {
|
|||||||
resp = &Response{Status: "error", Msg: msg}
|
resp = &Response{Status: "error", Msg: msg}
|
||||||
} else {
|
} else {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
|
|
||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
go object.AddRecord(record)
|
util.SafeGoroutine(func() {object.AddRecord(record)})
|
||||||
}
|
}
|
||||||
} else if form.Provider != "" {
|
} else if form.Provider != "" {
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
|
||||||
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
|
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
|
||||||
providerItem := application.GetProviderItem(provider.Name)
|
providerItem := application.GetProviderItem(provider.Name)
|
||||||
@ -221,7 +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)
|
idProvider := idp.GetIdProvider(provider.Type, provider.SubType, clientId, clientSecret, provider.AppId, form.RedirectUri, provider.Domain, provider.CustomAuthUrl, provider.CustomTokenUrl, provider.CustomUserInfoUrl)
|
||||||
if idProvider == nil {
|
if idProvider == nil {
|
||||||
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
|
||||||
return
|
return
|
||||||
@ -229,8 +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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,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 {
|
||||||
@ -328,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"}
|
||||||
}
|
}
|
||||||
@ -365,6 +427,11 @@ func (c *ApiController) Login() {
|
|||||||
if c.GetSessionUsername() != "" {
|
if c.GetSessionUsername() != "" {
|
||||||
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
|
||||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||||
|
if application == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The application: %s does not exist", form.Application))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user := c.getCurrentUser()
|
user := c.getCurrentUser()
|
||||||
resp = c.HandleLoggedIn(application, user, &form)
|
resp = c.HandleLoggedIn(application, user, &form)
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
|
|||||||
return user.(string)
|
return user.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ApiController) GetSessionApplication() *object.Application {
|
||||||
|
clientId := c.GetSession("aud")
|
||||||
|
if clientId == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
application := object.GetApplicationByClientId(clientId.(string))
|
||||||
|
return application
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ApiController) GetSessionOidc() (string, string) {
|
func (c *ApiController) GetSessionOidc() (string, string) {
|
||||||
sessionData := c.GetSessionData()
|
sessionData := c.GetSessionData()
|
||||||
if sessionData != nil &&
|
if sessionData != nil &&
|
||||||
@ -132,3 +141,11 @@ func wrapActionResponse(affected bool) *Response {
|
|||||||
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wrapErrorResponse(err error) *Response {
|
||||||
|
if err == nil {
|
||||||
|
return &Response{Status: "ok", Msg: ""}
|
||||||
|
} else {
|
||||||
|
return &Response{Status: "error", Msg: err.Error()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
271
controllers/cas.go
Normal file
271
controllers/cas.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
@ -170,6 +170,7 @@ func (c *ApiController) UpdateLdap() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevLdap := object.GetLdap(ldap.Id)
|
||||||
affected := object.UpdateLdap(&ldap)
|
affected := object.UpdateLdap(&ldap)
|
||||||
resp := wrapActionResponse(affected)
|
resp := wrapActionResponse(affected)
|
||||||
if affected {
|
if affected {
|
||||||
@ -177,6 +178,8 @@ 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 {
|
||||||
|
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["json"] = resp
|
c.Data["json"] = resp
|
||||||
@ -212,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,
|
||||||
|
@ -25,10 +25,10 @@ func (c *RootController) GetOidcDiscovery() {
|
|||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Title GetOidcCert
|
// @Title GetJwks
|
||||||
// @Tag OIDC API
|
// @Tag OIDC API
|
||||||
// @router /api/certs [get]
|
// @router /.well-known/jwks [get]
|
||||||
func (c *RootController) GetOidcCert() {
|
func (c *RootController) GetJwks() {
|
||||||
jwks, err := object.GetJsonWebKeySet()
|
jwks, err := object.GetJsonWebKeySet()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -48,6 +49,24 @@ func (c *ApiController) GetPayments() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserPayments
|
||||||
|
// @Title GetUserPayments
|
||||||
|
// @Tag Payment API
|
||||||
|
// @Description get payments for a user
|
||||||
|
// @Param owner query string true "The owner of payments"
|
||||||
|
// @Param organization query string true "The organization of the user"
|
||||||
|
// @Param user query string true "The username of the user"
|
||||||
|
// @Success 200 {array} object.Payment The Response object
|
||||||
|
// @router /get-user-payments [get]
|
||||||
|
func (c *ApiController) GetUserPayments() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
organization := c.Input().Get("organization")
|
||||||
|
user := c.Input().Get("user")
|
||||||
|
|
||||||
|
payments := object.GetUserPayments(owner, organization, user)
|
||||||
|
c.ResponseOk(payments)
|
||||||
|
}
|
||||||
|
|
||||||
// @Title GetPayment
|
// @Title GetPayment
|
||||||
// @Tag Payment API
|
// @Tag Payment API
|
||||||
// @Description get payment
|
// @Description get payment
|
||||||
@ -114,3 +133,45 @@ func (c *ApiController) DeletePayment() {
|
|||||||
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
|
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Title NotifyPayment
|
||||||
|
// @Tag Payment API
|
||||||
|
// @Description notify payment
|
||||||
|
// @Param body body object.Payment true "The details of the payment"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /notify-payment [post]
|
||||||
|
func (c *ApiController) NotifyPayment() {
|
||||||
|
owner := c.Ctx.Input.Param(":owner")
|
||||||
|
providerName := c.Ctx.Input.Param(":provider")
|
||||||
|
productName := c.Ctx.Input.Param(":product")
|
||||||
|
paymentName := c.Ctx.Input.Param(":payment")
|
||||||
|
|
||||||
|
body := c.Ctx.Input.RequestBody
|
||||||
|
|
||||||
|
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
|
||||||
|
if ok {
|
||||||
|
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title InvoicePayment
|
||||||
|
// @Tag Payment API
|
||||||
|
// @Description invoice payment
|
||||||
|
// @Param id query string true "The id of the payment"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /invoice-payment [post]
|
||||||
|
func (c *ApiController) InvoicePayment() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
payment := object.GetPayment(id)
|
||||||
|
invoiceUrl, err := object.InvoicePayment(payment)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
}
|
||||||
|
c.ResponseOk(invoiceUrl)
|
||||||
|
}
|
||||||
|
150
controllers/product.go
Normal file
150
controllers/product.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetProducts
|
||||||
|
// @Title GetProducts
|
||||||
|
// @Tag Product API
|
||||||
|
// @Description get products
|
||||||
|
// @Param owner query string true "The owner of products"
|
||||||
|
// @Success 200 {array} object.Product The Response object
|
||||||
|
// @router /get-products [get]
|
||||||
|
func (c *ApiController) GetProducts() {
|
||||||
|
owner := c.Input().Get("owner")
|
||||||
|
limit := c.Input().Get("pageSize")
|
||||||
|
page := c.Input().Get("p")
|
||||||
|
field := c.Input().Get("field")
|
||||||
|
value := c.Input().Get("value")
|
||||||
|
sortField := c.Input().Get("sortField")
|
||||||
|
sortOrder := c.Input().Get("sortOrder")
|
||||||
|
if limit == "" || page == "" {
|
||||||
|
c.Data["json"] = object.GetProducts(owner)
|
||||||
|
c.ServeJSON()
|
||||||
|
} else {
|
||||||
|
limit := util.ParseInt(limit)
|
||||||
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProductCount(owner, field, value)))
|
||||||
|
products := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
c.ResponseOk(products, paginator.Nums())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title GetProduct
|
||||||
|
// @Tag Product API
|
||||||
|
// @Description get product
|
||||||
|
// @Param id query string true "The id of the product"
|
||||||
|
// @Success 200 {object} object.Product The Response object
|
||||||
|
// @router /get-product [get]
|
||||||
|
func (c *ApiController) GetProduct() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
c.Data["json"] = object.GetProduct(id)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title UpdateProduct
|
||||||
|
// @Tag Product API
|
||||||
|
// @Description update product
|
||||||
|
// @Param id query string true "The id of the product"
|
||||||
|
// @Param body body object.Product true "The details of the product"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /update-product [post]
|
||||||
|
func (c *ApiController) UpdateProduct() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
|
||||||
|
var product object.Product
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.UpdateProduct(id, &product))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title AddProduct
|
||||||
|
// @Tag Product API
|
||||||
|
// @Description add product
|
||||||
|
// @Param body body object.Product true "The details of the product"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /add-product [post]
|
||||||
|
func (c *ApiController) AddProduct() {
|
||||||
|
var product object.Product
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.AddProduct(&product))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title DeleteProduct
|
||||||
|
// @Tag Product API
|
||||||
|
// @Description delete product
|
||||||
|
// @Param body body object.Product true "The details of the product"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /delete-product [post]
|
||||||
|
func (c *ApiController) DeleteProduct() {
|
||||||
|
var product object.Product
|
||||||
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &product)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = wrapActionResponse(object.DeleteProduct(&product))
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Title BuyProduct
|
||||||
|
// @Tag Product API
|
||||||
|
// @Description buy product
|
||||||
|
// @Param id query string true "The id of the product"
|
||||||
|
// @Param providerName query string true "The name of the provider"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /buy-product [post]
|
||||||
|
func (c *ApiController) BuyProduct() {
|
||||||
|
id := c.Input().Get("id")
|
||||||
|
providerName := c.Input().Get("providerName")
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
|
userId := c.GetSessionUsername()
|
||||||
|
if userId == "" {
|
||||||
|
c.ResponseError("Please login first")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user := object.GetUser(userId)
|
||||||
|
if user == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payUrl, err := object.BuyProduct(id, providerName, user, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(payUrl)
|
||||||
|
}
|
33
controllers/saml.go
Normal file
33
controllers/saml.go
Normal 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()
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/astaxie/beego/utils/pagination"
|
"github.com/astaxie/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -149,8 +150,9 @@ func (c *ApiController) GetOAuthCode() {
|
|||||||
c.ResponseError("Challenge method should be S256")
|
c.ResponseError("Challenge method should be S256")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge)
|
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, host)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,23 +172,46 @@ func (c *ApiController) GetOAuthToken() {
|
|||||||
clientSecret := c.Input().Get("client_secret")
|
clientSecret := c.Input().Get("client_secret")
|
||||||
code := c.Input().Get("code")
|
code := c.Input().Get("code")
|
||||||
verifier := c.Input().Get("code_verifier")
|
verifier := c.Input().Get("code_verifier")
|
||||||
|
scope := c.Input().Get("scope")
|
||||||
|
username := c.Input().Get("username")
|
||||||
|
password := c.Input().Get("password")
|
||||||
|
tag := c.Input().Get("tag")
|
||||||
|
avatar := c.Input().Get("avatar")
|
||||||
|
|
||||||
if clientId == "" && clientSecret == "" {
|
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
|
||||||
|
|
||||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier)
|
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar)
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefreshToken
|
// RefreshToken
|
||||||
// @Title RefreshToken
|
// @Title RefreshToken
|
||||||
|
// @Tag Token API
|
||||||
// @Description refresh OAuth access token
|
// @Description refresh OAuth access token
|
||||||
// @Param grant_type query string true "OAuth grant type"
|
// @Param grant_type query string true "OAuth grant type"
|
||||||
// @Param refresh_token query string true "OAuth refresh token"
|
// @Param refresh_token query string true "OAuth refresh token"
|
||||||
// @Param scope query string true "OAuth scope"
|
// @Param scope query string true "OAuth scope"
|
||||||
// @Param client_id query string true "OAuth client id"
|
// @Param client_id query string true "OAuth client id"
|
||||||
// @Param client_secret query string true "OAuth client secret"
|
// @Param client_secret query string false "OAuth client secret"
|
||||||
// @Success 200 {object} object.TokenWrapper The Response object
|
// @Success 200 {object} object.TokenWrapper The Response object
|
||||||
// @router /login/oauth/refresh_token [post]
|
// @router /login/oauth/refresh_token [post]
|
||||||
func (c *ApiController) RefreshToken() {
|
func (c *ApiController) RefreshToken() {
|
||||||
@ -195,7 +220,102 @@ func (c *ApiController) RefreshToken() {
|
|||||||
scope := c.Input().Get("scope")
|
scope := c.Input().Get("scope")
|
||||||
clientId := c.Input().Get("client_id")
|
clientId := c.Input().Get("client_id")
|
||||||
clientSecret := c.Input().Get("client_secret")
|
clientSecret := c.Input().Get("client_secret")
|
||||||
|
host := c.Ctx.Request.Host
|
||||||
|
|
||||||
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret)
|
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.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenLogout
|
||||||
|
// @Title TokenLogout
|
||||||
|
// @Tag Token API
|
||||||
|
// @Description delete token by AccessToken
|
||||||
|
// @Param id_token_hint query string true "id_token_hint"
|
||||||
|
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
|
||||||
|
// @Param state query string true "state"
|
||||||
|
// @Success 200 {object} controllers.Response The Response object
|
||||||
|
// @router /login/oauth/logout [get]
|
||||||
|
func (c *ApiController) TokenLogout() {
|
||||||
|
token := c.Input().Get("id_token_hint")
|
||||||
|
flag, application := object.DeleteTokenByAceessToken(token)
|
||||||
|
redirectUri := c.Input().Get("post_logout_redirect_uri")
|
||||||
|
state := c.Input().Get("state")
|
||||||
|
if application != nil && object.CheckRedirectUriValid(application, redirectUri) {
|
||||||
|
c.Ctx.Redirect(http.StatusFound, redirectUri+"?state="+state)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Data["json"] = wrapActionResponse(flag)
|
||||||
|
c.ServeJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntrospectToken
|
||||||
|
// @Title IntrospectToken
|
||||||
|
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
|
||||||
|
// parameter representing an OAuth 2.0 token and returns a JSON document
|
||||||
|
// representing the meta information surrounding the
|
||||||
|
// token, including whether this token is currently active.
|
||||||
|
// This endpoint only support Basic Authorization.
|
||||||
|
// @Param token formData string true "access_token's value or refresh_token's value"
|
||||||
|
// @Param token_type_hint formData string true "the token type access_token or refresh_token"
|
||||||
|
// @Success 200 {object} object.IntrospectionResponse The Response object
|
||||||
|
// @router /login/oauth/introspect [post]
|
||||||
|
func (c *ApiController) IntrospectToken() {
|
||||||
|
tokenValue := c.Input().Get("token")
|
||||||
|
clientId, clientSecret, ok := c.Ctx.Request.BasicAuth()
|
||||||
|
if !ok {
|
||||||
|
clientId = c.Input().Get("client_id")
|
||||||
|
clientSecret = c.Input().Get("client_secret")
|
||||||
|
if clientId == "" || clientSecret == "" {
|
||||||
|
c.ResponseError("empty clientId or clientSecret")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
application := object.GetApplicationByClientId(clientId)
|
||||||
|
if application == nil || application.ClientSecret != clientSecret {
|
||||||
|
c.ResponseError("invalid application or wrong clientSecret")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
|
||||||
|
if token == nil {
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||||
|
if err != nil || jwtToken.Valid() != nil {
|
||||||
|
// and token revoked case. but we not implement
|
||||||
|
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||||
|
// refs: https://tools.ietf.org/html/rfc7009
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{
|
||||||
|
Active: true,
|
||||||
|
Scope: jwtToken.Scope,
|
||||||
|
ClientId: clientId,
|
||||||
|
Username: token.User,
|
||||||
|
TokenType: token.TokenType,
|
||||||
|
Exp: jwtToken.ExpiresAt.Unix(),
|
||||||
|
Iat: jwtToken.IssuedAt.Unix(),
|
||||||
|
Nbf: jwtToken.NotBefore.Unix(),
|
||||||
|
Sub: jwtToken.Subject,
|
||||||
|
Aud: jwtToken.Audience,
|
||||||
|
Iss: jwtToken.Issuer,
|
||||||
|
Jti: jwtToken.Id,
|
||||||
|
}
|
||||||
c.ServeJSON()
|
c.ServeJSON()
|
||||||
}
|
}
|
||||||
|
29
controllers/types.go
Normal file
29
controllers/types.go
Normal 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"`
|
||||||
|
}
|
@ -44,6 +44,7 @@ func (c *ApiController) GetGlobalUsers() {
|
|||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
|
||||||
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
users = object.GetMaskedUsers(users)
|
||||||
c.ResponseOk(users, paginator.Nums())
|
c.ResponseOk(users, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,6 +71,7 @@ func (c *ApiController) GetUsers() {
|
|||||||
limit := util.ParseInt(limit)
|
limit := util.ParseInt(limit)
|
||||||
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
|
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
|
||||||
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
|
||||||
|
users = object.GetMaskedUsers(users)
|
||||||
c.ResponseOk(users, paginator.Nums())
|
c.ResponseOk(users, paginator.Nums())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -85,6 +87,17 @@ func (c *ApiController) GetUser() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
owner := c.Input().Get("owner")
|
owner := c.Input().Get("owner")
|
||||||
email := c.Input().Get("email")
|
email := c.Input().Get("email")
|
||||||
|
userOwner, _ := util.GetOwnerAndNameFromId(id)
|
||||||
|
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
|
||||||
|
|
||||||
|
if !organization.IsProfilePublic {
|
||||||
|
requestUserId := c.GetSessionUsername()
|
||||||
|
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
|
||||||
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var user *object.User
|
var user *object.User
|
||||||
if email == "" {
|
if email == "" {
|
||||||
@ -109,6 +122,10 @@ func (c *ApiController) UpdateUser() {
|
|||||||
id := c.Input().Get("id")
|
id := c.Input().Get("id")
|
||||||
columnsStr := c.Input().Get("columns")
|
columnsStr := c.Input().Get("columns")
|
||||||
|
|
||||||
|
if id == "" {
|
||||||
|
id = c.GetSessionUsername()
|
||||||
|
}
|
||||||
|
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -188,19 +205,23 @@ func (c *ApiController) GetEmailAndPhone() {
|
|||||||
|
|
||||||
user := object.GetUserByFields(form.Organization, form.Username)
|
user := object.GetUserByFields(form.Organization, form.Username)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s/%s doesn't exist", form.Organization, form.Username))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
|
respUser := object.User{Name: user.Name}
|
||||||
var contentType string
|
var contentType string
|
||||||
switch form.Username {
|
switch form.Username {
|
||||||
case user.Email:
|
case user.Email:
|
||||||
contentType = "email"
|
contentType = "email"
|
||||||
|
respUser.Email = user.Email
|
||||||
case user.Phone:
|
case user.Phone:
|
||||||
contentType = "phone"
|
contentType = "phone"
|
||||||
|
respUser.Phone = user.Phone
|
||||||
case user.Name:
|
case user.Name:
|
||||||
contentType = "username"
|
contentType = "username"
|
||||||
|
respUser.Email = util.GetMaskedEmail(user.Email)
|
||||||
|
respUser.Phone = util.GetMaskedPhone(user.Phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(respUser, contentType)
|
c.ResponseOk(respUser, contentType)
|
||||||
@ -223,39 +244,15 @@ func (c *ApiController) SetPassword() {
|
|||||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||||
|
|
||||||
requestUserId := c.GetSessionUsername()
|
requestUserId := c.GetSessionUsername()
|
||||||
if requestUserId == "" {
|
|
||||||
c.ResponseError("Please login first.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||||
targetUser := object.GetUser(userId)
|
|
||||||
if targetUser == nil {
|
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
|
||||||
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
if !hasPermission {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPermission := false
|
targetUser := object.GetUser(userId)
|
||||||
if strings.HasPrefix(requestUserId, "app/") {
|
|
||||||
hasPermission = true
|
|
||||||
} else {
|
|
||||||
requestUser := object.GetUser(requestUserId)
|
|
||||||
if requestUser == nil {
|
|
||||||
c.ResponseError("Session outdated. Please login again.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if requestUser.IsGlobalAdmin {
|
|
||||||
hasPermission = true
|
|
||||||
} else if requestUserId == userId {
|
|
||||||
hasPermission = true
|
|
||||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
|
||||||
hasPermission = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasPermission {
|
|
||||||
c.ResponseError("You don't have the permission to do this.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldPassword != "" {
|
if oldPassword != "" {
|
||||||
msg := object.CheckPassword(targetUser, oldPassword)
|
msg := object.CheckPassword(targetUser, oldPassword)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -68,15 +68,22 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
organization := object.GetOrganization(orgId)
|
organization := object.GetOrganization(orgId)
|
||||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||||
|
|
||||||
if checkUser == "true" && user == nil &&
|
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
|
||||||
object.GetUserByFields(organization.Name, dest) == nil {
|
c.ResponseError("Please login first")
|
||||||
c.ResponseError("No such user.")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sendResp := errors.New("Invalid dest type.")
|
sendResp := errors.New("Invalid dest type")
|
||||||
|
|
||||||
|
if user == nil && checkUser != "" && checkUser != "true" {
|
||||||
|
_, name := util.GetOwnerAndNameFromId(orgId)
|
||||||
|
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
|
||||||
|
}
|
||||||
switch destType {
|
switch destType {
|
||||||
case "email":
|
case "email":
|
||||||
|
if user != nil && util.GetMaskedEmail(user.Email) == dest {
|
||||||
|
dest = user.Email
|
||||||
|
}
|
||||||
if !util.IsEmailValid(dest) {
|
if !util.IsEmailValid(dest) {
|
||||||
c.ResponseError("Invalid Email address")
|
c.ResponseError("Invalid Email address")
|
||||||
return
|
return
|
||||||
@ -85,6 +92,9 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
provider := application.GetEmailProvider()
|
provider := application.GetEmailProvider()
|
||||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||||
case "phone":
|
case "phone":
|
||||||
|
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
|
||||||
|
dest = user.Phone
|
||||||
|
}
|
||||||
if !util.IsPhoneCnValid(dest) {
|
if !util.IsPhoneCnValid(dest) {
|
||||||
c.ResponseError("Invalid phone number")
|
c.ResponseError("Invalid phone number")
|
||||||
return
|
return
|
||||||
@ -121,7 +131,7 @@ func (c *ApiController) ResetEmailOrPhone() {
|
|||||||
|
|
||||||
user := object.GetUser(userId)
|
user := object.GetUser(userId)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
c.ResponseError("No such user.")
|
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ 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()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -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
39
cred/pbkdf2-salt.go
Normal 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)
|
||||||
|
}
|
@ -11,9 +11,10 @@ services:
|
|||||||
- 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/
|
||||||
restart: always
|
|
||||||
db:
|
db:
|
||||||
restart: always
|
restart: always
|
||||||
image: mysql:8.0.25
|
image: mysql:8.0.25
|
||||||
@ -23,4 +24,4 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: 123456
|
MYSQL_ROOT_PASSWORD: 123456
|
||||||
volumes:
|
volumes:
|
||||||
- /usr/local/docker/mysql:/var/lib/mysql
|
- /usr/local/docker/mysql:/var/lib/mysql
|
||||||
|
20
go.mod
20
go.mod
@ -3,32 +3,38 @@ module github.com/casdoor/casdoor
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
|
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
|
||||||
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.37.30
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||||
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin/v2 v2.30.1
|
github.com/casbin/casbin/v2 v2.30.1
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.1
|
github.com/casbin/xorm-adapter/v2 v2.5.1
|
||||||
github.com/casdoor/go-sms-sender v0.0.5
|
github.com/casdoor/go-sms-sender v0.2.0
|
||||||
|
github.com/casdoor/goth v1.69.0-FIX1
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-ldap/ldap/v3 v3.3.0
|
||||||
|
github.com/go-pay/gopay v1.5.72
|
||||||
github.com/go-sql-driver/mysql v1.5.0
|
github.com/go-sql-driver/mysql v1.5.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.1.0
|
github.com/golang-jwt/jwt/v4 v4.2.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/jinzhu/configor v1.2.1 // indirect
|
github.com/jinzhu/configor v1.2.1 // indirect
|
||||||
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
|
github.com/lestrrat-go/jwx v0.9.0
|
||||||
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
|
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.6.1
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/tealeg/xlsx v1.0.5
|
github.com/tealeg/xlsx v1.0.5
|
||||||
github.com/thanhpk/randstr v1.0.4
|
github.com/thanhpk/randstr v1.0.4
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
|
||||||
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
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
|
||||||
@ -38,5 +44,5 @@ require (
|
|||||||
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
|
||||||
)
|
)
|
||||||
|
131
go.sum
131
go.sum
@ -20,23 +20,18 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
|
|||||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA=
|
|
||||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
|
|
||||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
|
|
||||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
|
|
||||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
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=
|
||||||
@ -44,40 +39,32 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzU
|
|||||||
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=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible 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 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
|
||||||
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 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
|
||||||
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 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
|
||||||
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/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U=
|
|
||||||
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 h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI=
|
|
||||||
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.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
|
||||||
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 h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
|
||||||
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.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
|
||||||
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f 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 h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M=
|
|
||||||
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=
|
||||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk=
|
|
||||||
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
|
||||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||||
@ -85,7 +72,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
|
|||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||||
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||||
@ -95,96 +81,68 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
|
|||||||
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
|
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
|
||||||
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
|
||||||
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
|
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
|
||||||
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
|
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
|
||||||
|
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
|
||||||
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=
|
||||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
|
||||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d h1:OMrhQqj1QCyDT2sxHCDjE+k8aMdn2ngTCGG7g4wrdLo=
|
|
||||||
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
|
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
|
||||||
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808 h1:8s2l8TVUwMXl6tZMe3+hPCRJ25nQXiA3d1x622JtOqc=
|
|
||||||
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
|
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
|
||||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8=
|
|
||||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
|
||||||
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ=
|
|
||||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
|
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
|
|
||||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||||
github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+niDAlj5M8TK8=
|
|
||||||
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c h1:iRTj5SRYwbvsygdwVp+y9kZT145Y1s6xOPpeOEIeGc4=
|
|
||||||
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
|
github.com/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=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I=
|
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
|
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
|
|
||||||
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-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
|
github.com/go-pay/gopay v1.5.72 h1:3zm64xMBhJBa8rXbm//q5UiGgOa4WO5XYEnU394N2Zw=
|
||||||
|
github.com/go-pay/gopay v1.5.72/go.mod h1:0qOGIJuFW7PKDOjmecwKyW0mgsVImgwB9yPJj0ilpn8=
|
||||||
github.com/go-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=
|
||||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d h1:lBXNCxVENCipq4D1Is42JVOP4eQjlB8TQ6H69Yx5J9Q=
|
|
||||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
github.com/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 h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
|
||||||
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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
|
||||||
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=
|
||||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
@ -215,7 +173,6 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
|
|||||||
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
@ -226,11 +183,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
|
|
||||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
@ -239,15 +193,12 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
|
|||||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7 h1:k+KkMRk8mGOu1xG38StS7dQ+Z6oW1i9n3dgrAVU9Q/E=
|
|
||||||
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
@ -257,9 +208,7 @@ github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
|||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
|
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
|
||||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
|
||||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
|
||||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE=
|
|
||||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
@ -267,9 +216,7 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
|
|||||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 h1:UDMh68UUwekSh5iP2OMhRRZJiiBccgV7axzUG8vi56c=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9j1KzlHaXL09LyMVM9rupS39lncbXk=
|
|
||||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
|
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
|
||||||
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
|
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
|
||||||
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
|
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
|
||||||
@ -286,29 +233,24 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
|||||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
|
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible 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 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||||
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.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 h1:wxyqOzKxsRJ6vVRL9sXQ64Z45wmBuQ+OTH9sLsC5rKc=
|
|
||||||
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/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=
|
||||||
@ -318,8 +260,6 @@ 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-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
@ -336,10 +276,10 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
|
|||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
|
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
|
||||||
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 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
|
|
||||||
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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
|
||||||
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=
|
||||||
@ -348,11 +288,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||||
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233 h1:jmJndGFBPjNWW+MAYarU/Nl8QrQVzbw4B/AYE0LzETo=
|
|
||||||
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
|
||||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@ -395,31 +332,26 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
|||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
|
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
|
||||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||||
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0 h1:QIF48X1cihydXibm+4wfAc0r/qyPyuFiPFRNphdMpEE=
|
|
||||||
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||||
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400 h1:091wFNQB3PXcL5+me0joH7EiyqQaI0wGMpEjVCkK04U=
|
|
||||||
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
|
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
|
||||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs=
|
|
||||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA=
|
|
||||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||||
@ -430,26 +362,20 @@ github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL
|
|||||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
||||||
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
||||||
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
|
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
|
||||||
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83 h1:9AUN7+NK4IV+A11igqjQM5i8obiOAQo4SXgjaxe+orI=
|
|
||||||
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
|
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
|
||||||
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
|
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
|
||||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8=
|
|
||||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973 h1:iCnkJ/qjKZGdZnlcj1N55AxPDan814kpc3s1cDpQKd8=
|
|
||||||
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
|
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
|
||||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
|
||||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
@ -459,8 +385,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/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=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -470,10 +396,8 @@ golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y=
|
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
@ -484,17 +408,14 @@ golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHl
|
|||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -546,7 +467,6 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-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 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
|
|
||||||
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/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=
|
||||||
@ -586,7 +506,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
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 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/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
|
||||||
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/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=
|
||||||
@ -642,7 +561,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
|
|||||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f h1:18s2P7JILnVhIF2+ZtGJQ9czV5bvTsb13/UGtNPDbjA=
|
|
||||||
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -665,7 +583,6 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
|||||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts=
|
|
||||||
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
@ -705,7 +622,6 @@ google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6D
|
|||||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe h1:6SgESkjJknFUnsfQ2yxQbmTAi37BxhwS/riq+VdLo9c=
|
|
||||||
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200929141702-51c3e5b607fe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
@ -720,7 +636,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji
|
|||||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
|
|
||||||
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
@ -733,7 +648,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
|||||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
@ -743,7 +657,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
|||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
@ -752,7 +665,6 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkp
|
|||||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
|
|
||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
@ -774,17 +686,14 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
|
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
|
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
|
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
xorm.io/builder v0.3.7 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=
|
||||||
|
136
idp/adfs.go
Normal file
136
idp/adfs.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/lestrrat-go/jwx/jwa"
|
||||||
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
|
"github.com/lestrrat-go/jwx/jwt"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdfsIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAdfsIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *AdfsIdProvider {
|
||||||
|
idp := &AdfsIdProvider{}
|
||||||
|
|
||||||
|
config := idp.getConfig(hostUrl)
|
||||||
|
config.ClientID = clientId
|
||||||
|
config.ClientSecret = clientSecret
|
||||||
|
config.RedirectURL = redirectUrl
|
||||||
|
idp.Config = config
|
||||||
|
idp.Host = hostUrl
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *AdfsIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
idp.Client = client
|
||||||
|
idp.Client.Transport = tr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *AdfsIdProvider) getConfig(hostUrl string) *oauth2.Config {
|
||||||
|
var endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: fmt.Sprintf("%s/adfs/oauth2/authorize", hostUrl),
|
||||||
|
TokenURL: fmt.Sprintf("%s/adfs/oauth2/token", hostUrl),
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = &oauth2.Config{
|
||||||
|
Endpoint: endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdfsToken struct {
|
||||||
|
IdToken string `json:"id_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
ErrMsg string `json:"error_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// get more detail via: https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/overview/ad-fs-openid-connect-oauth-flows-scenarios#request-an-access-token
|
||||||
|
func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
payload := url.Values{}
|
||||||
|
payload.Set("code", code)
|
||||||
|
payload.Set("grant_type", "authorization_code")
|
||||||
|
payload.Set("client_id", idp.Config.ClientID)
|
||||||
|
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||||
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pToken := &AdfsToken{}
|
||||||
|
err = json.Unmarshal(data, pToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fail to unmarshal token response: %s", err.Error())
|
||||||
|
}
|
||||||
|
if pToken.ErrMsg != "" {
|
||||||
|
return nil, fmt.Errorf("pToken.Errmsg = %s", pToken.ErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.IdToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the userinfo endpoint of ADFS only returns sub,
|
||||||
|
// the id_token is used to resolve the userinfo
|
||||||
|
func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
resp, err := idp.Client.Get(fmt.Sprintf("%s/adfs/discovery/keys", idp.Host))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
keyset, err := jwk.Parse(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tokenSrc := []byte(token.AccessToken)
|
||||||
|
publicKey, _ := keyset.Keys[0].Materialize()
|
||||||
|
id_token, _ := jwt.Parse(bytes.NewReader(tokenSrc), jwt.WithVerify(jwa.RS256, publicKey))
|
||||||
|
sid, _ := id_token.Get("sid")
|
||||||
|
upn, _ := id_token.Get("upn")
|
||||||
|
name, _ := id_token.Get("unique_name")
|
||||||
|
userinfo := &UserInfo{
|
||||||
|
Id: sid.(string),
|
||||||
|
Username: name.(string),
|
||||||
|
DisplayName: name.(string),
|
||||||
|
Email: upn.(string),
|
||||||
|
}
|
||||||
|
return userinfo, nil
|
||||||
|
}
|
292
idp/alipay.go
Normal file
292
idp/alipay.go
Normal 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
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
159
idp/casdoor.go
Normal file
159
idp/casdoor.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package idp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CasdoorIdProvider struct {
|
||||||
|
Client *http.Client
|
||||||
|
Config *oauth2.Config
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCasdoorIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *CasdoorIdProvider {
|
||||||
|
idp := &CasdoorIdProvider{}
|
||||||
|
config := idp.getConfig(hostUrl)
|
||||||
|
config.ClientID = clientId
|
||||||
|
config.ClientSecret = clientSecret
|
||||||
|
config.RedirectURL = redirectUrl
|
||||||
|
idp.Config = config
|
||||||
|
idp.Host = hostUrl
|
||||||
|
return idp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CasdoorIdProvider) SetHttpClient(client *http.Client) {
|
||||||
|
idp.Client = client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CasdoorIdProvider) getConfig(hostUrl string) *oauth2.Config {
|
||||||
|
return &oauth2.Config{
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
TokenURL: hostUrl + "/api/login/oauth/access_token",
|
||||||
|
},
|
||||||
|
Scopes: []string{"openid email profile"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CasdoorToken struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
|
resp, err := http.PostForm(idp.Config.Endpoint.TokenURL, url.Values{
|
||||||
|
"client_id": {idp.Config.ClientID},
|
||||||
|
"client_secret": {idp.Config.ClientSecret},
|
||||||
|
"code": {code},
|
||||||
|
"grant_type": {"authorization_code"},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pToken := &CasdoorToken{}
|
||||||
|
err = json.Unmarshal(body, pToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if token is expired
|
||||||
|
if pToken.ExpiresIn <= 0 {
|
||||||
|
return nil, fmt.Errorf("%s", pToken.AccessToken)
|
||||||
|
}
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: pToken.AccessToken,
|
||||||
|
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
"sub": "2f80c349-4beb-407f-b1f0-528aac0f1acd",
|
||||||
|
"iss": "https://door.casbin.com",
|
||||||
|
"aud": "7a11****0fa2172",
|
||||||
|
"name": "admin",
|
||||||
|
"preferred_username": "Admin",
|
||||||
|
"email": "admin@example.com",
|
||||||
|
"picture": "https://casbin.org/img/casbin.svg",
|
||||||
|
"address": "Guangdong",
|
||||||
|
"phone": "12345678910"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
type CasdoorUserInfo struct {
|
||||||
|
Id string `json:"sub"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
DisplayName string `json:"preferred_username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
AvatarUrl string `json:"picture"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||||
|
cdUserinfo := &CasdoorUserInfo{}
|
||||||
|
accessToken := token.AccessToken
|
||||||
|
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/userinfo", idp.Host), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
//add accesstoken to bearer token
|
||||||
|
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||||
|
resp, err := idp.Client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(data, cdUserinfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cdUserinfo.Status != "" {
|
||||||
|
return nil, fmt.Errorf("err: %s", cdUserinfo.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo := &UserInfo{
|
||||||
|
Id: cdUserinfo.Id,
|
||||||
|
Username: cdUserinfo.Name,
|
||||||
|
DisplayName: cdUserinfo.DisplayName,
|
||||||
|
Email: cdUserinfo.Email,
|
||||||
|
AvatarUrl: cdUserinfo.AvatarUrl,
|
||||||
|
}
|
||||||
|
return userInfo, nil
|
||||||
|
|
||||||
|
}
|
109
idp/custom.go
Normal file
109
idp/custom.go
Normal 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
|
||||||
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -122,9 +122,9 @@ func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
type FacebookUserInfo struct {
|
type FacebookUserInfo struct {
|
||||||
Id string `json:"id"` // The app user's App-Scoped User ID. This ID is unique to the app and cannot be used by other apps.
|
Id string `json:"id"` // The app user's App-Scoped User ID. This ID is unique to the app and cannot be used by other apps.
|
||||||
Name string `json:"name"` // The person's full name.
|
Name string `json:"name"` // The person's full name.
|
||||||
NameFormat string `json:"name_format"` // The person's name formatted to correctly handle Chinese, Japanese, or Korean ordering.
|
NameFormat string `json:"name_format"` // The person's name formatted to correctly handle Chinese, Japanese, or Korean ordering.
|
||||||
Picture struct { // The person's profile picture.
|
Picture struct { // The person's profile picture.
|
||||||
Data struct { // This struct is different as https://developers.facebook.com/docs/graph-api/reference/user/picture/
|
Data struct { // This struct is different as https://developers.facebook.com/docs/graph-api/reference/user/picture/
|
||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
@ -164,6 +164,7 @@ func (idp *FacebookIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
|
|||||||
|
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: facebookUserInfo.Id,
|
Id: facebookUserInfo.Id,
|
||||||
|
Username: facebookUserInfo.Name,
|
||||||
DisplayName: facebookUserInfo.Name,
|
DisplayName: facebookUserInfo.Name,
|
||||||
Email: facebookUserInfo.Email,
|
Email: facebookUserInfo.Email,
|
||||||
AvatarUrl: facebookUserInfo.Picture.Data.Url,
|
AvatarUrl: facebookUserInfo.Picture.Data.Url,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
97
idp/goth.go
97
idp/goth.go
@ -22,34 +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/tumblr"
|
"github.com/casdoor/goth/providers/steam"
|
||||||
"github.com/markbates/goth/providers/twitter"
|
"github.com/casdoor/goth/providers/tumblr"
|
||||||
"github.com/markbates/goth/providers/yahoo"
|
"github.com/casdoor/goth/providers/twitter"
|
||||||
"github.com/markbates/goth/providers/yandex"
|
"github.com/casdoor/goth/providers/yahoo"
|
||||||
"github.com/markbates/goth/providers/zoom"
|
"github.com/casdoor/goth/providers/yandex"
|
||||||
|
"github.com/casdoor/goth/providers/zoom"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -171,6 +172,11 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Provider: slack.New(clientId, clientSecret, redirectUrl),
|
Provider: slack.New(clientId, clientSecret, redirectUrl),
|
||||||
Session: &slack.Session{},
|
Session: &slack.Session{},
|
||||||
}
|
}
|
||||||
|
case "Steam":
|
||||||
|
idp = GothIdProvider{
|
||||||
|
Provider: steam.New(clientSecret, redirectUrl),
|
||||||
|
Session: &steam.Session{},
|
||||||
|
}
|
||||||
case "Tumblr":
|
case "Tumblr":
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: tumblr.New(clientId, clientSecret, redirectUrl),
|
Provider: tumblr.New(clientId, clientSecret, redirectUrl),
|
||||||
@ -209,11 +215,26 @@ func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
|
|||||||
|
|
||||||
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||||
var expireAt time.Time
|
var expireAt time.Time
|
||||||
//Need to construct variables supported by goth
|
var value url.Values
|
||||||
//to call the function to obtain accessToken
|
var err error
|
||||||
value := url.Values{}
|
if idp.Provider.Name() == "steam" {
|
||||||
value.Add("code", code)
|
value, err = url.ParseQuery(code)
|
||||||
|
returnUrl := reflect.ValueOf(idp.Session).Elem().FieldByName("CallbackURL")
|
||||||
|
returnUrl.Set(reflect.ValueOf(value.Get("openid.return_to")))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Need to construct variables supported by goth
|
||||||
|
//to call the function to obtain accessToken
|
||||||
|
value = url.Values{}
|
||||||
|
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() {
|
||||||
@ -223,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) {
|
||||||
@ -231,10 +253,10 @@ func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return getUser(gothUser), nil
|
return getUser(gothUser, idp.Provider.Name()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUser(gothUser goth.User) *UserInfo {
|
func getUser(gothUser goth.User, provider string) *UserInfo {
|
||||||
user := UserInfo{
|
user := UserInfo{
|
||||||
Id: gothUser.UserID,
|
Id: gothUser.UserID,
|
||||||
Username: gothUser.Name,
|
Username: gothUser.Name,
|
||||||
@ -258,7 +280,10 @@ func getUser(gothUser goth.User) *UserInfo {
|
|||||||
user.DisplayName = user.Username
|
user.DisplayName = user.Username
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if provider == "steam" {
|
||||||
|
user.Username = user.DisplayName
|
||||||
|
user.Email = ""
|
||||||
|
}
|
||||||
return &user
|
return &user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
10
idp/lark.go
10
idp/lark.go
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) 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" {
|
||||||
@ -66,8 +66,14 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
} else if typ == "GitLab" {
|
} else if typ == "GitLab" {
|
||||||
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
||||||
|
} else if typ == "Adfs" {
|
||||||
|
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)
|
||||||
@ -76,6 +82,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
} else if typ == "Casdoor" {
|
||||||
|
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
|
||||||
} else if isGothSupport(typ) {
|
} else if isGothSupport(typ) {
|
||||||
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
|
||||||
}
|
}
|
||||||
@ -83,7 +91,7 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var gothList = []string{"Apple", "AzureAd", "Slack"}
|
var gothList = []string{"Apple", "AzureAd", "Slack", "Steam"}
|
||||||
|
|
||||||
func isGothSupport(provider string) bool {
|
func isGothSupport(provider string) bool {
|
||||||
for _, value := range gothList {
|
for _, value := range gothList {
|
||||||
|
15
idp/qq.go
15
idp/qq.go
@ -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
82
idp/wechat_miniprogram.go
Normal 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
|
||||||
|
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
11
main.go
@ -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
|
||||||
|
@ -16,7 +16,7 @@ data:
|
|||||||
defaultStorageProvider =
|
defaultStorageProvider =
|
||||||
isCloudIntranet = false
|
isCloudIntranet = false
|
||||||
authState = "casdoor"
|
authState = "casdoor"
|
||||||
httpProxy = "127.0.0.1:10808"
|
sock5Proxy = "127.0.0.1:10808"
|
||||||
verificationCodeTimeout = 10
|
verificationCodeTimeout = 10
|
||||||
initScore = 2000
|
initScore = 2000
|
||||||
logPostOnly = true
|
logPostOnly = true
|
||||||
|
@ -17,7 +17,6 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"xorm.io/core"
|
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/astaxie/beego"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@ -25,6 +24,7 @@ import (
|
|||||||
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
|
||||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||||
//_ "github.com/lib/pq" // db = postgres
|
//_ "github.com/lib/pq" // db = postgres
|
||||||
|
"xorm.io/core"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -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)
|
||||||
|
|
||||||
@ -183,6 +183,11 @@ func (a *Adapter) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(Product))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(Payment))
|
err = a.Engine.Sync2(new(Payment))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@ -38,6 +39,7 @@ type Application struct {
|
|||||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
||||||
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
|
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
@ -215,6 +217,18 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
|||||||
if application.ClientSecret != "" {
|
if application.ClientSecret != "" {
|
||||||
application.ClientSecret = "***"
|
application.ClientSecret = "***"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.OrganizationObj != nil {
|
||||||
|
if application.OrganizationObj.MasterPassword != "" {
|
||||||
|
application.OrganizationObj.MasterPassword = "***"
|
||||||
|
}
|
||||||
|
if application.OrganizationObj.PasswordType != "" {
|
||||||
|
application.OrganizationObj.PasswordType = "***"
|
||||||
|
}
|
||||||
|
if application.OrganizationObj.PasswordSalt != "" {
|
||||||
|
application.OrganizationObj.PasswordSalt = "***"
|
||||||
|
}
|
||||||
|
}
|
||||||
return application
|
return application
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,3 +296,14 @@ func DeleteApplication(application *Application) bool {
|
|||||||
func (application *Application) GetId() string {
|
func (application *Application) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
return fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
|
||||||
|
var validUri = false
|
||||||
|
for _, tmpUri := range application.RedirectUris {
|
||||||
|
if strings.Contains(redirectUri, tmpUri) {
|
||||||
|
validUri = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validUri
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,10 @@ type Cert struct {
|
|||||||
BitSize int `json:"bitSize"`
|
BitSize int `json:"bitSize"`
|
||||||
ExpireInYears int `json:"expireInYears"`
|
ExpireInYears int `json:"expireInYears"`
|
||||||
|
|
||||||
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
PublicKey string `xorm:"mediumtext" json:"publicKey"`
|
||||||
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
|
||||||
|
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
|
||||||
|
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMaskedCert(cert *Cert) *Cert {
|
func GetMaskedCert(cert *Cert) *Cert {
|
||||||
|
@ -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"
|
||||||
@ -33,7 +34,7 @@ func init() {
|
|||||||
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string) string {
|
||||||
if organization == nil {
|
if organization == nil {
|
||||||
return "organization does not exist"
|
return "organization does not exist"
|
||||||
}
|
}
|
||||||
@ -85,11 +86,19 @@ func CheckUserSignup(application *Application, organization *Organization, usern
|
|||||||
}
|
}
|
||||||
|
|
||||||
if application.IsSignupItemVisible("Display name") {
|
if application.IsSignupItemVisible("Display name") {
|
||||||
if displayName == "" {
|
if application.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") {
|
||||||
return "displayName cannot be blank"
|
if firstName == "" {
|
||||||
} else if application.GetSignupItemRule("Display name") == "Personal" {
|
return "firstName cannot be blank"
|
||||||
if !isValidPersonalName(displayName) {
|
} else if lastName == "" {
|
||||||
return "displayName is not valid personal name"
|
return "lastName cannot be blank"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if displayName == "" {
|
||||||
|
return "displayName cannot be blank"
|
||||||
|
} else if application.GetSignupItemRule("Display name") == "Real name" {
|
||||||
|
if !isValidRealName(displayName) {
|
||||||
|
return "displayName is not valid real name"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -171,19 +180,53 @@ func CheckUserPassword(organization string, username string, password string) (*
|
|||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
return nil, "the user is forbidden to sign in, please contact the administrator"
|
return nil, "the user is forbidden to sign in, please contact the administrator"
|
||||||
}
|
}
|
||||||
//for ldap users
|
|
||||||
if user.Ldap != "" {
|
if user.Ldap != "" {
|
||||||
|
//ONLY for ldap users
|
||||||
return checkLdapUserPassword(user, password)
|
return checkLdapUserPassword(user, password)
|
||||||
|
} else {
|
||||||
|
msg := CheckPassword(user, password)
|
||||||
|
if msg != "" {
|
||||||
|
return nil, msg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := CheckPassword(user, password)
|
|
||||||
if msg != "" {
|
|
||||||
return nil, msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, ""
|
return user, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterField(field string) bool {
|
func filterField(field string) bool {
|
||||||
return reFieldWhiteList.MatchString(field)
|
return reFieldWhiteList.MatchString(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
|
||||||
|
if requestUserId == "" {
|
||||||
|
return false, fmt.Errorf("please login first")
|
||||||
|
}
|
||||||
|
|
||||||
|
targetUser := GetUser(userId)
|
||||||
|
if targetUser == nil {
|
||||||
|
return false, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasPermission := false
|
||||||
|
if strings.HasPrefix(requestUserId, "app/") {
|
||||||
|
hasPermission = true
|
||||||
|
} else {
|
||||||
|
requestUser := GetUser(requestUserId)
|
||||||
|
if requestUser == nil {
|
||||||
|
return false, fmt.Errorf("session outdated, please login again")
|
||||||
|
}
|
||||||
|
if requestUser.IsGlobalAdmin {
|
||||||
|
hasPermission = true
|
||||||
|
} else if requestUserId == userId {
|
||||||
|
hasPermission = true
|
||||||
|
} else if targetUser.Owner == requestUser.Owner {
|
||||||
|
if strict {
|
||||||
|
hasPermission = requestUser.IsAdmin
|
||||||
|
} else {
|
||||||
|
hasPermission = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasPermission, fmt.Errorf("you don't have the permission to do this")
|
||||||
|
}
|
@ -16,16 +16,16 @@ package object
|
|||||||
|
|
||||||
import "regexp"
|
import "regexp"
|
||||||
|
|
||||||
var rePersonalName *regexp.Regexp
|
var reRealName *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
rePersonalName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
|
reRealName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidPersonalName(s string) bool {
|
func isValidRealName(s string) bool {
|
||||||
return rePersonalName.MatchString(s)
|
return reRealName.MatchString(s)
|
||||||
}
|
}
|
||||||
|
@ -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{
|
||||||
@ -47,11 +43,13 @@ func initBuiltInOrganization() {
|
|||||||
DisplayName: "Built-in Organization",
|
DisplayName: "Built-in Organization",
|
||||||
WebsiteUrl: "https://example.com",
|
WebsiteUrl: "https://example.com",
|
||||||
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||||
|
PasswordType: "plain",
|
||||||
PhonePrefix: "86",
|
PhonePrefix: "86",
|
||||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||||
PasswordType: "plain",
|
Tags: []string{},
|
||||||
}
|
}
|
||||||
AddOrganization(organization)
|
AddOrganization(organization)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func initBuiltInUser() {
|
func initBuiltInUser() {
|
||||||
@ -121,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
|
||||||
|
105
object/ldap.go
105
object/ldap.go
@ -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,11 +210,20 @@ 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
|
||||||
searchResult, err := l.Conn.Search(searchReq)
|
if l.IsAD {
|
||||||
|
searchReq = goldap.NewSearchRequest(baseDn,
|
||||||
|
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
SearchFilterMsAD, SearchAttributesMsAD, nil)
|
||||||
|
} else {
|
||||||
|
searchReq = goldap.NewSearchRequest(baseDn,
|
||||||
|
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
SearchFilter, SearchAttributes, nil)
|
||||||
|
}
|
||||||
|
searchResult, err := l.Conn.SearchWithPaging(searchReq, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -180,12 +241,14 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
|||||||
case "uidNumber":
|
case "uidNumber":
|
||||||
ldapUserItem.UidNumber = attribute.Values[0]
|
ldapUserItem.UidNumber = attribute.Values[0]
|
||||||
case "uid":
|
case "uid":
|
||||||
|
case "sAMAccountName":
|
||||||
ldapUserItem.Uid = attribute.Values[0]
|
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":
|
||||||
|
case "objectGUID":
|
||||||
ldapUserItem.Uuid = attribute.Values[0]
|
ldapUserItem.Uuid = attribute.Values[0]
|
||||||
case "mail":
|
case "mail":
|
||||||
ldapUserItem.Mail = attribute.Values[0]
|
ldapUserItem.Mail = attribute.Values[0]
|
||||||
@ -300,7 +363,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 +374,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 +407,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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +66,13 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
|
|||||||
ticker := time.NewTicker(time.Duration(ldap.AutoSync) * time.Minute)
|
ticker := time.NewTicker(time.Duration(ldap.AutoSync) * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
|
select {
|
||||||
|
case <-stopChan:
|
||||||
|
logs.Info(fmt.Sprintf("autoSync goroutine for %s stopped", ldap.Id))
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
}
|
||||||
|
|
||||||
UpdateLdapSyncTime(ldap.Id)
|
UpdateLdapSyncTime(ldap.Id)
|
||||||
//fetch all users
|
//fetch all users
|
||||||
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
|
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
|
||||||
@ -78,18 +86,12 @@ 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 {
|
||||||
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
|
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
|
||||||
}
|
}
|
||||||
select {
|
|
||||||
case <-stopChan:
|
|
||||||
logs.Info(fmt.Sprintf("autoSync goroutine for %s stopped", ldap.Id))
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego"
|
"github.com/casdoor/casdoor/conf"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -30,6 +30,7 @@ type OidcDiscovery struct {
|
|||||||
TokenEndpoint string `json:"token_endpoint"`
|
TokenEndpoint string `json:"token_endpoint"`
|
||||||
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||||
JwksUri string `json:"jwks_uri"`
|
JwksUri string `json:"jwks_uri"`
|
||||||
|
IntrospectionEndpoint string `json:"introspection_endpoint"`
|
||||||
ResponseTypesSupported []string `json:"response_types_supported"`
|
ResponseTypesSupported []string `json:"response_types_supported"`
|
||||||
ResponseModesSupported []string `json:"response_modes_supported"`
|
ResponseModesSupported []string `json:"response_modes_supported"`
|
||||||
GrantTypesSupported []string `json:"grant_types_supported"`
|
GrantTypesSupported []string `json:"grant_types_supported"`
|
||||||
@ -57,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
|
|||||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||||
originFrontend, originBackend := getOriginFromHost(host)
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
origin := beego.AppConfig.String("origin")
|
origin := conf.GetConfigString("origin")
|
||||||
if origin != "" {
|
if origin != "" {
|
||||||
originFrontend = origin
|
originFrontend = origin
|
||||||
originBackend = origin
|
originBackend = origin
|
||||||
@ -69,11 +70,12 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
// https://accounts.google.com/.well-known/openid-configuration
|
// https://accounts.google.com/.well-known/openid-configuration
|
||||||
// https://access.line.me/.well-known/openid-configuration
|
// https://access.line.me/.well-known/openid-configuration
|
||||||
oidcDiscovery := OidcDiscovery{
|
oidcDiscovery := OidcDiscovery{
|
||||||
Issuer: originFrontend,
|
Issuer: originBackend,
|
||||||
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
|
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", originFrontend),
|
||||||
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
|
||||||
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
|
||||||
JwksUri: fmt.Sprintf("%s/api/certs", originBackend),
|
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
|
||||||
|
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
|
||||||
ResponseTypesSupported: []string{"id_token"},
|
ResponseTypesSupported: []string{"id_token"},
|
||||||
ResponseModesSupported: []string{"login", "code", "link"},
|
ResponseModesSupported: []string{"login", "code", "link"},
|
||||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||||
@ -89,21 +91,24 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||||
cert := GetDefaultCert()
|
certs := GetCerts("admin")
|
||||||
|
jwks := jose.JSONWebKeySet{}
|
||||||
//follows the protocol rfc 7517(draft)
|
//follows the protocol rfc 7517(draft)
|
||||||
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
|
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
|
||||||
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
|
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
|
||||||
certPemBlock := []byte(cert.PublicKey)
|
for _, cert := range certs {
|
||||||
certDerBlock, _ := pem.Decode(certPemBlock)
|
certPemBlock := []byte(cert.PublicKey)
|
||||||
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
|
certDerBlock, _ := pem.Decode(certPemBlock)
|
||||||
|
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
|
||||||
|
|
||||||
var jwk jose.JSONWebKey
|
var jwk jose.JSONWebKey
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
var jwks jose.JSONWebKeySet
|
|
||||||
jwks.Keys = []jose.JSONWebKey{jwk}
|
|
||||||
return jwks, nil
|
return jwks, nil
|
||||||
}
|
}
|
||||||
|
@ -25,15 +25,17 @@ type Organization 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"`
|
||||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrganizationCount(owner, field, value string) int {
|
func GetOrganizationCount(owner, field, value string) int {
|
||||||
|
@ -16,6 +16,7 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
@ -27,15 +28,32 @@ type Payment struct {
|
|||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
Provider string `xorm:"varchar(100)" json:"provider"`
|
Provider string `xorm:"varchar(100)" json:"provider"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
Good string `xorm:"varchar(100)" json:"good"`
|
ProductName string `xorm:"varchar(100)" json:"productName"`
|
||||||
Amount string `xorm:"varchar(100)" json:"amount"`
|
ProductDisplayName string `xorm:"varchar(100)" json:"productDisplayName"`
|
||||||
Currency string `xorm:"varchar(100)" json:"currency"`
|
|
||||||
|
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
Detail string `xorm:"varchar(100)" json:"detail"`
|
||||||
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
|
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
|
||||||
|
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||||
|
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||||
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
|
Message string `xorm:"varchar(1000)" json:"message"`
|
||||||
|
|
||||||
|
PersonName string `xorm:"varchar(100)" json:"personName"`
|
||||||
|
PersonIdCard string `xorm:"varchar(100)" json:"personIdCard"`
|
||||||
|
PersonEmail string `xorm:"varchar(100)" json:"personEmail"`
|
||||||
|
PersonPhone string `xorm:"varchar(100)" json:"personPhone"`
|
||||||
|
InvoiceType string `xorm:"varchar(100)" json:"invoiceType"`
|
||||||
|
InvoiceTitle string `xorm:"varchar(100)" json:"invoiceTitle"`
|
||||||
|
InvoiceTaxId string `xorm:"varchar(100)" json:"invoiceTaxId"`
|
||||||
|
InvoiceRemark string `xorm:"varchar(100)" json:"invoiceRemark"`
|
||||||
|
InvoiceUrl string `xorm:"varchar(255)" json:"invoiceUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPaymentCount(owner, field, value string) int {
|
func GetPaymentCount(owner, field, value string) int {
|
||||||
@ -58,6 +76,16 @@ func GetPayments(owner string) []*Payment {
|
|||||||
return payments
|
return payments
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserPayments(owner string, organization string, user string) []*Payment {
|
||||||
|
payments := []*Payment{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&payments, &Payment{Owner: owner, Organization: organization, User: user})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return payments
|
||||||
|
}
|
||||||
|
|
||||||
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
|
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
|
||||||
payments := []*Payment{}
|
payments := []*Payment{}
|
||||||
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
@ -124,6 +152,99 @@ func DeletePayment(payment *Payment) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error) {
|
||||||
|
payment := getPayment(owner, paymentName)
|
||||||
|
if payment == nil {
|
||||||
|
return nil, fmt.Errorf("the payment: %s does not exist", paymentName)
|
||||||
|
}
|
||||||
|
|
||||||
|
product := getProduct(owner, productName)
|
||||||
|
if product == nil {
|
||||||
|
return nil, fmt.Errorf("the product: %s does not exist", productName)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := product.getProvider(providerName)
|
||||||
|
if err != nil {
|
||||||
|
return payment, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pProvider, cert, err := provider.getPaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
return payment, err
|
||||||
|
}
|
||||||
|
|
||||||
|
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return payment, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if productDisplayName != "" && productDisplayName != product.DisplayName {
|
||||||
|
return nil, fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if price != product.Price {
|
||||||
|
return nil, fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price)
|
||||||
|
}
|
||||||
|
|
||||||
|
return payment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) bool {
|
||||||
|
payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName)
|
||||||
|
|
||||||
|
if payment != nil {
|
||||||
|
if err != nil {
|
||||||
|
payment.State = "Error"
|
||||||
|
payment.Message = err.Error()
|
||||||
|
} else {
|
||||||
|
payment.State = "Paid"
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdatePayment(payment.GetId(), payment)
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := err == nil
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func invoicePayment(payment *Payment) (string, error) {
|
||||||
|
provider := getProvider(payment.Owner, payment.Provider)
|
||||||
|
if provider == nil {
|
||||||
|
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
pProvider, _, err := provider.getPaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceUrl, err := pProvider.GetInvoice(payment.Name, payment.PersonName, payment.PersonIdCard, payment.PersonEmail, payment.PersonPhone, payment.InvoiceType, payment.InvoiceTitle, payment.InvoiceTaxId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InvoicePayment(payment *Payment) (string, error) {
|
||||||
|
if payment.State != "Paid" {
|
||||||
|
return "", fmt.Errorf("the payment state is supposed to be: \"%s\", got: \"%s\"", "Paid", payment.State)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceUrl, err := invoicePayment(payment)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
payment.InvoiceUrl = invoiceUrl
|
||||||
|
affected := UpdatePayment(payment.GetId(), payment)
|
||||||
|
if !affected {
|
||||||
|
return "", fmt.Errorf("failed to update the payment: %s", payment.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoiceUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (payment *Payment) GetId() string {
|
func (payment *Payment) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
return fmt.Sprintf("%s/%s", payment.Owner, payment.Name)
|
||||||
}
|
}
|
||||||
|
211
object/product.go
Normal file
211
object/product.go
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"xorm.io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Product struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
|
||||||
|
Image string `xorm:"varchar(100)" json:"image"`
|
||||||
|
Detail string `xorm:"varchar(100)" json:"detail"`
|
||||||
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
|
Currency string `xorm:"varchar(100)" json:"currency"`
|
||||||
|
Price float64 `json:"price"`
|
||||||
|
Quantity int `json:"quantity"`
|
||||||
|
Sold int `json:"sold"`
|
||||||
|
Providers []string `xorm:"varchar(100)" json:"providers"`
|
||||||
|
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
|
||||||
|
|
||||||
|
State string `xorm:"varchar(100)" json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProductCount(owner, field, value string) int {
|
||||||
|
session := GetSession(owner, -1, -1, field, value, "", "")
|
||||||
|
count, err := session.Count(&Product{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProducts(owner string) []*Product {
|
||||||
|
products := []*Product{}
|
||||||
|
err := adapter.Engine.Desc("created_time").Find(&products, &Product{Owner: owner})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return products
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPaginationProducts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Product {
|
||||||
|
products := []*Product{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&products)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return products
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProduct(owner string, name string) *Product {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
product := Product{Owner: owner, Name: name}
|
||||||
|
existed, err := adapter.Engine.Get(&product)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &product
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProduct(id string) *Product {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getProduct(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateProduct(id string, product *Product) bool {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
if getProduct(owner, name) == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(product)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddProduct(product *Product) bool {
|
||||||
|
affected, err := adapter.Engine.Insert(product)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteProduct(product *Product) bool {
|
||||||
|
affected, err := adapter.Engine.ID(core.PK{product.Owner, product.Name}).Delete(&Product{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (product *Product) GetId() string {
|
||||||
|
return fmt.Sprintf("%s/%s", product.Owner, product.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (product *Product) isValidProvider(provider *Provider) bool {
|
||||||
|
for _, providerName := range product.Providers {
|
||||||
|
if providerName == provider.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (product *Product) getProvider(providerId string) (*Provider, error) {
|
||||||
|
provider := getProvider(product.Owner, providerId)
|
||||||
|
if provider == nil {
|
||||||
|
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !product.isValidProvider(provider) {
|
||||||
|
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuyProduct(id string, providerName string, user *User, host string) (string, error) {
|
||||||
|
product := GetProduct(id)
|
||||||
|
if product == nil {
|
||||||
|
return "", fmt.Errorf("the product: %s does not exist", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider, err := product.getProvider(providerName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pProvider, _, err := provider.getPaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
owner := product.Owner
|
||||||
|
productName := product.Name
|
||||||
|
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
|
||||||
|
paymentName := util.GenerateTimeId()
|
||||||
|
productDisplayName := product.DisplayName
|
||||||
|
|
||||||
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
|
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
|
||||||
|
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
|
||||||
|
|
||||||
|
payUrl, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
payment := Payment{
|
||||||
|
Owner: product.Owner,
|
||||||
|
Name: paymentName,
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
DisplayName: paymentName,
|
||||||
|
Provider: provider.Name,
|
||||||
|
Type: provider.Type,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
ProductName: productName,
|
||||||
|
ProductDisplayName: productDisplayName,
|
||||||
|
Detail: product.Detail,
|
||||||
|
Tag: product.Tag,
|
||||||
|
Currency: product.Currency,
|
||||||
|
Price: product.Price,
|
||||||
|
PayUrl: payUrl,
|
||||||
|
ReturnUrl: product.ReturnUrl,
|
||||||
|
State: "Created",
|
||||||
|
}
|
||||||
|
affected := AddPayment(&payment)
|
||||||
|
if !affected {
|
||||||
|
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||||
|
}
|
||||||
|
|
||||||
|
return payUrl, err
|
||||||
|
}
|
44
object/product_test.go
Normal file
44
object/product_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/pp"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProduct(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
|
||||||
|
product := GetProduct("admin/product_123")
|
||||||
|
provider := getProvider(product.Owner, "provider_pay_alipay")
|
||||||
|
cert := getCert(product.Owner, "cert-pay-alipay")
|
||||||
|
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||||
|
|
||||||
|
paymentId := util.GenerateTimeId()
|
||||||
|
returnUrl := ""
|
||||||
|
notifyUrl := ""
|
||||||
|
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
println(payUrl)
|
||||||
|
}
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/pp"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
@ -26,15 +27,21 @@ type Provider struct {
|
|||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Category string `xorm:"varchar(100)" json:"category"`
|
Category string `xorm:"varchar(100)" json:"category"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
SubType string `xorm:"varchar(100)" json:"subType"`
|
SubType string `xorm:"varchar(100)" json:"subType"`
|
||||||
Method string `xorm:"varchar(100)" json:"method"`
|
Method string `xorm:"varchar(100)" json:"method"`
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
||||||
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
|
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
||||||
|
CustomScope string `xorm:"varchar(200)" json:"customScope"`
|
||||||
|
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
||||||
|
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
|
||||||
|
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
|
||||||
|
|
||||||
Host string `xorm:"varchar(100)" json:"host"`
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
@ -149,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 {
|
||||||
@ -181,6 +198,23 @@ func DeleteProvider(provider *Provider) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
|
||||||
|
cert := &Cert{}
|
||||||
|
if p.Cert != "" {
|
||||||
|
cert = getCert(p.Owner, p.Cert)
|
||||||
|
if cert == nil {
|
||||||
|
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
|
||||||
|
if pProvider == nil {
|
||||||
|
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pProvider, cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Provider) GetId() string {
|
func (p *Provider) GetId() string {
|
||||||
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(250) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
@ -31,7 +31,7 @@ type Resource struct {
|
|||||||
Application string `xorm:"varchar(100)" json:"application"`
|
Application string `xorm:"varchar(100)" json:"application"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Parent string `xorm:"varchar(100)" json:"parent"`
|
Parent string `xorm:"varchar(100)" json:"parent"`
|
||||||
FileName string `xorm:"varchar(100)" json:"fileName"`
|
FileName string `xorm:"varchar(1000)" json:"fileName"`
|
||||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||||
FileSize int `json:"fileSize"`
|
FileSize int `json:"fileSize"`
|
||||||
|
352
object/saml_idp.go
Normal file
352
object/saml_idp.go
Normal 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
|
||||||
|
}
|
@ -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)
|
@ -18,6 +18,9 @@ import "github.com/casdoor/go-sms-sender"
|
|||||||
|
|
||||||
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||||
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
|
||||||
|
if provider.Type == go_sms_sender.HuaweiCloud {
|
||||||
|
client, err = go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -206,3 +206,8 @@ func (syncer *Syncer) getTable() string {
|
|||||||
return syncer.Table
|
return syncer.Table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RunSyncer(syncer *Syncer) {
|
||||||
|
syncer.initAdapter()
|
||||||
|
syncer.syncUsers()
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
413
object/token.go
413
object/token.go
@ -17,14 +17,20 @@ package object
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"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"`
|
||||||
@ -57,6 +63,22 @@ type TokenWrapper struct {
|
|||||||
TokenType string `json:"token_type"`
|
TokenType string `json:"token_type"`
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
Scope string `json:"scope"`
|
Scope string `json:"scope"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IntrospectionResponse struct {
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
ClientId string `json:"client_id,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
TokenType string `json:"token_type,omitempty"`
|
||||||
|
Exp int64 `json:"exp,omitempty"`
|
||||||
|
Iat int64 `json:"iat,omitempty"`
|
||||||
|
Nbf int64 `json:"nbf,omitempty"`
|
||||||
|
Sub string `json:"sub,omitempty"`
|
||||||
|
Aud []string `json:"aud,omitempty"`
|
||||||
|
Iss string `json:"iss,omitempty"`
|
||||||
|
Jti string `json:"jti,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTokenCount(owner, field, value string) int {
|
func GetTokenCount(owner, field, value string) int {
|
||||||
@ -168,6 +190,25 @@ func DeleteToken(token *Token) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteTokenByAceessToken(accessToken string) (bool, *Application) {
|
||||||
|
token := Token{AccessToken: accessToken}
|
||||||
|
existed, err := adapter.Engine.Get(&token)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existed {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
application := getApplication(token.Owner, token.Application)
|
||||||
|
affected, err := adapter.Engine.Where("access_token=?", accessToken).Delete(&Token{})
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected != 0, application
|
||||||
|
}
|
||||||
|
|
||||||
func GetTokenByAccessToken(accessToken string) *Token {
|
func GetTokenByAccessToken(accessToken string) *Token {
|
||||||
//Check if the accessToken is in the database
|
//Check if the accessToken is in the database
|
||||||
token := Token{AccessToken: accessToken}
|
token := Token{AccessToken: accessToken}
|
||||||
@ -178,9 +219,18 @@ func GetTokenByAccessToken(accessToken string) *Token {
|
|||||||
return &token
|
return &token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetTokenByTokenAndApplication(token string, application string) *Token {
|
||||||
|
tokenResult := Token{}
|
||||||
|
existed, err := adapter.Engine.Where("(refresh_token = ? or access_token = ? ) and application = ?", token, token, application).Get(&tokenResult)
|
||||||
|
if err != nil || !existed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &tokenResult
|
||||||
|
}
|
||||||
|
|
||||||
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
||||||
if responseType != "code" {
|
if responseType != "code" && responseType != "token" && responseType != "id_token" {
|
||||||
return "response_type should be \"code\"", nil
|
return fmt.Sprintf("error: grant_type: %s is not supported in this application", responseType), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
application := GetApplicationByClientId(clientId)
|
application := GetApplicationByClientId(clientId)
|
||||||
@ -204,7 +254,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
|
|||||||
return "", application
|
return "", application
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string) *Code {
|
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string) *Code {
|
||||||
user := GetUser(userId)
|
user := GetUser(userId)
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return &Code{
|
return &Code{
|
||||||
@ -227,7 +277,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, err := generateJwtToken(application, user, nonce, scope)
|
accessToken, refreshToken, err := generateJwtToken(application, user, nonce, scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -246,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,
|
||||||
@ -261,89 +311,58 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if grantType != "authorization_code" {
|
//Check if grantType is allowed in the current application
|
||||||
|
|
||||||
|
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: "error: grant_type should be \"authorization_code\"",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if code == "" {
|
var token *Token
|
||||||
return &TokenWrapper{
|
var err error
|
||||||
AccessToken: "error: authorization code should not be empty",
|
switch grantType {
|
||||||
TokenType: "",
|
case "authorization_code": // Authorization Code Grant
|
||||||
ExpiresIn: 0,
|
token, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
|
||||||
Scope: "",
|
case "password": // Resource Owner Password Credentials Grant
|
||||||
}
|
token, err = GetPasswordToken(application, username, password, scope, host)
|
||||||
|
case "client_credentials": // Client Credentials Grant
|
||||||
|
token, err = GetClientCredentialsToken(application, clientSecret, scope, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
token := getTokenByCode(code)
|
if tag == "wechat_miniprogram" {
|
||||||
if token == nil {
|
// Wechat Mini Program
|
||||||
return &TokenWrapper{
|
token, err = GetWechatMiniProgramToken(application, code, host, username, avatar)
|
||||||
AccessToken: "error: invalid authorization code",
|
|
||||||
TokenType: "",
|
|
||||||
ExpiresIn: 0,
|
|
||||||
Scope: "",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if application.Name != token.Application {
|
if err != nil {
|
||||||
|
errString = err.Error()
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: the token is for wrong application (client_id)",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
|
||||||
ExpiresIn: 0,
|
|
||||||
Scope: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if application.ClientSecret != clientSecret {
|
|
||||||
return &TokenWrapper{
|
|
||||||
AccessToken: "error: invalid client_secret",
|
|
||||||
TokenType: "",
|
|
||||||
ExpiresIn: 0,
|
|
||||||
Scope: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
|
||||||
return &TokenWrapper{
|
|
||||||
AccessToken: "error: incorrect code_verifier",
|
|
||||||
TokenType: "",
|
|
||||||
ExpiresIn: 0,
|
|
||||||
Scope: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if token.CodeIsUsed {
|
|
||||||
// anti replay attacks
|
|
||||||
return &TokenWrapper{
|
|
||||||
AccessToken: "error: authorization code has been used",
|
|
||||||
TokenType: "",
|
|
||||||
ExpiresIn: 0,
|
|
||||||
Scope: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Now().Unix() > token.CodeExpireIn {
|
|
||||||
// code must be used within 5 minutes
|
|
||||||
return &TokenWrapper{
|
|
||||||
AccessToken: "error: authorization code has expired",
|
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,66 +380,79 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
|
|||||||
return tokenWrapper
|
return tokenWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string) *TokenWrapper {
|
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string, host string) *TokenWrapper {
|
||||||
|
var errString string
|
||||||
// check parameters
|
// check parameters
|
||||||
if grantType != "refresh_token" {
|
if grantType != "refresh_token" {
|
||||||
|
errString = "error: grant_type should be \"refresh_token\""
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: grant_type should be \"refresh_token\"",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
application := GetApplicationByClientId(clientId)
|
application := GetApplicationByClientId(clientId)
|
||||||
if application == nil {
|
if application == nil {
|
||||||
|
errString = "error: invalid client_id"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid client_id",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if application.ClientSecret != clientSecret {
|
if clientSecret != "" && application.ClientSecret != clientSecret {
|
||||||
|
errString = "error: invalid client_secret"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid client_secret",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check whether the refresh token is valid, and has not expired.
|
// check whether the refresh token is valid, and has not expired.
|
||||||
token := Token{RefreshToken: refreshToken}
|
token := Token{RefreshToken: refreshToken}
|
||||||
existed, err := adapter.Engine.Get(&token)
|
existed, err := adapter.Engine.Get(&token)
|
||||||
if err != nil || !existed {
|
if err != nil || !existed {
|
||||||
|
errString = "error: invalid refresh_token"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: invalid refresh_token",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := getCertByApplication(application)
|
cert := getCertByApplication(application)
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
_, err = ParseJwtToken(refreshToken, cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errString := fmt.Sprintf("error: %s", err.Error())
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: fmt.Sprintf("error: %s", err.Error()),
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// generate a new token
|
// generate a new token
|
||||||
user := getUser(application.Organization, token.User)
|
user := getUser(application.Organization, token.User)
|
||||||
if user.IsForbidden {
|
if user.IsForbidden {
|
||||||
|
errString = "error: the user is forbidden to sign in, please contact the administrator"
|
||||||
return &TokenWrapper{
|
return &TokenWrapper{
|
||||||
AccessToken: "error: the user is forbidden to sign in, please contact the administrator",
|
AccessToken: errString,
|
||||||
TokenType: "",
|
TokenType: "",
|
||||||
ExpiresIn: 0,
|
ExpiresIn: 0,
|
||||||
Scope: "",
|
Scope: "",
|
||||||
|
Error: errString,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope)
|
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -435,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
|
||||||
@ -459,3 +492,223 @@ func pkceChallenge(verifier string) string {
|
|||||||
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
|
challenge := base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(sum[:])
|
||||||
return challenge
|
return challenge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if grantType is allowed in the current application
|
||||||
|
// authorization_code is allowed by default
|
||||||
|
func IsGrantTypeValid(method string, grantTypes []string) bool {
|
||||||
|
if method == "authorization_code" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, m := range grantTypes {
|
||||||
|
if m == method {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorization code flow
|
||||||
|
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, error) {
|
||||||
|
if code == "" {
|
||||||
|
return nil, errors.New("error: authorization code should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
token := getTokenByCode(code)
|
||||||
|
if token == nil {
|
||||||
|
return nil, errors.New("error: invalid authorization code")
|
||||||
|
}
|
||||||
|
if token.CodeIsUsed {
|
||||||
|
// anti replay attacks
|
||||||
|
return nil, errors.New("error: authorization code has been used")
|
||||||
|
}
|
||||||
|
|
||||||
|
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
|
||||||
|
return nil, errors.New("error: incorrect code_verifier")
|
||||||
|
}
|
||||||
|
|
||||||
|
if application.ClientSecret != clientSecret {
|
||||||
|
// when using PKCE, the Client Secret can be empty,
|
||||||
|
// but if it is provided, it must be accurate.
|
||||||
|
if token.CodeChallenge == "" {
|
||||||
|
return nil, errors.New("error: invalid client_secret")
|
||||||
|
} else {
|
||||||
|
if clientSecret != "" {
|
||||||
|
return nil, errors.New("error: invalid client_secret")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if application.Name != token.Application {
|
||||||
|
return nil, errors.New("error: the token is for wrong application (client_id)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().Unix() > token.CodeExpireIn {
|
||||||
|
// code must be used within 5 minutes
|
||||||
|
return nil, errors.New("error: authorization code has expired")
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource Owner Password Credentials flow
|
||||||
|
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, error) {
|
||||||
|
user := getUser(application.Organization, username)
|
||||||
|
if user == nil {
|
||||||
|
return nil, errors.New("error: the user does not exist")
|
||||||
|
}
|
||||||
|
msg := CheckPassword(user, password)
|
||||||
|
if msg != "" {
|
||||||
|
return nil, errors.New("error: invalid username or password")
|
||||||
|
}
|
||||||
|
if user.IsForbidden {
|
||||||
|
return nil, errors.New("error: the user is forbidden to sign in, please contact the administrator")
|
||||||
|
}
|
||||||
|
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: util.GenerateId(),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
AddToken(token)
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client Credentials flow
|
||||||
|
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, error) {
|
||||||
|
if application.ClientSecret != clientSecret {
|
||||||
|
return nil, errors.New("error: invalid client_secret")
|
||||||
|
}
|
||||||
|
nullUser := &User{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Id: application.GetId(),
|
||||||
|
Name: fmt.Sprintf("app/%s", application.Name),
|
||||||
|
}
|
||||||
|
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: util.GenerateId(),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: application.Organization,
|
||||||
|
User: nullUser.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
AddToken(token)
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implicit flow
|
||||||
|
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
|
||||||
|
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: util.GenerateId(),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: util.GenerateClientId(),
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * hourSeconds,
|
||||||
|
Scope: scope,
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
AddToken(token)
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wechat Mini Program flow
|
||||||
|
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, error) {
|
||||||
|
mpProvider := GetWechatMiniProgramProvider(application)
|
||||||
|
if mpProvider == nil {
|
||||||
|
return nil, errors.New("error: the application does not support wechat mini program")
|
||||||
|
}
|
||||||
|
provider := GetProvider(util.GetId(mpProvider.Name))
|
||||||
|
mpIdp := idp.NewWeChatMiniProgramIdProvider(provider.ClientId, provider.ClientSecret)
|
||||||
|
session, err := mpIdp.GetSessionByCode(code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
openId, unionId := session.Openid, session.Unionid
|
||||||
|
if openId == "" && unionId == "" {
|
||||||
|
return nil, errors.New("err: WeChat's openid and unionid are empty")
|
||||||
|
}
|
||||||
|
user := getUserByWechatId(openId, unionId)
|
||||||
|
if user == nil {
|
||||||
|
if !application.EnableSignUp {
|
||||||
|
return nil, errors.New("err: the application does not allow to sign up new account")
|
||||||
|
}
|
||||||
|
//Add new user
|
||||||
|
var name string
|
||||||
|
if username != "" {
|
||||||
|
name = username
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("wechat-%s", openId)
|
||||||
|
}
|
||||||
|
|
||||||
|
user = &User{
|
||||||
|
Owner: application.Organization,
|
||||||
|
Id: util.GenerateId(),
|
||||||
|
Name: name,
|
||||||
|
Avatar: avatar,
|
||||||
|
SignupApplication: application.Name,
|
||||||
|
WeChat: openId,
|
||||||
|
WeChatUnionId: unionId,
|
||||||
|
Type: "normal-user",
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
IsAdmin: false,
|
||||||
|
IsGlobalAdmin: false,
|
||||||
|
IsForbidden: false,
|
||||||
|
IsDeleted: false,
|
||||||
|
}
|
||||||
|
AddUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &Token{
|
||||||
|
Owner: application.Owner,
|
||||||
|
Name: util.GenerateId(),
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
Application: application.Name,
|
||||||
|
Organization: user.Owner,
|
||||||
|
User: user.Name,
|
||||||
|
Code: session.SessionKey, //a trick, because miniprogram does not use the code, so use the code field to save the session_key
|
||||||
|
AccessToken: accessToken,
|
||||||
|
RefreshToken: refreshToken,
|
||||||
|
ExpiresIn: application.ExpireInHours * 60,
|
||||||
|
Scope: "",
|
||||||
|
TokenType: "Bearer",
|
||||||
|
CodeIsUsed: true,
|
||||||
|
}
|
||||||
|
AddToken(token)
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
327
object/token_cas.go
Normal file
327
object/token_cas.go
Normal 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
|
||||||
|
}
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,12 +60,17 @@ func getShortClaims(claims Claims) ClaimsShort {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateJwtToken(application *Application, user *User, nonce string, scope string) (string, string, error) {
|
func generateJwtToken(application *Application, user *User, nonce string, scope string, host string) (string, string, error) {
|
||||||
nowTime := time.Now()
|
nowTime := time.Now()
|
||||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||||
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
|
||||||
|
|
||||||
user.Password = ""
|
user.Password = ""
|
||||||
|
origin := conf.GetConfigString("origin")
|
||||||
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
if origin != "" {
|
||||||
|
originBackend = origin
|
||||||
|
}
|
||||||
|
|
||||||
claims := Claims{
|
claims := Claims{
|
||||||
User: user,
|
User: user,
|
||||||
@ -75,7 +79,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
Tag: user.Tag,
|
Tag: user.Tag,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
RegisteredClaims: jwt.RegisteredClaims{
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
Issuer: beego.AppConfig.String("origin"),
|
Issuer: originBackend,
|
||||||
Subject: user.Id,
|
Subject: user.Id,
|
||||||
Audience: []string{application.ClientId},
|
Audience: []string{application.ClientId},
|
||||||
ExpiresAt: jwt.NewNumericDate(expireTime),
|
ExpiresAt: jwt.NewNumericDate(expireTime),
|
||||||
@ -142,3 +146,7 @@ func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
|||||||
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseJwtTokenByApplication(token string, application *Application) (*Claims, error) {
|
||||||
|
return ParseJwtToken(token, getCertByApplication(application))
|
||||||
|
}
|
||||||
|
115
object/user.go
115
object/user.go
@ -18,6 +18,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"xorm.io/core"
|
"xorm.io/core"
|
||||||
)
|
)
|
||||||
@ -33,9 +34,12 @@ type User struct {
|
|||||||
Password string `xorm:"varchar(100)" json:"password"`
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
|
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||||
|
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||||
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"`
|
||||||
@ -52,6 +56,7 @@ type User struct {
|
|||||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||||
Education string `xorm:"varchar(100)" json:"education"`
|
Education string `xorm:"varchar(100)" json:"education"`
|
||||||
Score int `json:"score"`
|
Score int `json:"score"`
|
||||||
|
Karma int `json:"karma"`
|
||||||
Ranking int `json:"ranking"`
|
Ranking int `json:"ranking"`
|
||||||
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
||||||
IsOnline bool `json:"isOnline"`
|
IsOnline bool `json:"isOnline"`
|
||||||
@ -67,28 +72,46 @@ 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"`
|
||||||
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||||
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
|
||||||
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
|
||||||
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
|
||||||
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
|
||||||
|
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
|
||||||
|
Apple string `xorm:"apple varchar(100)" json:"apple"`
|
||||||
|
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
|
||||||
|
Slack string `xorm:"slack varchar(100)" json:"slack"`
|
||||||
|
Steam string `xorm:"steam varchar(100)" json:"steam"`
|
||||||
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Userinfo struct {
|
||||||
|
Sub string `json:"sub"`
|
||||||
|
Iss string `json:"iss"`
|
||||||
|
Aud string `json:"aud"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
DisplayName string `json:"preferred_username,omitempty"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Avatar string `json:"picture,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Phone string `json:"phone,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func GetGlobalUserCount(field, value string) int {
|
func GetGlobalUserCount(field, value string) int {
|
||||||
session := GetSession("", -1, -1, field, value, "", "")
|
session := GetSession("", -1, -1, field, value, "", "")
|
||||||
count, err := session.Count(&User{})
|
count, err := session.Count(&User{})
|
||||||
@ -206,6 +229,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
|
||||||
@ -285,7 +325,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)
|
||||||
@ -334,6 +374,8 @@ func AddUser(user *User) bool {
|
|||||||
|
|
||||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||||
|
|
||||||
|
user.Ranking = GetUserCount(user.Owner, "", "") + 1
|
||||||
|
|
||||||
affected, err := adapter.Engine.Insert(user)
|
affected, err := adapter.Engine.Insert(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -360,7 +402,9 @@ func AddUsers(users []*User) bool {
|
|||||||
|
|
||||||
affected, err := adapter.Engine.Insert(users)
|
affected, err := adapter.Engine.Insert(users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0
|
return affected != 0
|
||||||
@ -401,6 +445,39 @@ func DeleteUser(user *User) bool {
|
|||||||
return affected != 0
|
return affected != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserInfo(userId string, scope string, aud string, host string) (*Userinfo, error) {
|
||||||
|
user := GetUser(userId)
|
||||||
|
if user == nil {
|
||||||
|
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
|
||||||
|
}
|
||||||
|
origin := conf.GetConfigString("origin")
|
||||||
|
_, originBackend := getOriginFromHost(host)
|
||||||
|
if origin != "" {
|
||||||
|
originBackend = origin
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := Userinfo{
|
||||||
|
Sub: user.Id,
|
||||||
|
Iss: originBackend,
|
||||||
|
Aud: aud,
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "profile") {
|
||||||
|
resp.Name = user.Name
|
||||||
|
resp.DisplayName = user.DisplayName
|
||||||
|
resp.Avatar = user.Avatar
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "email") {
|
||||||
|
resp.Email = user.Email
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "address") {
|
||||||
|
resp.Address = user.Location
|
||||||
|
}
|
||||||
|
if strings.Contains(scope, "phone") {
|
||||||
|
resp.Phone = user.Phone
|
||||||
|
}
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func LinkUserAccount(user *User, field string, value string) bool {
|
func LinkUserAccount(user *User, field string, value string) bool {
|
||||||
return SetUserField(user, field, value)
|
return SetUserField(user, field, value)
|
||||||
}
|
}
|
||||||
|
@ -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{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
96
pp/alipay.go
Normal file
96
pp/alipay.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// 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 pp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/go-pay/gopay"
|
||||||
|
"github.com/go-pay/gopay/alipay"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlipayPaymentProvider struct {
|
||||||
|
Client *alipay.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
|
||||||
|
pp := &AlipayPaymentProvider{}
|
||||||
|
|
||||||
|
client, err := alipay.NewClient(appId, appPrivateKey, true)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = client.SetCertSnByContent([]byte(appPublicKey), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pp.Client = client
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
bm := gopay.BodyMap{}
|
||||||
|
|
||||||
|
bm.Set("providerName", providerName)
|
||||||
|
bm.Set("productName", productName)
|
||||||
|
|
||||||
|
bm.Set("return_url", returnUrl)
|
||||||
|
bm.Set("notify_url", notifyUrl)
|
||||||
|
|
||||||
|
bm.Set("subject", productDisplayName)
|
||||||
|
bm.Set("out_trade_no", paymentName)
|
||||||
|
bm.Set("total_amount", getPriceString(price))
|
||||||
|
|
||||||
|
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return payUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||||
|
bm, err := alipay.ParseNotifyToBodyMap(request)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
providerName := bm.Get("providerName")
|
||||||
|
productName := bm.Get("productName")
|
||||||
|
|
||||||
|
productDisplayName := bm.Get("subject")
|
||||||
|
paymentName := bm.Get("out_trade_no")
|
||||||
|
price := util.ParseFloat(bm.Get("total_amount"))
|
||||||
|
|
||||||
|
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return "", "", 0, "", "", fmt.Errorf("VerifySignWithCert() failed: %v", ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
332
pp/gc.go
Normal file
332
pp/gc.go
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
// 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 pp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GcPaymentProvider struct {
|
||||||
|
Xmpch string
|
||||||
|
SecretKey string
|
||||||
|
Host string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GcPayReqInfo struct {
|
||||||
|
OrderDate string `json:"orderdate"`
|
||||||
|
OrderNo string `json:"orderno"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
Xmpch string `json:"xmpch"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
ReturnUrl string `json:"return_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 {
|
||||||
|
Jylsh string `json:"jylsh"`
|
||||||
|
Amount string `json:"amount"`
|
||||||
|
PayerId string `json:"payerid"`
|
||||||
|
PayerName string `json:"payername"`
|
||||||
|
PayUrl string `json:"payurl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GcNotifyRespInfo struct {
|
||||||
|
Xmpch string `json:"xmpch"`
|
||||||
|
OrderDate string `json:"orderdate"`
|
||||||
|
OrderNo string `json:"orderno"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Jylsh string `json:"jylsh"`
|
||||||
|
TradeNo string `json:"tradeno"`
|
||||||
|
PayMethod string `json:"paymethod"`
|
||||||
|
OrderState string `json:"orderstate"`
|
||||||
|
ReturnType string `json:"return_type"`
|
||||||
|
PayerId string `json:"payerid"`
|
||||||
|
PayerName string `json:"payername"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GcRequestBody struct {
|
||||||
|
Op string `json:"op"`
|
||||||
|
Xmpch string `json:"xmpch"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
RequestTime string `json:"requesttime"`
|
||||||
|
Sign string `json:"sign"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GcResponseBody struct {
|
||||||
|
Op string `json:"op"`
|
||||||
|
Xmpch string `json:"xmpch"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
ReturnCode string `json:"return_code"`
|
||||||
|
ReturnMsg string `json:"return_msg"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
NotifyTime string `json:"notifytime"`
|
||||||
|
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 {
|
||||||
|
pp := &GcPaymentProvider{}
|
||||||
|
|
||||||
|
pp.Xmpch = clientId
|
||||||
|
pp.SecretKey = clientSecret
|
||||||
|
pp.Host = host
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
var resp *http.Response
|
||||||
|
var err error
|
||||||
|
|
||||||
|
contentType := "text/plain;charset=UTF-8"
|
||||||
|
body := bytes.NewReader(postBytes)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", pp.Host, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", contentType)
|
||||||
|
|
||||||
|
resp, err = client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
err := Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
|
respBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return respBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
|
||||||
|
payReqInfo := GcPayReqInfo{
|
||||||
|
OrderDate: util.GenerateSimpleTimeId(),
|
||||||
|
OrderNo: paymentName,
|
||||||
|
Amount: getPriceString(price),
|
||||||
|
Xmpch: pp.Xmpch,
|
||||||
|
Body: productDisplayName,
|
||||||
|
ReturnUrl: returnUrl,
|
||||||
|
NotifyUrl: notifyUrl,
|
||||||
|
Remark1: payerName,
|
||||||
|
Remark2: productName,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(payReqInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
body := GcRequestBody{
|
||||||
|
Op: "OrderCreate",
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var payRespInfo GcPayRespInfo
|
||||||
|
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return payRespInfo.PayUrl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
|
||||||
|
reqBody := GcRequestBody{}
|
||||||
|
m, err := url.ParseQuery(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody.Op = m["op"][0]
|
||||||
|
reqBody.Xmpch = m["xmpch"][0]
|
||||||
|
reqBody.Version = m["version"][0]
|
||||||
|
reqBody.Data = m["data"][0]
|
||||||
|
reqBody.RequestTime = m["requesttime"][0]
|
||||||
|
reqBody.Sign = m["sign"][0]
|
||||||
|
|
||||||
|
notifyReqInfoBytes, err := base64.StdEncoding.DecodeString(reqBody.Data)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var notifyRespInfo GcNotifyRespInfo
|
||||||
|
err = json.Unmarshal(notifyReqInfoBytes, ¬ifyRespInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", 0, "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
providerName := ""
|
||||||
|
productName := ""
|
||||||
|
|
||||||
|
productDisplayName := ""
|
||||||
|
paymentName := notifyRespInfo.OrderNo
|
||||||
|
price := notifyRespInfo.Amount
|
||||||
|
|
||||||
|
if notifyRespInfo.OrderState != "1" {
|
||||||
|
return "", "", 0, "", "", fmt.Errorf("error order state: %s", notifyRespInfo.OrderDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
32
pp/provider.go
Normal file
32
pp/provider.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package pp
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type PaymentProvider interface {
|
||||||
|
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)
|
||||||
|
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 {
|
||||||
|
if typ == "Alipay" {
|
||||||
|
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
|
||||||
|
} else if typ == "GC" {
|
||||||
|
return NewGcPaymentProvider(appId, clientSecret, host)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
25
pp/util.go
Normal file
25
pp/util.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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 pp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPriceString(price float64) string {
|
||||||
|
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
|
||||||
|
return priceString
|
||||||
|
}
|
@ -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,17 +54,17 @@ func isAddressOpen(address string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getProxyHttpClient() *http.Client {
|
func getProxyHttpClient() *http.Client {
|
||||||
httpProxy := beego.AppConfig.String("httpProxy")
|
sock5Proxy := conf.GetConfigString("sock5Proxy")
|
||||||
if httpProxy == "" {
|
if sock5Proxy == "" {
|
||||||
return &http.Client{}
|
return &http.Client{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isAddressOpen(httpProxy) {
|
if !isAddressOpen(sock5Proxy) {
|
||||||
return &http.Client{}
|
return &http.Client{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
||||||
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
|
dialer, err := proxy.SOCKS5("tcp", sock5Proxy, nil, proxy.Direct)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/context"
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/casdoor/casdoor/authz"
|
"github.com/casdoor/casdoor/authz"
|
||||||
@ -57,6 +58,8 @@ func getSubject(ctx *context.Context) (string, string) {
|
|||||||
|
|
||||||
func getObject(ctx *context.Context) (string, string) {
|
func getObject(ctx *context.Context) (string, string) {
|
||||||
method := ctx.Request.Method
|
method := ctx.Request.Method
|
||||||
|
path := ctx.Request.URL.Path
|
||||||
|
|
||||||
if method == http.MethodGet {
|
if method == http.MethodGet {
|
||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
@ -78,6 +81,14 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
//panic(err)
|
//panic(err)
|
||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if path == "/api/delete-resource" {
|
||||||
|
tokens := strings.Split(obj.Name, "/")
|
||||||
|
if len(tokens) >= 5 {
|
||||||
|
obj.Name = tokens[4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return obj.Owner, obj.Name
|
return obj.Owner, obj.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,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)
|
||||||
|
@ -29,10 +29,8 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
|
|
||||||
// GET parameter like "/page?access_token=123" or
|
// GET parameter like "/page?access_token=123" or
|
||||||
// HTTP Bearer token like "Authorization: Bearer 123"
|
// HTTP Bearer token like "Authorization: Bearer 123"
|
||||||
accessToken := ctx.Input.Query("accessToken")
|
accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx))
|
||||||
if accessToken == "" {
|
|
||||||
accessToken = parseBearerToken(ctx)
|
|
||||||
}
|
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
token := object.GetTokenByAccessToken(accessToken)
|
token := object.GetTokenByAccessToken(accessToken)
|
||||||
if token == nil {
|
if token == nil {
|
||||||
@ -62,7 +60,7 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
// "/page?username=abc&password=123"
|
// "/page?username=abc&password=123"
|
||||||
userId = ctx.Input.Query("username")
|
userId = ctx.Input.Query("username")
|
||||||
password := ctx.Input.Query("password")
|
password := ctx.Input.Query("password")
|
||||||
if userId != "" && password != "" {
|
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
|
||||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||||
_, msg := object.CheckUserPassword(owner, name, password)
|
_, msg := object.CheckUserPassword(owner, name, password)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
|
@ -54,7 +54,7 @@ func getUserByClientIdSecret(ctx *context.Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RecordMessage(ctx *context.Context) {
|
func RecordMessage(ctx *context.Context) {
|
||||||
if ctx.Request.URL.Path == "/api/login" {
|
if ctx.Request.URL.Path == "/api/login" || ctx.Request.URL.Path == "/api/signup" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)})
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
@ -127,6 +128,8 @@ func initAPI() {
|
|||||||
beego.Router("/api/login/oauth/code", &controllers.ApiController{}, "POST:GetOAuthCode")
|
beego.Router("/api/login/oauth/code", &controllers.ApiController{}, "POST:GetOAuthCode")
|
||||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||||
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
|
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
|
||||||
|
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
|
||||||
|
beego.Router("/api/login/oauth/logout", &controllers.ApiController{}, "GET:TokenLogout")
|
||||||
|
|
||||||
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
||||||
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
||||||
@ -142,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")
|
||||||
@ -149,15 +153,35 @@ func initAPI() {
|
|||||||
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
|
||||||
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
|
||||||
|
|
||||||
|
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
|
||||||
|
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
|
||||||
|
beego.Router("/api/update-product", &controllers.ApiController{}, "POST:UpdateProduct")
|
||||||
|
beego.Router("/api/add-product", &controllers.ApiController{}, "POST:AddProduct")
|
||||||
|
beego.Router("/api/delete-product", &controllers.ApiController{}, "POST:DeleteProduct")
|
||||||
|
beego.Router("/api/buy-product", &controllers.ApiController{}, "POST:BuyProduct")
|
||||||
|
|
||||||
beego.Router("/api/get-payments", &controllers.ApiController{}, "GET:GetPayments")
|
beego.Router("/api/get-payments", &controllers.ApiController{}, "GET:GetPayments")
|
||||||
|
beego.Router("/api/get-user-payments", &controllers.ApiController{}, "GET:GetUserPayments")
|
||||||
beego.Router("/api/get-payment", &controllers.ApiController{}, "GET:GetPayment")
|
beego.Router("/api/get-payment", &controllers.ApiController{}, "GET:GetPayment")
|
||||||
beego.Router("/api/update-payment", &controllers.ApiController{}, "POST:UpdatePayment")
|
beego.Router("/api/update-payment", &controllers.ApiController{}, "POST:UpdatePayment")
|
||||||
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/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("/api/certs", &controllers.RootController{}, "*:GetOidcCert")
|
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")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 == "/" {
|
||||||
|
1730
swagger/swagger.json
1730
swagger/swagger.json
File diff suppressed because it is too large
Load Diff
1151
swagger/swagger.yml
1151
swagger/swagger.yml
File diff suppressed because it is too large
Load Diff
@ -20,10 +20,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var rePhoneCn *regexp.Regexp
|
var rePhoneCn *regexp.Regexp
|
||||||
|
var rePhone *regexp.Regexp
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// https://learnku.com/articles/31543
|
// https://learnku.com/articles/31543
|
||||||
rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
|
rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
|
||||||
|
rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsEmailValid(email string) bool {
|
func IsEmailValid(email string) bool {
|
||||||
@ -34,3 +36,7 @@ func IsEmailValid(email string) bool {
|
|||||||
func IsPhoneCnValid(phone string) bool {
|
func IsPhoneCnValid(phone string) bool {
|
||||||
return rePhoneCn.MatchString(phone)
|
return rePhoneCn.MatchString(phone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMaskedPhone(phone string) string {
|
||||||
|
return rePhone.ReplaceAllString(phone, "$1****$2")
|
||||||
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user