Compare commits

..

44 Commits

Author SHA1 Message Date
ERIK
121c212a0a Revert "fix: Using Crowdin action implement translation sync" 2021-07-19 12:11:29 +08:00
ERIK
3d5af2708f Merge branch 'master' into master 2021-07-19 12:10:22 +08:00
casbin-bot
cbdc57aaa7 Sync from crowdin 2021-07-19 02:16:13 +00:00
ErikQQY
a482faae16 fix: Using Crowdin action implement translation sync
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-19 00:41:58 +08:00
Yang Luo
eeda5e5629 Improve footer. 2021-07-18 17:50:50 +08:00
Yang Luo
bce606896f Use signup app for GetApplicationByUser(). 2021-07-18 17:50:38 +08:00
Yang Luo
4ff4961312 Merge pull request #153 from Kininaru/auto-login
feat: auto login session will expire after 24h
2021-07-18 11:30:00 +08:00
Kininaru
b2dd8f4fa3 feat: session without autosignin will expire
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-07-18 07:54:49 +08:00
Kininaru
ae9ebd2de1 refactor: SessionUser -> SessionUsername
Signed-off-by: Kininaru <shiftregister233@outlook.com>
2021-07-18 07:15:22 +08:00
Yang Luo
441d69f4ac Merge pull request #177 from oranges-eating/master
fix: Fix the user list cannot be displayed completely
2021-07-17 22:55:00 +08:00
killer
1550956c8e fix: Fix the user list cannot be displayed completely
Signed-off-by: killer <1533063601@qq.com>
2021-07-17 14:29:33 +08:00
casbin-bot
0967217778 Sync from crowdin 2021-07-17 06:14:09 +00:00
WindSpiritSR
3820a0185c feat: support LDAP (#160)
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2021-07-17 14:13:00 +08:00
casbin-bot
3905df8546 Sync from crowdin 2021-07-17 01:33:19 +00:00
Yang Luo
e16ff7f4a9 Merge pull request #174 from WindSpiritSR/patch-fix-err
fix: db data init and frontend warning
2021-07-17 09:32:09 +08:00
WindSpiritSR
bbec117fd6 fix: db data init and frontend warning
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2021-07-16 23:04:39 +08:00
casbin-bot
8ae4e30620 Sync from crowdin 2021-07-16 14:38:41 +00:00
Yang Luo
814ab9c11f Merge pull request #175 from turbodog03/master
feat: add language select box and background color change when hover
2021-07-16 22:37:31 +08:00
turbodog03
78c5757d85 feat: add language select box and background color change when hover
Signed-off-by: turbodog03 <63595854+turbodog03@users.noreply.github.com>
2021-07-16 22:32:29 +08:00
Yang Luo
ee92a9b7b4 Merge pull request #162 from oranges-eating/master
feat: add run casdoor through docker
2021-07-14 17:24:33 +08:00
killer
21eb1e8037 feat: add run casdoor through docker
Signed-off-by: killer <1533063601@qq.com>
2021-07-14 14:44:02 +08:00
casbin-bot
2297251dd7 Sync from crowdin 2021-07-13 15:06:48 +00:00
Yang Luo
532dc75033 Merge pull request #164 from MRGUOKING/door-dev1
fix: The count-down will be disabled
2021-07-13 23:05:25 +08:00
MRGUOKING
e7de0e4132 fix: The count-down will be disabled
Signed-off-by: MRGUOKING <420919469@qq.com>

The count-down will be disabled after sending the code

Signed-off-by: MRGUOKING <420919469@qq.com>
2021-07-13 21:02:14 +08:00
Yang Luo
7e6af1e858 Fix adding provider UI bug. 2021-07-12 01:08:48 +08:00
Yang Luo
cca6a635c3 Add defaultAvatar column. 2021-07-12 00:00:59 +08:00
Yang Luo
a355798a79 Replace getDefaultApplication() with getUserApplication(). 2021-07-11 23:51:01 +08:00
casbin-bot
14445e7c3b Sync from crowdin 2021-07-10 10:05:24 +00:00
Yang Luo
aae09648e9 Merge pull request #150 from oranges-eating/master
feat: Add log table and record all user behaviors into the table, add UI to view logs
2021-07-10 18:04:11 +08:00
killer
a95b168a54 feat: add UI to view logs
Signed-off-by: killer <1533063601@qq.com>
2021-07-10 17:27:21 +08:00
casbin-bot
834693a0a2 Sync from crowdin 2021-07-10 02:32:12 +00:00
Yang Luo
7b1386764c Improve translation. 2021-07-10 10:31:01 +08:00
casbin-bot
b7077c61be Sync from crowdin 2021-07-09 18:19:56 +00:00
Yang Luo
4c27ae68fb Merge pull request #159 from ErikQQY/master
fix: Change commit author to casbin bot
2021-07-10 02:18:47 +08:00
ErikQQY
1576c01d8f fix: Change commit author to casbin bot
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-10 02:09:26 +08:00
Yang Luo
968eccf193 Restrict app edit page values. 2021-07-10 01:19:31 +08:00
Yang Luo
8eb5a7b163 Restrict rule options. 2021-07-10 00:08:43 +08:00
Yang Luo
4a6ec33b9c Refactor out Setting.getDeduplicatedArray() 2021-07-10 00:05:40 +08:00
Yang Luo
f011dc06d8 Refactor out Setting.getArrayItem() 2021-07-09 23:05:50 +08:00
Yang Luo
d409de6591 Merge pull request #156 from ErikQQY/master
fix: Update yarn.lock
2021-07-09 22:39:49 +08:00
ErikQQY
c9b6bc79a2 fix: Update yarn.lock
Signed-off-by: ErikQQY <2283984853@qq.com>
2021-07-09 22:07:51 +08:00
Yang Luo
dd4f197454 Merge pull request #144 from sh1luo/feat-add-linkedin-provider
feat: add linkedin provider
2021-07-09 17:07:29 +08:00
hsluoyz
576d0f12dd Sync from crowdin 2021-07-09 08:17:04 +00:00
wasabi
c37e5d044a feat: add linkedin provider
Signed-off-by: wasabi <690898835@qq.com>
2021-07-04 16:28:52 +08:00
220 changed files with 4794 additions and 15096 deletions

View File

@@ -1 +0,0 @@
web/node_modules/

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
*.go linguist-detectable=true
*.js linguist-detectable=false

12
.github/semantic.yml vendored
View File

@@ -1,12 +0,0 @@
# Always validate the PR title AND all the commits
titleAndCommits: true
# Require at least one commit to be valid
# this is only relevant when using commitsOnly: true or titleAndCommits: true,
# which validate all commits by default
anyCommit: true
# Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowMergeCommits: false
# Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowRevertCommits: false

View File

@@ -1,94 +0,0 @@
name: Build
on: [push, pull_request]
jobs:
frontend:
name: Front-end
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14.17.0'
- run: yarn install && CI=false yarn run build
working-directory: ./web
backend:
name: Back-end
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.16.5'
- run: go version
- name: Build
run: |
go build -race -ldflags "-extldflags '-static'"
working-directory: ./
release-and-push:
name: Release And Push
runs-on: ubuntu-latest
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
needs: [ frontend, backend ]
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 12
- name: Fetch Previous version
id: get-previous-tag
uses: actions-ecosystem/action-get-latest-tag@v1
- name: Release
run: yarn global add semantic-release@17.4.4 && semantic-release
env:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
- name: Fetch Current version
id: get-current-tag
uses: actions-ecosystem/action-get-latest-tag@v1
- name: Decide Should_Push Or Not
id: should_push
run: |
old_version=${{steps.get-previous-tag.outputs.tag}}
new_version=${{steps.get-current-tag.outputs.tag }}
old_array=(${old_version//\./ })
new_array=(${new_version//\./ })
if [ ${old_array[0]} != ${new_array[0]} ]
then
echo ::set-output name=push::'true'
elif [ ${old_array[1]} != ${new_array[1]} ]
then
echo ::set-output name=push::'true'
else
echo ::set-output name=push::'false'
fi
- name: Log in to Docker Hub
uses: docker/login-action@v1
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' &&steps.should_push.outputs.push=='true'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push to Docker Hub
uses: docker/build-push-action@v2
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
push: true
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest

View File

@@ -1,35 +1,38 @@
name: Crowdin Action
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
jobs:
synchronize-with-crowdin:
build:
runs-on: ubuntu-latest
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
env:
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
strategy:
matrix:
node-version: [12.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- name: Checkout
uses: actions/checkout@v2
- name: crowdin action
uses: crowdin/github-action@1.2.0
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
upload_translations: true
node-version: ${{ matrix.node-version }}
- run: |
cd web
npm install
npm run crowdin:sync
download_translations: true
push_translations: true
commit_message: 'refactor: New Crowdin translations by Github Action'
localization_branch_name: l10n_crowdin_action
create_pull_request: true
pull_request_title: 'refactor: New Crowdin translations'
crowdin_branch_name: l10n_branch
config: './web/crowdin.yml'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_PROJECT_ID: '463556'
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Sync from crowdin
file_pattern: web/src/locales/*
commit_author: ${{ secrets.CROWDIN_BOT }}

2
.gitignore vendored
View File

@@ -17,12 +17,10 @@
.idea/
*.iml
.vscode/
tmp/
tmpFiles/
*.tmp
logs/
files/
lastupdate.tmp
commentsRouter*.go

View File

@@ -1,23 +0,0 @@
{
"debug": true,
"branches": [
"+([0-9])?(.{+([0-9]),x}).x",
"master",
{
"name": "rc"
},
{
"name": "beta",
"prerelease": true
},
{
"name": "alpha",
"prerelease": true
}
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github"
]
}

View File

@@ -1,22 +0,0 @@
FROM golang:1.17.5 AS BACK
WORKDIR /go/src/casdoor
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . \
&& apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
FROM node:16.13.0 AS FRONT
WORKDIR /web
COPY ./web .
RUN yarn config set registry https://registry.npm.taobao.org
RUN yarn install && yarn run build
FROM alpine:latest
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add curl
LABEL MAINTAINER="https://casdoor.org/"
COPY --from=BACK /go/src/casdoor/ ./
COPY --from=BACK /usr/bin/wait-for-it ./
RUN mkdir -p web/build && apk add --no-cache bash coreutils
COPY --from=FRONT /web/build /web/build
CMD ./wait-for-it db:3306 -- ./server

227
README.md
View File

@@ -1,172 +1,161 @@
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
<p align="center">
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
<a href="https://hub.docker.com/r/casbin/casdoor">
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
</a>
<a href="https://github.com/casbin/casdoor/actions/workflows/build.yml">
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casbin/jcasbin/workflows/build/badge.svg?style=flat-square">
</a>
<a href="https://github.com/casbin/casdoor/releases/latest">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
</a>
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
</a>
</p>
Casdoor
====
<p align="center">
<a href="https://goreportcard.com/report/github.com/casbin/casdoor">
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casbin/casdoor?style=flat-square">
</a>
<a href="https://github.com/casbin/casdoor/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license">
</a>
<a href="https://github.com/casbin/casdoor/issues">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
</a>
<a href="#">
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
</a>
<a href="https://github.com/casbin/casdoor/network">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
</a>
</p>
Casdoor is a UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.
## Online demo
Deployed site: https://door.casbin.com/
### Casdoor
## Quick Start
Casdoor is the authentication server. It serves both the web UI and the login requests from the application users.
Run your own casdoor program in a few minutes:smiley:
- Deployed site: https://door.casbin.com/
- Source code: https://github.com/casbin/casdoor (this repo)
### Download
Global admin login:
There are two methods, get code via go subcommand `get`:
- Username: `admin`
- Password: `123`
```shell
go get github.com/casbin/casdoor
```
### Web application
or `git`:
Casbin-OA is one of our applications that use Casdoor as authentication.
```bash
git clone https://github.com/casbin/casdoor
```
- Deployed site: https://oa.casbin.com/
- Source code: https://github.com/casbin/casbin-oa
Finally, change directory:
## Architecture
```bash
cd casdoor/
```
Casdoor contains 2 parts:
We provide two start up methods for all kinds of users.
Name | Description | Language | Source code
----|------|----|----
Frontend | Web frontend UI for Casdoor | Javascript + React | https://github.com/casbin/casdoor/tree/master/web
Backend | RESTful API backend for Casdoor | Golang + Beego + MySQL | https://github.com/casbin/casdoor
### Manual
## Installation
#### Simple configuration
Casdoor requires a running Relational database to be operational.Thus you need to modify configuration to point out the location of database.
- Get code via `go get`:
Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which follows this format:
```shell
go get github.com/casbin/casdoor
```
```bash
username:password@tcp(database_ip:database_port)/
```
or `git clone`:
#### Run
```shell
git clone https://github.com/casbin/casdoor
```
Casdoor provides two run modes, the difference is binary size and user prompt.
## Run through Docker
- Install Docker and Docker-compose,you see [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/)
- vi casdoor/conf/app.conf
- Modify dataSourceName = root:123@tcp(localhost:3306)/ to dataSourceName = root:123@tcp(db:3306)/
- Execute the following command
```shell
docker-compose up
```
- Open browser:
##### Dev Mode
http://localhost:8000/
Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
## Run (Dev Environment)
```bash
cd web/ && yarn && yarn run start
```
*❗ A word of caution ❗: Casdoor's front-end is built using yarn. You should use `yarn` instead of `npm`. It has a potential failure during building the files if you use `npm`.*
- Run backend (in port 8000):
Then build back-end binary file, change directory to root(Relative to casdoor):
```shell
go run main.go
```
```bash
go run main.go
```
- Run frontend (in the same machine's port 7001):
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
**But make sure you always request the backend port 8000 when you are using SDKs.**
```shell
cd web
## npm
npm install
npm run start
## yarn
yarn install
yarn run start
```
##### Production Mode
- Open browser:
Edit `conf/app.conf`, set `runmode=prod`. Firstly build front-end files:
http://localhost:7001/
```bash
cd web/ && yarn && yarn run build
```
## Run (Production Environment)
Then build back-end binary file, change directory to root(Relative to casdoor):
- build static pages:
```bash
go build main.go && sudo ./main
```
```
cd web
## npm
npm run build
## yarn
yarn run build
## back to casdoor directory
cd ..
```
> Notice, you should visit back-end port, default 8000. Now try to visit **http://SERVER_IP:8000/**
- build and run go code:
### Docker
```
go build
./casdoor
```
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
Now, Casdoor is running on port 8000. You can access Casdoor pages directly in your browser, or you can setup a reverse proxy to hold your domain name, SSL, etc.
#### Simple configuration
For the convenience of your first attempt, docker-compose.yml contains commands to start a database via docker.
## Config
Thus edit `conf/app.conf` to point out the location of database(db:3306), modify `dataSourceName` to the fixed content:
- Setup database (MySQL):
```bash
dataSourceName = root:123456@tcp(db:3306)/
```
Casdoor will store its users, nodes and topics informations in a MySQL database named: `casdoor`, will create it if not existed. The DB connection string can be specified at: https://github.com/casbin/casdoor/blob/master/conf/app.conf
> If you need to modify `conf/app.conf`, you need to re-run `docker-compose up`.
```ini
db = mysql
dataSourceName = root:123@tcp(localhost:3306)/
dbName = casdoor
```
#### Run
- Setup database (Postgres):
```bash
docker-compose up
```
Since we must choose a database when opening Postgres with xorm, you should prepare a database manually before running Casdoor. Let's assume that you have already prepared a database called `casdoor`, then you should specify `app.conf` like this:
That's it! Try to visit http://localhost:8000/. :small_airplane:
``` ini
db = postgres
dataSourceName = "user=postgres password=xxx sslmode=disable dbname="
dbName = casdoor
```
### Docker Hub
**Please notice:** You can add Postgres parameters in `dataSourceName`, but please make sure that `dataSourceName` ends with `dbname=`. Or database adapter may crash when you launch Casdoor.
This method requires [docker](https://docs.docker.com/get-docker/) and [docker-compose](https://docs.docker.com/compose/install/) to be installed first.
Casdoor uses XORM to connect to DB, so all DBs supported by XORM can also be used.
```bash
docker pull casbin/casdoor
```
- Github corner
## Detailed documentation
We added a Github icon in the upper right corner, linking to your Github repository address.
You could set `ShowGithubCorner` to hidden it.
We also provide a complete [document](https://casdoor.org/) as a reference.
Configuration (`web/src/commo/Conf.js`):
## Other examples
```javascript
export const ShowGithubCorner = true
These all use casdoor as a centralized authentication platform.
export const GithubRepo = "https://github.com/casbin/casdoor" //your github repository
```
- [Casnode](https://github.com/casbin/casnode): Next-generation forum software based on React + Golang.
- [Casbin-OA](https://github.com/casbin/casbin-oa): A full-featured OA(Office Assistant) system.
- ......
- OSS conf
## Contribute
We use an OSS to store and provide user avatars. You must modify the file `conf/oss.conf` to tell the backend your OSS info. For OSS providers, we support Aliyun(`[aliyun]`), awss3(`[s3]`) now.
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
```
[provider]
accessId = id
accessKey = key
bucket = bucket
endpoint = endpoint
```
### I18n notice
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-web) as translating platform and i18next as translating tool. When you add some words using i18next in the ```web/``` directory, please remember to add what you have added to the ```web/src/locales/en/data.json``` file.
## License
[Apache-2.0](https://github.com/casbin/casdoor/blob/master/LICENSE)
Please fill out this conf correctly, or the avatar server won't work!

View File

@@ -18,7 +18,6 @@ import (
"github.com/astaxie/beego"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casdoor/conf"
xormadapter "github.com/casbin/xorm-adapter/v2"
stringadapter "github.com/qiangmzsx/string-adapter/v2"
)
@@ -28,7 +27,7 @@ var Enforcer *casbin.Enforcer
func InitAuthz() {
var err error
a, err := xormadapter.NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), true)
a, err := xormadapter.NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName")+beego.AppConfig.String("dbName"), true)
if err != nil {
panic(err)
}
@@ -72,7 +71,6 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
if true {
ruleText := `
p, built-in, *, *, *, *, *
p, app, *, *, *, *, *
p, *, *, POST, /api/signup, *, *
p, *, *, POST, /api/get-email-and-phone, *, *
p, *, *, POST, /api/login, *, *
@@ -86,18 +84,12 @@ p, *, *, GET, /api/get-user, *, *
p, *, *, GET, /api/get-organizations, *, *
p, *, *, GET, /api/get-user-application, *, *
p, *, *, GET, /api/get-default-providers, *, *
p, *, *, GET, /api/get-resources, *, *
p, *, *, POST, /api/upload-avatar, *, *
p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *
p, *, *, GET, /api/get-human-check, *, *
p, *, *, POST, /api/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, *
p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, *, /api/certs, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
`
sa := stringadapter.NewAdapter(ruleText)

View File

@@ -4,14 +4,8 @@ runmode = dev
SessionOn = true
copyrequestbody = true
driverName = mysql
dataSourceName = root:123456@tcp(localhost:3306)/
dataSourceName = root:123@tcp(localhost:3306)/
dbName = casdoor
redisEndpoint =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
httpProxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
logPostOnly = true
origin = "https://door.casbin.com"
verificationCodeTimeout = 10

6
conf/oss.conf Normal file
View File

@@ -0,0 +1,6 @@
[provider]
endpoint = endpoint
accessId = id
accessKey = key
domain = domain
bucket = bucket

View File

@@ -15,12 +15,15 @@
package controllers
import (
"encoding/base64"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/original"
"github.com/casdoor/casdoor/util"
)
const (
@@ -38,7 +41,6 @@ type RequestForm struct {
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
Region string `json:"region"`
Application string `json:"application"`
Provider string `json:"provider"`
@@ -52,15 +54,11 @@ type RequestForm struct {
PhonePrefix string `json:"phonePrefix"`
AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"`
SamlResponse string `json:"samlResponse"`
}
type Response struct {
Status string `json:"status"`
Msg string `json:"msg"`
Sub string `json:"sub"`
Data interface{} `json:"data"`
Data2 interface{} `json:"data2"`
}
@@ -73,8 +71,6 @@ type HumanCheck struct {
CaptchaImage interface{} `json:"captchaImage"`
}
// Signup
// @Tag Login API
// @Title Signup
// @Description sign up a new user
// @Param username formData string true "The username to sign up"
@@ -82,8 +78,10 @@ type HumanCheck struct {
// @Success 200 {object} controllers.Response The Response object
// @router /signup [post]
func (c *ApiController) Signup() {
var resp Response
if c.GetSessionUsername() != "" {
c.ResponseError("Please sign out first before signing up", c.GetSessionUsername())
c.ResponseErrorWithData("Please sign out first before signing up", c.GetSessionUsername())
return
}
@@ -99,6 +97,26 @@ func (c *ApiController) Signup() {
return
}
if application.IsSignupItemEnabled("Email") {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email%s", checkResult))
return
}
}
var checkPhone string
if application.IsSignupItemEnabled("Phone") {
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Phone%s", checkResult))
return
}
}
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
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)
if msg != "" {
@@ -106,26 +124,6 @@ func (c *ApiController) Signup() {
return
}
if application.IsSignupItemVisible("Email") && form.Email != "" {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Email: %s", checkResult))
return
}
}
var checkPhone string
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode)
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("Phone: %s", checkResult))
return
}
}
userId := fmt.Sprintf("%s/%s", form.Organization, form.Username)
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization)
@@ -151,24 +149,18 @@ func (c *ApiController) Signup() {
Phone: form.Phone,
Address: []string{},
Affiliation: form.Affiliation,
Region: form.Region,
Score: getInitScore(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
SignupApplication: application.Name,
Properties: map[string]string{},
}
affected := object.AddUser(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))
return
if affected {
original.AddUserToOriginalDatabase(user)
}
object.AddUserToOriginalDatabase(user)
if application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId())
@@ -179,28 +171,31 @@ func (c *ApiController) Signup() {
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId)
resp = Response{Status: "ok", Msg: "", Data: userId}
c.Data["json"] = resp
c.ServeJSON()
}
// Logout
// @Title Logout
// @Tag Login API
// @Description logout the current user
// @Success 200 {object} controllers.Response The Response object
// @router /logout [post]
func (c *ApiController) Logout() {
var resp Response
user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ResponseOk(user)
resp = Response{Status: "ok", Msg: "", Data: user}
c.Data["json"] = resp
c.ServeJSON()
}
// GetAccount
// @Title GetAccount
// @Tag Account API
// @Description get the details of the current account
// @Success 200 {object} controllers.Response The Response object
// @router /get-account [get]
@@ -210,27 +205,63 @@ func (c *ApiController) GetAccount() {
return
}
var resp Response
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
resp := Response{Status: "error", Msg: fmt.Sprintf("The user: %s doesn't exist", userId)}
c.Data["json"] = resp
c.ServeJSON()
return
}
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
resp := Response{
Status: "ok",
Sub: user.Id,
Data: user,
Data2: organization,
}
organization := object.GetOrganizationByUser(user)
resp = Response{Status: "ok", Msg: "", Data: user, Data2: organization}
c.Data["json"] = resp
c.ServeJSON()
}
// @Title UploadAvatar
// @Description upload avatar
// @Param avatarfile formData string true "The base64 encode of avatarfile"
// @Param password formData string true "The password"
// @Success 200 {object} controllers.Response The Response object
// @router /upload-avatar [post]
func (c *ApiController) UploadAvatar() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
var resp Response
user := object.GetUser(userId)
avatarBase64 := c.Ctx.Request.Form.Get("avatarfile")
index := strings.Index(avatarBase64, ",")
if index < 0 || avatarBase64[0:index] != "data:image/png;base64" {
resp = Response{Status: "error", Msg: "File encoding error"}
c.Data["json"] = resp
c.ServeJSON()
return
}
dist, _ := base64.StdEncoding.DecodeString(avatarBase64[index+1:])
msg := object.UploadAvatar(user.GetId(), dist)
if msg != "" {
resp = Response{Status: "error", Msg: msg}
c.Data["json"] = resp
c.ServeJSON()
return
}
user.Avatar = fmt.Sprintf("%s%s.png?time=%s", object.GetAvatarPath(), user.GetId(), util.GetCurrentUnixTime())
object.UpdateUser(user.GetId(), user)
resp = Response{Status: "ok", Msg: ""}
c.Data["json"] = resp
c.ServeJSON()
}
// GetHumanCheck ...
// @Tag Login API
// @Title GetHumancheck
// @router /api/get-human-check [get]
func (c *ApiController) GetHumanCheck() {
c.Data["json"] = HumanCheck{Type: "none"}

View File

@@ -17,36 +17,22 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
)
// GetApplications
// @Title GetApplications
// @Tag Application API
// @Description get all applications
// @Param owner query string true "The owner of applications."
// @Success 200 {array} object.Application The Response object
// @router /get-applications [get]
func (c *ApiController) GetApplications() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetApplications(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner)))
applications := object.GetPaginationApplications(owner, paginator.Offset(), limit)
c.ResponseOk(applications, paginator.Nums())
}
c.Data["json"] = object.GetApplications(owner)
c.ServeJSON()
}
// GetApplication
// @Title GetApplication
// @Tag Application API
// @Description get the detail of an application
// @Param id query string true "The id of the application."
// @Success 200 {object} object.Application The Response object
@@ -58,9 +44,7 @@ func (c *ApiController) GetApplication() {
c.ServeJSON()
}
// GetUserApplication
// @Title GetUserApplication
// @Tag Application API
// @Description get the detail of the user's application
// @Param id query string true "The id of the user"
// @Success 200 {object} object.Application The Response object
@@ -77,9 +61,7 @@ func (c *ApiController) GetUserApplication() {
c.ServeJSON()
}
// UpdateApplication
// @Title UpdateApplication
// @Tag Application API
// @Description update an application
// @Param id query string true "The id of the application"
// @Param body body object.Application true "The details of the application"
@@ -98,9 +80,7 @@ func (c *ApiController) UpdateApplication() {
c.ServeJSON()
}
// AddApplication
// @Title AddApplication
// @Tag Application API
// @Description add an application
// @Param body body object.Application true "The details of the application"
// @Success 200 {object} controllers.Response The Response object
@@ -116,9 +96,7 @@ func (c *ApiController) AddApplication() {
c.ServeJSON()
}
// DeleteApplication
// @Title DeleteApplication
// @Tag Application API
// @Description delete an application
// @Param body body object.Application true "The details of the application"
// @Success 200 {object} controllers.Response The Response object

View File

@@ -15,32 +15,29 @@
package controllers
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/idp"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/proxy"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func codeToResponse(code *object.Code) *Response {
if code.Code == "" {
return &Response{Status: "error", Msg: code.Message, Data: code.Code}
} else {
return &Response{Status: "ok", Msg: "", Data: code.Code}
}
return &Response{Status: "ok", Msg: "", Data: code.Code}
}
// 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) *Response {
userId := user.GetId()
resp := &Response{}
if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
@@ -51,8 +48,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
redirectUri := c.Input().Get("redirectUri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
nonce := c.Input().Get("nonce")
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce)
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state)
resp = codeToResponse(code)
if application.HasPromptPage() {
@@ -75,9 +72,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
return resp
}
// GetApplicationLogin ...
// @Title GetApplicationLogin
// @Tag Login API
// @Description get application login
// @Param clientId query string true "client id"
// @Param responseType query string true "response type"
@@ -87,6 +82,8 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// @Success 200 {object} controllers.api_controller.Response The Response object
// @router /update-application [get]
func (c *ApiController) GetApplicationLogin() {
var resp Response
clientId := c.Input().Get("clientId")
responseType := c.Input().Get("responseType")
redirectUri := c.Input().Get("redirectUri")
@@ -95,42 +92,37 @@ func (c *ApiController) GetApplicationLogin() {
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
if msg != "" {
c.ResponseError(msg, application)
resp = Response{Status: "error", Msg: msg, Data: application}
} else {
c.ResponseOk(application)
resp = Response{Status: "ok", Msg: "", Data: application}
}
c.Data["json"] = resp
c.ServeJSON()
}
func setHttpClient(idProvider idp.IdProvider, providerType string) {
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" {
idProvider.SetHttpClient(proxy.ProxyHttpClient)
} else {
idProvider.SetHttpClient(proxy.DefaultHttpClient)
}
}
// Login ...
// @Title Login
// @Tag Login API
// @Description login
// @Param oAuthParams query string true "oAuth parameters"
// @Param body body RequestForm true "Login information"
// @Success 200 {object} controllers.api_controller.Response The Response object
// @router /login [post]
func (c *ApiController) Login() {
resp := &Response{}
resp := &Response{Status: "null", Msg: ""}
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
c.ResponseError(err.Error())
resp = &Response{Status: "error", Msg: err.Error()}
c.Data["json"] = resp
c.ServeJSON()
return
}
if form.Username != "" {
if form.Type == ResponseTypeLogin {
if c.GetSessionUsername() != "" {
c.ResponseError("Please sign out first before signing in", c.GetSessionUsername())
resp = &Response{Status: "error", Msg: "Please log out first before signing in", Data: c.GetSessionUsername()}
c.Data["json"] = resp
c.ServeJSON()
return
}
}
@@ -140,146 +132,168 @@ func (c *ApiController) Login() {
if form.Password == "" {
var verificationCodeType string
var checkResult string
// check result through Email or Phone
if strings.Contains(form.Username, "@") {
if strings.Contains(form.Email, "@") {
verificationCodeType = "email"
checkResult = object.CheckVerificationCode(form.Username, form.Code)
} else {
verificationCodeType = "phone"
if len(form.PhonePrefix) == 0 {
responseText := fmt.Sprintf("%s%s", verificationCodeType, "No phone prefix")
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode)
if len(checkResult) != 0 {
responseText := fmt.Sprintf("Email%s", checkResult)
c.ResponseError(responseText)
return
}
} else {
verificationCodeType = "phone"
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Email)
checkResult := object.CheckVerificationCode(checkPhone, form.EmailCode)
if len(checkResult) != 0 {
responseText := fmt.Sprintf("Phone%s", checkResult)
c.ResponseError(responseText)
return
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
}
if len(checkResult) != 0 {
responseText := fmt.Sprintf("%s%s", verificationCodeType, checkResult)
c.ResponseError(responseText)
return
}
// disable the verification code
object.DisableVerificationCode(form.Username)
// get user
var userId string
if form.Username == "" {
userId, _ = c.RequireSignedIn()
} else {
userId = fmt.Sprintf("%s/%s", form.Organization, form.Username)
}
user = object.GetUserByFields(form.Organization, form.Username)
user = object.GetUser(userId)
if user == nil {
c.ResponseError("No such user.")
return
}
// disable the verification code
switch verificationCodeType {
case "email":
if user.Email != form.Email {
c.ResponseError("wrong email!")
}
object.DisableVerificationCode(form.Email)
break
case "phone":
if user.Phone != form.Email {
c.ResponseError("wrong phone!")
}
object.DisableVerificationCode(form.Email)
break
}
} else {
password := form.Password
user, msg = object.CheckUserPassword(form.Organization, form.Username, password)
user, msg = object.CheckUserLogin(form.Organization, form.Username, password)
}
if msg != "" {
resp = &Response{Status: "error", Msg: msg}
resp = &Response{Status: "error", Msg: msg, Data: ""}
} else {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
record.Username = user.Name
object.AddRecord(record)
}
} else if form.Provider != "" {
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", application.Organization))
provider := object.GetProvider(fmt.Sprintf("admin/%s", form.Provider))
providerItem := application.GetProviderItem(provider.Name)
if !providerItem.IsProviderVisible() {
c.ResponseError(fmt.Sprintf("The provider: %s is not enabled for the application", provider.Name))
idProvider := idp.GetIdProvider(provider.Type, provider.ClientId, provider.ClientSecret, form.RedirectUri)
if idProvider == nil {
resp = &Response{Status: "error", Msg: fmt.Sprintf("provider: %s does not exist", provider.Type)}
c.Data["json"] = resp
c.ServeJSON()
return
}
userInfo := &idp.UserInfo{}
if provider.Category == "SAML" {
// SAML
userInfo.Id, err = object.ParseSamlResponse(form.SamlResponse, provider.Type)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if provider.Category == "OAuth" {
// OAuth
idProvider := idp.GetIdProvider(provider.Type, provider.ClientId, provider.ClientSecret, form.RedirectUri)
if idProvider == nil {
c.ResponseError(fmt.Sprintf("The provider type: %s is not supported", provider.Type))
return
}
idProvider.SetHttpClient(httpClient)
setHttpClient(idProvider, provider.Type)
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
resp = &Response{Status: "error", Msg: fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State)}
c.Data["json"] = resp
c.ServeJSON()
return
}
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
return
}
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
token, err := idProvider.GetToken(form.Code)
if err != nil {
resp = &Response{Status: "error", Msg: err.Error()}
c.Data["json"] = resp
c.ServeJSON()
return
}
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
token, err := idProvider.GetToken(form.Code)
if err != nil {
c.ResponseError(err.Error())
return
}
if !token.Valid() {
resp = &Response{Status: "error", Msg: "Invalid token"}
c.Data["json"] = resp
c.ServeJSON()
return
}
if !token.Valid() {
c.ResponseError("Invalid token")
return
}
userInfo, err = idProvider.GetUserInfo(token)
if err != nil {
c.ResponseError(fmt.Sprintf("Failed to login in: %s", err.Error()))
return
}
userInfo, err := idProvider.GetUserInfo(token)
if err != nil {
resp = &Response{Status: "error", Msg: fmt.Sprintf("Failed to login in: %s", err.Error())}
c.Data["json"] = resp
c.ServeJSON()
return
}
if form.Method == "signup" {
user := &object.User{}
if provider.Category == "SAML" {
user = object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Id))
} else if provider.Category == "OAuth" {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if user == nil {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if user == nil {
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
}
user := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if user == nil {
user = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if user == nil {
user = object.GetUserByField(application.Organization, "name", userInfo.Username)
}
if user != nil && user.IsDeleted == false {
// Sign in via OAuth (want to sign up but already have account)
if user != nil {
// Sign in via OAuth
if user.IsForbidden {
c.ResponseError("the user is forbidden to sign in, please contact the administrator")
}
//if object.IsForbidden(userId) {
// c.forbiddenAccountResp(userId)
// return
//}
//if len(object.GetMemberAvatar(userId)) == 0 {
// avatar := UploadAvatarToOSS(res.Avatar, userId)
// object.LinkMemberAccount(userId, "avatar", avatar)
//}
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
} else if provider.Category == "OAuth" {
record.Username = user.Name
object.AddRecord(record)
} else {
// Sign up via OAuth
if !application.EnableSignUp {
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", provider.Type, userInfo.Username, userInfo.DisplayName))
resp = &Response{Status: "error", Msg: fmt.Sprintf("The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support", provider.Type, userInfo.Username, userInfo.DisplayName)}
c.Data["json"] = resp
c.ServeJSON()
return
}
if !providerItem.CanSignUp {
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %s, please use another way to sign up", provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
resp = &Response{Status: "error", Msg: fmt.Sprintf("The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %s, please use another way to sign up", provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type)}
c.Data["json"] = resp
c.ServeJSON()
return
}
properties := map[string]string{}
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
user = &object.User{
user := &object.User{
Owner: application.Organization,
Name: userInfo.Username,
CreatedTime: util.GetCurrentTime(),
@@ -287,41 +301,36 @@ func (c *ApiController) Login() {
Type: "normal-user",
DisplayName: userInfo.DisplayName,
Avatar: userInfo.AvatarUrl,
Address: []string{},
Email: userInfo.Email,
Score: getInitScore(),
Score: 200,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
SignupApplication: application.Name,
Properties: properties,
}
object.AddUser(user)
// sync info from 3rd-party if possible
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
affected := object.AddUser(user)
if !affected {
c.ResponseError(fmt.Sprintf("Failed to create user, user information is invalid: %s", util.StructToJson(user)))
return
}
object.LinkUserAccount(user, provider.Type, userInfo.Id)
resp = c.HandleLoggedIn(application, user, &form)
record := object.NewRecord(c.Ctx)
record := util.Records(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
go object.AddRecord(record)
} else if provider.Category == "SAML" {
resp = &Response{Status: "error", Msg: "The account does not exist"}
record.Username = user.Name
object.AddRecord(record)
}
//resp = &Response{Status: "ok", Msg: "", Data: res}
} else { // form.Method != "signup"
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("The account does not exist", userInfo)
resp = &Response{Status: "error", Msg: "The account does not exist", Data: userInfo}
c.Data["json"] = resp
c.ServeJSON()
return
}
@@ -330,7 +339,9 @@ func (c *ApiController) Login() {
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
}
if oldUser != nil {
c.ResponseError(fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName))
resp = &Response{Status: "error", Msg: fmt.Sprintf("The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", provider.Type, userInfo.Username, userInfo.DisplayName, oldUser.Name, oldUser.DisplayName)}
c.Data["json"] = resp
c.ServeJSON()
return
}
@@ -345,44 +356,15 @@ func (c *ApiController) Login() {
} else {
resp = &Response{Status: "error", Msg: "Failed to link user account", Data: isLinked}
}
//if len(object.GetMemberAvatar(userId)) == 0 {
// avatar := UploadAvatarToOSS(tempUserAccount.AvatarUrl, userId)
// object.LinkUserAccount(userId, "avatar", avatar)
//}
}
} else {
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
user := c.getCurrentUser()
resp = c.HandleLoggedIn(application, user, &form)
} else {
c.ResponseError(fmt.Sprintf("unknown authentication type (not password or provider), form = %s", util.StructToJson(form)))
return
}
panic("unknown authentication type (not password or provider), form = " + util.StructToJson(form))
}
c.Data["json"] = resp
c.ServeJSON()
}
func (c *ApiController) GetSamlLogin() {
providerId := c.Input().Get("id")
relayState := c.Input().Get("relayState")
authURL, method, err := object.GenerateSamlLoginUrl(providerId, relayState)
if err != nil {
c.ResponseError(err.Error())
}
c.ResponseOk(authURL, method)
}
func (c *ApiController) HandleSamlLogin() {
relayState := c.Input().Get("RelayState")
samlResponse := c.Input().Get("SAMLResponse")
decode, err := base64.StdEncoding.DecodeString(relayState)
if err != nil {
c.ResponseError(err.Error())
}
slice := strings.Split(string(decode), "&")
relayState = url.QueryEscape(relayState)
samlResponse = url.QueryEscape(samlResponse)
targetUrl := fmt.Sprintf("%s?relayState=%s&samlResponse=%s",
slice[4], relayState, samlResponse)
c.Redirect(targetUrl, 303)
}

View File

@@ -18,24 +18,17 @@ import (
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
)
// controller for handlers under /api uri
type ApiController struct {
beego.Controller
}
// controller for handlers directly under / (root)
type RootController struct {
ApiController
}
type SessionData struct {
ExpireTime int64
}
// GetSessionUsername ...
func (c *ApiController) GetSessionUsername() string {
// check if user session expired
sessionData := c.GetSessionData()
@@ -55,12 +48,10 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
// SetSessionUsername ...
func (c *ApiController) SetSessionUsername(user string) {
c.SetSession("username", user)
}
// GetSessionData ...
func (c *ApiController) GetSessionData() *SessionData {
session := c.GetSession("SessionData")
if session == nil {
@@ -76,7 +67,6 @@ func (c *ApiController) GetSessionData() *SessionData {
return sessionData
}
// SetSessionData ...
func (c *ApiController) SetSessionData(s *SessionData) {
if s == nil {
c.DelSession("SessionData")

View File

@@ -16,9 +16,8 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
type LdapServer struct {
@@ -44,9 +43,6 @@ type LdapSyncResp struct {
Failed []object.LdapRespUser `json:"failed"`
}
// @Tag Account API
// @Title GetLdapser
// @router /get-ldap-user [post]
func (c *ApiController) GetLdapUser() {
ldapServer := LdapServer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
@@ -59,13 +55,15 @@ func (c *ApiController) GetLdapUser() {
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
if err != nil {
c.ResponseError(err.Error())
c.Data["json"] = Response{Status: "error", Msg: err.Error()}
c.ServeJSON()
return
}
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
//if err != nil {
// c.ResponseError(err.Error())
// c.Data["json"] = Response{Status: "error", Msg: err.Error()}
// c.ServeJSON()
// return
//}
@@ -78,7 +76,8 @@ func (c *ApiController) GetLdapUser() {
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
if err != nil {
c.ResponseError(err.Error())
c.Data["json"] = Response{Status: "error", Msg: err.Error()}
c.ServeJSON()
return
}
@@ -98,11 +97,9 @@ func (c *ApiController) GetLdapUser() {
c.Data["json"] = Response{Status: "ok", Data: resp}
c.ServeJSON()
return
}
// @Tag Account API
// @Title GetLdaps
// @router /get-ldaps [post]
func (c *ApiController) GetLdaps() {
owner := c.Input().Get("owner")
@@ -110,9 +107,6 @@ func (c *ApiController) GetLdaps() {
c.ServeJSON()
}
// @Tag Account API
// @Title GetLdap
// @router /get-ldap [post]
func (c *ApiController) GetLdap() {
id := c.Input().Get("id")
@@ -125,9 +119,6 @@ func (c *ApiController) GetLdap() {
c.ServeJSON()
}
// @Tag Account API
// @Title AddLdap
// @router /add-ldap [post]
func (c *ApiController) AddLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
@@ -151,17 +142,11 @@ func (c *ApiController) AddLdap() {
if affected {
resp.Data2 = ldap
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
}
c.Data["json"] = resp
c.ServeJSON()
}
// @Tag Account API
// @Title UpdateLdap
// @router /update-ldap [post]
func (c *ApiController) UpdateLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
@@ -175,17 +160,11 @@ func (c *ApiController) UpdateLdap() {
if affected {
resp.Data2 = ldap
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
}
c.Data["json"] = resp
c.ServeJSON()
}
// @Tag Account API
// @Title DeleteLdap
// @router /delete-ldap [post]
func (c *ApiController) DeleteLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
@@ -193,14 +172,10 @@ func (c *ApiController) DeleteLdap() {
panic(err)
}
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
c.ServeJSON()
}
// @Tag Account API
// @Title SyncLdapUsers
// @router /sync-ldap-users [post]
func (c *ApiController) SyncLdapUsers() {
owner := c.Input().Get("owner")
ldapId := c.Input().Get("ldapId")
@@ -220,9 +195,6 @@ func (c *ApiController) SyncLdapUsers() {
c.ServeJSON()
}
// @Tag Account API
// @Title CheckLdapUserExist
// @router /check-ldap-users-exist [post]
func (c *ApiController) CheckLdapUsersExist() {
owner := c.Input().Get("owner")
var uuids []string

View File

@@ -17,22 +17,21 @@ package controllers
import (
"encoding/json"
"github.com/casbin/casdoor/object"
"github.com/casdoor/casdoor/object"
)
type LinkForm struct {
ProviderType string `json:"providerType"`
}
// Unlink ...
// @router /unlink [post]
// @Tag Login API
func (c *ApiController) Unlink() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
var resp Response
var form LinkForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
@@ -44,12 +43,16 @@ func (c *ApiController) Unlink() {
value := object.GetUserField(user, providerType)
if value == "" {
c.ResponseError("Please link first", value)
resp = Response{Status: "error", Msg: "Please link first", Data: value}
c.Data["json"] = resp
c.ServeJSON()
return
}
object.ClearUserOAuthProperties(user, providerType)
object.LinkUserAccount(user, providerType, "")
c.ResponseOk()
resp = Response{Status: "ok", Msg: ""}
c.Data["json"] = resp
c.ServeJSON()
}

View File

@@ -1,38 +0,0 @@
// Copyright 2021 The casbin 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 "github.com/casbin/casdoor/object"
// @Title GetOidcDiscovery
// @Tag OIDC API
// @router /.well-known/openid-configuration [get]
func (c *RootController) GetOidcDiscovery() {
c.Data["json"] = object.GetOidcDiscovery()
c.ServeJSON()
}
// @Title GetOidcCert
// @Tag OIDC API
// @router /api/certs [get]
func (c *RootController) GetOidcCert() {
jwks, err := object.GetJSONWebKeySet()
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = jwks
c.ServeJSON()
}

View File

@@ -17,36 +17,22 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
)
// GetOrganizations ...
// @Title GetOrganizations
// @Tag Organization API
// @Description get organizations
// @Param owner query string true "owner"
// @Success 200 {array} object.Organization The Response object
// @router /get-organizations [get]
func (c *ApiController) GetOrganizations() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedOrganizations(object.GetOrganizations(owner))
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner)))
organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit))
c.ResponseOk(organizations, paginator.Nums())
}
c.Data["json"] = object.GetOrganizations(owner)
c.ServeJSON()
}
// GetOrganization ...
// @Title GetOrganization
// @Tag Organization API
// @Description get organization
// @Param id query string true "organization id"
// @Success 200 {object} object.Organization The Response object
@@ -54,13 +40,11 @@ func (c *ApiController) GetOrganizations() {
func (c *ApiController) GetOrganization() {
id := c.Input().Get("id")
c.Data["json"] = object.GetMaskedOrganization(object.GetOrganization(id))
c.Data["json"] = object.GetOrganization(id)
c.ServeJSON()
}
// UpdateOrganization ...
// @Title UpdateOrganization
// @Tag Organization API
// @Description update organization
// @Param id query string true "The id of the organization"
// @Param body body object.Organization true "The details of the organization"
@@ -79,9 +63,7 @@ func (c *ApiController) UpdateOrganization() {
c.ServeJSON()
}
// AddOrganization ...
// @Title AddOrganization
// @Tag Organization API
// @Description add organization
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object
@@ -97,9 +79,7 @@ func (c *ApiController) AddOrganization() {
c.ServeJSON()
}
// DeleteOrganization ...
// @Title DeleteOrganization
// @Tag Organization API
// @Description delete organization
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object

View File

@@ -17,35 +17,22 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
)
// GetProviders
// @Title GetProviders
// @Tag Provider API
// @Description get providers
// @Param owner query string true "The owner of providers"
// @Success 200 {array} object.Provider The Response object
// @router /get-providers [get]
func (c *ApiController) GetProviders() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetProviders(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner)))
providers := object.GetPaginationProviders(owner, paginator.Offset(), limit)
c.ResponseOk(providers, paginator.Nums())
}
c.Data["json"] = object.GetProviders(owner)
c.ServeJSON()
}
// @Title GetProvider
// @Tag Provider API
// @Description get provider
// @Param id query string true "The id of the provider"
// @Success 200 {object} object.Provider The Response object
@@ -58,7 +45,6 @@ func (c *ApiController) GetProvider() {
}
// @Title UpdateProvider
// @Tag Provider API
// @Description update provider
// @Param id query string true "The id of the provider"
// @Param body body object.Provider true "The details of the provider"
@@ -78,7 +64,6 @@ func (c *ApiController) UpdateProvider() {
}
// @Title AddProvider
// @Tag Provider API
// @Description add provider
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object
@@ -95,7 +80,6 @@ func (c *ApiController) AddProvider() {
}
// @Title DeleteProvider
// @Tag Provider API
// @Description delete provider
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object

View File

@@ -15,49 +15,32 @@
package controllers
import (
"github.com/astaxie/beego/utils/pagination"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"encoding/json"
"github.com/casdoor/casdoor/object"
)
// GetRecords
// @Title GetRecords
// @Tag Record API
// @Description get all records
// @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the page"
// @Success 200 {array} object.Records The Response object
// @router /get-records [get]
func (c *ApiController) GetRecords() {
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetRecords()
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount()))
records := object.GetPaginationRecords(paginator.Offset(), limit)
c.ResponseOk(records, paginator.Nums())
}
c.Data["json"] = object.GetRecords()
c.ServeJSON()
}
// GetRecordsByFilter
// @Tag Record API
// @Title GetRecordsByFilter
// @Description get records by filter
// @Param body body object.Records true "filter Record message"
// @Success 200 {array} object.Records The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
body := string(c.Ctx.Input.RequestBody)
record := &object.Record{}
err := util.JsonToStruct(body, record)
var record object.Records
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
panic(err)
}
c.Data["json"] = object.GetRecordsByField(record)
c.Data["json"] = object.GetRecordsByField(&record)
c.ServeJSON()
}

View File

@@ -1,210 +0,0 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime"
"path/filepath"
"github.com/astaxie/beego/utils/pagination"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
// @router /get-resources [get]
// @Tag Resource API
// @Title GetResources
func (c *ApiController) GetResources() {
owner := c.Input().Get("owner")
user := c.Input().Get("user")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetResources(owner, user)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user)))
resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit)
c.ResponseOk(resources, paginator.Nums())
}
}
// @Tag Resource API
// @Title GetResource
// @router /get-resource [get]
func (c *ApiController) GetResource() {
id := c.Input().Get("id")
c.Data["json"] = object.GetResource(id)
c.ServeJSON()
}
// @Tag Resource API
// @Title UpdateResource
// @router /update-resource [post]
func (c *ApiController) UpdateResource() {
id := c.Input().Get("id")
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.UpdateResource(id, &resource))
c.ServeJSON()
}
// @Tag Resource API
// @Title AddResource
// @router /add-resource [post]
func (c *ApiController) AddResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
c.Data["json"] = wrapActionResponse(object.AddResource(&resource))
c.ServeJSON()
}
// @Tag Resource API
// @Title DeleteResource
// @router /delete-resource [post]
func (c *ApiController) DeleteResource() {
var resource object.Resource
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
if err != nil {
panic(err)
}
provider, _, ok := c.GetProviderFromContext("Storage")
if !ok {
return
}
err = object.DeleteFile(provider, resource.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteResource(&resource))
c.ServeJSON()
}
// @Tag Resource API
// @Title UploadResource
// @router /upload-resource [post]
func (c *ApiController) UploadResource() {
owner := c.Input().Get("owner")
username := c.Input().Get("user")
application := c.Input().Get("application")
tag := c.Input().Get("tag")
parent := c.Input().Get("parent")
fullFilePath := c.Input().Get("fullFilePath")
createdTime := c.Input().Get("createdTime")
description := c.Input().Get("description")
file, header, err := c.GetFile("file")
if err != nil {
c.ResponseError(err.Error())
return
}
defer file.Close()
if username == "" || fullFilePath == "" {
c.ResponseError(fmt.Sprintf("username or fullFilePath is empty: username = %s, fullFilePath = %s", username, fullFilePath))
return
}
filename := filepath.Base(fullFilePath)
fileBuffer := bytes.NewBuffer(nil)
if _, err = io.Copy(fileBuffer, file); err != nil {
c.ResponseError(err.Error())
return
}
provider, user, ok := c.GetProviderFromContext("Storage")
if !ok {
return
}
fileType := "unknown"
contentType := header.Header.Get("Content-Type")
fileType, _ = util.GetOwnerAndNameFromId(contentType)
if fileType != "image" && fileType != "video" {
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
}
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer)
if err != nil {
c.ResponseError(err.Error())
return
}
if createdTime == "" {
createdTime = util.GetCurrentTime()
}
fileFormat := filepath.Ext(fullFilePath)
fileSize := int(header.Size)
resource := &object.Resource{
Owner: owner,
Name: objectKey,
CreatedTime: createdTime,
User: username,
Provider: provider.Name,
Application: application,
Tag: tag,
Parent: parent,
FileName: filename,
FileType: fileType,
FileFormat: fileFormat,
FileSize: fileSize,
Url: fileUrl,
Description: description,
}
object.AddOrUpdateResource(resource)
switch tag {
case "avatar":
if user == nil {
user = object.GetUserNoCheck(username)
if user == nil {
c.ResponseError("user is nil for tag: \"avatar\"")
return
}
}
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user, []string{"avatar"})
case "termsOfUse":
applicationId := fmt.Sprintf("admin/%s", parent)
app := object.GetApplication(applicationId)
app.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, app)
}
c.ResponseOk(fileUrl, objectKey)
}

View File

@@ -1,131 +0,0 @@
// Copyright 2021 The casbin 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.
// Casdoor will expose its providers as services to SDK
// We are going to implement those services as APIs here
package controllers
import (
"encoding/json"
"fmt"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
)
// SendEmail
// @Title SendEmail
// @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param body body emailForm true "Details of the email request"
// @Success 200 {object} Response object
// @router /api/send-email [post]
func (c *ApiController) SendEmail() {
provider, _, ok := c.GetProviderFromContext("Email")
if !ok {
return
}
var emailForm struct {
Title string `json:"title"`
Content string `json:"content"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
if err != nil {
c.ResponseError(err.Error())
return
}
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
return
}
invalidReceivers := []string{}
for _, receiver := range emailForm.Receivers {
if !util.IsEmailValid(receiver) {
invalidReceivers = append(invalidReceivers, receiver)
}
}
if len(invalidReceivers) != 0 {
c.ResponseError(fmt.Sprintf("Invalid Email receivers: %s", invalidReceivers))
return
}
for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.ResponseOk()
}
// SendSms
// @Title SendSms
// @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param body body smsForm true "Details of the sms request"
// @Success 200 {object} Response object
// @router /api/send-sms [post]
func (c *ApiController) SendSms() {
provider, _, ok := c.GetProviderFromContext("SMS")
if !ok {
return
}
var smsForm struct {
Content string `json:"content"`
Receivers []string `json:"receivers"`
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil {
c.ResponseError(err.Error())
return
}
org := object.GetOrganization(smsForm.OrgId)
var invalidReceivers []string
for idx, receiver := range smsForm.Receivers {
if !util.IsPhoneCnValid(receiver) {
invalidReceivers = append(invalidReceivers, receiver)
} else {
smsForm.Receivers[idx] = fmt.Sprintf("+%s%s", org.PhonePrefix, receiver)
}
}
if len(invalidReceivers) != 0 {
c.ResponseError(fmt.Sprintf("Invalid phone receivers: %s", invalidReceivers))
return
}
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}

View File

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

View File

@@ -17,38 +17,22 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
)
// GetTokens
// @Title GetTokens
// @Tag Token API
// @Description get tokens
// @Param owner query string true "The owner of tokens"
// @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the page"
// @Success 200 {array} object.Token The Response object
// @router /get-tokens [get]
func (c *ApiController) GetTokens() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetTokens(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner)))
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit)
c.ResponseOk(tokens, paginator.Nums())
}
c.Data["json"] = object.GetTokens(owner)
c.ServeJSON()
}
// GetToken
// @Title GetToken
// @Tag Token API
// @Description get token
// @Param id query string true "The id of token"
// @Success 200 {object} object.Token The Response object
@@ -60,9 +44,7 @@ func (c *ApiController) GetToken() {
c.ServeJSON()
}
// UpdateToken
// @Title UpdateToken
// @Tag Token API
// @Description update token
// @Param id query string true "The id of token"
// @Param body body object.Token true "Details of the token"
@@ -81,9 +63,7 @@ func (c *ApiController) UpdateToken() {
c.ServeJSON()
}
// AddToken
// @Title AddToken
// @Tag Token API
// @Description add token
// @Param body body object.Token true "Details of the token"
// @Success 200 {object} controllers.Response The Response object
@@ -99,8 +79,6 @@ func (c *ApiController) AddToken() {
c.ServeJSON()
}
// DeleteToken
// @Tag Token API
// @Title DeleteToken
// @Description delete token
// @Param body body object.Token true "Details of the token"
@@ -117,39 +95,12 @@ func (c *ApiController) DeleteToken() {
c.ServeJSON()
}
// GetOAuthCode
// @Title GetOAuthCode
// @Tag Token API
// @Description get OAuth code
// @Param user_id query string true "The id of user"
// @Param client_id query string true "OAuth client id"
// @Param response_type query string true "OAuth response type"
// @Param redirect_uri query string true "OAuth redirect URI"
// @Param scope query string true "OAuth scope"
// @Param state query string true "OAuth state"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/code [post]
func (c *ApiController) GetOAuthCode() {
userId := c.Input().Get("user_id")
clientId := c.Input().Get("client_id")
responseType := c.Input().Get("response_type")
redirectUri := c.Input().Get("redirect_uri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
nonce := c.Input().Get("nonce")
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce)
c.ServeJSON()
}
// GetOAuthToken
// @Title GetOAuthToken
// @Tag Token API
// @Description get OAuth access token
// @Param grant_type query string true "OAuth grant type"
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string true "OAuth client secret"
// @Param code query string true "OAuth code"
// @Description get oAuth token
// @Param grant_type query string true "oAuth grant type"
// @Param client_id query string true "oAuth client id"
// @Param client_secret query string true "oAuth client secret"
// @Param code query string true "oAuth code"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
@@ -158,31 +109,6 @@ func (c *ApiController) GetOAuthToken() {
clientSecret := c.Input().Get("client_secret")
code := c.Input().Get("code")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code)
c.ServeJSON()
}
// RefreshToken
// @Title RefreshToken
// @Description refresh OAuth access token
// @Param grant_type query string true "OAuth grant type"
// @Param refresh_token query string true "OAuth refresh token"
// @Param scope query string true "OAuth scope"
// @Param client_id query string true "OAuth client id"
// @Param client_secret query string true "OAuth client secret"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/refresh_token [post]
func (c *ApiController) RefreshToken() {
grantType := c.Input().Get("grant_type")
refreshToken := c.Input().Get("refresh_token")
scope := c.Input().Get("scope")
clientId := c.Input().Get("client_id")
clientSecret := c.Input().Get("client_secret")
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret)
c.ServeJSON()
}

View File

@@ -19,79 +19,44 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego/utils/pagination"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/original"
)
// GetGlobalUsers
// @Title GetGlobalUsers
// @Tag User API
// @Description get global users
// @Success 200 {array} object.User The Response object
// @router /get-global-users [get]
func (c *ApiController) GetGlobalUsers() {
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount()))
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit)
c.ResponseOk(users, paginator.Nums())
}
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
c.ServeJSON()
}
// GetUsers
// @Title GetUsers
// @Tag User API
// @Description
// @Param owner query string true "The owner of users"
// @Success 200 {array} object.User The Response object
// @router /get-users [get]
func (c *ApiController) GetUsers() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner)))
users := object.GetPaginationUsers(owner, paginator.Offset(), limit)
c.ResponseOk(users, paginator.Nums())
}
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
c.ServeJSON()
}
// GetUser
// @Title GetUser
// @Tag User API
// @Description get user
// @Param id query string true "The id of the user"
// @Success 200 {object} object.User The Response object
// @router /get-user [get]
func (c *ApiController) GetUser() {
id := c.Input().Get("id")
owner := c.Input().Get("owner")
email := c.Input().Get("email")
var user *object.User
if email == "" {
user = object.GetUser(id)
} else {
user = object.GetUserByEmail(owner, email)
}
c.Data["json"] = object.GetMaskedUser(user)
c.Data["json"] = object.GetMaskedUser(object.GetUser(id))
c.ServeJSON()
}
// UpdateUser
// @Title UpdateUser
// @Tag User API
// @Description update user
// @Param id query string true "The id of the user"
// @Param body body object.User true "The details of the user"
@@ -99,7 +64,6 @@ func (c *ApiController) GetUser() {
// @router /update-user [post]
func (c *ApiController) UpdateUser() {
id := c.Input().Get("id")
columnsStr := c.Input().Get("columns")
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
@@ -112,23 +76,17 @@ func (c *ApiController) UpdateUser() {
return
}
columns := []string{}
if columnsStr != "" {
columns = strings.Split(columnsStr, ",")
}
affected := object.UpdateUser(id, &user, columns)
affected := object.UpdateUser(id, &user)
if affected {
object.UpdateUserToOriginalDatabase(&user)
newUser := object.GetUser(user.GetId())
original.UpdateUserToOriginalDatabase(newUser)
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
// AddUser
// @Title AddUser
// @Tag User API
// @Description add user
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
@@ -144,9 +102,7 @@ func (c *ApiController) AddUser() {
c.ServeJSON()
}
// DeleteUser
// @Title DeleteUser
// @Tag User API
// @Description delete user
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
@@ -162,15 +118,15 @@ func (c *ApiController) DeleteUser() {
c.ServeJSON()
}
// GetEmailAndPhone
// @Title GetEmailAndPhone
// @Tag User API
// @Description get email and phone by username
// @Param username formData string true "The username of the user"
// @Param organization formData string true "The organization of the user"
// @Success 200 {object} controllers.Response The Response object
// @router /get-email-and-phone [post]
func (c *ApiController) GetEmailAndPhone() {
var resp Response
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
if err != nil {
@@ -194,12 +150,13 @@ func (c *ApiController) GetEmailAndPhone() {
contentType = "username"
}
c.ResponseOk(respUser, contentType)
resp = Response{Status: "ok", Msg: "", Data: respUser, Data2: contentType}
c.Data["json"] = resp
c.ServeJSON()
}
// SetPassword
// @Title SetPassword
// @Tag Account API
// @Description set password
// @Param userOwner formData string true "The owner of the user"
// @Param userName formData string true "The name of the user"
@@ -227,7 +184,7 @@ func (c *ApiController) SetPassword() {
userId := fmt.Sprintf("%s/%s", userOwner, userName)
targetUser := object.GetUser(userId)
if targetUser == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
c.ResponseError("Invalid user id.")
return
}
@@ -252,9 +209,11 @@ func (c *ApiController) SetPassword() {
c.ResponseError(msg)
return
}
} else {
}
if strings.Contains(newPassword, " ") {
if strings.Index(newPassword, " ") >= 0 {
c.ResponseError("New password cannot contain blank space.")
return
}
@@ -271,62 +230,3 @@ func (c *ApiController) SetPassword() {
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
}
// @Title CheckUserPassword
// @router /check-user-password [post]
// @Tag User API
func (c *ApiController) CheckUserPassword() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
}
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password)
if msg == "" {
c.ResponseOk()
} else {
c.ResponseError(msg)
}
}
// GetSortedUsers
// @Title GetSortedUsers
// @Tag User API
// @Description
// @Param owner query string true "The owner of users"
// @Param sorter query string true "The DB column name to sort by, e.g., created_time"
// @Param limit query string true "The count of users to return, e.g., 25"
// @Success 200 {array} object.User The Response object
// @router /get-sorted-users [get]
func (c *ApiController) GetSortedUsers() {
owner := c.Input().Get("owner")
sorter := c.Input().Get("sorter")
limit := util.ParseInt(c.Input().Get("limit"))
c.Data["json"] = object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
c.ServeJSON()
}
// GetUserCount
// @Title GetUserCount
// @Tag User API
// @Description
// @Param owner query string true "The owner of users"
// @Param isOnline query string true "The filter for query, 1 for online, 0 for offline, empty string for all users"
// @Success 200 {int} int The count of filtered users for an organization
// @router /get-user-count [get]
func (c *ApiController) GetUserCount() {
owner := c.Input().Get("owner")
isOnline := c.Input().Get("isOnline")
count := 0
if isOnline == "" {
count = object.GetUserCount(owner)
} else {
count = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
}
c.Data["json"] = count
c.ServeJSON()
}

View File

@@ -15,88 +15,57 @@
package controllers
import (
"fmt"
"strconv"
"net/http"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"golang.org/x/net/proxy"
)
// ResponseOk ...
func (c *ApiController) ResponseOk(data ...interface{}) {
resp := Response{Status: "ok"}
switch len(data) {
case 2:
resp.Data2 = data[1]
fallthrough
case 1:
resp.Data = data[0]
}
c.Data["json"] = resp
c.ServeJSON()
}
var httpClient *http.Client
// ResponseError ...
func (c *ApiController) ResponseError(error string, data ...interface{}) {
resp := Response{Status: "error", Msg: error}
switch len(data) {
case 2:
resp.Data2 = data[1]
fallthrough
case 1:
resp.Data = data[0]
func InitHttpClient() {
httpProxy := beego.AppConfig.String("httpProxy")
if httpProxy == "" {
httpClient = &http.Client{}
return
}
c.Data["json"] = resp
c.ServeJSON()
}
// RequireSignedIn ...
func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please sign in first")
return "", false
}
return userId, true
}
func getInitScore() int {
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
if err != nil {
panic(err)
}
return score
tr := &http.Transport{Dial: dialer.Dial}
httpClient = &http.Client{
Transport: tr,
}
//resp, err2 := httpClient.Get("https://google.com")
//if err2 != nil {
// panic(err2)
//}
//defer resp.Body.Close()
//println("Response status: %s", resp.Status)
}
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
providerName := c.Input().Get("provider")
if providerName != "" {
provider := object.GetProvider(util.GetId(providerName))
if provider == nil {
c.ResponseError(fmt.Sprintf("The provider: %s is not found", providerName))
return nil, nil, false
}
return provider, nil, true
}
userId, ok := c.RequireSignedIn()
if !ok {
return nil, nil, false
}
application, user := object.GetApplicationByUserId(userId)
if application == nil {
c.ResponseError(fmt.Sprintf("No application is found for userId: \"%s\"", userId))
return nil, nil, false
}
provider := application.GetProviderByCategory(category)
if provider == nil {
c.ResponseError(fmt.Sprintf("No provider for category: \"%s\" is found for application: %s", category, application.Name))
return nil, nil, false
}
return provider, user, true
func (c *ApiController) ResponseError(error string) {
c.Data["json"] = Response{Status: "error", Msg: error}
c.ServeJSON()
}
func (c *ApiController) ResponseErrorWithData(error string, data interface{}) {
c.Data["json"] = Response{Status: "error", Msg: error, Data: data}
c.ServeJSON()
}
func (c *ApiController) RequireSignedIn() (string, bool) {
userId := c.GetSessionUsername()
if userId == "" {
resp := Response{Status: "error", Msg: "Please sign in first"}
c.Data["json"] = resp
c.ServeJSON()
return "", false
}
return userId, true
}

View File

@@ -15,12 +15,11 @@
package controllers
import (
"errors"
"fmt"
"strings"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) getCurrentUser() *object.User {
@@ -34,10 +33,6 @@ func (c *ApiController) getCurrentUser() *object.User {
return user
}
// SendVerificationCode ...
// @Title SendVerificationCode
// @Tag Verification API
// @router /send-verification-code [post]
func (c *ApiController) SendVerificationCode() {
destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest")
@@ -45,10 +40,10 @@ func (c *ApiController) SendVerificationCode() {
checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey")
checkUser := c.Ctx.Request.Form.Get("checkUser")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
remoteAddr := c.Ctx.Request.RemoteAddr
remoteAddr = remoteAddr[:strings.LastIndex(remoteAddr, ":")]
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || !strings.Contains(orgId, "/") || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
if len(destType) == 0 || len(dest) == 0 || len(orgId) == 0 || strings.Index(orgId, "/") < 0 || len(checkType) == 0 || len(checkId) == 0 || len(checkKey) == 0 {
c.ResponseError("Missing parameter.")
return
}
@@ -68,13 +63,7 @@ func (c *ApiController) SendVerificationCode() {
organization := object.GetOrganization(orgId)
application := object.GetApplicationByOrganizationName(organization.Name)
if checkUser == "true" && user == nil &&
object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError("No such user.")
return
}
sendResp := errors.New("Invalid dest type.")
msg := "Invalid dest type."
switch destType {
case "email":
if !util.IsEmailValid(dest) {
@@ -83,7 +72,7 @@ func (c *ApiController) SendVerificationCode() {
}
provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
msg = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone":
if !util.IsPhoneCnValid(dest) {
c.ResponseError("Invalid phone number")
@@ -97,22 +86,18 @@ func (c *ApiController) SendVerificationCode() {
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
provider := application.GetSmsProvider()
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
msg = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
}
if sendResp != nil {
c.Data["json"] = Response{Status: "error", Msg: sendResp.Error()}
} else {
c.Data["json"] = Response{Status: "ok"}
status := "ok"
if msg != "" {
status = "error"
}
c.Data["json"] = Response{Status: status, Msg: msg}
c.ServeJSON()
}
// ResetEmailOrPhone ...
// @Tag Account API
// @Title ResetEmailOrPhone
// @router /api/reset-email-or-phone [post]
func (c *ApiController) ResetEmailOrPhone() {
userId, ok := c.RequireSignedIn()
if !ok {

View File

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

View File

@@ -1,31 +0,0 @@
// Copyright 2021 The casbin 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
type CredManager interface {
GetSealedPassword(password string, userSalt string, organizationSalt string) string
}
func GetCredManager(passwordType string) CredManager {
if passwordType == "plain" {
return NewPlainCredManager()
} else if passwordType == "salt" {
return NewSha256SaltCredManager()
} else if passwordType == "md5-salt" {
return NewMd5UserSaltCredManager()
}
return nil
}

View File

@@ -1,44 +0,0 @@
// Copyright 2021 The casbin 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/md5"
"encoding/hex"
)
type Md5UserSaltCredManager struct{}
func getMd5(data []byte) []byte {
hash := md5.Sum(data)
return hash[:]
}
func getMd5HexDigest(s string) string {
b := getMd5([]byte(s))
res := hex.EncodeToString(b)
return res
}
func NewMd5UserSaltCredManager() *Sha256SaltCredManager {
cm := &Sha256SaltCredManager{}
return cm
}
func (cm *Md5UserSaltCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
hash := getMd5HexDigest(password)
res := getMd5HexDigest(hash + userSalt)
return res
}

View File

@@ -3,21 +3,17 @@ services:
casdoor:
build:
context: ./
dockerfile: Dockerfile
dockerfile: go-dockerfile
ports:
- "8000:8000"
- 8000:8000
depends_on:
- db
environment:
RUNNING_IN_DOCKER: "true"
volumes:
- ./conf:/conf/
- db
db:
restart: always
image: mysql:8.0.25
ports:
- "3306:3306"
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: 123456
MYSQL_ROOT_PASSWORD: 123
volumes:
- /usr/local/docker/mysql:/var/lib/mysql

25
go-dockerfile Normal file
View File

@@ -0,0 +1,25 @@
FROM golang:1.17-rc-buster
WORKDIR /casdoor
COPY ./ /casdoor
RUN go env -w CGO_ENABLED=0 GOPROXY=https://goproxy.io,direct GOOS=linux GOARCH=amd64 \
&& apt update && apt install sudo \
&& wget https://nodejs.org/dist/v12.22.0/node-v12.22.0-linux-x64.tar.gz \
&& sudo tar xf node-v12.22.0-linux-x64.tar.gz \
&& sudo apt install wait-for-it
ENV PATH=$PATH:/casdoor/node-v12.22.0-linux-x64/bin
RUN npm install -g yarn \
&& cd web \
&& yarn install \
&& yarn run build \
&& rm -rf node_modules \
&& cd /casdoor \
&& go build main.go
FROM alpine:3.7
COPY --from=0 /casdoor /
COPY --from=0 /usr/bin/wait-for-it /
RUN set -eux \
&& sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
&& apk update \
&& apk upgrade \
&& apk add bash
CMD ./wait-for-it db:3306 && ./main

18
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/casbin/casdoor
module github.com/casdoor/casdoor
go 1.16
go 1.15
require (
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
@@ -9,30 +9,26 @@ require (
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.3.1
github.com/casdoor/go-sms-sender v0.0.5
github.com/casdoor/go-sms-sender v0.0.1
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-sql-driver/mysql v1.5.0
github.com/golang-jwt/jwt/v4 v4.1.0
github.com/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
github.com/mileusna/crontab v1.0.1
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
github.com/russellhaering/gosaml2 v0.6.0
github.com/russellhaering/goxmldsig v1.1.1
github.com/satori/go.uuid v1.2.0 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/thanhpk/randstr v1.0.4
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/ini.v1 v1.62.0
xorm.io/core v0.7.2
xorm.io/xorm v1.0.3
)

405
go.sum
View File

@@ -1,45 +1,10 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.67.0 h1:YIkzmqUfVGiGPpT98L8sVvUIkDno6UlrDxw4NR6z5ak=
cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28=
github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
@@ -57,15 +22,12 @@ github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/astaxie/beego v1.12.3 h1:SAQkdD2ePye+v8Gn1r4X6IKZM1wd28EyUOVQ3PDSOOQ=
github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/aws/aws-sdk-go v1.37.30 h1:fZeVg3QuTkWE/dEvPQbK6AL32+3G9ofJfGFSPS1XLH0=
github.com/aws/aws-sdk-go v1.37.30/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -79,21 +41,14 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.3.1 h1:RVGsM6KYFP9s4OQJXrP/gv56Wmt5P40mzvcyXgv5xeg=
github.com/casbin/xorm-adapter/v2 v2.3.1/go.mod h1:GZ+nlIdasVFunQ71SlvkL/HcQQBvFncphDf+2Yl167c=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/casdoor/go-sms-sender v0.0.1 h1:n/r6fGgXsV+6uMxXvb0XLZnUCjmbUB1uSB817Ej0/gI=
github.com/casdoor/go-sms-sender v0.0.1/go.mod h1:rr4na8Zc+0vgPVY5JPB0LZkRVuj5AhNVhE1G7W8lDk8=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -101,21 +56,17 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
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/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
@@ -131,89 +82,36 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/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.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/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-20200708004538-1a94d8640e99/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/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -221,43 +119,24 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/lestrrat-go/jwx v0.9.0 h1:Fnd0EWzTm0kFrBPzE/PEPp9nzllES5buMkksPMjEKpM=
github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 h1:JibQrkJapVsb0pweJ5T14jZuuYZZTjll0PZBw4XfSCI=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
@@ -272,9 +151,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c h1:3wkDRdxK92dF+c1ke2dtj7ZzemFWBHB9plnJOtlwdFA=
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -286,10 +164,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/pelletier/go-toml v1.0.1/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/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -299,7 +175,6 @@ github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@@ -313,15 +188,6 @@ github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbO
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/russellhaering/gosaml2 v0.6.0 h1:OED8FLgczXxXAPlKhnJHQfmEig52tDX2qeXdPtZRIKc=
github.com/russellhaering/gosaml2 v0.6.0/go.mod h1:CtzxpPr4+bevsATaqR0rw3aqrNlX274b+3C6vFTLCk8=
github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o=
github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM=
github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
@@ -342,7 +208,6 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
@@ -354,298 +219,75 @@ github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZ
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/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-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/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-20200130185559-910be7a94367/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-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 h1:3B43BWw0xEBsLZ/NO1VALz6fppU3481pik+2Ksv45z8=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/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-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.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.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/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-20200904004341-0bd0a958aa1d/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.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
@@ -654,8 +296,6 @@ gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -664,19 +304,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/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-2020.1.3/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/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=

View File

@@ -1,97 +0,0 @@
// Copyright 2021 The casbin 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 i18n
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/casbin/casdoor/util"
)
type I18nData map[string]map[string]string
var reI18n *regexp.Regexp
func init() {
reI18n, _ = regexp.Compile("i18next.t\\(\"(.*?)\"\\)")
}
func getAllI18nStrings(fileContent string) []string {
res := []string{}
matches := reI18n.FindAllStringSubmatch(fileContent, -1)
if matches == nil {
return res
}
for _, match := range matches {
res = append(res, match[1])
}
return res
}
func getAllJsFilePaths() []string {
path := "../web/src"
res := []string{}
err := filepath.Walk(path,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !strings.HasSuffix(info.Name(), ".js") {
return nil
}
res = append(res, path)
fmt.Println(path, info.Name())
return nil
})
if err != nil {
panic(err)
}
return res
}
func parseToData() *I18nData {
allWords := []string{}
paths := getAllJsFilePaths()
for _, path := range paths {
fileContent := util.ReadStringFromPath(path)
words := getAllI18nStrings(fileContent)
allWords = append(allWords, words...)
}
fmt.Printf("%v\n", allWords)
data := I18nData{}
for _, word := range allWords {
tokens := strings.Split(word, ":")
namespace := tokens[0]
key := tokens[1]
if _, ok := data[namespace]; !ok {
data[namespace] = map[string]string{}
}
data[namespace][key] = key
}
return &data
}

View File

@@ -1,37 +0,0 @@
// Copyright 2021 The casbin 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 i18n
import "testing"
func applyToOtherLanguage(dataEn *I18nData, lang string) {
dataOther := readI18nFile(lang)
println(dataOther)
applyData(dataEn, dataOther)
writeI18nFile(lang, dataEn)
}
func TestGenerateI18nStrings(t *testing.T) {
dataEn := parseToData()
writeI18nFile("en", dataEn)
applyToOtherLanguage(dataEn, "de")
applyToOtherLanguage(dataEn, "fr")
applyToOtherLanguage(dataEn, "ja")
applyToOtherLanguage(dataEn, "ko")
applyToOtherLanguage(dataEn, "ru")
applyToOtherLanguage(dataEn, "zh")
}

View File

@@ -1,63 +0,0 @@
// Copyright 2021 The casbin 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 i18n
import (
"fmt"
"strings"
"github.com/casbin/casdoor/util"
)
func getI18nFilePath(language string) string {
return fmt.Sprintf("../web/src/locales/%s/data.json", language)
}
func readI18nFile(language string) *I18nData {
s := util.ReadStringFromPath(getI18nFilePath(language))
data := &I18nData{}
err := util.JsonToStruct(s, data)
if err != nil {
panic(err)
}
return data
}
func writeI18nFile(language string, data *I18nData) {
s := util.StructToJsonFormatted(data)
s = strings.ReplaceAll(s, "\\u0026", "&")
println(s)
util.WriteStringToPath(s, getI18nFilePath(language))
}
func applyData(data1 *I18nData, data2 *I18nData) {
for namespace, pairs2 := range *data2 {
if _, ok := (*data1)[namespace]; !ok {
continue
}
pairs1 := (*data1)[namespace]
for key, value := range pairs2 {
if _, ok := pairs1[key]; !ok {
continue
}
pairs1[key] = value
}
}
}

View File

@@ -20,8 +20,10 @@ import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@@ -46,7 +48,6 @@ type DingTalkIdProvider struct {
Config *oauth2.Config
}
// NewDingTalkIdProvider ...
func NewDingTalkIdProvider(clientId string, clientSecret string, redirectUrl string) *DingTalkIdProvider {
idp := &DingTalkIdProvider{}
@@ -56,7 +57,6 @@ func NewDingTalkIdProvider(clientId string, clientSecret string, redirectUrl str
return idp
}
// SetHttpClient ...
func (idp *DingTalkIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
@@ -131,12 +131,12 @@ func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
}(resp.Body)
body, _ := io.ReadAll(resp.Body)
body, _ := ioutil.ReadAll(resp.Body)
info := InfoResp{}
_ = json.Unmarshal(body, &info)
errCode := info.Errcode
if errCode != 0 {
return nil, fmt.Errorf("%d: %s", errCode, info.Errmsg)
return nil, errors.New(fmt.Sprintf("%d: %s", errCode, info.Errmsg))
}
u2 := fmt.Sprintf("%s?appkey=%s&appsecret=%s", idp.Config.Endpoint.TokenURL, idp.Config.ClientID, idp.Config.ClientSecret)
@@ -147,11 +147,11 @@ func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
return
}
}(resp.Body)
body, _ = io.ReadAll(resp.Body)
body, _ = ioutil.ReadAll(resp.Body)
tokenResp := DingTalkAccessToken{}
_ = json.Unmarshal(body, &tokenResp)
if tokenResp.ErrCode != 0 {
return nil, fmt.Errorf("%d: %s", tokenResp.ErrCode, tokenResp.ErrMsg)
return nil, errors.New(fmt.Sprintf("%d: %s", tokenResp.ErrCode, tokenResp.ErrMsg))
}
// use unionid to get userid
@@ -183,7 +183,6 @@ type UnionIdResponse struct {
RequestId string `json:"request_id"`
}
// GetUseridByUnionid ...
func (idp *DingTalkIdProvider) GetUseridByUnionid(accesstoken, unionid string) (userid string, err error) {
u := fmt.Sprintf("https://oapi.dingtalk.com/topapi/user/getbyunionid?access_token=%s&unionid=%s",
accesstoken, unionid)
@@ -196,7 +195,7 @@ func (idp *DingTalkIdProvider) GetUseridByUnionid(accesstoken, unionid string) (
_ = json.Unmarshal([]byte(useridInfo), &uresp)
errcode := uresp.Errcode
if errcode != 0 {
return "", fmt.Errorf("%d: %s", errcode, uresp.Errmsg)
return "", errors.New(fmt.Sprintf("%d: %s", errcode, uresp.Errmsg))
}
return uresp.Result.Userid, nil
}

View File

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

View File

@@ -17,7 +17,7 @@ package idp
import (
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strconv"
"time"
@@ -61,7 +61,7 @@ func (idp *GithubIdProvider) getConfig() *oauth2.Config {
}
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
ctx := context.WithValue(oauth2.NoContext, oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
@@ -172,7 +172,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@@ -1,230 +0,0 @@
// Copyright 2021 The casbin 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"
"net/http"
"net/url"
"strconv"
"time"
"golang.org/x/oauth2"
)
type GitlabIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewGitlabIdProvider(clientId string, clientSecret string, redirectUrl string) *GitlabIdProvider {
idp := &GitlabIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *GitlabIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *GitlabIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://gitlab.com/oauth/token",
}
var config = &oauth2.Config{
Scopes: []string{"read_user+profile"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type GitlabProviderToken struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
RefreshToken string `json:"refresh_token"`
CreatedAt int `json:"created_at"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://docs.gitlab.com/ee/api/oauth2.html
func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := url.Values{}
params.Add("grant_type", "authorization_code")
params.Add("client_id", idp.Config.ClientID)
params.Add("client_secret", idp.Config.ClientSecret)
params.Add("code", code)
params.Add("redirect_uri", idp.Config.RedirectURL)
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
resp, err := idp.Client.Post(accessTokenUrl, "application/json;charset=UTF-8", nil)
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
gtoken := &GitlabProviderToken{}
if err = json.Unmarshal(data, gtoken); err != nil {
return nil, err
}
// gtoken.ExpiresIn always returns 0, so we set Expiry=7200 to avoid verification errors.
token := &oauth2.Token{
AccessToken: gtoken.AccessToken,
TokenType: gtoken.TokenType,
RefreshToken: gtoken.RefreshToken,
Expiry: time.Unix(time.Now().Unix()+int64(7200), 0),
}
return token, nil
}
/*
{
"id":5162115,
"name":"shiluo",
"username":"shiluo",
"state":"active",
"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5162115/avatar.png",
"web_url":"https://gitlab.com/shiluo",
"created_at":"2019-12-23T02:50:10.348Z",
"bio":"",
"bio_html":"",
"location":"China",
"public_email":"silo1999@163.com",
"skype":"",
"linkedin":"",
"twitter":"",
"website_url":"",
"organization":"",
"job_title":"",
"pronouns":null,
"bot":false,
"work_information":null,
"followers":0,
"following":0,
"last_sign_in_at":"2019-12-26T13:24:42.941Z",
"confirmed_at":"2019-12-23T02:52:10.778Z",
"last_activity_on":"2021-08-19",
"email":"silo1999@163.com",
"theme_id":1,
"color_scheme_id":1,
"projects_limit":100000,
"current_sign_in_at":"2021-08-19T09:46:46.004Z",
"identities":[
{
"provider":"github",
"extern_uid":"51157931",
"saml_provider_id":null
}
],
"can_create_group":true,
"can_create_project":true,
"two_factor_enabled":false,
"external":false,
"private_profile":false,
"commit_email":"silo1999@163.com",
"shared_runners_minutes_limit":null,
"extra_shared_runners_minutes_limit":null
}
*/
type GitlabUserInfo struct {
Id int `json:"id"`
Name string `json:"name"`
Username string `json:"username"`
State string `json:"state"`
AvatarUrl string `json:"avatar_url"`
WebUrl string `json:"web_url"`
CreatedAt time.Time `json:"created_at"`
Bio string `json:"bio"`
BioHtml string `json:"bio_html"`
Location string `json:"location"`
PublicEmail string `json:"public_email"`
Skype string `json:"skype"`
Linkedin string `json:"linkedin"`
Twitter string `json:"twitter"`
WebsiteUrl string `json:"website_url"`
Organization string `json:"organization"`
JobTitle string `json:"job_title"`
Pronouns interface{} `json:"pronouns"`
Bot bool `json:"bot"`
WorkInformation interface{} `json:"work_information"`
Followers int `json:"followers"`
Following int `json:"following"`
LastSignInAt time.Time `json:"last_sign_in_at"`
ConfirmedAt time.Time `json:"confirmed_at"`
LastActivityOn string `json:"last_activity_on"`
Email string `json:"email"`
ThemeId int `json:"theme_id"`
ColorSchemeId int `json:"color_scheme_id"`
ProjectsLimit int `json:"projects_limit"`
CurrentSignInAt time.Time `json:"current_sign_in_at"`
Identities []struct {
Provider string `json:"provider"`
ExternUid string `json:"extern_uid"`
SamlProviderId interface{} `json:"saml_provider_id"`
} `json:"identities"`
CanCreateGroup bool `json:"can_create_group"`
CanCreateProject bool `json:"can_create_project"`
TwoFactorEnabled bool `json:"two_factor_enabled"`
External bool `json:"external"`
PrivateProfile bool `json:"private_profile"`
CommitEmail string `json:"commit_email"`
SharedRunnersMinutesLimit interface{} `json:"shared_runners_minutes_limit"`
ExtraSharedRunnersMinutesLimit interface{} `json:"extra_shared_runners_minutes_limit"`
}
// GetUserInfo use GitlabProviderToken gotten before return GitlabUserInfo
func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
resp, err := idp.Client.Get("https://gitlab.com/api/v4/user?access_token=" + token.AccessToken)
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
guser := GitlabUserInfo{}
if err = json.Unmarshal(data, &guser); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: strconv.Itoa(guser.Id),
Username: guser.Username,
DisplayName: guser.Name,
AvatarUrl: guser.AvatarUrl,
Email: guser.Email,
}
return &userInfo, nil
}

View File

@@ -19,7 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@@ -61,7 +61,7 @@ func (idp *GoogleIdProvider) getConfig() *oauth2.Config {
}
func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
ctx := context.WithValue(oauth2.NoContext, oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
@@ -95,7 +95,7 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@@ -1,254 +0,0 @@
// Copyright 2021 The casbin 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 (
"fmt"
"net/http"
"net/url"
"reflect"
"time"
"github.com/markbates/goth"
"github.com/markbates/goth/providers/amazon"
"github.com/markbates/goth/providers/apple"
"github.com/markbates/goth/providers/azuread"
"github.com/markbates/goth/providers/bitbucket"
"github.com/markbates/goth/providers/digitalocean"
"github.com/markbates/goth/providers/discord"
"github.com/markbates/goth/providers/dropbox"
"github.com/markbates/goth/providers/facebook"
"github.com/markbates/goth/providers/gitea"
"github.com/markbates/goth/providers/github"
"github.com/markbates/goth/providers/gitlab"
"github.com/markbates/goth/providers/google"
"github.com/markbates/goth/providers/heroku"
"github.com/markbates/goth/providers/instagram"
"github.com/markbates/goth/providers/kakao"
"github.com/markbates/goth/providers/line"
"github.com/markbates/goth/providers/linkedin"
"github.com/markbates/goth/providers/microsoftonline"
"github.com/markbates/goth/providers/paypal"
"github.com/markbates/goth/providers/salesforce"
"github.com/markbates/goth/providers/shopify"
"github.com/markbates/goth/providers/slack"
"github.com/markbates/goth/providers/tumblr"
"github.com/markbates/goth/providers/twitter"
"github.com/markbates/goth/providers/yahoo"
"github.com/markbates/goth/providers/yandex"
"github.com/markbates/goth/providers/zoom"
"golang.org/x/oauth2"
)
type GothIdProvider struct {
Provider goth.Provider
Session goth.Session
}
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) *GothIdProvider {
var idp GothIdProvider
switch providerType {
case "Amazon":
idp = GothIdProvider{
Provider: amazon.New(clientId, clientSecret, redirectUrl),
Session: &amazon.Session{},
}
case "Apple":
idp = GothIdProvider{
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
Session: &apple.Session{},
}
case "AzureAD":
idp = GothIdProvider{
Provider: azuread.New(clientId, clientSecret, redirectUrl, nil),
Session: &azuread.Session{},
}
case "Bitbucket":
idp = GothIdProvider{
Provider: bitbucket.New(clientId, clientSecret, redirectUrl),
Session: &bitbucket.Session{},
}
case "DigitalOcean":
idp = GothIdProvider{
Provider: digitalocean.New(clientId, clientSecret, redirectUrl),
Session: &digitalocean.Session{},
}
case "Discord":
idp = GothIdProvider{
Provider: discord.New(clientId, clientSecret, redirectUrl),
Session: &discord.Session{},
}
case "Dropbox":
idp = GothIdProvider{
Provider: dropbox.New(clientId, clientSecret, redirectUrl),
Session: &dropbox.Session{},
}
case "Facebook":
idp = GothIdProvider{
Provider: facebook.New(clientId, clientSecret, redirectUrl),
Session: &facebook.Session{},
}
case "Gitea":
idp = GothIdProvider{
Provider: gitea.New(clientId, clientSecret, redirectUrl),
Session: &gitea.Session{},
}
case "GitHub":
idp = GothIdProvider{
Provider: github.New(clientId, clientSecret, redirectUrl),
Session: &github.Session{},
}
case "GitLab":
idp = GothIdProvider{
Provider: gitlab.New(clientId, clientSecret, redirectUrl),
Session: &gitlab.Session{},
}
case "Google":
idp = GothIdProvider{
Provider: google.New(clientId, clientSecret, redirectUrl),
Session: &google.Session{},
}
case "Heroku":
idp = GothIdProvider{
Provider: heroku.New(clientId, clientSecret, redirectUrl),
Session: &heroku.Session{},
}
case "Instagram":
idp = GothIdProvider{
Provider: instagram.New(clientId, clientSecret, redirectUrl),
Session: &instagram.Session{},
}
case "Kakao":
idp = GothIdProvider{
Provider: kakao.New(clientId, clientSecret, redirectUrl),
Session: &kakao.Session{},
}
case "Linkedin":
idp = GothIdProvider{
Provider: linkedin.New(clientId, clientSecret, redirectUrl),
Session: &linkedin.Session{},
}
case "Line":
idp = GothIdProvider{
Provider: line.New(clientId, clientSecret, redirectUrl),
Session: &line.Session{},
}
case "MicrosoftOnline":
idp = GothIdProvider{
Provider: microsoftonline.New(clientId, clientSecret, redirectUrl),
Session: &microsoftonline.Session{},
}
case "Paypal":
idp = GothIdProvider{
Provider: paypal.New(clientId, clientSecret, redirectUrl),
Session: &paypal.Session{},
}
case "SalesForce":
idp = GothIdProvider{
Provider: salesforce.New(clientId, clientSecret, redirectUrl),
Session: &salesforce.Session{},
}
case "Shopify":
idp = GothIdProvider{
Provider: shopify.New(clientId, clientSecret, redirectUrl),
Session: &shopify.Session{},
}
case "Slack":
idp = GothIdProvider{
Provider: slack.New(clientId, clientSecret, redirectUrl),
Session: &slack.Session{},
}
case "Tumblr":
idp = GothIdProvider{
Provider: tumblr.New(clientId, clientSecret, redirectUrl),
Session: &tumblr.Session{},
}
case "Twitter":
idp = GothIdProvider{
Provider: twitter.New(clientId, clientSecret, redirectUrl),
Session: &twitter.Session{},
}
case "Yahoo":
idp = GothIdProvider{
Provider: yahoo.New(clientId, clientSecret, redirectUrl),
Session: &yahoo.Session{},
}
case "Yandex":
idp = GothIdProvider{
Provider: yandex.New(clientId, clientSecret, redirectUrl),
Session: &yandex.Session{},
}
case "Zoom":
idp = GothIdProvider{
Provider: zoom.New(clientId, clientSecret, redirectUrl),
Session: &zoom.Session{},
}
}
return &idp
}
//Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
idpClient.Set(reflect.ValueOf(client))
}
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
var expireAt time.Time
//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)
//Get ExpiresAt's value
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
if valueOfExpire.IsValid() {
expireAt = valueOfExpire.Interface().(time.Time)
}
token := oauth2.Token{
AccessToken: accessToken,
Expiry: expireAt,
}
return &token, err
}
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
gothUser, err := idp.Provider.FetchUser(idp.Session)
if err != nil {
return nil, err
}
return getUser(gothUser), nil
}
func getUser(gothUser goth.User) *UserInfo {
user := UserInfo{
Id: gothUser.UserID,
Username: gothUser.Name,
DisplayName: gothUser.NickName,
Email: gothUser.Email,
AvatarUrl: gothUser.AvatarURL,
}
//Some idp return an empty Name
//so construct the Name with firstname and lastname or nickname
if user.Username == "" {
user.Username = fmt.Sprintf("%v%v", gothUser.FirstName, gothUser.LastName)
}
if user.Username == "" {
user.Username = gothUser.NickName
}
return &user
}

View File

@@ -1,215 +0,0 @@
// Copyright 2021 The casbin 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"
"io"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
)
type LarkIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewLarkIdProvider(clientId string, clientSecret string, redirectUrl string) *LarkIdProvider {
idp := &LarkIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *LarkIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *LarkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
}
var config = &oauth2.Config{
Scopes: []string{},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
/*
{
"code": 0,
"msg": "success",
"tenant_access_token": "t-caecc734c2e3328a62489fe0648c4b98779515d3",
"expire": 7140
}
*/
type LarkAccessToken struct {
Code int `json:"code"`
Msg string `json:"msg"`
TenantAccessToken string `json:"tenant_access_token"`
Expire int `json:"expire"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://docs.microsoft.com/en-us/linkedIn/shared/authentication/authorization-code-flow?context=linkedIn%2Fcontext&tabs=HTTPS
func (idp *LarkIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := &struct {
AppID string `json:"app_id"`
AppSecret string `json:"app_secret"`
}{idp.Config.ClientID, idp.Config.ClientSecret}
data, err := idp.postWithBody(params, idp.Config.Endpoint.TokenURL)
appToken := &LarkAccessToken{}
if err = json.Unmarshal(data, appToken); err != nil || appToken.Code != 0 {
return nil, err
}
t := &oauth2.Token{
AccessToken: appToken.TenantAccessToken,
TokenType: "Bearer",
Expiry: time.Unix(time.Now().Unix()+int64(appToken.Expire), 0),
}
raw := make(map[string]interface{})
raw["code"] = code
t = t.WithExtra(raw)
return t, nil
}
/*
{
"code": 0,
"msg": "success",
"data": {
"access_token": "u-6U1SbDiM6XIH2DcTCPyeub",
"token_type": "Bearer",
"expires_in": 7140,
"name": "zhangsan",
"en_name": "Three Zhang",
"avatar_url": "www.feishu.cn/avatar/icon",
"avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
"avatar_middle": "www.feishu.cn/avatar/icon_middle",
"avatar_big": "www.feishu.cn/avatar/icon_big",
"open_id": "ou-caecc734c2e3328a62489fe0648c4b98779515d3",
"union_id": "on-d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj",
"email": "zhangsan@feishu.cn",
"user_id": "5d9bdxxx",
"mobile": "+86130002883xx",
"tenant_key": "736588c92lxf175d",
"refresh_expires_in": 2591940,
"refresh_token": "ur-t9HHgRCjMqGqIU9v05Zhos"
}
}
*/
type LarkUserInfo struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Name string `json:"name"`
EnName string `json:"en_name"`
AvatarUrl string `json:"avatar_url"`
AvatarThumb string `json:"avatar_thumb"`
AvatarMiddle string `json:"avatar_middle"`
AvatarBig string `json:"avatar_big"`
OpenId string `json:"open_id"`
UnionId string `json:"union_id"`
Email string `json:"email"`
UserId string `json:"user_id"`
Mobile string `json:"mobile"`
TenantKey string `json:"tenant_key"`
RefreshExpiresIn int `json:"refresh_expires_in"`
RefreshToken string `json:"refresh_token"`
} `json:"data"`
}
// GetUserInfo use LarkAccessToken gotten before return LinkedInUserInfo
// get more detail via: https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin?context=linkedin/consumer/context
func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
body := &struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
}{"authorization_code", token.Extra("code").(string)}
data, _ := json.Marshal(body)
req, err := http.NewRequest("POST", "https://open.feishu.cn/open-apis/authen/v1/access_token", strings.NewReader(string(data)))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
resp, err := idp.Client.Do(req)
data, err = io.ReadAll(resp.Body)
err = resp.Body.Close()
if err != nil {
return nil, err
}
var larkUserInfo LarkUserInfo
if err = json.Unmarshal(data, &larkUserInfo); err != nil {
return nil, err
}
userInfo := UserInfo{
Id: larkUserInfo.Data.OpenId,
DisplayName: larkUserInfo.Data.EnName,
Username: larkUserInfo.Data.Name,
Email: larkUserInfo.Data.Email,
AvatarUrl: larkUserInfo.Data.AvatarUrl,
}
return &userInfo, nil
}
func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
r := strings.NewReader(string(bs))
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
return data, nil
}

View File

@@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@@ -84,22 +85,22 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := io.ReadAll(resp.Body)
rbs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
tokenResp := LinkedInAccessToken{}
if err = json.Unmarshal(rbs, &tokenResp); err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: tokenResp.AccessToken,
AccessToken: tokenResp.AccessToken,
TokenType: "Bearer",
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
}
return token, nil
}
@@ -185,14 +186,14 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
type LinkedInUserInfo struct {
FirstName struct {
Localized map[string]string `json:"localized"`
Localized map[string]string `json:"localized"`
PreferredLocale struct {
Country string `json:"country"`
Language string `json:"language"`
} `json:"preferredLocale"`
} `json:"firstName"`
LastName struct {
Localized map[string]string `json:"localized"`
Localized map[string]string `json:"localized"`
PreferredLocale struct {
Country string `json:"country"`
Language string `json:"language"`
@@ -300,7 +301,7 @@ func (idp *LinkedInIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
userInfo := UserInfo{
Id: linkedInUserInfo.Id,
DisplayName: username,
Username: username,
Username: username,
Email: linkedInUserEmail.Elements[0].Handle.EmailAddress,
AvatarUrl: linkedInUserInfo.ProfilePicture.DisplayImage1.Elements[0].Identifiers[0].Identifier,
}
@@ -322,7 +323,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
}
}(resp.Body)
bs, err := io.ReadAll(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@@ -16,7 +16,6 @@ package idp
import (
"net/http"
"strings"
"golang.org/x/oauth2"
)
@@ -54,26 +53,7 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "LinkedIn" {
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "WeCom" {
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "Lark" {
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
} else if providerType == "GitLab" {
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
} else if isGothSupport(providerType) {
return NewGothIdProvider(providerType, clientId, clientSecret, redirectUrl)
}
return nil
}
var gothList = []string{"Apple", "AzureAd", "Slack"}
func isGothSupport(provider string) bool {
for _, value := range gothList {
if strings.EqualFold(value, provider) {
return true
}
}
return false
}

View File

@@ -18,7 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
@@ -75,7 +75,7 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
defer resp.Body.Close()
tokenContent, err := io.ReadAll(resp.Body)
tokenContent, err := ioutil.ReadAll(resp.Body)
re := regexp.MustCompile("token=(.*?)&")
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
@@ -145,7 +145,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
openIdBody, err := io.ReadAll(resp.Body)
openIdBody, err := ioutil.ReadAll(resp.Body)
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
@@ -161,7 +161,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
userInfoBody, err := io.ReadAll(resp.Body)
userInfoBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@@ -173,7 +173,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
if qqUserInfo.Ret != 0 {
return nil, fmt.Errorf("ret expected 0, got %d", qqUserInfo.Ret)
return nil, errors.New(fmt.Sprintf("ret expected 0, got %d", qqUserInfo.Ret))
}
userInfo := UserInfo{

View File

@@ -99,7 +99,7 @@ func (idp *WeChatIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
var wechatAccessToken WechatAccessToken
if err = json.Unmarshal(buf.Bytes(), &wechatAccessToken); err != nil {
if err = json.Unmarshal([]byte(buf.String()), &wechatAccessToken); err != nil {
return nil, err
}
@@ -168,7 +168,7 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
if err != nil {
return nil, err
}
if err = json.Unmarshal(buf.Bytes(), &wechatUserInfo); err != nil {
if err = json.Unmarshal([]byte(buf.String()), &wechatUserInfo); err != nil {
return nil, err
}
@@ -179,7 +179,6 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
userInfo := UserInfo{
Id: id,
Username: wechatUserInfo.Nickname,
DisplayName: wechatUserInfo.Nickname,
AvatarUrl: wechatUserInfo.Headimgurl,
}

View File

@@ -1,209 +0,0 @@
// Copyright 2021 The casbin 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"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
)
type WeComIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
func NewWeComIdProvider(clientId string, clientSecret string, redirectUrl string) *WeComIdProvider {
idp := &WeComIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
func (idp *WeComIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *WeComIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
TokenURL: "https://graph.qq.com/oauth2.0/token",
}
var config = &oauth2.Config{
Scopes: []string{"snsapi_login"},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type WeComProviderToken struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
ProviderAccessToken string `json:"provider_access_token"`
ExpiresIn int `json:"expires_in"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
func (idp *WeComIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
CorpId string `json:"corpid"`
ProviderSecret string `json:"provider_secret"`
}{idp.Config.ClientID, idp.Config.ClientSecret}
data, err := idp.postWithBody(pTokenParams, "https://qyapi.weixin.qq.com/cgi-bin/service/get_provider_token")
pToken := &WeComProviderToken{}
err = json.Unmarshal(data, pToken)
if err != nil {
return nil, err
}
if pToken.Errcode != 0 {
return nil, fmt.Errorf("pToken.Errcode = %d, pToken.Errmsg = %s", pToken.Errcode, pToken.Errmsg)
}
token := &oauth2.Token{
AccessToken: pToken.ProviderAccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
raw := make(map[string]interface{})
raw["code"] = code
token = token.WithExtra(raw)
return token, nil
}
/*
{
"errcode":0,
"errmsg":"ok",
"usertype": 1,
"user_info":{
"userid":"xxxx",
"open_userid":"xxx",
"name":"xxxx",
"avatar":"xxxx"
},
"corp_info":{
"corpid":"wxCorpId",
},
"agent":[
{"agentid":0,"auth_type":1},
{"agentid":1,"auth_type":1},
{"agentid":2,"auth_type":1}
],
"auth_info":{
"department":[
{
"id":2,
"writable":true
}
]
}
}
*/
type WeComUserInfo struct {
Errcode int `json:"errcode"`
Errmsg string `json:"errmsg"`
Usertype int `json:"usertype"`
UserInfo struct {
Userid string `json:"userid"`
OpenUserid string `json:"open_userid"`
Name string `json:"name"`
Avatar string `json:"avatar"`
} `json:"user_info"`
CorpInfo struct {
Corpid string `json:"corpid"`
} `json:"corp_info"`
Agent []struct {
Agentid int `json:"agentid"`
AuthType int `json:"auth_type"`
} `json:"agent"`
AuthInfo struct {
Department []struct {
Id int `json:"id"`
Writable bool `json:"writable"`
} `json:"department"`
} `json:"auth_info"`
}
// GetUserInfo use WeComProviderToken gotten before return WeComUserInfo
// get more detail via: https://work.weixin.qq.com/api/doc/90001/90143/91125
func (idp *WeComIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
accessToken := token.AccessToken
code := token.Extra("code").(string)
requestBody := &struct {
AuthCode string `json:"auth_code"`
}{code}
data, err := idp.postWithBody(requestBody, fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/service/get_login_info?access_token=%s", accessToken))
if err != nil {
return nil, err
}
wecomUserInfo := WeComUserInfo{}
err = json.Unmarshal(data, &wecomUserInfo)
if err != nil {
return nil, err
}
if wecomUserInfo.Errcode != 0 {
return nil, fmt.Errorf("wecomUserInfo.Errcode = %d, wecomUserInfo.Errmsg = %s", wecomUserInfo.Errcode, wecomUserInfo.Errmsg)
}
userInfo := UserInfo{
Id: wecomUserInfo.UserInfo.OpenUserid,
Username: wecomUserInfo.UserInfo.Name,
DisplayName: wecomUserInfo.UserInfo.Name,
AvatarUrl: wecomUserInfo.UserInfo.Avatar,
}
return &userInfo, nil
}
func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
r := strings.NewReader(string(bs))
resp, err := idp.Client.Post(url, "application/json;charset=UTF-8", r)
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
return data, nil
}

View File

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

35
main.go
View File

@@ -18,25 +18,20 @@ import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
"github.com/astaxie/beego/plugins/cors"
_ "github.com/astaxie/beego/session/redis"
"github.com/casbin/casdoor/authz"
"github.com/casbin/casdoor/object"
"github.com/casbin/casdoor/proxy"
"github.com/casbin/casdoor/routers"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/controllers"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/routers"
_ "github.com/casbin/casdoor/routers"
_ "github.com/casdoor/casdoor/routers"
)
func main() {
object.InitAdapter()
object.InitDb()
object.InitDefaultStorageProvider()
object.InitLdapAutoSynchronizer()
proxy.InitHttpClient()
controllers.InitHttpClient()
authz.InitAuthz()
go object.RunSyncUsersJob()
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "PUT", "PATCH"},
@@ -49,29 +44,23 @@ func main() {
beego.SetStaticPath("/static", "web/build/static")
beego.BConfig.WebConfig.DirectoryIndex = true
beego.SetStaticPath("/swagger", "swagger")
beego.SetStaticPath("/files", "files")
// https://studygolang.com/articles/2303
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
if beego.AppConfig.String("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
} else {
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 365
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
err := logs.SetLogger("file", `{"filename":"logs/casdoor.log","maxdays":99999,"perm":"0770"}`)
err := logs.SetLogger("file", `{"filename":"logs/casdoor.log","maxdays":99999}`)
if err != nil {
panic(err)
}
//logs.SetLevel(logs.LevelInformational)
logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false)
beego.Run()

View File

@@ -19,7 +19,6 @@ import (
"runtime"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/conf"
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
"xorm.io/xorm"
@@ -37,7 +36,7 @@ func InitConfig() {
}
func InitAdapter() {
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("dbName"))
adapter.createTable()
}
@@ -100,7 +99,7 @@ func (a *Adapter) open() {
}
func (a *Adapter) close() {
_ = a.Engine.Close()
a.Engine.Close()
a.Engine = nil
}
@@ -125,11 +124,6 @@ func (a *Adapter) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Resource))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Token))
if err != nil {
panic(err)
@@ -139,18 +133,7 @@ func (a *Adapter) createTable() {
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Record))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Webhook))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Syncer))
err = a.Engine.Sync2(new(Records))
if err != nil {
panic(err)
}

View File

@@ -15,7 +15,7 @@
package object
import (
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -24,40 +24,25 @@ type Application struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Logo string `xorm:"varchar(100)" json:"logo"`
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
Description string `xorm:"varchar(100)" json:"description"`
Organization string `xorm:"varchar(100)" json:"organization"`
EnablePassword bool `json:"enablePassword"`
EnableSignUp bool `json:"enableSignUp"`
EnableCodeSignin bool `json:"enableCodeSignin"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Logo string `xorm:"varchar(100)" json:"logo"`
HomepageUrl string `xorm:"varchar(100)" json:"homepageUrl"`
Description string `xorm:"varchar(100)" json:"description"`
Organization string `xorm:"varchar(100)" json:"organization"`
EnablePassword bool `json:"enablePassword"`
EnableSignUp bool `json:"enableSignUp"`
Providers []*ProviderItem `xorm:"varchar(10000)" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
ExpireInHours int `json:"expireInHours"`
RefreshExpireInHours int `json:"refreshExpireInHours"`
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
SigninUrl string `xorm:"varchar(200)" json:"signinUrl"`
ForgetUrl string `xorm:"varchar(200)" json:"forgetUrl"`
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
TermsOfUse string `xorm:"varchar(100)" json:"termsOfUse"`
SignupHtml string `xorm:"mediumtext" json:"signupHtml"`
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
}
func GetApplicationCount(owner string) int {
count, err := adapter.Engine.Count(&Application{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
ExpireInHours int `json:"expireInHours"`
SignupUrl string `xorm:"varchar(100)" json:"signupUrl"`
SigninUrl string `xorm:"varchar(100)" json:"signinUrl"`
ForgetUrl string `xorm:"varchar(100)" json:"forgetUrl"`
AffiliationUrl string `xorm:"varchar(100)" json:"affiliationUrl"`
}
func GetApplications(owner string) []*Application {
@@ -70,16 +55,6 @@ func GetApplications(owner string) []*Application {
return applications
}
func GetPaginationApplications(owner string, offset, limit int) []*Application {
applications := []*Application{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&applications, &Application{Owner: owner})
if err != nil {
panic(err)
}
return applications
}
func getProviderMap(owner string) map[string]*Provider {
providers := GetProviders(owner)
m := map[string]*Provider{}
@@ -151,21 +126,6 @@ func GetApplicationByUser(user *User) *Application {
}
}
func GetApplicationByUserId(userId string) (*Application, *User) {
var application *Application
owner, name := util.GetOwnerAndNameFromId(userId)
if owner == "app" {
application = getApplication("admin", name)
return application, nil
}
user := GetUser(userId)
application = GetApplicationByUser(user)
return application, user
}
func GetApplicationByClientId(clientId string) *Application {
application := Application{}
existed, err := adapter.Engine.Where("client_id=?", clientId).Get(&application)
@@ -182,19 +142,6 @@ func GetApplicationByClientId(clientId string) *Application {
}
}
func GetApplicationByClientIdAndSecret(clientId, clientSecret string) *Application {
if util.IsStrsEmpty(clientId, clientSecret) {
return nil
}
app := GetApplicationByClientId(clientId)
if app == nil || app.ClientSecret != clientSecret {
return nil
}
return app
}
func GetApplication(id string) *Application {
owner, name := util.GetOwnerAndNameFromId(id)
return getApplication(owner, name)

View File

@@ -14,7 +14,7 @@
package object
func (application *Application) GetProviderByCategory(category string) *Provider {
func (application *Application) getProviderByCategory(category string) *Provider {
providers := GetProviders(application.Owner)
m := map[string]*Provider{}
for _, provider := range providers {
@@ -35,15 +35,11 @@ func (application *Application) GetProviderByCategory(category string) *Provider
}
func (application *Application) GetEmailProvider() *Provider {
return application.GetProviderByCategory("Email")
return application.getProviderByCategory("Email")
}
func (application *Application) GetSmsProvider() *Provider {
return application.GetProviderByCategory("SMS")
}
func (application *Application) GetStorageProvider() *Provider {
return application.GetProviderByCategory("Storage")
return application.getProviderByCategory("SMS")
}
func (application *Application) getSignupItem(itemName string) *SignupItem {
@@ -55,6 +51,10 @@ func (application *Application) getSignupItem(itemName string) *SignupItem {
return nil
}
func (application *Application) IsSignupItemEnabled(itemName string) bool {
return application.getSignupItem(itemName) != nil
}
func (application *Application) IsSignupItemVisible(itemName string) bool {
signupItem := application.getSignupItem(itemName)
if signupItem == nil {
@@ -64,15 +64,6 @@ func (application *Application) IsSignupItemVisible(itemName string) bool {
return signupItem.Visible
}
func (application *Application) IsSignupItemRequired(itemName string) bool {
signupItem := application.getSignupItem(itemName)
if signupItem == nil {
return false
}
return signupItem.Required
}
func (application *Application) GetSignupItemRule(itemName string) string {
signupItem := application.getSignupItem(itemName)
if signupItem == nil {

View File

@@ -1,72 +0,0 @@
// Copyright 2021 The casbin 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"
"fmt"
"io"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/proxy"
)
var defaultStorageProvider *Provider = nil
func InitDefaultStorageProvider() {
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
if defaultStorageProviderStr != "" {
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
}
}
func downloadFile(url string) (*bytes.Buffer, error) {
httpClient := proxy.GetHttpClient(url)
resp, err := httpClient.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
fileBuffer := bytes.NewBuffer(nil)
_, err = io.Copy(fileBuffer, resp.Body)
if err != nil {
return nil, err
}
return fileBuffer, nil
}
func getPermanentAvatarUrl(organization string, username string, url string) string {
if defaultStorageProvider == nil {
return ""
}
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)
fileBuffer, err := downloadFile(url)
if err != nil {
panic(err)
}
_, _, err = UploadFileSafe(defaultStorageProvider, fullFilePath, fileBuffer)
if err != nil {
panic(err)
}
return uploadedFileUrl
}

View File

@@ -1,39 +0,0 @@
// Copyright 2021 The casbin 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"
"testing"
"github.com/casbin/casdoor/proxy"
)
func TestSyncPermanentAvatars(t *testing.T) {
InitConfig()
InitDefaultStorageProvider()
proxy.InitHttpClient()
users := GetGlobalUsers()
for i, user := range users {
if user.Avatar == "" {
continue
}
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
updateUserColumn("permanent_avatar", user)
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
}
}

View File

@@ -18,15 +18,13 @@ import (
"fmt"
"regexp"
"github.com/casbin/casdoor/cred"
"github.com/casbin/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/casdoor/casdoor/util"
)
var reWhiteSpace *regexp.Regexp
func init() {
reWhiteSpace, _ = regexp.Compile(`\s`)
reWhiteSpace, _ = regexp.Compile("\\s")
}
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
@@ -49,14 +47,6 @@ func CheckUserSignup(application *Application, organization *Organization, usern
}
if application.IsSignupItemVisible("Email") {
if email == "" {
if application.IsSignupItemRequired("Email") {
return "email cannot be empty"
} else {
return ""
}
}
if HasUserByField(organization.Name, "email", email) {
return "email already exists"
} else if !util.IsEmailValid(email) {
@@ -65,14 +55,6 @@ func CheckUserSignup(application *Application, organization *Organization, usern
}
if application.IsSignupItemVisible("Phone") {
if phone == "" {
if application.IsSignupItemRequired("Phone") {
return "phone cannot be empty"
} else {
return ""
}
}
if HasUserByField(organization.Name, "phone", phone) {
return "phone already exists"
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
@@ -101,75 +83,33 @@ func CheckUserSignup(application *Application, organization *Organization, usern
func CheckPassword(user *User, password string) string {
organization := GetOrganizationByUser(user)
if organization == nil {
return "organization does not exist"
}
credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil {
if organization.MasterPassword != "" && organization.MasterPassword == password {
if organization.PasswordType == "plain" {
if password == user.Password {
return ""
} else {
return "password incorrect"
}
sealedPassword := credManager.GetSealedPassword(password, user.PasswordSalt, organization.PasswordSalt)
if user.Password == sealedPassword {
} else if organization.PasswordType == "salt" {
if password == user.Password || getSaltedPassword(password, organization.PasswordSalt) == user.Password {
return ""
} else {
return "password incorrect"
}
return "password incorrect"
} else {
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
}
}
func checkLdapUserPassword(user *User, password string) (*User, string) {
ldaps := GetLdaps(user.Owner)
ldapLoginSuccess := false
for _, ldapServer := range ldaps {
conn, err := GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
if err != nil {
continue
}
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, []string{}, nil)
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
return nil, err.Error()
}
if len(searchResult.Entries) == 0 {
continue
} else if len(searchResult.Entries) > 1 {
return nil, "Error: multiple accounts with same uid, please check your ldap server"
}
dn := searchResult.Entries[0].DN
if err := conn.Conn.Bind(dn, password); err == nil {
ldapLoginSuccess = true
break
}
}
if !ldapLoginSuccess {
return nil, "ldap user name or password incorrect"
}
return user, ""
}
func CheckUserPassword(organization string, username string, password string) (*User, string) {
func CheckUserLogin(organization string, username string, password string) (*User, string) {
user := GetUserByFields(organization, username)
if user == nil || user.IsDeleted == true {
if user == nil {
return nil, "the user does not exist, please sign up first"
}
if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator"
}
//for ldap users
if user.Ldap != "" {
return checkLdapUserPassword(user, password)
}
msg := CheckPassword(user, password)
if msg != "" {

View File

@@ -18,7 +18,7 @@ package object
import "github.com/go-gomail/gomail"
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
func SendEmail(provider *Provider, title, content, dest, sender string) string {
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
message := gomail.NewMessage()
@@ -27,5 +27,10 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
message.SetHeader("Subject", title)
message.SetBody("text/html", content)
return dialer.DialAndSend(message)
err := dialer.DialAndSend(message)
if err == nil {
return ""
} else {
return err.Error()
}
}

View File

@@ -14,7 +14,7 @@
package object
import "github.com/casbin/casdoor/util"
import "github.com/casdoor/casdoor/util"
func InitDb() {
initBuiltInOrganization()
@@ -63,12 +63,9 @@ func initBuiltInUser() {
Address: []string{},
Affiliation: "Example Inc.",
Tag: "staff",
Score: 2000,
Ranking: 1,
IsAdmin: true,
IsGlobalAdmin: true,
IsForbidden: false,
IsDeleted: false,
Properties: make(map[string]string),
}
AddUser(user)
@@ -91,18 +88,9 @@ func initBuiltInApplication() {
EnablePassword: true,
EnableSignUp: true,
Providers: []*ProviderItem{},
SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Display name", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Confirm password", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Email", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Phone", Visible: true, Required: true, Prompted: false, Rule: "None"},
{Name: "Agreement", Visible: true, Required: true, Prompted: false, Rule: "None"},
},
RedirectUris: []string{},
ExpireInHours: 168,
SignupItems: []*SignupItem{},
RedirectUris: []string{},
ExpireInHours: 168,
}
AddApplication(application)
}

View File

@@ -17,11 +17,10 @@ package object
import (
"errors"
"fmt"
"strings"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
"strings"
)
type Ldap struct {
@@ -78,32 +77,6 @@ type LdapRespUser struct {
Address string `json:"address"`
}
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
returnAnyNotEmpty := func(strs ...string) string {
for _, str := range strs {
if str != "" {
return str
}
}
return ""
}
res := make([]LdapRespUser, 0)
for _, user := range users {
res = append(res, LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
Uuid: user.Uuid,
Email: returnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
Phone: returnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
Address: returnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
})
}
return res
}
func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*ldapConn, error) {
conn, err := goldap.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
@@ -179,30 +152,43 @@ func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
switch attribute.Name {
case "uidNumber":
ldapUserItem.UidNumber = attribute.Values[0]
break
case "uid":
ldapUserItem.Uid = attribute.Values[0]
break
case "cn":
ldapUserItem.Cn = attribute.Values[0]
break
case "gidNumber":
ldapUserItem.GidNumber = attribute.Values[0]
break
case "entryUUID":
ldapUserItem.Uuid = attribute.Values[0]
break
case "mail":
ldapUserItem.Mail = attribute.Values[0]
break
case "email":
ldapUserItem.Email = attribute.Values[0]
break
case "emailAddress":
ldapUserItem.EmailAddress = attribute.Values[0]
break
case "telephoneNumber":
ldapUserItem.TelephoneNumber = attribute.Values[0]
break
case "mobile":
ldapUserItem.Mobile = attribute.Values[0]
break
case "mobileTelephoneNumber":
ldapUserItem.MobileTelephoneNumber = attribute.Values[0]
break
case "registeredAddress":
ldapUserItem.RegisteredAddress = attribute.Values[0]
break
case "postalAddress":
ldapUserItem.PostalAddress = attribute.Values[0]
break
}
}
ldapUsers = append(ldapUsers, ldapUserItem)
@@ -312,16 +298,15 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
existUuids := CheckLdapUuidExist(owner, uuids)
for _, user := range users {
found := false
if len(existUuids) > 0 {
for _, existUuid := range existUuids {
for index, existUuid := range existUuids {
if user.Uuid == existUuid {
existUsers = append(existUsers, user)
found = true
existUuids = append(existUuids[:index], existUuids[index+1:]...)
}
}
}
if !found && !AddUser(&User{
if !AddUser(&User{
Owner: owner,
Name: buildLdapUserName(user.Uid, user.UidNumber),
CreatedTime: util.GetCurrentTime(),
@@ -333,7 +318,6 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
Address: []string{user.Address},
Affiliation: "Example Inc.",
Tag: "staff",
Score: 2000,
Ldap: user.Uuid,
}) {
failedUsers = append(failedUsers, user)
@@ -354,7 +338,6 @@ func UpdateLdapSyncTime(ldapId string) {
func CheckLdapUuidExist(owner string, uuids []string) []string {
var results []User
var existUuids []string
existUuidSet := make(map[string]struct{})
//whereStr := ""
//for i, uuid := range uuids {
@@ -365,20 +348,16 @@ func CheckLdapUuidExist(owner string, uuids []string) []string {
// }
//}
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&results)
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'" + strings.Join(uuids, "','") + "'"), owner).Find(&results)
if err != nil {
panic(err)
}
if len(results) > 0 {
for _, result := range results {
existUuidSet[result.Ldap] = struct{}{}
existUuids = append(existUuids, result.Ldap)
}
}
for uuid, _ := range existUuidSet {
existUuids = append(existUuids, uuid)
}
return existUuids
}

View File

@@ -1,111 +0,0 @@
package object
import (
"fmt"
"sync"
"time"
"github.com/astaxie/beego/logs"
)
type LdapAutoSynchronizer struct {
sync.Mutex
ldapIdToStopChan map[string]chan struct{}
}
var globalLdapAutoSynchronizer *LdapAutoSynchronizer
func InitLdapAutoSynchronizer() {
globalLdapAutoSynchronizer = NewLdapAutoSynchronizer()
globalLdapAutoSynchronizer.LdapAutoSynchronizerStartUpAll()
}
func NewLdapAutoSynchronizer() *LdapAutoSynchronizer {
return &LdapAutoSynchronizer{
ldapIdToStopChan: make(map[string]chan struct{}),
}
}
func GetLdapAutoSynchronizer() *LdapAutoSynchronizer {
return globalLdapAutoSynchronizer
}
//start autosync for specified ldap, old existing autosync goroutine will be ceased
func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
l.Lock()
defer l.Unlock()
ldap := GetLdap(ldapId)
if ldap == nil {
return fmt.Errorf("ldap %s doesn't exist", ldapId)
}
if res, ok := l.ldapIdToStopChan[ldapId]; ok {
res <- struct{}{}
delete(l.ldapIdToStopChan, ldapId)
}
stopChan := make(chan struct{})
l.ldapIdToStopChan[ldapId] = stopChan
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
go l.syncRoutine(ldap, stopChan)
return nil
}
func (l *LdapAutoSynchronizer) StopAutoSync(ldapId string) {
l.Lock()
defer l.Unlock()
if res, ok := l.ldapIdToStopChan[ldapId]; ok {
res <- struct{}{}
delete(l.ldapIdToStopChan, ldapId)
}
}
//autosync goroutine
func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
ticker := time.NewTicker(time.Duration(ldap.AutoSync) * time.Minute)
defer ticker.Stop()
for {
UpdateLdapSyncTime(ldap.Id)
//fetch all users
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
if err != nil {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
users, err := conn.GetLdapUsers(ldap.BaseDn)
if err != nil {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users))
if len(*failed) != 0 {
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
} else {
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:
}
}
}
//start all autosync goroutine for existing ldap servers in each organizations
func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
organizations := []*Organization{}
err := adapter.Engine.Desc("created_time").Find(&organizations)
if err != nil {
logs.Info("failed to Star up LdapAutoSynchronizer; ")
}
for _, org := range organizations {
for _, ldap := range GetLdaps(org.Name) {
if ldap.AutoSync != 0 {
l.StartAutoSync(ldap.Id)
}
}
}
}

View File

@@ -1,90 +0,0 @@
// Copyright 2021 The casbin 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/x509"
"encoding/pem"
"fmt"
"github.com/astaxie/beego"
"gopkg.in/square/go-jose.v2"
)
type OidcDiscovery struct {
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
JwksUri string `json:"jwks_uri"`
ResponseTypesSupported []string `json:"response_types_supported"`
ResponseModesSupported []string `json:"response_modes_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
SubjectTypesSupported []string `json:"subject_types_supported"`
IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
ScopesSupported []string `json:"scopes_supported"`
ClaimsSupported []string `json:"claims_supported"`
RequestParameterSupported bool `json:"request_parameter_supported"`
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
}
var oidcDiscovery OidcDiscovery
func init() {
origin := beego.AppConfig.String("origin")
// Examples:
// https://login.okta.com/.well-known/openid-configuration
// https://auth0.auth0.com/.well-known/openid-configuration
// https://accounts.google.com/.well-known/openid-configuration
// https://access.line.me/.well-known/openid-configuration
oidcDiscovery = OidcDiscovery{
Issuer: origin,
AuthorizationEndpoint: fmt.Sprintf("%s/login/oauth/authorize", origin),
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", origin),
UserinfoEndpoint: fmt.Sprintf("%s/api/get-account", origin),
JwksUri: fmt.Sprintf("%s/api/certs", origin),
ResponseTypesSupported: []string{"id_token"},
ResponseModesSupported: []string{"login", "code", "link"},
GrantTypesSupported: []string{"password", "authorization_code"},
SubjectTypesSupported: []string{"public"},
IdTokenSigningAlgValuesSupported: []string{"RS256"},
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isGlobalAdmin", "isForbidden", "signupApplication", "ldap"},
RequestParameterSupported: true,
RequestObjectSigningAlgValuesSupported: []string{"HS256", "HS384", "HS512"},
}
}
func GetOidcDiscovery() OidcDiscovery {
return oidcDiscovery
}
func GetJSONWebKeySet() (jose.JSONWebKeySet, error) {
//follows the protocol rfc 7517(draft)
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
certPEMBlock := []byte(tokenJwtPublicKey)
certDERBlock, _ := pem.Decode(certPEMBlock)
x509Cert, _ := x509.ParseCertificate(certDERBlock.Bytes)
var jwk jose.JSONWebKey
jwk.Key = x509Cert.PublicKey
jwk.Certificates = []*x509.Certificate{x509Cert}
var jwks jose.JSONWebKeySet
jwks.Keys = []jose.JSONWebKey{jwk}
return jwks, nil
}

View File

@@ -15,7 +15,7 @@
package object
import (
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -24,24 +24,13 @@ type Organization struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
}
func GetOrganizationCount(owner string) int {
count, err := adapter.Engine.Count(&Organization{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
}
func GetOrganizations(owner string) []*Organization {
@@ -54,16 +43,6 @@ func GetOrganizations(owner string) []*Organization {
return organizations
}
func GetPaginationOrganizations(owner string, offset, limit int) []*Organization {
organizations := []*Organization{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&organizations, &Provider{Owner: owner})
if err != nil {
panic(err)
}
return organizations
}
func getOrganization(owner string, name string) *Organization {
if owner == "" || name == "" {
return nil
@@ -77,9 +56,9 @@ func getOrganization(owner string, name string) *Organization {
if existed {
return &organization
} else {
return nil
}
return nil
}
func GetOrganization(id string) *Organization {
@@ -87,24 +66,6 @@ func GetOrganization(id string) *Organization {
return getOrganization(owner, name)
}
func GetMaskedOrganization(organization *Organization) *Organization {
if organization == nil {
return nil
}
if organization.MasterPassword != "" {
organization.MasterPassword = "***"
}
return organization
}
func GetMaskedOrganizations(organizations []*Organization) []*Organization {
for _, organization := range organizations {
organization = GetMaskedOrganization(organization)
}
return organizations
}
func UpdateOrganization(id string, organization *Organization) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getOrganization(owner, name) == nil {

130
object/oss.go Normal file
View File

@@ -0,0 +1,130 @@
// Copyright 2021 The casbin 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"
"fmt"
awss3 "github.com/aws/aws-sdk-go/service/s3"
"github.com/qor/oss"
"github.com/qor/oss/aliyun"
//"github.com/qor/oss/qiniu"
"github.com/qor/oss/s3"
"gopkg.in/ini.v1"
)
var storage oss.StorageInterface
var domain string
func AliyunInit(section *ini.Section) string {
endpoint := section.Key("endpoint").String()
accessId := section.Key("accessId").String()
accessKey := section.Key("accessKey").String()
domain = section.Key("domain").String()
bucket := section.Key("bucket").String()
if accessId == "" || accessKey == "" || bucket == "" || endpoint == "" {
return "Config oss.conf wrong"
}
storage = aliyun.New(&aliyun.Config{
AccessID: accessId,
AccessKey: accessKey,
Bucket: bucket,
Endpoint: endpoint,
})
return ""
}
//func QiniuInit(section *ini.Section) string {
// endpoint := section.Key("endpoint").String()
// accessId := section.Key("accessId").String()
// accessKey := section.Key("accessKey").String()
// domain = section.Key("domain").String()
// bucket := section.Key("bucket").String()
// region := section.Key("region").String()
// if accessId == "" || accessKey == "" || bucket == "" || endpoint == "" || region == "" {
// return "Config oss.conf wrong"
// }
// storage = qiniu.New(&qiniu.Config{
// AccessID: accessId,
// AccessKey: accessKey,
// Bucket: bucket,
// Region: region,
// Endpoint: endpoint,
// })
// return ""
//}
func Awss3Init(section *ini.Section) string {
endpoint := section.Key("endpoint").String()
accessId := section.Key("accessId").String()
accessKey := section.Key("accessKey").String()
domain = section.Key("domain").String()
bucket := section.Key("bucket").String()
region := section.Key("region").String()
if accessId == "" || accessKey == "" || bucket == "" || endpoint == "" || region == "" {
return "Config oss.conf wrong"
}
storage = s3.New(&s3.Config{
AccessID: accessId,
AccessKey: accessKey,
Region: region,
Bucket: bucket,
Endpoint: endpoint,
ACL: awss3.BucketCannedACLPublicRead,
})
return ""
}
func InitOssClient() {
if storage != nil {
return
}
ossConf, err := ini.Load("./conf/oss.conf")
if err != nil {
panic(err)
return
}
aliyunSection, _ := ossConf.GetSection("aliyun")
qiniuSection, _ := ossConf.GetSection("qiniu")
awss3Section, _ := ossConf.GetSection("s3")
if aliyunSection != nil {
AliyunInit(aliyunSection)
} else if qiniuSection != nil {
//QiniuInit(qiniuSection)
} else {
Awss3Init(awss3Section)
}
}
func UploadAvatar(username string, avatar []byte) string {
if storage == nil {
InitOssClient()
if storage == nil {
return "oss error"
}
}
path := fmt.Sprintf("/casdoor/avatar/%s.png", username)
_, err := storage.Put(path, bytes.NewReader(avatar))
if err != nil {
panic(err)
}
return ""
}
func GetAvatarPath() string {
return fmt.Sprintf("https://%s/casdoor/avatar/", domain)
}

View File

@@ -12,15 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package cred
package object
import (
"crypto/sha256"
"encoding/hex"
)
type Sha256SaltCredManager struct{}
func getSha256(data []byte) []byte {
hash := sha256.Sum256(data)
return hash[:]
@@ -32,13 +30,8 @@ func getSha256HexDigest(s string) string {
return res
}
func NewSha256SaltCredManager() *Sha256SaltCredManager {
cm := &Sha256SaltCredManager{}
return cm
}
func (cm *Sha256SaltCredManager) GetSealedPassword(password string, userSalt string, organizationSalt string) string {
hash := getSha256HexDigest(password)
res := getSha256HexDigest(hash + organizationSalt)
func getSaltedPassword(password string, salt string) string {
hash1 := getSha256HexDigest(password)
res := getSha256HexDigest(hash1 + salt)
return res
}

View File

@@ -15,9 +15,7 @@
package object
import (
"fmt"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -29,7 +27,6 @@ type Provider struct {
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(100)" json:"category"`
Type string `xorm:"varchar(100)" json:"type"`
Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
@@ -43,16 +40,6 @@ type Provider struct {
TemplateCode string `xorm:"varchar(100)" json:"templateCode"`
AppId string `xorm:"varchar(100)" json:"appId"`
Endpoint string `xorm:"varchar(1000)" json:"endpoint"`
IntranetEndpoint string `xorm:"varchar(100)" json:"intranetEndpoint"`
Domain string `xorm:"varchar(100)" json:"domain"`
Bucket string `xorm:"varchar(100)" json:"bucket"`
Metadata string `xorm:"mediumtext" json:"metadata"`
IdP string `xorm:"mediumtext" json:"idP"`
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
}
@@ -64,21 +51,11 @@ func getMaskedProvider(provider *Provider) *Provider {
DisplayName: provider.DisplayName,
Category: provider.Category,
Type: provider.Type,
Method: provider.Method,
ClientId: provider.ClientId,
}
return p
}
func GetProviderCount(owner string) int {
count, err := adapter.Engine.Count(&Provider{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
}
func GetProviders(owner string) []*Provider {
providers := []*Provider{}
err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner})
@@ -89,16 +66,6 @@ func GetProviders(owner string) []*Provider {
return providers
}
func GetPaginationProviders(owner string, offset, limit int) []*Provider {
providers := []*Provider{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&providers, &Provider{Owner: owner})
if err != nil {
panic(err)
}
return providers
}
func getProvider(owner string, name string) *Provider {
if owner == "" || name == "" {
return nil
@@ -167,7 +134,3 @@ func DeleteProvider(provider *Provider) bool {
return affected != 0
}
func (p *Provider) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}

View File

@@ -33,10 +33,10 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
return nil
}
func (pi *ProviderItem) IsProviderVisible() bool {
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
func (pi *ProviderItem) isProviderVisible() bool {
return pi.Provider.Category == "OAuth"
}
func (pi *ProviderItem) isProviderPrompted() bool {
return pi.IsProviderVisible() && pi.Prompted
return pi.isProviderVisible() && pi.Prompted
}

View File

@@ -15,83 +15,19 @@
package object
import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
)
var logPostOnly bool
func init() {
var err error
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
if err != nil {
//panic(err)
}
type Records struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Record util.Record `xorm:"extends"`
}
type Record struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
func AddRecord(record *util.Record) bool {
records := new(Records)
records.Record = *record
Owner string `xorm:"varchar(100) index" json:"owner"`
Name string `xorm:"varchar(100) index" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Organization string `xorm:"varchar(100)" json:"organization"`
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
User string `xorm:"varchar(100)" json:"user"`
Method string `xorm:"varchar(100)" json:"method"`
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
Action string `xorm:"varchar(1000)" json:"action"`
IsTriggered bool `json:"isTriggered"`
}
func NewRecord(ctx *context.Context) *Record {
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
if len(requestUri) > 1000 {
requestUri = requestUri[0:1000]
}
record := Record{
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
ClientIp: ip,
User: "",
Method: ctx.Request.Method,
RequestUri: requestUri,
Action: action,
IsTriggered: false,
}
return &record
}
func AddRecord(record *Record) bool {
if logPostOnly {
if record.Method == "GET" {
return false
}
}
if record.Organization == "app" {
return false
}
record.Owner = record.Organization
errWebhook := SendWebhooks(record)
if errWebhook == nil {
record.IsTriggered = true
} else {
fmt.Println(errWebhook)
}
affected, err := adapter.Engine.Insert(record)
affected, err := adapter.Engine.Insert(records)
if err != nil {
panic(err)
}
@@ -100,7 +36,7 @@ func AddRecord(record *Record) bool {
}
func GetRecordCount() int {
count, err := adapter.Engine.Count(&Record{})
count, err := adapter.Engine.Count(&Records{})
if err != nil {
panic(err)
}
@@ -108,8 +44,8 @@ func GetRecordCount() int {
return int(count)
}
func GetRecords() []*Record {
records := []*Record{}
func GetRecords() []*Records {
records := []*Records{}
err := adapter.Engine.Desc("id").Find(&records)
if err != nil {
panic(err)
@@ -118,18 +54,8 @@ func GetRecords() []*Record {
return records
}
func GetPaginationRecords(offset, limit int) []*Record {
records := []*Record{}
err := adapter.Engine.Desc("id").Limit(limit, offset).Find(&records)
if err != nil {
panic(err)
}
return records
}
func GetRecordsByField(record *Record) []*Record {
records := []*Record{}
func GetRecordsByField(record *Records) []*Records {
records := []*Records{}
err := adapter.Engine.Find(&records, record)
if err != nil {
panic(err)
@@ -137,25 +63,3 @@ func GetRecordsByField(record *Record) []*Record {
return records
}
func SendWebhooks(record *Record) error {
webhooks := getWebhooksByOrganization(record.Organization)
for _, webhook := range webhooks {
matched := false
for _, event := range webhook.Events {
if record.Action == event {
matched = true
break
}
}
if matched {
err := sendWebhook(webhook, record)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -1,143 +0,0 @@
// Copyright 2021 The casbin 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/casbin/casdoor/util"
"xorm.io/core"
)
type Resource 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"`
User string `xorm:"varchar(100)" json:"user"`
Provider string `xorm:"varchar(100)" json:"provider"`
Application string `xorm:"varchar(100)" json:"application"`
Tag string `xorm:"varchar(100)" json:"tag"`
Parent string `xorm:"varchar(100)" json:"parent"`
FileName string `xorm:"varchar(100)" json:"fileName"`
FileType string `xorm:"varchar(100)" json:"fileType"`
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
FileSize int `json:"fileSize"`
Url string `xorm:"varchar(1000)" json:"url"`
Description string `xorm:"varchar(1000)" json:"description"`
}
func GetResourceCount(owner string, user string) int {
count, err := adapter.Engine.Count(&Resource{Owner: owner, User: user})
if err != nil {
panic(err)
}
return int(count)
}
func GetResources(owner string, user string) []*Resource {
if owner == "built-in" {
owner = ""
user = ""
}
resources := []*Resource{}
err := adapter.Engine.Desc("created_time").Find(&resources, &Resource{Owner: owner, User: user})
if err != nil {
panic(err)
}
return resources
}
func GetPaginationResources(owner, user string, offset, limit int) []*Resource {
if owner == "built-in" {
owner = ""
user = ""
}
resources := []*Resource{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&resources, &Resource{Owner: owner, User: user})
if err != nil {
panic(err)
}
return resources
}
func getResource(owner string, name string) *Resource {
resource := Resource{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&resource)
if err != nil {
panic(err)
}
if existed {
return &resource
}
return nil
}
func GetResource(id string) *Resource {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
return getResource(owner, name)
}
func UpdateResource(id string, resource *Resource) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
if getResource(owner, name) == nil {
return false
}
_, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(resource)
if err != nil {
panic(err)
}
//return affected != 0
return true
}
func AddResource(resource *Resource) bool {
affected, err := adapter.Engine.Insert(resource)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteResource(resource *Resource) bool {
affected, err := adapter.Engine.ID(core.PK{resource.Owner, resource.Name}).Delete(&Resource{})
if err != nil {
panic(err)
}
return affected != 0
}
func (resource *Resource) GetId() string {
return fmt.Sprintf("%s/%s", resource.Owner, resource.Name)
}
func AddOrUpdateResource(resource *Resource) bool {
if getResource(resource.Owner, resource.Name) == nil {
return AddResource(resource)
} else {
return UpdateResource(resource.GetId(), resource)
}
}

View File

@@ -1,135 +0,0 @@
// Copyright 2021 The casbin 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/tls"
"crypto/x509"
"encoding/base64"
"fmt"
"net/url"
"regexp"
"strings"
"github.com/astaxie/beego"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
func ParseSamlResponse(samlResponse string, providerType string) (string, error) {
samlResponse, _ = url.QueryUnescape(samlResponse)
sp, err := buildSp(&Provider{Type: providerType}, samlResponse)
if err != nil {
return "", err
}
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
if err != nil {
panic(err)
}
return assertionInfo.NameID, nil
}
func GenerateSamlLoginUrl(id, relayState string) (string, string, error) {
provider := GetProvider(id)
if provider.Category != "SAML" {
return "", "", fmt.Errorf("Provider %s's category is not SAML", provider.Name)
}
sp, err := buildSp(provider, "")
if err != nil {
return "", "", err
}
auth := ""
method := ""
if provider.EnableSignAuthnRequest {
post, err := sp.BuildAuthBodyPost(relayState)
if err != nil {
return "", "", err
}
auth = string(post[:])
method = "POST"
} else {
auth, err = sp.BuildAuthURL(relayState)
if err != nil {
return "", "", err
}
method = "GET"
}
return auth, method, nil
}
func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvider, error) {
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
origin := beego.AppConfig.String("origin")
certEncodedData := ""
if samlResponse != "" {
certEncodedData = parseSamlResponse(samlResponse, provider.Type)
} else if provider.IdP != "" {
certEncodedData = provider.IdP
}
certData, err := base64.StdEncoding.DecodeString(certEncodedData)
if err != nil {
return nil, err
}
idpCert, err := x509.ParseCertificate(certData)
if err != nil {
return nil, err
}
certStore.Roots = append(certStore.Roots, idpCert)
sp := &saml2.SAMLServiceProvider{
ServiceProviderIssuer: fmt.Sprintf("%s/api/acs", origin),
AssertionConsumerServiceURL: fmt.Sprintf("%s/api/acs", origin),
IDPCertificateStore: &certStore,
SignAuthnRequests: false,
SPKeyStore: dsig.RandomKeyStoreForTest(),
}
if provider.Endpoint != "" {
sp.IdentityProviderSSOURL = provider.Endpoint
sp.IdentityProviderIssuer = provider.IssuerUrl
}
if provider.EnableSignAuthnRequest {
sp.SignAuthnRequests = true
sp.SPKeyStore = buildSpKeyStore()
}
return sp, nil
}
func parseSamlResponse(samlResponse string, providerType string) string {
de, err := base64.StdEncoding.DecodeString(samlResponse)
if err != nil {
panic(err)
}
deStr := strings.Replace(string(de), "\n", "", -1)
tagMap := map[string]string{
"Aliyun IDaaS": "ds",
"Keycloak": "dsig",
}
tag := tagMap[providerType]
expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
return res[1]
}
func buildSpKeyStore() dsig.X509KeyStore {
keyPair, err := tls.LoadX509KeyPair("object/token_jwt_key.pem", "object/token_jwt_key.key")
if err != nil {
panic(err)
}
return &dsig.TLSCertKeyStore {
PrivateKey: keyPair.PrivateKey,
Certificate: keyPair.Certificate,
}
}

View File

@@ -14,21 +14,24 @@
package object
import "github.com/casdoor/go-sms-sender"
import (
"fmt"
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
if err != nil {
return err
"github.com/casdoor/go-sms-sender"
)
func SendCodeToPhone(provider *Provider, phone, code string) string {
client := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode, provider.AppId)
if client == nil {
return fmt.Sprintf("Unsupported provide type: %s", provider.Type)
}
params := map[string]string{}
if provider.Type == go_sms_sender.TencentCloud {
params["0"] = content
param := make(map[string]string)
if provider.Type == "tencent" {
param["0"] = code
} else {
params["code"] = content
param["code"] = code
}
err = client.SendMessage(params, phoneNumbers...)
return err
client.SendMessage(param, phone)
return ""
}

View File

@@ -1,122 +0,0 @@
// Copyright 2021 The casbin 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"
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/storage"
"github.com/casbin/casdoor/util"
)
var isCloudIntranet bool
func init() {
var err error
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
if err != nil {
//panic(err)
}
}
func getProviderEndpoint(provider *Provider) string {
endpoint := provider.Endpoint
if provider.IntranetEndpoint != "" && isCloudIntranet {
endpoint = provider.IntranetEndpoint
}
return endpoint
}
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
host := ""
if provider.Type != "Local File System" {
// provider.Domain = "https://cdn.casbin.com/casdoor/"
host = util.GetUrlHost(provider.Domain)
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = fmt.Sprintf("https://%s", host)
}
} else {
// provider.Domain = "http://localhost:8000" or "https://door.casbin.com"
host = util.UrlJoin(provider.Domain, "/files")
}
fileUrl := util.UrlJoin(host, objectKey)
if hasTimestamp {
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
}
return fileUrl, objectKey
}
func uploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
endpoint := getProviderEndpoint(provider)
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
if storageProvider == nil {
return "", "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
}
if provider.Domain == "" {
provider.Domain = storageProvider.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
fileUrl, objectKey := getUploadFileUrl(provider, fullFilePath, true)
_, err := storageProvider.Put(objectKey, fileBuffer)
if err != nil {
return "", "", err
}
return fileUrl, objectKey, nil
}
func UploadFileSafe(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
var fileUrl string
var objectKey string
var err error
times := 0
for {
fileUrl, objectKey, err = uploadFile(provider, fullFilePath, fileBuffer)
if err != nil {
times += 1
if times >= 5 {
return "", "", err
}
} else {
break
}
}
return fileUrl, objectKey, nil
}
func DeleteFile(provider *Provider, objectKey string) error {
endpoint := getProviderEndpoint(provider)
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, endpoint)
if storageProvider == nil {
return fmt.Errorf("the provider type: %s is not supported", provider.Type)
}
if provider.Domain == "" {
provider.Domain = storageProvider.GetEndpoint()
UpdateProvider(provider.GetId(), provider)
}
return storageProvider.Delete(objectKey)
}

View File

@@ -1,160 +0,0 @@
// Copyright 2021 The casbin 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/casbin/casdoor/util"
"xorm.io/core"
)
type TableColumn struct {
Name string `json:"name"`
Type string `json:"type"`
CasdoorName string `json:"casdoorName"`
IsHashed bool `json:"isHashed"`
Values []string `json:"values"`
}
type Syncer 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"`
Organization string `xorm:"varchar(100)" json:"organization"`
Type string `xorm:"varchar(100)" json:"type"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"`
TablePrimaryKey string `xorm:"varchar(100)" json:"tablePrimaryKey"`
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
AffiliationTable string `xorm:"varchar(100)" json:"affiliationTable"`
AvatarBaseUrl string `xorm:"varchar(100)" json:"avatarBaseUrl"`
SyncInterval int `json:"syncInterval"`
IsEnabled bool `json:"isEnabled"`
Adapter *Adapter `xorm:"-" json:"-"`
}
func GetSyncerCount(owner string) int {
count, err := adapter.Engine.Count(&Syncer{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
}
func GetSyncers(owner string) []*Syncer {
syncers := []*Syncer{}
err := adapter.Engine.Desc("created_time").Find(&syncers, &Syncer{Owner: owner})
if err != nil {
panic(err)
}
return syncers
}
func GetPaginationSyncers(owner string, offset, limit int) []*Syncer {
syncers := []*Syncer{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&syncers, &Syncer{Owner: owner})
if err != nil {
panic(err)
}
return syncers
}
func getSyncer(owner string, name string) *Syncer {
if owner == "" || name == "" {
return nil
}
syncer := Syncer{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&syncer)
if err != nil {
panic(err)
}
if existed {
return &syncer
} else {
return nil
}
}
func GetSyncer(id string) *Syncer {
owner, name := util.GetOwnerAndNameFromId(id)
return getSyncer(owner, name)
}
func GetMaskedSyncer(syncer *Syncer) *Syncer {
if syncer == nil {
return nil
}
if syncer.Password != "" {
syncer.Password = "***"
}
return syncer
}
func GetMaskedSyncers(syncers []*Syncer) []*Syncer {
for _, syncer := range syncers {
syncer = GetMaskedSyncer(syncer)
}
return syncers
}
func UpdateSyncer(id string, syncer *Syncer) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getSyncer(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(syncer)
if err != nil {
panic(err)
}
return affected != 0
}
func AddSyncer(syncer *Syncer) bool {
affected, err := adapter.Engine.Insert(syncer)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteSyncer(syncer *Syncer) bool {
affected, err := adapter.Engine.ID(core.PK{syncer.Owner, syncer.Name}).Delete(&Syncer{})
if err != nil {
panic(err)
}
return affected != 0
}
func (syncer *Syncer) GetId() string {
return fmt.Sprintf("%s/%s", syncer.Owner, syncer.Name)
}

View File

@@ -1,40 +0,0 @@
// Copyright 2021 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import "github.com/mileusna/crontab"
var cronMap map[string]*crontab.Crontab
func init() {
cronMap = map[string]*crontab.Crontab{}
}
func getCrontab(name string) *crontab.Crontab {
ctab, ok := cronMap[name]
if !ok {
ctab = crontab.New()
cronMap[name] = ctab
}
return ctab
}
func clearCrontab(name string) {
ctab, ok := cronMap[name]
if ok {
ctab.Clear()
delete(cronMap, name)
}
}

View File

@@ -1,61 +0,0 @@
// Copyright 2021 The casbin 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"
func getDbSyncerForUser(user *User) *Syncer {
syncers := GetSyncers("admin")
for _, syncer := range syncers {
if syncer.Organization == user.Owner && syncer.IsEnabled && syncer.Type == "Database" {
return syncer
}
}
return nil
}
func getEnabledSyncerForOrganization(organization string) *Syncer {
syncers := GetSyncers("admin")
for _, syncer := range syncers {
if syncer.Organization == organization && syncer.IsEnabled {
return syncer
}
}
return nil
}
func AddUserToOriginalDatabase(user *User) {
syncer := getEnabledSyncerForOrganization(user.Owner)
if syncer == nil {
return
}
updatedOUser := syncer.createOriginalUserFromUser(user)
syncer.addUser(updatedOUser)
fmt.Printf("Add from user to oUser: %v\n", updatedOUser)
}
func UpdateUserToOriginalDatabase(user *User) {
syncer := getEnabledSyncerForOrganization(user.Owner)
if syncer == nil {
return
}
newUser := GetUser(user.GetId())
updatedOUser := syncer.createOriginalUserFromUser(newUser)
syncer.updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
}

View File

@@ -1,80 +0,0 @@
// Copyright 2021 The casbin 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"
func (syncer *Syncer) syncUsers() {
fmt.Printf("Running syncUsers()..\n")
users, userMap := syncer.getUserMap()
oUsers, oUserMap := syncer.getOriginalUserMap()
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
_, affiliationMap := syncer.getAffiliationMap()
newUsers := []*User{}
for _, oUser := range oUsers {
id := oUser.Id
if _, ok := userMap[id]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
} else {
user := userMap[id]
oHash := syncer.calculateHash(oUser)
if user.Hash == user.PreHash {
if user.Hash != oHash {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
syncer.updateUserForOriginalFields(updatedUser)
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
}
} else {
if user.PreHash == oHash {
updatedOUser := syncer.createOriginalUserFromUser(user)
syncer.updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
// update preHash
user.PreHash = user.Hash
SetUserField(user, "pre_hash", user.PreHash)
} else {
if user.Hash == oHash {
// update preHash
user.PreHash = user.Hash
SetUserField(user, "pre_hash", user.PreHash)
} else {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
syncer.updateUserForOriginalFields(updatedUser)
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
}
}
}
}
}
AddUsersInBatch(newUsers)
for _, user := range users {
id := user.Id
if _, ok := oUserMap[id]; !ok {
panic(fmt.Sprintf("New original user: cannot create now, user = %v", user))
}
}
}

View File

@@ -1,154 +0,0 @@
// Copyright 2021 The casbin 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"
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/util"
"xorm.io/core"
)
type OriginalUser = User
func (syncer *Syncer) getOriginalUsers() []*OriginalUser {
sql := fmt.Sprintf("select * from %s", syncer.Table)
results, err := syncer.Adapter.Engine.QueryString(sql)
if err != nil {
panic(err)
}
return syncer.getOriginalUsersFromMap(results)
}
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser) {
users := syncer.getOriginalUsers()
m := map[string]*OriginalUser{}
for _, user := range users {
m[user.Id] = user
}
return users, m
}
func (syncer *Syncer) addUser(user *OriginalUser) bool {
m := syncer.getMapFromOriginalUser(user)
affected, err := syncer.Adapter.Engine.Table(syncer.Table).Insert(m)
if err != nil {
panic(err)
}
return affected != 0
}
func (syncer *Syncer) getOriginalColumns() []string {
res := []string{}
for _, tableColumn := range syncer.TableColumns {
if tableColumn.CasdoorName != "Id" {
res = append(res, tableColumn.Name)
}
}
return res
}
func (syncer *Syncer) getCasdoorColumns() []string {
res := []string{}
for _, tableColumn := range syncer.TableColumns {
if tableColumn.CasdoorName != "Id" {
v := util.CamelToSnakeCase(tableColumn.CasdoorName)
res = append(res, v)
}
}
return res
}
func (syncer *Syncer) updateUser(user *OriginalUser) bool {
m := syncer.getMapFromOriginalUser(user)
columns := syncer.getOriginalColumns()
affected, err := syncer.Adapter.Engine.Table(syncer.Table).ID(syncer.TablePrimaryKey).Cols(columns...).Update(m)
if err != nil {
panic(err)
}
return affected != 0
}
func (syncer *Syncer) updateUserForOriginalFields(user *User) bool {
owner, name := util.GetOwnerAndNameFromId(user.GetId())
oldUser := getUser(owner, name)
if oldUser == nil {
return false
}
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
columns := syncer.getCasdoorColumns()
columns = append(columns, "affiliation", "hash", "pre_hash")
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols(columns...).Update(user)
if err != nil {
panic(err)
}
return affected != 0
}
func (syncer *Syncer) calculateHash(user *OriginalUser) string {
values := []string{}
m := syncer.getMapFromOriginalUser(user)
for _, tableColumn := range syncer.TableColumns {
if tableColumn.IsHashed {
values = append(values, m[tableColumn.Name])
}
}
s := strings.Join(values, "|")
return util.GetMd5Hash(s)
}
func (syncer *Syncer) initAdapter() {
if syncer.Adapter == nil {
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
syncer.Adapter = NewAdapter(beego.AppConfig.String("driverName"), dataSourceName, syncer.Database)
}
}
func RunSyncUsersJob() {
syncers := GetSyncers("admin")
for _, syncer := range syncers {
if !syncer.IsEnabled {
continue
}
syncer.initAdapter()
syncer.syncUsers()
// run at every minute
//schedule := fmt.Sprintf("* * * * %d", syncer.SyncInterval)
schedule := "* * * * *"
ctab := getCrontab(syncer.Name)
err := ctab.AddJob(schedule, syncer.syncUsers)
if err != nil {
panic(err)
}
}
time.Sleep(time.Duration(1<<63 - 1))
}

View File

@@ -1,204 +0,0 @@
// Copyright 2021 The casbin 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"
"strconv"
"strings"
"github.com/casbin/casdoor/util"
)
func (syncer *Syncer) getFullAvatarUrl(avatar string) string {
if syncer.AvatarBaseUrl == "" {
return avatar
}
if !strings.HasPrefix(avatar, "https://") {
return fmt.Sprintf("%s%s", syncer.AvatarBaseUrl, avatar)
}
return avatar
}
func (syncer *Syncer) getPartialAvatarUrl(avatar string) string {
if strings.HasPrefix(avatar, syncer.AvatarBaseUrl) {
return avatar[len(syncer.AvatarBaseUrl):]
}
return avatar
}
func (syncer *Syncer) createUserFromOriginalUser(originalUser *OriginalUser, affiliationMap map[int]string) *User {
user := *originalUser
user.Owner = syncer.Organization
if user.Name == "" {
user.Name = originalUser.Id
}
if user.CreatedTime == "" {
user.CreatedTime = util.GetCurrentTime()
}
if user.Type == "" {
user.Type = "normal-user"
}
user.Avatar = syncer.getFullAvatarUrl(user.Avatar)
if originalUser.Score != 0 {
affiliation, ok := affiliationMap[originalUser.Score]
if !ok {
panic(fmt.Sprintf("Affiliation not found: %d", originalUser.Score))
}
user.Affiliation = affiliation
}
if user.Properties == nil {
user.Properties = map[string]string{}
}
return &user
}
func (syncer *Syncer) createOriginalUserFromUser(user *User) *OriginalUser {
return user
}
func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
switch key {
case "Name":
user.Name = value
case "CreatedTime":
user.CreatedTime = value
case "UpdatedTime":
user.UpdatedTime = value
case "Id":
user.Id = value
case "Type":
user.Type = value
case "Password":
user.Password = value
case "PasswordSalt":
user.PasswordSalt = value
case "DisplayName":
user.DisplayName = value
case "Avatar":
user.Avatar = syncer.getPartialAvatarUrl(value)
case "PermanentAvatar":
user.PermanentAvatar = value
case "Email":
user.Email = value
case "Phone":
user.Phone = value
case "Location":
user.Location = value
case "Address":
user.Address = []string{value}
case "Affiliation":
user.Affiliation = value
case "Title":
user.Title = value
case "IdCardType":
user.IdCardType = value
case "IdCard":
user.IdCard = value
case "Homepage":
user.Homepage = value
case "Bio":
user.Bio = value
case "Tag":
user.Tag = value
case "Region":
user.Region = value
case "Language":
user.Language = value
case "Gender":
user.Gender = value
case "Birthday":
user.Birthday = value
case "Education":
user.Education = value
case "Score":
user.Score = util.ParseInt(value)
case "Ranking":
user.Ranking = util.ParseInt(value)
case "IsDefaultAvatar":
user.IsDefaultAvatar = util.ParseBool(value)
case "IsOnline":
user.IsOnline = util.ParseBool(value)
case "IsAdmin":
user.IsAdmin = util.ParseBool(value)
case "IsGlobalAdmin":
user.IsGlobalAdmin = util.ParseBool(value)
case "IsForbidden":
user.IsForbidden = util.ParseBool(value)
case "IsDeleted":
user.IsDeleted = util.ParseBool(value)
case "CreatedIp":
user.CreatedIp = value
}
}
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser {
users := []*OriginalUser{}
for _, result := range results {
originalUser := &OriginalUser{}
for _, tableColumn := range syncer.TableColumns {
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name])
}
users = append(users, originalUser)
}
return users
}
func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]string {
m := map[string]string{}
m["Name"] = user.Name
m["CreatedTime"] = user.CreatedTime
m["UpdatedTime"] = user.UpdatedTime
m["Id"] = user.Id
m["Type"] = user.Type
m["Password"] = user.Password
m["PasswordSalt"] = user.PasswordSalt
m["DisplayName"] = user.DisplayName
m["Avatar"] = syncer.getFullAvatarUrl(user.Avatar)
m["PermanentAvatar"] = user.PermanentAvatar
m["Email"] = user.Email
m["Phone"] = user.Phone
m["Location"] = user.Location
m["Address"] = strings.Join(user.Address, "|")
m["Affiliation"] = user.Affiliation
m["Title"] = user.Title
m["IdCardType"] = user.IdCardType
m["IdCard"] = user.IdCard
m["Homepage"] = user.Homepage
m["Bio"] = user.Bio
m["Tag"] = user.Tag
m["Region"] = user.Region
m["Language"] = user.Language
m["Gender"] = user.Gender
m["Birthday"] = user.Birthday
m["Education"] = user.Education
m["Score"] = strconv.Itoa(user.Score)
m["Ranking"] = strconv.Itoa(user.Ranking)
m["IsDefaultAvatar"] = util.BoolToString(user.IsDefaultAvatar)
m["IsOnline"] = util.BoolToString(user.IsOnline)
m["IsAdmin"] = util.BoolToString(user.IsAdmin)
m["IsGlobalAdmin"] = util.BoolToString(user.IsGlobalAdmin)
m["IsForbidden"] = util.BoolToString(user.IsForbidden)
m["IsDeleted"] = util.BoolToString(user.IsDeleted)
m["CreatedIp"] = user.CreatedIp
m2 := map[string]string{}
for _, tableColumn := range syncer.TableColumns {
m2[tableColumn.Name] = m[tableColumn.CasdoorName]
}
return m2
}

View File

@@ -15,11 +15,9 @@
package object
import (
"fmt"
"strings"
"time"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -37,31 +35,20 @@ type Token struct {
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
Code string `xorm:"varchar(100)" json:"code"`
AccessToken string `xorm:"mediumtext" json:"accessToken"`
RefreshToken string `xorm:"mediumtext" json:"refreshToken"`
ExpiresIn int `json:"expiresIn"`
Scope string `xorm:"varchar(100)" json:"scope"`
TokenType string `xorm:"varchar(100)" json:"tokenType"`
Code string `xorm:"varchar(100)" json:"code"`
AccessToken string `xorm:"mediumtext" json:"accessToken"`
ExpiresIn int `json:"expiresIn"`
Scope string `xorm:"varchar(100)" json:"scope"`
TokenType string `xorm:"varchar(100)" json:"tokenType"`
}
type TokenWrapper struct {
AccessToken string `json:"access_token"`
IdToken string `json:"id_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
func GetTokenCount(owner string) int {
count, err := adapter.Engine.Count(&Token{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
}
func GetTokens(owner string) []*Token {
tokens := []*Token{}
err := adapter.Engine.Desc("created_time").Find(&tokens, &Token{Owner: owner})
@@ -72,16 +59,6 @@ func GetTokens(owner string) []*Token {
return tokens
}
func GetPaginationTokens(owner string, offset, limit int) []*Token {
tokens := []*Token{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&tokens, &Token{Owner: owner})
if err != nil {
panic(err)
}
return tokens
}
func getToken(owner string, name string) *Token {
if owner == "" || name == "" {
return nil
@@ -95,9 +72,9 @@ func getToken(owner string, name string) *Token {
if existed {
return &token
} else {
return nil
}
return nil
}
func getTokenByCode(code string) *Token {
@@ -109,9 +86,9 @@ func getTokenByCode(code string) *Token {
if existed {
return &token
} else {
return nil
}
return nil
}
func GetToken(id string) *Token {
@@ -169,19 +146,17 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
}
}
if !validUri {
return fmt.Sprintf("Redirect URI: \"%s\" doesn't exist in the allowed Redirect URI list", redirectUri), application
return "redirect_uri doesn't exist in the allowed Redirect URL list", application
}
// Mask application for /api/get-app-login
application.ClientSecret = ""
return "", application
}
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string, nonce string) *Code {
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string) *Code {
user := GetUser(userId)
if user == nil {
return &Code{
Message: fmt.Sprintf("The user: %s doesn't exist", userId),
Message: "Invalid user_id",
Code: "",
}
}
@@ -194,7 +169,7 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
}
}
accessToken, refreshToken, err := generateJwtToken(application, user, nonce)
accessToken, err := generateJwtToken(application, user)
if err != nil {
panic(err)
}
@@ -208,7 +183,6 @@ func GetOAuthCode(userId string, clientId string, responseType string, redirectU
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: scope,
TokenType: "Bearer",
@@ -280,7 +254,6 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,
IdToken: token.AccessToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
@@ -288,75 +261,3 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
return tokenWrapper
}
func RefreshToken(grantType string, refreshToken string, scope string, clientId string, clientSecret string) *Code {
// check parameters
if grantType != "refresh_token" {
return &Code{
Message: "error: grant_type should be \"refresh_token\"",
Code: "",
}
}
application := GetApplicationByClientId(clientId)
if application == nil {
return &Code{
Message: "error: invalid client_id",
Code: "",
}
}
if application.ClientSecret != clientSecret {
return &Code{
Message: "error: invalid client_secret",
Code: "",
}
}
// check whether the refresh token is valid, and has not expired.
token := Token{RefreshToken: refreshToken}
existed, err := adapter.Engine.Get(&token)
if err != nil || !existed {
return &Code{
Message: "error: invalid refresh_token",
Code: "",
}
}
claims, err := ParseJwtToken(refreshToken)
if err != nil {
return &Code{
Message: "error: invalid refresh_token",
Code: "",
}
}
if time.Now().Unix() > claims.ExpiresAt.Unix() {
return &Code{
Message: "error: expired refresh_token",
Code: "",
}
}
// generate a new token
user := getUser(application.Owner, token.User)
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "")
if err != nil {
panic(err)
}
newToken := &Token{
Owner: application.Owner,
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: util.GenerateClientId(),
AccessToken: newAccessToken,
RefreshToken: newRefreshToken,
ExpiresIn: application.ExpireInHours * 60,
Scope: scope,
TokenType: "Bearer",
}
AddToken(newToken)
return &Code{
Message: "",
Code: token.Code,
}
}

View File

@@ -15,92 +15,70 @@
package object
import (
_ "embed"
"fmt"
"time"
"github.com/astaxie/beego"
"github.com/golang-jwt/jwt/v4"
"github.com/dgrijalva/jwt-go"
)
//go:embed token_jwt_key.pem
var tokenJwtPublicKey string
//go:embed token_jwt_key.key
var tokenJwtPrivateKey string
var jwtSecret = []byte("CasdoorSecret")
type Claims struct {
*User
Name string `json:"name,omitempty"`
Owner string `json:"owner,omitempty"`
Nonce string `json:"nonce,omitempty"`
jwt.RegisteredClaims
Organization string `json:"organization"`
Username string `json:"username"`
Type string `json:"type"`
Name string `json:"name"`
Avatar string `json:"avatar"`
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
Tag string `json:"tag"`
Language string `json:"language"`
Score int `json:"score"`
IsAdmin bool `json:"isAdmin"`
jwt.StandardClaims
}
func generateJwtToken(application *Application, user *User, nonce string) (string, string, error) {
func generateJwtToken(application *Application, user *User) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
user.Password = ""
claims := Claims{
User: user,
Nonce: nonce,
RegisteredClaims: jwt.RegisteredClaims{
Issuer: beego.AppConfig.String("origin"),
Organization: user.Owner,
Username: user.Name,
Type: user.Type,
Name: user.DisplayName,
Avatar: user.Avatar,
Email: user.Email,
Phone: user.Phone,
Affiliation: user.Affiliation,
Tag: user.Tag,
Language: user.Language,
Score: user.Score,
IsAdmin: user.IsAdmin,
StandardClaims: jwt.StandardClaims{
Audience: application.ClientId,
ExpiresAt: expireTime.Unix(),
Id: "",
IssuedAt: nowTime.Unix(),
Issuer: "casdoor",
NotBefore: nowTime.Unix(),
Subject: user.Id,
Audience: []string{application.ClientId},
ExpiresAt: jwt.NewNumericDate(expireTime),
NotBefore: jwt.NewNumericDate(nowTime),
IssuedAt: jwt.NewNumericDate(nowTime),
ID: "",
},
}
//all fields of the User struct are not added in "JWT-Empty" format
if application.TokenFormat == "JWT-Empty" {
claims.User = nil
}
claims.Name = user.Name
claims.Owner = user.Owner
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
claims.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
refreshToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(jwtSecret)
// Use "token_jwt_key.key" as RSA private key
privateKey := tokenJwtPrivateKey
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateKey))
if err != nil {
return "", "", err
}
tokenString, err := token.SignedString(key)
if err != nil {
return "", "", err
}
refreshTokenString, err := refreshToken.SignedString(key)
return tokenString, refreshTokenString, err
return token, err
}
func ParseJwtToken(token string) (*Claims, error) {
t, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// Use "token_jwt_key.pem" as RSA public key
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(tokenJwtPublicKey))
if err != nil {
return nil, err
}
return publicKey, nil
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if t != nil {
if claims, ok := t.Claims.(*Claims); ok && t.Valid {
if tokenClaims != nil {
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}

View File

@@ -1,78 +0,0 @@
// Copyright 2021 The casbin 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/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
"github.com/casbin/casdoor/util"
)
func generateRsaKeys(fileId string) {
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
bitSize := 4096
// Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
panic(err)
}
// Encode private key to PKCS#1 ASN.1 PEM.
privateKeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
},
)
tml := x509.Certificate{
// you can add any attr that you need
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(20, 0, 0),
// you have to generate a different serial number each execution
SerialNumber: big.NewInt(123456),
Subject: pkix.Name{
CommonName: "Casdoor Cert",
Organization: []string{"Casdoor Organization"},
},
BasicConstraintsValid: true,
}
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil {
panic(err)
}
// Generate a pem block with the certificate
certPem := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: cert,
})
// Write private key to file.
util.WriteBytesToPath(privateKeyPem, fmt.Sprintf("%s.key", fileId))
// Write certificate (aka public key) to file.
util.WriteBytesToPath(certPem, fmt.Sprintf("%s.pem", fileId))
}

View File

@@ -1,51 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIJKQIBAAKCAgEAsInpb5E1/ym0f1RfSDSSE8IR7y+lw+RJjI74e5ejrq4b8zMY
k7HeHCyZr/hmNEwEVXnhXu1P0mBeQ5ypp/QGo8vgEmjAETNmzkI1NjOQCjCYwUra
sO/f/MnI1C0j13vx6mV1kHZjSrKsMhYY1vaxTEP3+VB8Hjg3MHFWrb07uvFMCJe5
W8+0rKErZCKTR8+9VB3janeBz//zQePFVh79bFZate/hLirPK0Go9P1gOvwIoC1A
3sarHTP4Qm/LQRt0rHqZFybdySpyWAQvhNaDFE7mTstRSBb/wUjNCUBDPTSLVjC0
4WllSf6Nkfx0Z7KvmbPstSj+btvcqsvRAGtvdsB9h62Kptjs1Yn7GAuoI3qt/4zo
KbiURYxkQJXIvwCQsEftUuk5ew5zuPSlDRLoLByQTLbx0JqLAFNfW3g/pzSDjgd/
60d6HTmvbZni4SmjdyFhXCDb1Kn7N+xTojnfaNkwep2REV+RMc0fx4GuhRsnLsmk
mUDeyIZ9aBL9oj11YEQfM2JZEq+RVtUx+wB4y8K/tD1bcY+IfnG5rBpwIDpS262b
oq4SRSvb3Z7bB0w4ZxvOfJ/1VLoRftjPbLIf0bhfr/AeZMHpIKOXvfz4yE+hqzi6
8wdF0VR9xYc/RbSAf7323OsjYnjjEgInUtRohnRgCpjIk/Mt2Kt84Kb0wn8CAwEA
AQKCAgAHP7JxHVJNRuYdcFZ1PYtd+lMIMjmpQH9woRI86O4UpxuIselpbx1CpOYu
npF7xj9LTzTc0/u6FLDqL82bkt6O7TknKFvymNy4zWkn75gTgwlSroMqTr8wvwxb
Aft9xp4ZVM8t/l53W7zMVbHxabHAAu50s0RVbVN+zriTa7i/JVdM5wX6ah3uFLQW
aYEIqtQIVy3WWk/fPZA8fWDF94HKaAVTgSUK40EccpbAcIL6CQ1FnnYSb6/pBBBG
khaTdtAkoOgWVkc3EmIdkRZuaux48gBs7dZJkoAv7JBWt+fK5JRwFpHmy5AYKLah
bu9Mrr6dHhEzIxrHbIm0DahoTwEFmso8kbU26caGEhufo4YiMk+B4bE6QsmNsNR9
Msau8qkSLprYo6Mrj1Q7y1q3rShf8SuZBa3Kxk0hBy8Zs9TjkJ+Dbtq2zakQWzDG
JLEttbGgdeYUMS2ycC/FUYUN/YPCdn769kw7lmOR2Kl56wpbFYwR9JYgynQqb3jd
4AORgsR3ADaxVDXw1ol7Bcie334WusvxNCJVRuzBY/DK0/W7ijxjJvdXevHxGrhe
1Gc+FkKebfiNfq6Dzdlkx66N80nyZuZvyrnRiavm9bVcrarb5XhS5ICfEOHOw0gH
5GdesqMuqTSOeveD1RUncF1CWWMvvPeEushW9jbL3BBh4wfLsQKCAQEAy4x+c+F8
IcbaKfssrhPRMfYUjWee39tOvHDtxM/3sx60ysrEUx1LsjagQz7noLljF7XfJ0H+
vc0G6A0ojwA6J+QdoEf8fevO4t56uMae3dFWP4J000vHCIvrgAo9GC2HOc940/Yn
6EqjWYqc2AXu156RA3P/XetgsxivVCFlGzPxFylbqzKB71Yb9hc5zz26G3K8+FVZ
dp+DYFjHBo6LRuVwXdXKi/QgUXv7iFR7zFqvkGhm5ZqvcKXfqWSpx4H71kQAUTQE
cJ8lvgquFTouViopOJD6DBII0PJ/TBtg5g9a+jsnXxcpCjj9XychGsj1dTzSDmQd
ha/rKyN4dNHy2QKCAQEA3gekrROPdglcooamedrvlwpIrxryKI5MrhEAgIBeJidp
98HhNcJ08wra5XuMS6ZC7R0xOKikSQLp4giT22W59lq/nPQ7PN5PdN2RzOlrmHcS
kAGF3Qg9x0cCelSqzyTl4RKrefYPLJUElVLmTxEgBiis7s2gxpsJ3q92WtWf8aU/
7Pc3ztAV/DqzeUCOACVz74l4QuE9LJlro0shs0TaHWM0AZ9eVk4V8xBM8VJ2DAX8
vpTYcNDxjEeByEMgWdXlyOndvolRDMCMrQMVg+ZoeX5SpDe2b6fgw5ZjZSngnrJ4
ifesFEbwmXmb3XDDNyWADG5/xAkpCGPCa34JmG7ZFwKCAQEAlsrrNx/ZnRA6uRUZ
wZBuzut1yFf2i/JlPxcOHlrPLwRVfVJ/5O70D/+F9KtaX2hXr84NloC+no+QSULO
RDov2zOUexQ5SnPyHYIiOlbyhHO7yGr17z7ZIUy+12k+X3YDEuHPqn9WizEYGJKm
pSaoDVasKXm6ujJQvf1Qjiv7Qg7V0YnTHl3ZgpwxNLt6GTyqbgEvW22nTEjZw/ug
3gulxIzfFLT4S3w8oQEPk6y61eZs37doWzqgM/y+WDh5ypJSJibUcVPu4hwUkthI
pPMoNq8fQIeupliJ7XlostIpk+XWSUCfZ0O6JJeZpO9RCA3OQd8f4odqk4qC1r99
UlXi6QKCAQEApSF+IpNXsWxJDz+h9SMV6nnlkQYzcGJVOWi/vNK8MxhBQdlajEcx
/8jlAKQgterT/9IkV4Vlmj+mf0vt29EOu+DGfg9PN3gIFFzuIT7BnUWB8sSPMNL+
T4XKm/z4hNNmfT0Ld8u/gWLbY8uiKtALx0jdRUZ9+vg4IPzSw7/6ExjaMH21bgVp
NIzcCqQueIFidpcBcIxgmRkJ6wrn55KfvheYCFTlLr8op/xJnXm8/jg9v+ioCU/9
Nl3AcpcqKmZhXkpBd4JdW2Shu9N9XvowXZvMDwK4ltZ+3jiteAHrY1xNNh+URgh0
zVCa0dkZ95vWXmiYcc52TB0V7ihxLoPSxQKCAQAwP3D0JyRvWepLasQd0NQeVt0q
0/ExXjq+qmIeT6q2GHhgZJjm6Ysovr+cEdZC6sV6JiqW9NhHfTYO4EkJetzlHMt5
jseFq14QMAgVuo7cYLkONgGxpYGo1DaddxlkMKpmpsTeFsvRyCnyWpHVG/sA1eVp
caanw6S2tnhxx2Yq78bv8Vj8jA0k8j34j2bMBFcuIEsaQ2Sdfw/1TSkKGB2k2crP
txbblVR4BN1DC8wpK/M67B097uMUmWe1UQxsjc0P9S/rlWUhKyBEeLOV3/yRPDSM
GMRXh780wMWlS34RUYxxv6dtWB9KI7++XRrnrBPrZa0xUUOpSYILm7OLeuQ5
-----END PRIVATE KEY-----

View File

@@ -1,29 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIE+TCCAuGgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMDYxHTAbBgNVBAoTFENh
c2Rvb3IgT3JnYW5pemF0aW9uMRUwEwYDVQQDEwxDYXNkb29yIENlcnQwHhcNMjEx
MDE1MDgxMTUyWhcNNDExMDE1MDgxMTUyWjA2MR0wGwYDVQQKExRDYXNkb29yIE9y
Z2FuaXphdGlvbjEVMBMGA1UEAxMMQ2FzZG9vciBDZXJ0MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAsInpb5E1/ym0f1RfSDSSE8IR7y+lw+RJjI74e5ej
rq4b8zMYk7HeHCyZr/hmNEwEVXnhXu1P0mBeQ5ypp/QGo8vgEmjAETNmzkI1NjOQ
CjCYwUrasO/f/MnI1C0j13vx6mV1kHZjSrKsMhYY1vaxTEP3+VB8Hjg3MHFWrb07
uvFMCJe5W8+0rKErZCKTR8+9VB3janeBz//zQePFVh79bFZate/hLirPK0Go9P1g
OvwIoC1A3sarHTP4Qm/LQRt0rHqZFybdySpyWAQvhNaDFE7mTstRSBb/wUjNCUBD
PTSLVjC04WllSf6Nkfx0Z7KvmbPstSj+btvcqsvRAGtvdsB9h62Kptjs1Yn7GAuo
I3qt/4zoKbiURYxkQJXIvwCQsEftUuk5ew5zuPSlDRLoLByQTLbx0JqLAFNfW3g/
pzSDjgd/60d6HTmvbZni4SmjdyFhXCDb1Kn7N+xTojnfaNkwep2REV+RMc0fx4Gu
hRsnLsmkmUDeyIZ9aBL9oj11YEQfM2JZEq+RVtUx+wB4y8K/tD1bcY+IfnG5rBpw
IDpS262boq4SRSvb3Z7bB0w4ZxvOfJ/1VLoRftjPbLIf0bhfr/AeZMHpIKOXvfz4
yE+hqzi68wdF0VR9xYc/RbSAf7323OsjYnjjEgInUtRohnRgCpjIk/Mt2Kt84Kb0
wn8CAwEAAaMQMA4wDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn2lf
DKkLX+F1vKRO/5gJ+Plr8P5NKuQkmwH97b8CS2gS1phDyNgIc4/LSdzuf4Awe6ve
C06lVdWSIis8UPUPdjmT2uMPSNjwLxG3QsrimMURNwFlLTfRem/heJe0Zgur9J1M
8haawdSdJjH2RgmFoDeE2r8NVRfhbR8KnCO1ddTJKuS1N0/irHz21W4jt4rxzCvl
2nR42Fybap3O/g2JXMhNNROwZmNjgpsF7XVENCSuFO1jTywLaqjuXCg54IL7XVLG
omKNNNcc8h1FCeKj/nnbGMhodnFWKDTsJcbNmcOPNHo6ixzqMy/Hqc+mWYv7maAG
Jtevs3qgMZ8F9Qzr3HpUc6R3ZYYWDY/xxPisuKftOPZgtH979XC4mdf0WPnOBLqL
2DJ1zaBmjiGJolvb7XNVKcUfDXYw85ZTZQ5b9clI4e+6bmyWqQItlwt+Ati/uFEV
XzCj70B4lALX6xau1kLEpV9O1GERizYRz5P9NJNA7KoO5AVMp9w0DQTkt+LbXnZE
HHnWKy8xHQKZF9sR7YBPGLs/Ac6tviv5Ua15OgJ/8dLRZ/veyFfGo2yZsI+hKVU5
nCCJHBcAyFnm1hdvdwEdH33jDBjNB6ciotJZrf/3VYaIWSalADosHAgMWfXuWP+h
8XKXmzlxuHbTMQYtZPDgspS5aK+S4Q9wb8RRAYo=
-----END CERTIFICATE-----

View File

@@ -17,7 +17,7 @@ package object
import (
"fmt"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -30,42 +30,22 @@ type User struct {
Id string `xorm:"varchar(100)" json:"id"`
Type string `xorm:"varchar(100)" json:"type"`
Password string `xorm:"varchar(100)" json:"password"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Avatar string `xorm:"varchar(255)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(255)" json:"permanentAvatar"`
Email string `xorm:"varchar(100)" json:"email"`
Phone string `xorm:"varchar(100)" json:"phone"`
Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"`
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
Title string `xorm:"varchar(100)" json:"title"`
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
IdCard string `xorm:"varchar(100)" json:"idCard"`
Homepage string `xorm:"varchar(100)" json:"homepage"`
Bio string `xorm:"varchar(100)" json:"bio"`
Tag string `xorm:"varchar(100)" json:"tag"`
Region string `xorm:"varchar(100)" json:"region"`
Language string `xorm:"varchar(100)" json:"language"`
Gender string `xorm:"varchar(100)" json:"gender"`
Birthday string `xorm:"varchar(100)" json:"birthday"`
Education string `xorm:"varchar(100)" json:"education"`
Score int `json:"score"`
Ranking int `json:"ranking"`
IsDefaultAvatar bool `json:"isDefaultAvatar"`
IsOnline bool `json:"isOnline"`
IsAdmin bool `json:"isAdmin"`
IsGlobalAdmin bool `json:"isGlobalAdmin"`
IsForbidden bool `json:"isForbidden"`
IsDeleted bool `json:"isDeleted"`
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
Hash string `xorm:"varchar(100)" json:"hash"`
PreHash string `xorm:"varchar(100)" json:"preHash"`
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"`
@@ -75,26 +55,11 @@ type User struct {
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
Lark string `xorm:"lark varchar(100)" json:"lark"`
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
Slack string `xorm:"slack varchar(100)" json:"slack"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
}
func GetGlobalUserCount() int {
count, err := adapter.Engine.Count(&User{})
if err != nil {
panic(err)
}
return int(count)
}
func GetGlobalUsers() []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Find(&users)
@@ -105,34 +70,6 @@ func GetGlobalUsers() []*User {
return users
}
func GetPaginationGlobalUsers(offset, limit int) []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&users)
if err != nil {
panic(err)
}
return users
}
func GetUserCount(owner string) int {
count, err := adapter.Engine.Count(&User{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
}
func GetOnlineUserCount(owner string, isOnline int) int {
count, err := adapter.Engine.Where("is_online = ?", isOnline).Count(&User{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
}
func GetUsers(owner string) []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Find(&users, &User{Owner: owner})
@@ -143,26 +80,6 @@ func GetUsers(owner string) []*User {
return users
}
func GetSortedUsers(owner string, sorter string, limit int) []*User {
users := []*User{}
err := adapter.Engine.Desc(sorter).Limit(limit, 0).Find(&users, &User{Owner: owner})
if err != nil {
panic(err)
}
return users
}
func GetPaginationUsers(owner string, offset, limit int) []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&users, &User{Owner: owner})
if err != nil {
panic(err)
}
return users
}
func getUser(owner string, name string) *User {
if owner == "" || name == "" {
return nil
@@ -181,34 +98,11 @@ func getUser(owner string, name string) *User {
}
}
func GetUserByEmail(owner string, email string) *User {
if owner == "" || email == "" {
return nil
}
user := User{Owner: owner, Email: email}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUser(id string) *User {
owner, name := util.GetOwnerAndNameFromId(id)
return getUser(owner, name)
}
func GetUserNoCheck(id string) *User {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
return getUser(owner, name)
}
func GetMaskedUser(user *User) *User {
if user == nil {
return nil
@@ -236,31 +130,22 @@ func GetLastUser(owner string) *User {
if existed {
return &user
} else {
return nil
}
return nil
}
func UpdateUser(id string, user *User, columns []string) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
oldUser := getUser(owner, name)
if oldUser == nil {
func UpdateUser(id string, user *User) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getUser(owner, name) == nil {
return false
}
user.UpdateUserHash()
if user.Avatar != oldUser.Avatar && user.Avatar != "" && user.PermanentAvatar != "*" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
if len(columns) == 0 {
columns = []string{"owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols("owner", "display_name", "avatar",
"address", "language", "affiliation", "score", "tag", "is_admin", "is_global_admin", "is_forbidden",
"hash", "properties").Update(user)
if err != nil {
panic(err)
}
@@ -268,20 +153,24 @@ func UpdateUser(id string, user *User, columns []string) bool {
return affected != 0
}
func UpdateUserForAllFields(id string, user *User) bool {
func UpdateUserInternal(id string, user *User) bool {
owner, name := util.GetOwnerAndNameFromId(id)
oldUser := getUser(owner, name)
if oldUser == nil {
if getUser(owner, name) == nil {
return false
}
user.UpdateUserHash()
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
if err != nil {
panic(err)
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
return affected != 0
}
func UpdateUserForOriginal(user *User) bool {
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("display_name", "password", "phone", "avatar", "affiliation", "score", "is_forbidden", "hash", "pre_hash").Update(user)
if err != nil {
panic(err)
}
@@ -294,18 +183,12 @@ func AddUser(user *User) bool {
user.Id = util.GenerateId()
}
if user.Owner == "" || user.Name == "" {
return false
}
organization := GetOrganizationByUser(user)
user.UpdateUserPassword(organization)
user.UpdateUserHash()
user.PreHash = user.Hash
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
affected, err := adapter.Engine.Insert(user)
if err != nil {
panic(err)
@@ -325,8 +208,6 @@ func AddUsers(users []*User) bool {
user.UpdateUserHash()
user.PreHash = user.Hash
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
}
affected, err := adapter.Engine.Insert(users)
@@ -337,7 +218,7 @@ func AddUsers(users []*User) bool {
return affected != 0
}
func AddUsersInBatch(users []*User) bool {
func AddUsersSafe(users []*User) bool {
batchSize := 1000
if len(users) == 0 {
@@ -353,8 +234,7 @@ func AddUsersInBatch(users []*User) bool {
}
tmp := users[start:end]
// TODO: save to log instead of standard output
// fmt.Printf("Add users: [%d - %d].\n", start, end)
fmt.Printf("Add users: [%d - %d].\n", start, end)
if AddUsers(tmp) {
affected = true
}

View File

@@ -14,15 +14,16 @@
package object
import "github.com/casbin/casdoor/cred"
import (
"strconv"
"strings"
"github.com/casdoor/casdoor/util"
)
func calculateHash(user *User) string {
syncer := getDbSyncerForUser(user)
if syncer == nil {
return ""
}
return syncer.calculateHash(user)
s := strings.Join([]string{user.Id, user.Password, user.DisplayName, user.Avatar, user.Phone, strconv.Itoa(user.Score)}, "|")
return util.GetMd5Hash(s)
}
func (user *User) UpdateUserHash() {
@@ -31,9 +32,7 @@ func (user *User) UpdateUserHash() {
}
func (user *User) UpdateUserPassword(organization *Organization) {
credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil {
sealedPassword := credManager.GetSealedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
user.Password = sealedPassword
if organization.PasswordType == "salt" {
user.Password = getSaltedPassword(user.Password, organization.PasswordSalt)
}
}

View File

@@ -16,10 +16,9 @@ package object
import (
"fmt"
"reflect"
"testing"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -74,26 +73,8 @@ func TestSyncHashes(t *testing.T) {
}
}
func TestGetMaskedUsers(t *testing.T) {
type args struct {
users []*User
}
tests := []struct {
name string
args args
want []*User
}{
{
name: "1",
args: args{users: []*User{{Password: "casdoor"}, {Password: "casbin"}}},
want: []*User{{Password: "***"}, {Password: "***"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GetMaskedUsers(tt.args.users); !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetMaskedUsers() = %v, want %v", got, tt.want)
}
})
}
func TestGetSaltedPassword(t *testing.T) {
password := "123456"
salt := "123"
fmt.Printf("%s -> %s\n", password, getSaltedPassword(password, salt))
}

View File

@@ -19,7 +19,7 @@ import (
"reflect"
"strings"
"github.com/casbin/casdoor/idp"
"github.com/casdoor/casdoor/idp"
"xorm.io/core"
)
@@ -135,7 +135,7 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
}
}
affected := UpdateUserForAllFields(user.GetId(), user)
affected := UpdateUserInternal(user.GetId(), user)
return affected
}

View File

@@ -15,13 +15,12 @@
package object
import (
"errors"
"fmt"
"math/rand"
"time"
"github.com/astaxie/beego"
"github.com/casbin/casdoor/util"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@@ -40,9 +39,9 @@ type VerificationRecord struct {
IsUsed bool
}
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) string {
if provider == nil {
return fmt.Errorf("Please set an Email provider first")
return "Please set an Email provider first"
}
sender := organization.DisplayName
@@ -51,27 +50,27 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(provider.Content, code)
if err := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); err != nil {
return err
if result := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); len(result) != 0 {
return result
}
return SendEmail(provider, title, content, dest, sender)
}
func SendVerificationCodeToPhone(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
func SendVerificationCodeToPhone(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) string {
if provider == nil {
return errors.New("Please set a SMS provider first")
return "Please set a SMS provider first"
}
code := getRandomCode(5)
if err := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); err != nil {
return err
if result := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); len(result) != 0 {
return result
}
return SendSms(provider, code, dest)
return SendCodeToPhone(provider, dest, code)
}
func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordType, dest, code string) error {
func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordType, dest, code string) string {
var record VerificationRecord
record.RemoteAddr = remoteAddr
record.Type = recordType
@@ -80,12 +79,12 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
}
has, err := adapter.Engine.Desc("created_time").Get(&record)
if err != nil {
return err
panic(err)
}
now := time.Now().Unix()
if has && now-record.Time < 60 {
return errors.New("You can only send one code in 60s.")
return "You can only send one code in 60s."
}
record.Owner = provider.Owner
@@ -103,16 +102,16 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
_, err = adapter.Engine.Insert(record)
if err != nil {
return err
panic(err)
}
return nil
return ""
}
func getVerificationRecord(dest string) *VerificationRecord {
var record VerificationRecord
record.Receiver = dest
has, err := adapter.Engine.Desc("time").Where("is_used = false").Get(&record)
has, err := adapter.Engine.Desc("time").Where("is_used = 0").Get(&record)
if err != nil {
panic(err)
}

View File

@@ -1,132 +0,0 @@
// Copyright 2021 The casbin 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/casbin/casdoor/util"
"xorm.io/core"
)
type Webhook 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"`
Url string `xorm:"varchar(100)" json:"url"`
ContentType string `xorm:"varchar(100)" json:"contentType"`
Events []string `xorm:"varchar(100)" json:"events"`
Organization string `xorm:"varchar(100) index" json:"organization"`
}
func GetWebhookCount(owner string) int {
count, err := adapter.Engine.Count(&Webhook{Owner: owner})
if err != nil {
panic(err)
}
return int(count)
}
func GetWebhooks(owner string) []*Webhook {
webhooks := []*Webhook{}
err := adapter.Engine.Desc("created_time").Find(&webhooks, &Webhook{Owner: owner})
if err != nil {
panic(err)
}
return webhooks
}
func GetPaginationWebhooks(owner string, offset, limit int) []*Webhook {
webhooks := []*Webhook{}
err := adapter.Engine.Desc("created_time").Limit(limit, offset).Find(&webhooks, &Webhook{Owner: owner})
if err != nil {
panic(err)
}
return webhooks
}
func getWebhooksByOrganization(organization string) []*Webhook {
webhooks := []*Webhook{}
err := adapter.Engine.Desc("created_time").Find(&webhooks, &Webhook{Organization: organization})
if err != nil {
panic(err)
}
return webhooks
}
func getWebhook(owner string, name string) *Webhook {
if owner == "" || name == "" {
return nil
}
webhook := Webhook{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&webhook)
if err != nil {
panic(err)
}
if existed {
return &webhook
} else {
return nil
}
}
func GetWebhook(id string) *Webhook {
owner, name := util.GetOwnerAndNameFromId(id)
return getWebhook(owner, name)
}
func UpdateWebhook(id string, webhook *Webhook) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getWebhook(owner, name) == nil {
return false
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(webhook)
if err != nil {
panic(err)
}
return affected != 0
}
func AddWebhook(webhook *Webhook) bool {
affected, err := adapter.Engine.Insert(webhook)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteWebhook(webhook *Webhook) bool {
affected, err := adapter.Engine.ID(core.PK{webhook.Owner, webhook.Name}).Delete(&Webhook{})
if err != nil {
panic(err)
}
return affected != 0
}
func (p *Webhook) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}

View File

@@ -1,38 +0,0 @@
// Copyright 2021 The casbin 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 (
"net/http"
"strings"
"github.com/casbin/casdoor/util"
)
func sendWebhook(webhook *Webhook, record *Record) error {
client := &http.Client{}
body := strings.NewReader(util.StructToJson(record))
req, err := http.NewRequest("POST", webhook.Url, body)
if err != nil {
return err
}
req.Header.Set("Content-Type", webhook.ContentType)
_, err = client.Do(req)
return err
}

View File

@@ -12,22 +12,39 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package conf
package original
import (
"os"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/object"
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
)
func GetBeegoConfDataSourceName() string {
dataSourceName := beego.AppConfig.String("dataSourceName")
var adapter *object.Adapter
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" {
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
func initConfig() {
err := beego.LoadAppConfig("ini", "../conf/app.conf")
if err != nil {
panic(err)
}
return dataSourceName
initAdapter()
}
func initAdapter() {
if dbName == "dbName" {
adapter = nil
return
}
adapter = object.NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), dbName)
createTable(adapter)
}
func createTable(a *object.Adapter) {
err := a.Engine.Sync2(new(User))
if err != nil {
panic(err)
}
}

View File

@@ -12,16 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package object
package original
type Affiliation struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Name string `xorm:"varchar(128)" json:"name"`
}
func (syncer *Syncer) getAffiliations() []*Affiliation {
func (Affiliation) TableName() string {
return affiliationTableName
}
func getAffiliations() []*Affiliation {
affiliations := []*Affiliation{}
err := syncer.Adapter.Engine.Table(syncer.AffiliationTable).Asc("id").Find(&affiliations)
err := adapter.Engine.Asc("id").Find(&affiliations)
if err != nil {
panic(err)
}
@@ -29,8 +33,8 @@ func (syncer *Syncer) getAffiliations() []*Affiliation {
return affiliations
}
func (syncer *Syncer) getAffiliationMap() ([]*Affiliation, map[int]string) {
affiliations := syncer.getAffiliations()
func getAffiliationMap() ([]*Affiliation, map[int]string) {
affiliations := getAffiliations()
m := map[int]string{}
for _, affiliation := range affiliations {

View File

@@ -12,16 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package cred
package original
import (
"fmt"
"testing"
)
var dbName = "dbName"
var userTableName = "userTableName"
var affiliationTableName = "affiliationTableName"
var avatarBaseUrl = "https://cdn.example.com/"
func TestGetSaltedPassword(t *testing.T) {
password := "123456"
salt := "123"
cm := NewSha256SaltCredManager()
fmt.Printf("%s -> %s\n", password, cm.GetSealedPassword(password, "", salt))
}
var orgName = "orgName"

View File

@@ -12,11 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package object
package original
import "testing"
import "github.com/mileusna/crontab"
func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key"
generateRsaKeys(fileId)
var ctab *crontab.Crontab
func init() {
ctab = crontab.New()
}

59
original/public_api.go Normal file
View File

@@ -0,0 +1,59 @@
// Copyright 2021 The casbin 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 original
import (
"fmt"
"github.com/casdoor/casdoor/object"
)
func isEnabled() bool {
if adapter == nil {
initAdapter()
if adapter == nil {
return false
}
}
return true
}
func AddUserToOriginalDatabase(user *object.User) {
if user.Owner != orgName {
return
}
if !isEnabled() {
return
}
updatedOUser := createOriginalUserFromUser(user)
addUser(updatedOUser)
fmt.Printf("Add from user to oUser: %v\n", updatedOUser)
}
func UpdateUserToOriginalDatabase(user *object.User) {
if user.Owner != orgName {
return
}
if !isEnabled() {
return
}
updatedOUser := createOriginalUserFromUser(user)
updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
}

151
original/sync.go Normal file
View File

@@ -0,0 +1,151 @@
// Copyright 2021 The casbin 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 original
import (
"fmt"
"strconv"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func getFullAvatarUrl(avatar string) string {
if !strings.HasPrefix(avatar, "https://") {
return fmt.Sprintf("%s%s", avatarBaseUrl, avatar)
}
return avatar
}
func getPartialAvatarUrl(avatar string) string {
if strings.HasPrefix(avatar, avatarBaseUrl) {
return avatar[len(avatarBaseUrl):]
}
return avatar
}
func createUserFromOriginalUser(originalUser *User, affiliationMap map[int]string) *object.User {
affiliation := ""
if originalUser.SchoolId != 0 {
var ok bool
affiliation, ok = affiliationMap[originalUser.SchoolId]
if !ok {
panic(fmt.Sprintf("SchoolId not found: %d", originalUser.SchoolId))
}
}
user := &object.User{
Owner: orgName,
Name: strconv.Itoa(originalUser.Id),
CreatedTime: util.GetCurrentTime(),
Id: strconv.Itoa(originalUser.Id),
Type: "normal-user",
Password: originalUser.Password,
DisplayName: originalUser.Name,
Avatar: getFullAvatarUrl(originalUser.Avatar),
Email: "",
Phone: originalUser.Cellphone,
Address: []string{},
Affiliation: affiliation,
Score: originalUser.SchoolId,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: originalUser.Deleted != 0,
Properties: map[string]string{},
}
return user
}
func createOriginalUserFromUser(user *object.User) *User {
deleted := 0
if user.IsForbidden {
deleted = 1
}
originalUser := &User{
Id: util.ParseInt(user.Id),
Name: user.DisplayName,
Password: user.Password,
Cellphone: user.Phone,
SchoolId: user.Score,
Avatar: getPartialAvatarUrl(user.Avatar),
Deleted: deleted,
}
return originalUser
}
func syncUsers() {
fmt.Printf("Running syncUsers()..\n")
users, userMap := getUserMap()
oUsers, oUserMap := getUserMapOriginal()
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
_, affiliationMap := getAffiliationMap()
newUsers := []*object.User{}
for _, oUser := range oUsers {
id := strconv.Itoa(oUser.Id)
if _, ok := userMap[id]; !ok {
newUser := createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
} else {
user := userMap[id]
oHash := calculateHash(oUser)
if user.Hash == user.PreHash {
if user.Hash != oHash {
updatedUser := createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
object.UpdateUserForOriginal(updatedUser)
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
}
} else {
if user.PreHash == oHash {
updatedOUser := createOriginalUserFromUser(user)
updateUser(updatedOUser)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
// update preHash
user.PreHash = user.Hash
object.SetUserField(user, "pre_hash", user.PreHash)
} else {
if user.Hash == oHash {
// update preHash
user.PreHash = user.Hash
object.SetUserField(user, "pre_hash", user.PreHash)
} else {
updatedUser := createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash
updatedUser.PreHash = oHash
object.UpdateUserForOriginal(updatedUser)
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
}
}
}
}
}
object.AddUsersSafe(newUsers)
for _, user := range users {
id := user.Id
if _, ok := oUserMap[id]; !ok {
panic(fmt.Sprintf("New original user: cannot create now, user = %v", user))
}
}
}

View File

@@ -12,17 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package object
package original
func (syncer *Syncer) getUsers() []*User {
users := GetUsers(syncer.Organization)
import "github.com/casdoor/casdoor/object"
func getUsers() []*object.User {
users := object.GetUsers(orgName)
return users
}
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User) {
users := syncer.getUsers()
func getUserMap() ([]*object.User, map[string]*object.User) {
users := getUsers()
m := map[string]*User{}
m := map[string]*object.User{}
for _, user := range users {
m[user.Name] = user
}

79
original/user_original.go Normal file
View File

@@ -0,0 +1,79 @@
// Copyright 2021 The casbin 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 original
import (
"strconv"
"strings"
"github.com/casdoor/casdoor/util"
)
type User struct {
Id int `xorm:"int notnull pk autoincr" json:"id"`
Name string `xorm:"varchar(128)" json:"name"`
Password string `xorm:"varchar(128)" json:"password"`
Cellphone string `xorm:"varchar(128)" json:"cellphone"`
SchoolId int `json:"schoolId"`
Avatar string `xorm:"varchar(128)" json:"avatar"`
Deleted int `xorm:"tinyint(1)" json:"deleted"`
}
func (User) TableName() string {
return userTableName
}
func getUsersOriginal() []*User {
users := []*User{}
err := adapter.Engine.Asc("id").Find(&users)
if err != nil {
panic(err)
}
return users
}
func getUserMapOriginal() ([]*User, map[string]*User) {
users := getUsersOriginal()
m := map[string]*User{}
for _, user := range users {
m[strconv.Itoa(user.Id)] = user
}
return users, m
}
func addUser(user *User) bool {
affected, err := adapter.Engine.Insert(user)
if err != nil {
panic(err)
}
return affected != 0
}
func updateUser(user *User) bool {
affected, err := adapter.Engine.ID(user.Id).Cols("name", "password", "cellphone", "school_id", "avatar", "deleted").Update(user)
if err != nil {
panic(err)
}
return affected != 0
}
func calculateHash(user *User) string {
s := strings.Join([]string{strconv.Itoa(user.Id), user.Password, user.Name, getFullAvatarUrl(user.Avatar), user.Cellphone, strconv.Itoa(user.SchoolId)}, "|")
return util.GetMd5Hash(s)
}

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