feat: add casdoor support
This commit is contained in:
parent
bb9034b267
commit
67d868a804
|
@ -0,0 +1,26 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.swp
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
tmp/
|
||||
tmpFiles/
|
||||
*.tmp
|
||||
logs/
|
||||
lastupdate.tmp
|
||||
commentsRouter*.go
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -0,0 +1,161 @@
|
|||
Casdoor
|
||||
====
|
||||
|
||||
Casdoor is a UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.
|
||||
|
||||
## Online demo
|
||||
|
||||
### Casdoor
|
||||
|
||||
Casdoor is the authentication server. It serves both the web UI and the login requests from the application users.
|
||||
|
||||
- Deployed site: https://door.casbin.com/
|
||||
- Source code: https://github.com/casbin/casdoor (this repo)
|
||||
|
||||
Global admin login:
|
||||
|
||||
- Username: `admin`
|
||||
- Password: `123`
|
||||
|
||||
### Web application
|
||||
|
||||
Casbin-OA is one of our applications that use Casdoor as authentication.
|
||||
|
||||
- Deployed site: https://oa.casbin.com/
|
||||
- Source code: https://github.com/casbin/casbin-oa
|
||||
|
||||
## Architecture
|
||||
|
||||
Casdoor contains 2 parts:
|
||||
|
||||
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
|
||||
|
||||
## Installation
|
||||
|
||||
- Get code via `go get`:
|
||||
|
||||
```shell
|
||||
go get github.com/casbin/casdoor
|
||||
```
|
||||
|
||||
or `git clone`:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/casbin/casdoor
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
http://localhost:8000/
|
||||
|
||||
## Run (Dev Environment)
|
||||
|
||||
- Run backend (in port 8000):
|
||||
|
||||
```shell
|
||||
go run main.go
|
||||
```
|
||||
|
||||
- Run frontend (in the same machine's port 7001):
|
||||
|
||||
```shell
|
||||
cd web
|
||||
## npm
|
||||
npm install
|
||||
npm run start
|
||||
## yarn
|
||||
yarn install
|
||||
yarn run start
|
||||
```
|
||||
|
||||
- Open browser:
|
||||
|
||||
http://localhost:7001/
|
||||
|
||||
## Run (Production Environment)
|
||||
|
||||
- build static pages:
|
||||
|
||||
```
|
||||
cd web
|
||||
## npm
|
||||
npm run build
|
||||
## yarn
|
||||
yarn run build
|
||||
## back to casdoor directory
|
||||
cd ..
|
||||
```
|
||||
|
||||
- build and run go code:
|
||||
|
||||
```
|
||||
go build
|
||||
./casdoor
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Config
|
||||
|
||||
- Setup database (MySQL):
|
||||
|
||||
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
|
||||
|
||||
```ini
|
||||
db = mysql
|
||||
dataSourceName = root:123@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
```
|
||||
|
||||
- Setup database (Postgres):
|
||||
|
||||
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:
|
||||
|
||||
``` ini
|
||||
db = postgres
|
||||
dataSourceName = "user=postgres password=xxx sslmode=disable dbname="
|
||||
dbName = casdoor
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
Casdoor uses XORM to connect to DB, so all DBs supported by XORM can also be used.
|
||||
|
||||
- Github corner
|
||||
|
||||
We added a Github icon in the upper right corner, linking to your Github repository address.
|
||||
You could set `ShowGithubCorner` to hidden it.
|
||||
|
||||
Configuration (`web/src/commo/Conf.js`):
|
||||
|
||||
```javascript
|
||||
export const ShowGithubCorner = true
|
||||
|
||||
export const GithubRepo = "https://github.com/casbin/casdoor" //your github repository
|
||||
```
|
||||
|
||||
- OSS conf
|
||||
|
||||
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.
|
||||
|
||||
```
|
||||
[provider]
|
||||
accessId = id
|
||||
accessKey = key
|
||||
bucket = bucket
|
||||
endpoint = endpoint
|
||||
```
|
||||
|
||||
Please fill out this conf correctly, or the avatar server won't work!
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
// 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 authz
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
xormadapter "github.com/casbin/xorm-adapter/v2"
|
||||
stringadapter "github.com/qiangmzsx/string-adapter/v2"
|
||||
)
|
||||
|
||||
var Enforcer *casbin.Enforcer
|
||||
|
||||
func InitAuthz() {
|
||||
var err error
|
||||
|
||||
a, err := xormadapter.NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName")+beego.AppConfig.String("dbName"), true)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
modelText := `
|
||||
[request_definition]
|
||||
r = subOwner, subName, method, urlPath, objOwner, objName
|
||||
|
||||
[policy_definition]
|
||||
p = subOwner, subName, method, urlPath, objOwner, objName
|
||||
|
||||
[role_definition]
|
||||
g = _, _
|
||||
|
||||
[policy_effect]
|
||||
e = some(where (p.eft == allow))
|
||||
|
||||
[matchers]
|
||||
m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
||||
(r.subName == p.subName || p.subName == "*" || r.subName != "anonymous" && p.subName == "!anonymous") && \
|
||||
(r.method == p.method || p.method == "*") && \
|
||||
(r.urlPath == p.urlPath || p.urlPath == "*") && \
|
||||
(r.objOwner == p.objOwner || p.objOwner == "*") && \
|
||||
(r.objName == p.objName || p.objName == "*") || \
|
||||
(r.urlPath == "/api/update-user" && r.subOwner == r.objOwner && r.subName == r.objName)
|
||||
`
|
||||
|
||||
m, err := model.NewModelFromString(modelText)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Enforcer, err = casbin.NewEnforcer(m, a)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
Enforcer.ClearPolicy()
|
||||
|
||||
//if len(Enforcer.GetPolicy()) == 0 {
|
||||
if true {
|
||||
ruleText := `
|
||||
p, built-in, *, *, *, *, *
|
||||
p, *, *, POST, /api/signup, *, *
|
||||
p, *, *, POST, /api/get-email-and-phone, *, *
|
||||
p, *, *, POST, /api/login, *, *
|
||||
p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, POST, /api/login/oauth/access_token, *, *
|
||||
p, *, *, GET, /api/get-application, *, *
|
||||
p, *, *, GET, /api/get-users, *, *
|
||||
p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-organizations, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, GET, /api/get-default-providers, *, *
|
||||
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, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
// load all rules from string adapter to enforcer's memory
|
||||
err := sa.LoadPolicy(Enforcer.GetModel())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// save all rules from enforcer's memory to Xorm adapter (DB)
|
||||
// same as:
|
||||
// a.SavePolicy(Enforcer.GetModel())
|
||||
err = Enforcer.SavePolicy()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func IsAllowed(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,11 @@
|
|||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
SessionOn = true
|
||||
copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:cappuccino@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
authState = "casdoor"
|
||||
httpProxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
|
@ -0,0 +1,6 @@
|
|||
[provider]
|
||||
endpoint = endpoint
|
||||
accessId = id
|
||||
accessKey = key
|
||||
domain = domain
|
||||
bucket = bucket
|
|
@ -0,0 +1,277 @@
|
|||
// 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/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/original"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
const (
|
||||
ResponseTypeLogin = "login"
|
||||
ResponseTypeCode = "code"
|
||||
)
|
||||
|
||||
type RequestForm struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
Organization string `json:"organization"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Affiliation string `json:"affiliation"`
|
||||
|
||||
Application string `json:"application"`
|
||||
Provider string `json:"provider"`
|
||||
Code string `json:"code"`
|
||||
State string `json:"state"`
|
||||
RedirectUri string `json:"redirectUri"`
|
||||
Method string `json:"method"`
|
||||
|
||||
EmailCode string `json:"emailCode"`
|
||||
PhoneCode string `json:"phoneCode"`
|
||||
PhonePrefix string `json:"phonePrefix"`
|
||||
|
||||
AutoSignin bool `json:"autoSignin"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Msg string `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
Data2 interface{} `json:"data2"`
|
||||
}
|
||||
|
||||
type HumanCheck struct {
|
||||
Type string `json:"type"`
|
||||
AppKey string `json:"appKey"`
|
||||
Scene string `json:"scene"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
CaptchaImage interface{} `json:"captchaImage"`
|
||||
}
|
||||
|
||||
// @Title Signup
|
||||
// @Description sign up a new user
|
||||
// @Param username formData string true "The username to sign up"
|
||||
// @Param password formData string true "The password"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /signup [post]
|
||||
func (c *ApiController) Signup() {
|
||||
var resp Response
|
||||
|
||||
if c.GetSessionUsername() != "" {
|
||||
c.ResponseErrorWithData("Please sign out first before signing up", c.GetSessionUsername())
|
||||
return
|
||||
}
|
||||
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
|
||||
if !application.EnableSignUp {
|
||||
c.ResponseError("The application does not allow to sign up new account")
|
||||
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 != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser := object.GetLastUser(form.Organization)
|
||||
lastIdInt := util.ParseInt(lastUser.Id)
|
||||
id = strconv.Itoa(lastIdInt + 1)
|
||||
}
|
||||
|
||||
username := form.Username
|
||||
if !application.IsSignupItemVisible("Username") {
|
||||
username = id
|
||||
}
|
||||
|
||||
user := &object.User{
|
||||
Owner: form.Organization,
|
||||
Name: username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: id,
|
||||
Type: "normal-user",
|
||||
Password: form.Password,
|
||||
DisplayName: form.Name,
|
||||
Avatar: organization.DefaultAvatar,
|
||||
Email: form.Email,
|
||||
Phone: form.Phone,
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
SignupApplication: application.Name,
|
||||
Properties: map[string]string{},
|
||||
}
|
||||
|
||||
affected := object.AddUser(user)
|
||||
if affected {
|
||||
original.AddUserToOriginalDatabase(user)
|
||||
}
|
||||
|
||||
if application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(user.GetId())
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(form.Email)
|
||||
object.DisableVerificationCode(checkPhone)
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
|
||||
|
||||
resp = Response{Status: "ok", Msg: "", Data: userId}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Logout
|
||||
// @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)
|
||||
|
||||
resp = Response{Status: "ok", Msg: "", Data: user}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetAccount
|
||||
// @Description get the details of the current account
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /get-account [get]
|
||||
func (c *ApiController) GetAccount() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var resp Response
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
resp := Response{Status: "error", Msg: fmt.Sprintf("The user: %s doesn't exist", userId)}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetHumanCheck() {
|
||||
c.Data["json"] = HumanCheck{Type: "none"}
|
||||
|
||||
provider := object.GetDefaultHumanCheckProvider()
|
||||
if provider == nil {
|
||||
id, img := object.GetCaptcha()
|
||||
c.Data["json"] = HumanCheck{Type: "captcha", CaptchaId: id, CaptchaImage: img}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
// 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/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// @Title GetApplications
|
||||
// @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")
|
||||
|
||||
c.Data["json"] = object.GetApplications(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetApplication
|
||||
// @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
|
||||
// @router /get-application [get]
|
||||
func (c *ApiController) GetApplication() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetApplication(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetUserApplication
|
||||
// @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
|
||||
// @router /get-user-application [get]
|
||||
func (c *ApiController) GetUserApplication() {
|
||||
id := c.Input().Get("id")
|
||||
user := object.GetUser(id)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetApplicationByUser(user)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateApplication
|
||||
// @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"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-application [post]
|
||||
func (c *ApiController) UpdateApplication() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddApplication
|
||||
// @Description add an application
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-application [post]
|
||||
func (c *ApiController) AddApplication() {
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddApplication(&application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteApplication
|
||||
// @Description delete an application
|
||||
// @Param body body object.Application true "The details of the application"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-application [post]
|
||||
func (c *ApiController) DeleteApplication() {
|
||||
var application object.Application
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteApplication(&application))
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
// 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"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"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}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
resp = &Response{Status: "ok", Msg: "", Data: userId}
|
||||
} else if form.Type == ResponseTypeCode {
|
||||
clientId := c.Input().Get("clientId")
|
||||
responseType := c.Input().Get("responseType")
|
||||
redirectUri := c.Input().Get("redirectUri")
|
||||
scope := c.Input().Get("scope")
|
||||
state := c.Input().Get("state")
|
||||
|
||||
code := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state)
|
||||
resp = codeToResponse(code)
|
||||
|
||||
if application.HasPromptPage() {
|
||||
// The prompt page needs the user to be signed in
|
||||
c.SetSessionUsername(userId)
|
||||
}
|
||||
} else {
|
||||
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)}
|
||||
}
|
||||
|
||||
// if user did not check auto signin
|
||||
if resp.Status == "ok" && !form.AutoSignin {
|
||||
timestamp := time.Now().Unix()
|
||||
timestamp += 3600 * 24
|
||||
c.SetSessionData(&SessionData{
|
||||
ExpireTime: timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// @Title GetApplicationLogin
|
||||
// @Description get application login
|
||||
// @Param clientId query string true "client id"
|
||||
// @Param responseType query string true "response type"
|
||||
// @Param redirectUri query string true "redirect uri"
|
||||
// @Param scope query string true "scope"
|
||||
// @Param state query string true "state"
|
||||
// @Success 200 {object} controllers.api_controller.Response The Response object
|
||||
// @router /update-application [get]
|
||||
func (c *ApiController) GetApplicationLogin() {
|
||||
var resp Response
|
||||
|
||||
clientId := c.Input().Get("clientId")
|
||||
responseType := c.Input().Get("responseType")
|
||||
redirectUri := c.Input().Get("redirectUri")
|
||||
scope := c.Input().Get("scope")
|
||||
state := c.Input().Get("state")
|
||||
|
||||
msg, application := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||
if msg != "" {
|
||||
resp = Response{Status: "error", Msg: msg, Data: application}
|
||||
} else {
|
||||
resp = Response{Status: "ok", Msg: "", Data: application}
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title Login
|
||||
// @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{Status: "null", Msg: ""}
|
||||
var form RequestForm
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
|
||||
if err != nil {
|
||||
resp = &Response{Status: "error", Msg: err.Error()}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
if form.Username != "" {
|
||||
if form.Type == ResponseTypeLogin {
|
||||
if c.GetSessionUsername() != "" {
|
||||
resp = &Response{Status: "error", Msg: "Please log out first before signing in", Data: c.GetSessionUsername()}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
var msg string
|
||||
|
||||
if form.Password == "" {
|
||||
var verificationCodeType string
|
||||
|
||||
// check result through Email or Phone
|
||||
if strings.Contains(form.Email, "@") {
|
||||
verificationCodeType = "email"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// get user
|
||||
var userId string
|
||||
if form.Username == "" {
|
||||
userId, _ = c.RequireSignedIn()
|
||||
} else {
|
||||
userId = fmt.Sprintf("%s/%s", 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.CheckUserLogin(form.Organization, form.Username, password)
|
||||
}
|
||||
|
||||
if 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 := util.Records(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
idProvider.SetHttpClient(httpClient)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
if !token.Valid() {
|
||||
resp = &Response{Status: "error", Msg: "Invalid token"}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
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.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 {
|
||||
// Sign in via OAuth
|
||||
|
||||
//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 := util.Records(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.Username = user.Name
|
||||
|
||||
object.AddRecord(record)
|
||||
} else {
|
||||
// Sign up via OAuth
|
||||
if !application.EnableSignUp {
|
||||
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 {
|
||||
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{
|
||||
Owner: application.Organization,
|
||||
Name: userInfo.Username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
DisplayName: userInfo.DisplayName,
|
||||
Avatar: userInfo.AvatarUrl,
|
||||
Email: userInfo.Email,
|
||||
Score: 200,
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
SignupApplication: application.Name,
|
||||
Properties: properties,
|
||||
}
|
||||
object.AddUser(user)
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
|
||||
object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
|
||||
resp = c.HandleLoggedIn(application, user, &form)
|
||||
|
||||
record := util.Records(c.Ctx)
|
||||
record.Organization = application.Organization
|
||||
record.Username = user.Name
|
||||
|
||||
object.AddRecord(record)
|
||||
}
|
||||
//resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||
} else { // form.Method != "signup"
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
resp = &Response{Status: "error", Msg: "The account does not exist", Data: userInfo}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
oldUser := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
|
||||
if oldUser == nil {
|
||||
oldUser = object.GetUserByField(application.Organization, provider.Type, userInfo.Username)
|
||||
}
|
||||
if oldUser != nil {
|
||||
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
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
|
||||
// sync info from 3rd-party if possible
|
||||
object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
|
||||
|
||||
isLinked := object.LinkUserAccount(user, provider.Type, userInfo.Id)
|
||||
if isLinked {
|
||||
resp = &Response{Status: "ok", Msg: "", Data: isLinked}
|
||||
} 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 {
|
||||
panic("unknown authentication type (not password or provider), form = " + util.StructToJson(form))
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// 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 (
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type ApiController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type SessionData struct {
|
||||
ExpireTime int64
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionUsername() string {
|
||||
// check if user session expired
|
||||
sessionData := c.GetSessionData()
|
||||
if sessionData != nil &&
|
||||
sessionData.ExpireTime != 0 &&
|
||||
sessionData.ExpireTime < time.Now().Unix() {
|
||||
c.SetSessionUsername("")
|
||||
c.SetSessionData(nil)
|
||||
return ""
|
||||
}
|
||||
|
||||
user := c.GetSession("username")
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func (c *ApiController) SetSessionUsername(user string) {
|
||||
c.SetSession("username", user)
|
||||
}
|
||||
|
||||
func (c *ApiController) GetSessionData() *SessionData {
|
||||
session := c.GetSession("SessionData")
|
||||
if session == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessionData := &SessionData{}
|
||||
err := util.JsonToStruct(session.(string), sessionData)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return sessionData
|
||||
}
|
||||
|
||||
func (c *ApiController) SetSessionData(s *SessionData) {
|
||||
if s == nil {
|
||||
c.DelSession("SessionData")
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSession("SessionData", util.StructToJson(s))
|
||||
}
|
||||
|
||||
func wrapActionResponse(affected bool) *Response {
|
||||
if affected {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Affected"}
|
||||
} else {
|
||||
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
// 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/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type LdapServer struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Admin string `json:"admin"`
|
||||
Passwd string `json:"passwd"`
|
||||
BaseDn string `json:"baseDn"`
|
||||
}
|
||||
|
||||
type LdapResp struct {
|
||||
//Groups []LdapRespGroup `json:"groups"`
|
||||
Users []object.LdapRespUser `json:"users"`
|
||||
}
|
||||
|
||||
//type LdapRespGroup struct {
|
||||
// GroupId string
|
||||
// GroupName string
|
||||
//}
|
||||
|
||||
type LdapSyncResp struct {
|
||||
Exist []object.LdapRespUser `json:"exist"`
|
||||
Failed []object.LdapRespUser `json:"failed"`
|
||||
}
|
||||
|
||||
func (c *ApiController) GetLdapUser() {
|
||||
ldapServer := LdapServer{}
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
|
||||
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
var resp LdapResp
|
||||
|
||||
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
|
||||
if err != nil {
|
||||
c.Data["json"] = Response{Status: "error", Msg: err.Error()}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
|
||||
//if err != nil {
|
||||
// c.Data["json"] = Response{Status: "error", Msg: err.Error()}
|
||||
// c.ServeJSON()
|
||||
// return
|
||||
//}
|
||||
|
||||
//for _, group := range groupsMap {
|
||||
// resp.Groups = append(resp.Groups, LdapRespGroup{
|
||||
// GroupId: group.GidNumber,
|
||||
// GroupName: group.Cn,
|
||||
// })
|
||||
//}
|
||||
|
||||
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
|
||||
if err != nil {
|
||||
c.Data["json"] = Response{Status: "error", Msg: err.Error()}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
resp.Users = append(resp.Users, object.LdapRespUser{
|
||||
UidNumber: user.UidNumber,
|
||||
Uid: user.Uid,
|
||||
Cn: user.Cn,
|
||||
GroupId: user.GidNumber,
|
||||
//GroupName: groupsMap[user.GidNumber].Cn,
|
||||
Uuid: user.Uuid,
|
||||
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
|
||||
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
|
||||
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
|
||||
})
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: resp}
|
||||
c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *ApiController) GetLdaps() {
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdaps(owner)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetLdap() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
if util.IsStrsEmpty(id) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: object.GetLdap(id)}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) AddLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
if object.CheckLdapExist(&ldap) {
|
||||
c.ResponseError("Ldap server exist")
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.AddLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) UpdateLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
|
||||
c.ResponseError("Missing parameter")
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.UpdateLdap(&ldap)
|
||||
resp := wrapActionResponse(affected)
|
||||
if affected {
|
||||
resp.Data2 = ldap
|
||||
}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) DeleteLdap() {
|
||||
var ldap object.Ldap
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) SyncLdapUsers() {
|
||||
owner := c.Input().Get("owner")
|
||||
ldapId := c.Input().Get("ldapId")
|
||||
var users []object.LdapRespUser
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
object.UpdateLdapSyncTime(ldapId)
|
||||
|
||||
exist, failed := object.SyncLdapUsers(owner, users)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
|
||||
Exist: *exist,
|
||||
Failed: *failed,
|
||||
}}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) CheckLdapUsersExist() {
|
||||
owner := c.Input().Get("owner")
|
||||
var uuids []string
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
exist := object.CheckLdapUuidExist(owner, uuids)
|
||||
c.Data["json"] = &Response{Status: "ok", Data: exist}
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
// 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/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
type LinkForm struct {
|
||||
ProviderType string `json:"providerType"`
|
||||
}
|
||||
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
providerType := form.ProviderType
|
||||
|
||||
user := object.GetUser(userId)
|
||||
value := object.GetUserField(user, providerType)
|
||||
|
||||
if 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, "")
|
||||
resp = Response{Status: "ok", Msg: ""}
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// 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/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// @Title GetOrganizations
|
||||
// @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")
|
||||
|
||||
c.Data["json"] = object.GetOrganizations(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetOrganization
|
||||
// @Description get organization
|
||||
// @Param id query string true "organization id"
|
||||
// @Success 200 {object} object.Organization The Response object
|
||||
// @router /get-organization [get]
|
||||
func (c *ApiController) GetOrganization() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetOrganization(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateOrganization
|
||||
// @Description update organization
|
||||
// @Param id query string true "The id of the organization"
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-organization [post]
|
||||
func (c *ApiController) UpdateOrganization() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddOrganization
|
||||
// @Description add organization
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-organization [post]
|
||||
func (c *ApiController) AddOrganization() {
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddOrganization(&organization))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteOrganization
|
||||
// @Description delete organization
|
||||
// @Param body body object.Organization true "The details of the organization"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-organization [post]
|
||||
func (c *ApiController) DeleteOrganization() {
|
||||
var organization object.Organization
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteOrganization(&organization))
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
// 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/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// @Title GetProviders
|
||||
// @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")
|
||||
|
||||
c.Data["json"] = object.GetProviders(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetProvider
|
||||
// @Description get provider
|
||||
// @Param id query string true "The id of the provider"
|
||||
// @Success 200 {object} object.Provider The Response object
|
||||
// @router /get-provider [get]
|
||||
func (c *ApiController) GetProvider() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetProvider(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateProvider
|
||||
// @Description update provider
|
||||
// @Param id query string true "The id of the provider"
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-provider [post]
|
||||
func (c *ApiController) UpdateProvider() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddProvider
|
||||
// @Description add provider
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-provider [post]
|
||||
func (c *ApiController) AddProvider() {
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteProvider
|
||||
// @Description delete provider
|
||||
// @Param body body object.Provider true "The details of the provider"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-provider [post]
|
||||
func (c *ApiController) DeleteProvider() {
|
||||
var provider object.Provider
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
// 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/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// @Title GetRecords
|
||||
// @Description get all records
|
||||
// @Success 200 {array} object.Records The Response object
|
||||
// @router /get-records [get]
|
||||
func (c *ApiController) GetRecords() {
|
||||
c.Data["json"] = object.GetRecords()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @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() {
|
||||
var record object.Records
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = object.GetRecordsByField(&record)
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
// 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/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// @Title GetTokens
|
||||
// @Description get tokens
|
||||
// @Param owner query string true "The owner of tokens"
|
||||
// @Success 200 {array} object.Token The Response object
|
||||
// @router /get-tokens [get]
|
||||
func (c *ApiController) GetTokens() {
|
||||
owner := c.Input().Get("owner")
|
||||
|
||||
c.Data["json"] = object.GetTokens(owner)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetToken
|
||||
// @Description get token
|
||||
// @Param id query string true "The id of token"
|
||||
// @Success 200 {object} object.Token The Response object
|
||||
// @router /get-token [get]
|
||||
func (c *ApiController) GetToken() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
c.Data["json"] = object.GetToken(id)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateToken
|
||||
// @Description update token
|
||||
// @Param id query string true "The id of token"
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-token [post]
|
||||
func (c *ApiController) UpdateToken() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.UpdateToken(id, &token))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddToken
|
||||
// @Description add token
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-token [post]
|
||||
func (c *ApiController) AddToken() {
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddToken(&token))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteToken
|
||||
// @Description delete token
|
||||
// @Param body body object.Token true "Details of the token"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-token [post]
|
||||
func (c *ApiController) DeleteToken() {
|
||||
var token object.Token
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteToken(&token))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetOAuthToken
|
||||
// @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() {
|
||||
grantType := c.Input().Get("grant_type")
|
||||
clientId := c.Input().Get("client_id")
|
||||
clientSecret := c.Input().Get("client_secret")
|
||||
code := c.Input().Get("code")
|
||||
|
||||
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code)
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
// 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"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/original"
|
||||
)
|
||||
|
||||
// @Title GetGlobalUsers
|
||||
// @Description get global users
|
||||
// @Success 200 {array} object.User The Response object
|
||||
// @router /get-global-users [get]
|
||||
func (c *ApiController) GetGlobalUsers() {
|
||||
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetUsers
|
||||
// @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")
|
||||
|
||||
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetUser
|
||||
// @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")
|
||||
|
||||
c.Data["json"] = object.GetMaskedUser(object.GetUser(id))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title UpdateUser
|
||||
// @Description update user
|
||||
// @Param id query string true "The id of the user"
|
||||
// @Param body body object.User true "The details of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /update-user [post]
|
||||
func (c *ApiController) UpdateUser() {
|
||||
id := c.Input().Get("id")
|
||||
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if user.DisplayName == "" {
|
||||
c.ResponseError("Display name cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.UpdateUser(id, &user)
|
||||
if affected {
|
||||
newUser := object.GetUser(user.GetId())
|
||||
original.UpdateUserToOriginalDatabase(newUser)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(affected)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title AddUser
|
||||
// @Description add user
|
||||
// @Param body body object.User true "The details of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /add-user [post]
|
||||
func (c *ApiController) AddUser() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title DeleteUser
|
||||
// @Description delete user
|
||||
// @Param body body object.User true "The details of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /delete-user [post]
|
||||
func (c *ApiController) DeleteUser() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title GetEmailAndPhone
|
||||
// @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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
user := object.GetUserByFields(form.Organization, form.Username)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
|
||||
var contentType string
|
||||
switch form.Username {
|
||||
case user.Email:
|
||||
contentType = "email"
|
||||
case user.Phone:
|
||||
contentType = "phone"
|
||||
case user.Name:
|
||||
contentType = "username"
|
||||
}
|
||||
|
||||
resp = Response{Status: "ok", Msg: "", Data: respUser, Data2: contentType}
|
||||
|
||||
c.Data["json"] = resp
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// @Title SetPassword
|
||||
// @Description set password
|
||||
// @Param userOwner formData string true "The owner of the user"
|
||||
// @Param userName formData string true "The name of the user"
|
||||
// @Param oldPassword formData string true "The old password of the user"
|
||||
// @Param newPassword formData string true "The new password of the user"
|
||||
// @Success 200 {object} controllers.Response The Response object
|
||||
// @router /set-password [post]
|
||||
func (c *ApiController) SetPassword() {
|
||||
userOwner := c.Ctx.Request.Form.Get("userOwner")
|
||||
userName := c.Ctx.Request.Form.Get("userName")
|
||||
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
|
||||
newPassword := c.Ctx.Request.Form.Get("newPassword")
|
||||
|
||||
requestUserId := c.GetSessionUsername()
|
||||
if requestUserId == "" {
|
||||
c.ResponseError("Please login first.")
|
||||
return
|
||||
}
|
||||
requestUser := object.GetUser(requestUserId)
|
||||
if requestUser == nil {
|
||||
c.ResponseError("Session outdated. Please login again.")
|
||||
return
|
||||
}
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", userOwner, userName)
|
||||
targetUser := object.GetUser(userId)
|
||||
if targetUser == nil {
|
||||
c.ResponseError("Invalid user id.")
|
||||
return
|
||||
}
|
||||
|
||||
hasPermission := false
|
||||
|
||||
if requestUser.IsGlobalAdmin {
|
||||
hasPermission = true
|
||||
} else if requestUserId == userId {
|
||||
hasPermission = true
|
||||
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
|
||||
hasPermission = true
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
c.ResponseError("You don't have the permission to do this.")
|
||||
return
|
||||
}
|
||||
|
||||
if oldPassword != "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword)
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
if strings.Index(newPassword, " ") >= 0 {
|
||||
c.ResponseError("New password cannot contain blank space.")
|
||||
return
|
||||
}
|
||||
|
||||
if len(newPassword) <= 5 {
|
||||
c.ResponseError("New password must have at least 6 characters")
|
||||
return
|
||||
}
|
||||
|
||||
c.SetSessionUsername("")
|
||||
|
||||
targetUser.Password = newPassword
|
||||
object.SetUserField(targetUser, "password", targetUser.Password)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
// 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 (
|
||||
"net/http"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
var httpClient *http.Client
|
||||
|
||||
func InitHttpClient() {
|
||||
httpProxy := beego.AppConfig.String("httpProxy")
|
||||
if httpProxy == "" {
|
||||
httpClient = &http.Client{}
|
||||
return
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
||||
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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) 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
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (c *ApiController) getCurrentUser() *object.User {
|
||||
var user *object.User
|
||||
userId := c.GetSessionUsername()
|
||||
if userId == "" {
|
||||
user = nil
|
||||
} else {
|
||||
user = object.GetUser(userId)
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (c *ApiController) SendVerificationCode() {
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
orgId := c.Ctx.Request.Form.Get("organizationId")
|
||||
checkType := c.Ctx.Request.Form.Get("checkType")
|
||||
checkId := c.Ctx.Request.Form.Get("checkId")
|
||||
checkKey := c.Ctx.Request.Form.Get("checkKey")
|
||||
remoteAddr := c.Ctx.Request.RemoteAddr
|
||||
remoteAddr = remoteAddr[:strings.LastIndex(remoteAddr, ":")]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
isHuman := false
|
||||
captchaProvider := object.GetDefaultHumanCheckProvider()
|
||||
if captchaProvider == nil {
|
||||
isHuman = object.VerifyCaptcha(checkId, checkKey)
|
||||
}
|
||||
|
||||
if !isHuman {
|
||||
c.ResponseError("Turing test failed.")
|
||||
return
|
||||
}
|
||||
|
||||
user := c.getCurrentUser()
|
||||
organization := object.GetOrganization(orgId)
|
||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||
|
||||
msg := "Invalid dest type."
|
||||
switch destType {
|
||||
case "email":
|
||||
if !util.IsEmailValid(dest) {
|
||||
c.ResponseError("Invalid Email address")
|
||||
return
|
||||
}
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
msg = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if !util.IsPhoneCnValid(dest) {
|
||||
c.ResponseError("Invalid phone number")
|
||||
return
|
||||
}
|
||||
org := object.GetOrganization(orgId)
|
||||
if org == nil {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
|
||||
provider := application.GetSmsProvider()
|
||||
msg = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
|
||||
}
|
||||
|
||||
status := "ok"
|
||||
if msg != "" {
|
||||
status = "error"
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: status, Msg: msg}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) ResetEmailOrPhone() {
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
if user == nil {
|
||||
c.ResponseError("No such user.")
|
||||
return
|
||||
}
|
||||
|
||||
destType := c.Ctx.Request.Form.Get("type")
|
||||
dest := c.Ctx.Request.Form.Get("dest")
|
||||
code := c.Ctx.Request.Form.Get("code")
|
||||
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
|
||||
c.ResponseError("Missing parameter.")
|
||||
return
|
||||
}
|
||||
|
||||
checkDest := dest
|
||||
if destType == "phone" {
|
||||
org := object.GetOrganizationByUser(user)
|
||||
phonePrefix := "86"
|
||||
if org != nil && org.PhonePrefix != "" {
|
||||
phonePrefix = org.PhonePrefix
|
||||
}
|
||||
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
|
||||
}
|
||||
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
|
||||
c.ResponseError(ret)
|
||||
return
|
||||
}
|
||||
|
||||
switch destType {
|
||||
case "email":
|
||||
user.Email = dest
|
||||
object.SetUserField(user, "email", user.Email)
|
||||
case "phone":
|
||||
user.Phone = dest
|
||||
object.SetUserField(user, "phone", user.Phone)
|
||||
default:
|
||||
c.ResponseError("Unknown type.")
|
||||
return
|
||||
}
|
||||
|
||||
object.DisableVerificationCode(checkDest)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
version: '3.1'
|
||||
services:
|
||||
casdoor:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: go-dockerfile
|
||||
ports:
|
||||
- 8000:8000
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
restart: always
|
||||
image: mysql:8.0.25
|
||||
ports:
|
||||
- 3306:3306
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 123
|
||||
volumes:
|
||||
- /usr/local/docker/mysql:/var/lib/mysql
|
|
@ -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
|
|
@ -0,0 +1,34 @@
|
|||
module github.com/casdoor/casdoor
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible // indirect
|
||||
github.com/astaxie/beego v1.12.3
|
||||
github.com/aws/aws-sdk-go v1.37.30
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||
github.com/casbin/casbin/v2 v2.30.1
|
||||
github.com/casbin/xorm-adapter/v2 v2.3.1
|
||||
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/google/uuid v1.2.0
|
||||
github.com/jinzhu/configor v1.2.1 // indirect
|
||||
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/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-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
|
||||
xorm.io/core v0.7.2
|
||||
xorm.io/xorm v1.0.3
|
||||
)
|
|
@ -0,0 +1,314 @@
|
|||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
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/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=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075 h1:Z0SzZttfYI/raZ5O9WF3cezZJTSW4Yz4Kow9uWdyRwg=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1075/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible h1:Ft+KeWIJxFP76LqgJbvtOA1qBIoC8vGkTV3QeCOeJC4=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.1.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/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/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/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=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ=
|
||||
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
|
||||
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/casbin/casbin/v2 v2.25.5/go.mod h1:wUgota0cQbTXE6Vd+KWpg41726jFRi7upxio0sR+Xd0=
|
||||
github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeKF0=
|
||||
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.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/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
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/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=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/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/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-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=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0 h1:lwx+SJpgOHd8tG6SumBQZXCmNX51zM8B1cfxJ5gv4tQ=
|
||||
github.com/go-ldap/ldap/v3 v3.3.0/go.mod h1:iYS1MdmrmceOJ1QOTnRXrIs7i3kloqtmGQjRvjKpyMg=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
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-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
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.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.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/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/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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/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/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/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=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
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/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/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/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/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/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
|
||||
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/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mileusna/crontab v1.0.1 h1:YrDLc7l3xOiznmXq2FtAgg+1YQ3yC6pfFVPe+ywXNtg=
|
||||
github.com/mileusna/crontab v1.0.1/go.mod h1:dbns64w/u3tUnGZGf8pAa76ZqOfeBX4olW4U1ZwExmc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
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/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=
|
||||
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
|
||||
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
|
||||
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/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=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
|
||||
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.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=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0 h1:q0y8TPa/sTwtriJPRe8gWL++PuZ+XbOUuvKU+hvtTYs=
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0/go.mod h1:PElPB7b7HnGKTsuADAffFpOQXHqjEGJz1+U1a6yR5wA=
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 h1:J2Xj92efYLxPl3BiibgEDEUiMsCBzwTurE/8JjD8CG4=
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
|
||||
github.com/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=
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
|
||||
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
|
||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.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=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154 h1:THBgwGwUQtsw6L53cSSA2wwL3sLrm+HJ3Dk+ye/lMCI=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.154/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
||||
github.com/thanhpk/randstr v1.0.4 h1:IN78qu/bR+My+gHCvMEXhR/i5oriVHcTB/BJJIRTsNo=
|
||||
github.com/thanhpk/randstr v1.0.4/go.mod h1:M/H2P1eNLZzlDwAzpkkkUvoyNNMbzRGhESZuEQk3r0U=
|
||||
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||
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=
|
||||
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-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/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-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-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-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-20200202094626-16171245cfb2/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-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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/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-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/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-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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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-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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
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=
|
||||
google.golang.org/appengine v1.4.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/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.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
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-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/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=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
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/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=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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=
|
||||
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
|
||||
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
|
@ -0,0 +1,364 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// A total of three steps are required:
|
||||
//
|
||||
// 1. Construct the link and get the temporary authorization code
|
||||
// tmp_auth_code through the code at the end of the url.
|
||||
//
|
||||
// 2. Use hmac256 to calculate the signature, and then submit it together with timestamp,
|
||||
// tmp_auth_code, accessKey to obtain unionid, userid, accessKey.
|
||||
//
|
||||
// 3. Get detailed information through userid.
|
||||
|
||||
type DingTalkIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewDingTalkIdProvider(clientId string, clientSecret string, redirectUrl string) *DingTalkIdProvider {
|
||||
idp := &DingTalkIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *DingTalkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://oapi.dingtalk.com/sns/getuserinfo_bycode",
|
||||
TokenURL: "https://oapi.dingtalk.com/gettoken",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
// DingTalk not allow to set scopes,here it is just a placeholder,
|
||||
// convenient to use later
|
||||
Scopes: []string{"", ""},
|
||||
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type DingTalkAccessToken struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
AccessToken string `json:"access_token"` // Interface call credentials
|
||||
ExpiresIn int64 `json:"expires_in"` // access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
type DingTalkIds struct {
|
||||
UserId string `json:"user_id"`
|
||||
UnionId string `json:"union_id"`
|
||||
}
|
||||
|
||||
type InfoResp struct {
|
||||
Errcode int `json:"errcode"`
|
||||
UserInfo struct {
|
||||
Nick string `json:"nick"`
|
||||
Unionid string `json:"unionid"`
|
||||
Openid string `json:"openid"`
|
||||
MainOrgAuthHighLevel bool `json:"main_org_auth_high_level"`
|
||||
} `json:"user_info"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.dingtalk.com/document/app/dingtalk-retrieve-user-information?spm=ding_open_doc.document.0.0.51b91a31wWV3tY#doc-api-dingtalk-GetUser
|
||||
func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
timestamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
|
||||
signature := EncodeSHA256(timestamp, idp.Config.ClientSecret)
|
||||
u := fmt.Sprintf(
|
||||
"%s?accessKey=%s×tamp=%s&signature=%s", idp.Config.Endpoint.AuthURL,
|
||||
idp.Config.ClientID, timestamp, signature)
|
||||
|
||||
tmpCode := struct {
|
||||
TmpAuthCode string `json:"tmp_auth_code"`
|
||||
}{code}
|
||||
bs, _ := json.Marshal(tmpCode)
|
||||
r := strings.NewReader(string(bs))
|
||||
resp, err := http.Post(u, "application/json;charset=UTF-8", r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
info := InfoResp{}
|
||||
_ = json.Unmarshal(body, &info)
|
||||
errCode := info.Errcode
|
||||
if errCode != 0 {
|
||||
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)
|
||||
resp, _ = http.Get(u2)
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
body, _ = ioutil.ReadAll(resp.Body)
|
||||
tokenResp := DingTalkAccessToken{}
|
||||
_ = json.Unmarshal(body, &tokenResp)
|
||||
if tokenResp.ErrCode != 0 {
|
||||
return nil, errors.New(fmt.Sprintf("%d: %s", tokenResp.ErrCode, tokenResp.ErrMsg))
|
||||
}
|
||||
|
||||
// use unionid to get userid
|
||||
unionid := info.UserInfo.Unionid
|
||||
userid, err := idp.GetUseridByUnionid(tokenResp.AccessToken, unionid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Since DingTalk does not require scopes, put userid and unionid into
|
||||
// idp.config.scopes to facilitate GetUserInfo() to obtain these two parameters.
|
||||
idp.Config.Scopes = []string{unionid, userid}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: tokenResp.AccessToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
type UnionIdResponse struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
Result struct {
|
||||
ContactType string `json:"contact_type"`
|
||||
Userid string `json:"userid"`
|
||||
} `json:"result"`
|
||||
RequestId string `json:"request_id"`
|
||||
}
|
||||
|
||||
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)
|
||||
useridInfo, err := idp.GetUrlResp(u)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uresp := UnionIdResponse{}
|
||||
_ = json.Unmarshal([]byte(useridInfo), &uresp)
|
||||
errcode := uresp.Errcode
|
||||
if errcode != 0 {
|
||||
return "", errors.New(fmt.Sprintf("%d: %s", errcode, uresp.Errmsg))
|
||||
}
|
||||
return uresp.Result.Userid, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"errcode":0,
|
||||
"result":{
|
||||
"boss":false,
|
||||
"unionid":"5M6zgZBKQPCxdiPdANeJ6MgiEiE",
|
||||
"role_list":[
|
||||
{
|
||||
"group_name":"默认",
|
||||
"name":"主管理员",
|
||||
"id":2062489174
|
||||
}
|
||||
],
|
||||
"exclusive_account":false,
|
||||
"mobile":"15236176076",
|
||||
"active":true,
|
||||
"admin":true,
|
||||
"avatar":"https://static-legacy.dingtalk.com/media/lALPDeRETW9WAnnNAyDNAyA_800_800.png",
|
||||
"hide_mobile":false,
|
||||
"userid":"manager4713",
|
||||
"senior":false,
|
||||
"dept_order_list":[
|
||||
{
|
||||
"dept_id":1,
|
||||
"order":176294576350761512
|
||||
}
|
||||
],
|
||||
"real_authed":true,
|
||||
"name":"刘继坤",
|
||||
"dept_id_list":[
|
||||
1
|
||||
],
|
||||
"state_code":"86",
|
||||
"email":"",
|
||||
"leader_in_dept":[
|
||||
{
|
||||
"leader":false,
|
||||
"dept_id":1
|
||||
}
|
||||
]
|
||||
},
|
||||
"errmsg":"ok",
|
||||
"request_id":"3sug9d2exsla"
|
||||
}
|
||||
*/
|
||||
|
||||
type DingTalkUserResponse struct {
|
||||
Errcode int `json:"errcode"`
|
||||
Errmsg string `json:"errmsg"`
|
||||
Result struct {
|
||||
Extension string `json:"extension"`
|
||||
Unionid string `json:"unionid"`
|
||||
Boss bool `json:"boss"`
|
||||
UnionEmpExt struct {
|
||||
CorpId string `json:"corpId"`
|
||||
Userid string `json:"userid"`
|
||||
UnionEmpMapList []struct {
|
||||
CorpId string `json:"corpId"`
|
||||
Userid string `json:"userid"`
|
||||
} `json:"unionEmpMapList"`
|
||||
} `json:"unionEmpExt"`
|
||||
RoleList []struct {
|
||||
GroupName string `json:"group_name"`
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
} `json:"role_list"`
|
||||
Admin bool `json:"admin"`
|
||||
Remark string `json:"remark"`
|
||||
Title string `json:"title"`
|
||||
HiredDate int64 `json:"hired_date"`
|
||||
Userid string `json:"userid"`
|
||||
WorkPlace string `json:"work_place"`
|
||||
DeptOrderList []struct {
|
||||
DeptId int `json:"dept_id"`
|
||||
Order int64 `json:"order"`
|
||||
} `json:"dept_order_list"`
|
||||
RealAuthed bool `json:"real_authed"`
|
||||
DeptIdList []int `json:"dept_id_list"`
|
||||
JobNumber string `json:"job_number"`
|
||||
Email string `json:"email"`
|
||||
LeaderInDept []struct {
|
||||
DeptId int `json:"dept_id"`
|
||||
Leader bool `json:"leader"`
|
||||
} `json:"leader_in_dept"`
|
||||
ManagerUserid string `json:"manager_userid"`
|
||||
Mobile string `json:"mobile"`
|
||||
Active bool `json:"active"`
|
||||
Telephone string `json:"telephone"`
|
||||
Avatar string `json:"avatar"`
|
||||
HideMobile bool `json:"hide_mobile"`
|
||||
Senior bool `json:"senior"`
|
||||
Name string `json:"name"`
|
||||
StateCode string `json:"state_code"`
|
||||
} `json:"result"`
|
||||
RequestId string `json:"request_id"`
|
||||
}
|
||||
|
||||
// GetUserInfo Use userid and access_token to get UserInfo
|
||||
// get more detail via: https://developers.dingtalk.com/document/app/query-user-details
|
||||
func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var dtUserInfo DingTalkUserResponse
|
||||
accessToken := token.AccessToken
|
||||
|
||||
u := fmt.Sprintf("https://oapi.dingtalk.com/topapi/v2/user/get?access_token=%s&userid=%s",
|
||||
accessToken, idp.Config.Scopes[1])
|
||||
|
||||
userinfoResp, err := idp.GetUrlResp(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userinfoResp), &dtUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(dtUserInfo.Result.RoleList[0].Id),
|
||||
Username: dtUserInfo.Result.RoleList[0].Name,
|
||||
DisplayName: dtUserInfo.Result.Name,
|
||||
Email: dtUserInfo.Result.Email,
|
||||
AvatarUrl: dtUserInfo.Result.Avatar,
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *DingTalkIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// EncodeSHA256 Use the HmacSHA256 algorithm to sign, the signature data is the current timestamp,
|
||||
// and the key is the appSecret corresponding to the appId. Use this key to calculate the timestamp signature value.
|
||||
// get more detail via: https://developers.dingtalk.com/document/app/signature-calculation-for-logon-free-scenarios-1?spm=ding_open_doc.document.0.0.63262ea7l6iEm1#topic-2021698
|
||||
func EncodeSHA256(message, secret string) string {
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(message))
|
||||
sum := h.Sum(nil)
|
||||
msg1 := base64.StdEncoding.EncodeToString(sum)
|
||||
|
||||
uv := url.Values{}
|
||||
uv.Add("0", msg1)
|
||||
msg2 := uv.Encode()[2:]
|
||||
return msg2
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type FacebookIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewFacebookIdProvider(clientId string, clientSecret string, redirectUrl string) *FacebookIdProvider {
|
||||
idp := &FacebookIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *FacebookIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *FacebookIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://graph.facebook.com/oauth/access_token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"email,public_profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type FacebookAccessToken struct {
|
||||
AccessToken string `json:"access_token"` //Interface call credentials
|
||||
TokenType string `json:"token_type"` //Access token type
|
||||
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
type FacebookCheckToken struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// Get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
|
||||
type FacebookCheckTokenData struct {
|
||||
UserId string `json:"user_id"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#confirm
|
||||
func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("https://graph.facebook.com/oauth/access_token?%s", params.Encode())
|
||||
|
||||
accessTokenResp, err := idp.GetUrlResp(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var facebookAccessToken FacebookAccessToken
|
||||
if err = json.Unmarshal([]byte(accessTokenResp), &facebookAccessToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: facebookAccessToken.AccessToken,
|
||||
TokenType: "FacebookAccessToken",
|
||||
Expiry: time.Time{},
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
//{
|
||||
// "id": "123456789",
|
||||
// "name": "Example Name",
|
||||
// "name_format": "{first} {last}",
|
||||
// "picture": {
|
||||
// "data": {
|
||||
// "height": 50,
|
||||
// "is_silhouette": false,
|
||||
// "url": "https://example.com",
|
||||
// "width": 50
|
||||
// }
|
||||
// },
|
||||
// "email": "test@example.com"
|
||||
//}
|
||||
|
||||
type FacebookUserInfo struct {
|
||||
Id string `json:"id"` // The app user's App-Scoped User ID. This ID is unique to the app and cannot be used by other apps.
|
||||
Name string `json:"name"` // The person's full name.
|
||||
NameFormat string `json:"name_format"` // The person's name formatted to correctly handle Chinese, Japanese, or Korean ordering.
|
||||
Picture struct { // The person's profile picture.
|
||||
Data struct { // This struct is different as https://developers.facebook.com/docs/graph-api/reference/user/picture/
|
||||
Height int `json:"height"`
|
||||
IsSilhouette bool `json:"is_silhouette"`
|
||||
Url string `json:"url"`
|
||||
Width int `json:"width"`
|
||||
} `json:"data"`
|
||||
} `json:"picture"`
|
||||
Email string `json:"email"` // The User's primary email address listed on their profile. This field will not be returned if no valid email address is available.
|
||||
}
|
||||
|
||||
// GetUserInfo use FacebookAccessToken gotten before return FacebookUserInfo
|
||||
// get more detail via: https://developers.facebook.com/docs/graph-api/reference/user
|
||||
func (idp *FacebookIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var facebookUserInfo FacebookUserInfo
|
||||
accessToken := token.AccessToken
|
||||
|
||||
userIdUrl := fmt.Sprintf("https://graph.facebook.com/me?access_token=%s", accessToken)
|
||||
userIdResp, err := idp.GetUrlResp(userIdUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userIdResp), &facebookUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://graph.facebook.com/%s?fields=id,name,name_format,picture,email&access_token=%s", facebookUserInfo.Id, accessToken)
|
||||
userInfoResp, err := idp.GetUrlResp(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userInfoResp), &facebookUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: facebookUserInfo.Id,
|
||||
DisplayName: facebookUserInfo.Name,
|
||||
Email: facebookUserInfo.Email,
|
||||
AvatarUrl: facebookUserInfo.Picture.Data.Url,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *FacebookIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GiteeIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGiteeIdProvider(clientId string, clientSecret string, redirectUrl string) *GiteeIdProvider {
|
||||
idp := &GiteeIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GiteeIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *GiteeIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://gitee.com/oauth/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"user_info emails"},
|
||||
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type GiteeAccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Scope string `json:"scope"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// The POST Url format of submission is: https://gitee.com/oauth/token?grant_type=authorization_code&code={code}&client_id={client_id}&redirect_uri={redirect_uri}&client_secret={client_secret}
|
||||
// get more detail via: https://gitee.com/api/v5/oauth_doc#/
|
||||
func (idp *GiteeIdProvider) 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())
|
||||
bs, _ := json.Marshal(params.Encode())
|
||||
req, _ := http.NewRequest("POST", accessTokenUrl, strings.NewReader(string(bs)))
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rbs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenResp := GiteeAccessToken{}
|
||||
if err = json.Unmarshal(rbs, &tokenResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := &oauth2.Token{
|
||||
AccessToken: tokenResp.AccessToken,
|
||||
TokenType: tokenResp.TokenType,
|
||||
RefreshToken: tokenResp.RefreshToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(tokenResp.ExpiresIn), 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"id": 999999,
|
||||
"login": "xxx",
|
||||
"name": "xxx",
|
||||
"avatar_url": "https://gitee.com/assets/no_portrait.png",
|
||||
"url": "https://gitee.com/api/v5/users/xxx",
|
||||
"html_url": "https://gitee.com/xxx",
|
||||
"followers_url": "https://gitee.com/api/v5/users/xxx/followers",
|
||||
"following_url": "https://gitee.com/api/v5/users/xxx/following_url{/other_user}",
|
||||
"gists_url": "https://gitee.com/api/v5/users/xxx/gists{/gist_id}",
|
||||
"starred_url": "https://gitee.com/api/v5/users/xxx/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://gitee.com/api/v5/users/xxx/subscriptions",
|
||||
"organizations_url": "https://gitee.com/api/v5/users/xxx/orgs",
|
||||
"repos_url": "https://gitee.com/api/v5/users/xxx/repos",
|
||||
"events_url": "https://gitee.com/api/v5/users/xxx/events{/privacy}",
|
||||
"received_events_url": "https://gitee.com/api/v5/users/xxx/received_events",
|
||||
"type": "User",
|
||||
"blog": null,
|
||||
"weibo": null,
|
||||
"bio": "个人博客:https://gitee.com/xxx/xxx/pages",
|
||||
"public_repos": 2,
|
||||
"public_gists": 0,
|
||||
"followers": 0,
|
||||
"following": 0,
|
||||
"stared": 0,
|
||||
"watched": 2,
|
||||
"created_at": "2019-08-03T23:21:16+08:00",
|
||||
"updated_at": "2021-06-14T12:47:09+08:00",
|
||||
"email": null
|
||||
}
|
||||
*/
|
||||
|
||||
type GiteeUserResponse struct {
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
Bio string `json:"bio"`
|
||||
Blog string `json:"blog"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Email string `json:"email"`
|
||||
EventsUrl string `json:"events_url"`
|
||||
Followers int `json:"followers"`
|
||||
FollowersUrl string `json:"followers_url"`
|
||||
Following int `json:"following"`
|
||||
FollowingUrl string `json:"following_url"`
|
||||
GistsUrl string `json:"gists_url"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
Id int `json:"id"`
|
||||
Login string `json:"login"`
|
||||
MemberRole string `json:"member_role"`
|
||||
Name string `json:"name"`
|
||||
OrganizationsUrl string `json:"organizations_url"`
|
||||
PublicGists int `json:"public_gists"`
|
||||
PublicRepos int `json:"public_repos"`
|
||||
ReceivedEventsUrl string `json:"received_events_url"`
|
||||
ReposUrl string `json:"repos_url"`
|
||||
Stared int `json:"stared"`
|
||||
StarredUrl string `json:"starred_url"`
|
||||
SubscriptionsUrl string `json:"subscriptions_url"`
|
||||
Type string `json:"type"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Url string `json:"url"`
|
||||
Watched int `json:"watched"`
|
||||
Weibo string `json:"weibo"`
|
||||
}
|
||||
|
||||
// GetUserInfo Use userid and access_token to get UserInfo
|
||||
// get more detail via: https://gitee.com/api/v5/swagger#/getV5User
|
||||
func (idp *GiteeIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var gtUserInfo GiteeUserResponse
|
||||
accessToken := token.AccessToken
|
||||
|
||||
u := fmt.Sprintf("https://gitee.com/api/v5/user?access_token=%s",
|
||||
accessToken)
|
||||
|
||||
userinfoResp, err := idp.GetUrlResp(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(userinfoResp), >UserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(gtUserInfo.Id),
|
||||
Username: gtUserInfo.Name,
|
||||
DisplayName: gtUserInfo.Name,
|
||||
Email: gtUserInfo.Email,
|
||||
AvatarUrl: gtUserInfo.AvatarUrl,
|
||||
}
|
||||
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *GiteeIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GithubIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGithubIdProvider(clientId string, clientSecret string, redirectUrl string) *GithubIdProvider {
|
||||
idp := &GithubIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://github.com/login/oauth/authorize",
|
||||
TokenURL: "https://github.com/login/oauth/access_token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"user:email", "read:user"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
ctx := context.WithValue(oauth2.NoContext, oauth2.HTTPClient, idp.Client)
|
||||
return idp.Config.Exchange(ctx, code)
|
||||
}
|
||||
|
||||
//{
|
||||
// "login": "jimgreen",
|
||||
// "id": 3781234,
|
||||
// "node_id": "MDQ6VXNlcjM3O123456=",
|
||||
// "avatar_url": "https://avatars.githubusercontent.com/u/3781234?v=4",
|
||||
// "gravatar_id": "",
|
||||
// "url": "https://api.github.com/users/jimgreen",
|
||||
// "html_url": "https://github.com/jimgreen",
|
||||
// "followers_url": "https://api.github.com/users/jimgreen/followers",
|
||||
// "following_url": "https://api.github.com/users/jimgreen/following{/other_user}",
|
||||
// "gists_url": "https://api.github.com/users/jimgreen/gists{/gist_id}",
|
||||
// "starred_url": "https://api.github.com/users/jimgreen/starred{/owner}{/repo}",
|
||||
// "subscriptions_url": "https://api.github.com/users/jimgreen/subscriptions",
|
||||
// "organizations_url": "https://api.github.com/users/jimgreen/orgs",
|
||||
// "repos_url": "https://api.github.com/users/jimgreen/repos",
|
||||
// "events_url": "https://api.github.com/users/jimgreen/events{/privacy}",
|
||||
// "received_events_url": "https://api.github.com/users/jimgreen/received_events",
|
||||
// "type": "User",
|
||||
// "site_admin": false,
|
||||
// "name": "Jim Green",
|
||||
// "company": "Casbin",
|
||||
// "blog": "https://casbin.org",
|
||||
// "location": "Bay Area",
|
||||
// "email": "jimgreen@gmail.com",
|
||||
// "hireable": true,
|
||||
// "bio": "My bio",
|
||||
// "twitter_username": null,
|
||||
// "public_repos": 45,
|
||||
// "public_gists": 3,
|
||||
// "followers": 123,
|
||||
// "following": 31,
|
||||
// "created_at": "2016-03-06T13:16:13Z",
|
||||
// "updated_at": "2020-05-30T12:15:29Z",
|
||||
// "private_gists": 0,
|
||||
// "total_private_repos": 12,
|
||||
// "owned_private_repos": 12,
|
||||
// "disk_usage": 46331,
|
||||
// "collaborators": 5,
|
||||
// "two_factor_authentication": true,
|
||||
// "plan": {
|
||||
// "name": "free",
|
||||
// "space": 976562499,
|
||||
// "collaborators": 0,
|
||||
// "private_repos": 10000
|
||||
// }
|
||||
//}
|
||||
|
||||
type GitHubUserInfo struct {
|
||||
Login string `json:"login"`
|
||||
Id int `json:"id"`
|
||||
NodeId string `json:"node_id"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
GravatarId string `json:"gravatar_id"`
|
||||
Url string `json:"url"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
FollowersUrl string `json:"followers_url"`
|
||||
FollowingUrl string `json:"following_url"`
|
||||
GistsUrl string `json:"gists_url"`
|
||||
StarredUrl string `json:"starred_url"`
|
||||
SubscriptionsUrl string `json:"subscriptions_url"`
|
||||
OrganizationsUrl string `json:"organizations_url"`
|
||||
ReposUrl string `json:"repos_url"`
|
||||
EventsUrl string `json:"events_url"`
|
||||
ReceivedEventsUrl string `json:"received_events_url"`
|
||||
Type string `json:"type"`
|
||||
SiteAdmin bool `json:"site_admin"`
|
||||
Name string `json:"name"`
|
||||
Company string `json:"company"`
|
||||
Blog string `json:"blog"`
|
||||
Location string `json:"location"`
|
||||
Email string `json:"email"`
|
||||
Hireable bool `json:"hireable"`
|
||||
Bio string `json:"bio"`
|
||||
TwitterUsername interface{} `json:"twitter_username"`
|
||||
PublicRepos int `json:"public_repos"`
|
||||
PublicGists int `json:"public_gists"`
|
||||
Followers int `json:"followers"`
|
||||
Following int `json:"following"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PrivateGists int `json:"private_gists"`
|
||||
TotalPrivateRepos int `json:"total_private_repos"`
|
||||
OwnedPrivateRepos int `json:"owned_private_repos"`
|
||||
DiskUsage int `json:"disk_usage"`
|
||||
Collaborators int `json:"collaborators"`
|
||||
TwoFactorAuthentication bool `json:"two_factor_authentication"`
|
||||
Plan struct {
|
||||
Name string `json:"name"`
|
||||
Space int `json:"space"`
|
||||
Collaborators int `json:"collaborators"`
|
||||
PrivateRepos int `json:"private_repos"`
|
||||
} `json:"plan"`
|
||||
}
|
||||
|
||||
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
req.Header.Add("Authorization", "token "+token.AccessToken)
|
||||
resp, err := idp.Client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var githubUserInfo GitHubUserInfo
|
||||
err = json.Unmarshal(body, &githubUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(githubUserInfo.Id),
|
||||
Username: githubUserInfo.Login,
|
||||
DisplayName: githubUserInfo.Name,
|
||||
Email: githubUserInfo.Email,
|
||||
AvatarUrl: githubUserInfo.AvatarUrl,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// 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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GoogleIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGoogleIdProvider(clientId string, clientSecret string, redirectUrl string) *GoogleIdProvider {
|
||||
idp := &GoogleIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"profile", "email"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
ctx := context.WithValue(oauth2.NoContext, oauth2.HTTPClient, idp.Client)
|
||||
return idp.Config.Exchange(ctx, code)
|
||||
}
|
||||
|
||||
//{
|
||||
// "id": "110613473084924141234",
|
||||
// "email": "jimgreen@gmail.com",
|
||||
// "verified_email": true,
|
||||
// "name": "Jim Green",
|
||||
// "given_name": "Jim",
|
||||
// "family_name": "Green",
|
||||
// "picture": "https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg",
|
||||
// "locale": "en"
|
||||
//}
|
||||
|
||||
type GoogleUserInfo struct {
|
||||
Id string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
VerifiedEmail bool `json:"verified_email"`
|
||||
Name string `json:"name"`
|
||||
GivenName string `json:"given_name"`
|
||||
FamilyName string `json:"family_name"`
|
||||
Picture string `json:"picture"`
|
||||
Locale string `json:"locale"`
|
||||
}
|
||||
|
||||
func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
url := fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=%s", token.AccessToken)
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var googleUserInfo GoogleUserInfo
|
||||
err = json.Unmarshal(body, &googleUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if googleUserInfo.Email == "" {
|
||||
return nil, errors.New("google email is empty")
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: googleUserInfo.Id,
|
||||
Username: googleUserInfo.Email,
|
||||
DisplayName: googleUserInfo.Name,
|
||||
Email: googleUserInfo.Email,
|
||||
AvatarUrl: googleUserInfo.Picture,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
// 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"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type LinkedInIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewLinkedInIdProvider(clientId string, clientSecret string, redirectUrl string) *LinkedInIdProvider {
|
||||
idp := &LinkedInIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *LinkedInIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *LinkedInIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://www.linkedIn.com/oauth/v2/accessToken",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"email,public_profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type LinkedInAccessToken struct {
|
||||
AccessToken string `json:"access_token"` //Interface call credentials
|
||||
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
|
||||
}
|
||||
|
||||
// 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 *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
bs, _ := json.Marshal(params.Encode())
|
||||
req, _ := http.NewRequest("POST", accessTokenUrl, strings.NewReader(string(bs)))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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,
|
||||
TokenType: "Bearer",
|
||||
Expiry: time.Unix(time.Now().Unix()+tokenResp.ExpiresIn, 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"firstName": {
|
||||
"localized": {
|
||||
"zh_CN": "继坤"
|
||||
},
|
||||
"preferredLocale": {
|
||||
"country": "CN",
|
||||
"language": "zh"
|
||||
}
|
||||
},
|
||||
"lastName": {
|
||||
"localized": {
|
||||
"zh_CN": "刘"
|
||||
},
|
||||
"preferredLocale": {
|
||||
"country": "CN",
|
||||
"language": "zh"
|
||||
}
|
||||
},
|
||||
"profilePicture": {
|
||||
"displayImage": "urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg",
|
||||
"displayImage~": {
|
||||
"paging": {
|
||||
"count": 10,
|
||||
"start": 0,
|
||||
"links": []
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"artifact": "urn:li:digitalmediaMediaArtifact:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100)",
|
||||
"authorizationMethod": "PUBLIC",
|
||||
"data": {
|
||||
"com.linkedin.digitalmedia.mediaartifact.StillImage": {
|
||||
"mediaType": "image/jpeg",
|
||||
"rawCodecSpec": {
|
||||
"name": "jpeg",
|
||||
"type": "image"
|
||||
},
|
||||
"displaySize": {
|
||||
"width": 100.0,
|
||||
"uom": "PX",
|
||||
"height": 100.0
|
||||
},
|
||||
"storageSize": {
|
||||
"width": 100,
|
||||
"height": 100
|
||||
},
|
||||
"storageAspectRatio": {
|
||||
"widthAspect": 1.0,
|
||||
"heightAspect": 1.0,
|
||||
"formatted": "1.00:1.00"
|
||||
},
|
||||
"displayAspectRatio": {
|
||||
"widthAspect": 1.0,
|
||||
"heightAspect": 1.0,
|
||||
"formatted": "1.00:1.00"
|
||||
}
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"identifier": "https://media.licdn.cn/dms/image/C5603AQHbdR8RkG62yg/profile-displayphoto-shrink_100_100/0/1625279434135?e=1630540800&v=beta&t=Z-bQKf_jFv8L1uwr6X5AJLoTQRWZrueT7qrITDSvxWM",
|
||||
"index": 0,
|
||||
"mediaType": "image/jpeg",
|
||||
"file": "urn:li:digitalmediaFile:(urn:li:digitalmediaAsset:C5603AQHbdR8RkG62yg,urn:li:digitalmediaMediaArtifactClass:profile-displayphoto-shrink_100_100,0)",
|
||||
"identifierType": "EXTERNAL_URL",
|
||||
"identifierExpiresInSeconds": 1630540800
|
||||
}
|
||||
]
|
||||
},
|
||||
// ...
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"id": "vvMfLsLIRs"
|
||||
}
|
||||
*/
|
||||
|
||||
type LinkedInUserInfo struct {
|
||||
FirstName struct {
|
||||
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"`
|
||||
PreferredLocale struct {
|
||||
Country string `json:"country"`
|
||||
Language string `json:"language"`
|
||||
} `json:"preferredLocale"`
|
||||
} `json:"lastName"`
|
||||
ProfilePicture struct {
|
||||
DisplayImage string `json:"displayImage"`
|
||||
DisplayImage1 struct {
|
||||
Paging struct {
|
||||
Count int `json:"count"`
|
||||
Start int `json:"start"`
|
||||
Links []interface{} `json:"links"`
|
||||
} `json:"paging"`
|
||||
Elements []struct {
|
||||
Artifact string `json:"artifact"`
|
||||
AuthorizationMethod string `json:"authorizationMethod"`
|
||||
Data struct {
|
||||
ComLinkedinDigitalmediaMediaartifactStillImage struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
RawCodecSpec struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
} `json:"rawCodecSpec"`
|
||||
DisplaySize struct {
|
||||
Width float64 `json:"width"`
|
||||
Uom string `json:"uom"`
|
||||
Height float64 `json:"height"`
|
||||
} `json:"displaySize"`
|
||||
StorageSize struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
} `json:"storageSize"`
|
||||
StorageAspectRatio struct {
|
||||
WidthAspect float64 `json:"widthAspect"`
|
||||
HeightAspect float64 `json:"heightAspect"`
|
||||
Formatted string `json:"formatted"`
|
||||
} `json:"storageAspectRatio"`
|
||||
DisplayAspectRatio struct {
|
||||
WidthAspect float64 `json:"widthAspect"`
|
||||
HeightAspect float64 `json:"heightAspect"`
|
||||
Formatted string `json:"formatted"`
|
||||
} `json:"displayAspectRatio"`
|
||||
} `json:"com.linkedin.digitalmedia.mediaartifact.StillImage"`
|
||||
} `json:"data"`
|
||||
Identifiers []struct {
|
||||
Identifier string `json:"identifier"`
|
||||
Index int `json:"index"`
|
||||
MediaType string `json:"mediaType"`
|
||||
File string `json:"file"`
|
||||
IdentifierType string `json:"identifierType"`
|
||||
IdentifierExpiresInSeconds int `json:"identifierExpiresInSeconds"`
|
||||
} `json:"identifiers"`
|
||||
} `json:"elements"`
|
||||
} `json:"displayImage~"`
|
||||
} `json:"profilePicture"`
|
||||
Id string `json:"id"`
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"handle": "urn:li:emailAddress:3775708763",
|
||||
"handle~": {
|
||||
"emailAddress": "hsimpson@linkedin.com"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
type LinkedInUserEmail struct {
|
||||
Elements []struct {
|
||||
Handle struct {
|
||||
EmailAddress string `json:"emailAddress"`
|
||||
} `json:"handle~"`
|
||||
Handle1 string `json:"handle"`
|
||||
} `json:"elements"`
|
||||
}
|
||||
|
||||
// GetUserInfo use LinkedInAccessToken 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 *LinkedInIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var linkedInUserInfo LinkedInUserInfo
|
||||
bs, err := idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))", token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bs, &linkedInUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var linkedInUserEmail LinkedInUserEmail
|
||||
bs, err = idp.GetUrlRespWithAuthorization("https://api.linkedIn.com/v2/emailAddress?q=members&projection=(elements*(handle~))", token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal(bs, &linkedInUserEmail); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username := ""
|
||||
for _, name := range linkedInUserInfo.FirstName.Localized {
|
||||
username += name
|
||||
}
|
||||
for _, name := range linkedInUserInfo.LastName.Localized {
|
||||
username += name
|
||||
}
|
||||
userInfo := UserInfo{
|
||||
Id: linkedInUserInfo.Id,
|
||||
DisplayName: username,
|
||||
Username: username,
|
||||
Email: linkedInUserEmail.Elements[0].Handle.EmailAddress,
|
||||
AvatarUrl: linkedInUserInfo.ProfilePicture.DisplayImage1.Elements[0].Identifiers[0].Identifier,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([]byte, error) {
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs, nil
|
||||
}
|
|
@ -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 idp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type UserInfo struct {
|
||||
Id string
|
||||
Username string
|
||||
DisplayName string
|
||||
Email string
|
||||
AvatarUrl string
|
||||
}
|
||||
|
||||
type IdProvider interface {
|
||||
SetHttpClient(client *http.Client)
|
||||
GetToken(code string) (*oauth2.Token, error)
|
||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
}
|
||||
|
||||
func GetIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string) IdProvider {
|
||||
if providerType == "GitHub" {
|
||||
return NewGithubIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Google" {
|
||||
return NewGoogleIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "QQ" {
|
||||
return NewQqIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "WeChat" {
|
||||
return NewWeChatIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Facebook" {
|
||||
return NewFacebookIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "DingTalk" {
|
||||
return NewDingTalkIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Weibo" {
|
||||
return NewWeiBoIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Gitee" {
|
||||
return NewGiteeIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "LinkedIn" {
|
||||
return NewLinkedInIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
// 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type QqIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewQqIdProvider(clientId string, clientSecret string, redirectUrl string) *QqIdProvider {
|
||||
idp := &QqIdProvider{}
|
||||
|
||||
config := idp.getConfig()
|
||||
config.ClientID = clientId
|
||||
config.ClientSecret = clientSecret
|
||||
config.RedirectURL = redirectUrl
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) getConfig() *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://graph.qq.com/oauth2.0/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"get_user_info"},
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) 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("https://graph.qq.com/oauth2.0/token?%s", params.Encode())
|
||||
resp, err := idp.Client.Get(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
tokenContent, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
re := regexp.MustCompile("token=(.*?)&")
|
||||
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
|
||||
accessToken := matched[0][1]
|
||||
token := &oauth2.Token{
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
//{
|
||||
// "ret": 0,
|
||||
// "msg": "",
|
||||
// "is_lost": 0,
|
||||
// "nickname": "飞翔的企鹅",
|
||||
// "gender": "男",
|
||||
// "gender_type": 1,
|
||||
// "province": "",
|
||||
// "city": "安道尔城",
|
||||
// "year": "1968",
|
||||
// "constellation": "",
|
||||
// "figureurl": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/30",
|
||||
// "figureurl_1": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/50",
|
||||
// "figureurl_2": "http:\/\/qzapp.qlogo.cn\/qzapp\/101896710\/C0D022F92B604AA4B1CDF92CC79463A4\/100",
|
||||
// "figureurl_qq_1": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=40&t=1557635654",
|
||||
// "figureurl_qq_2": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=100&t=1557635654",
|
||||
// "figureurl_qq": "http://thirdqq.qlogo.cn/g?b=oidb&k=QtAu5OiaSfqGD0kfclwvxJA&s=640&t=1557635654",
|
||||
// "figureurl_type": "1",
|
||||
// "is_yellow_vip": "0",
|
||||
// "vip": "0",
|
||||
// "yellow_vip_level": "0",
|
||||
// "level": "0",
|
||||
// "is_yellow_year_vip": "0"
|
||||
//}
|
||||
|
||||
type QqUserInfo struct {
|
||||
Ret int `json:"ret"`
|
||||
Msg string `json:"msg"`
|
||||
IsLost int `json:"is_lost"`
|
||||
Nickname string `json:"nickname"`
|
||||
Gender string `json:"gender"`
|
||||
GenderType int `json:"gender_type"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
Year string `json:"year"`
|
||||
Constellation string `json:"constellation"`
|
||||
Figureurl string `json:"figureurl"`
|
||||
Figureurl1 string `json:"figureurl_1"`
|
||||
Figureurl2 string `json:"figureurl_2"`
|
||||
FigureurlQq1 string `json:"figureurl_qq_1"`
|
||||
FigureurlQq2 string `json:"figureurl_qq_2"`
|
||||
FigureurlQq string `json:"figureurl_qq"`
|
||||
FigureurlType string `json:"figureurl_type"`
|
||||
IsYellowVip string `json:"is_yellow_vip"`
|
||||
Vip string `json:"vip"`
|
||||
YellowVipLevel string `json:"yellow_vip_level"`
|
||||
Level string `json:"level"`
|
||||
IsYellowYearVip string `json:"is_yellow_year_vip"`
|
||||
}
|
||||
|
||||
func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
openIdUrl := fmt.Sprintf("https://graph.qq.com/oauth2.0/me?access_token=%s", token.AccessToken)
|
||||
resp, err := idp.Client.Get(openIdUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
openIdBody, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
|
||||
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
|
||||
openId := matched[0][1]
|
||||
if openId == "" {
|
||||
return nil, errors.New("openId is empty")
|
||||
}
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s", token.AccessToken, idp.Config.ClientID, openId)
|
||||
resp, err = idp.Client.Get(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
userInfoBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var qqUserInfo QqUserInfo
|
||||
err = json.Unmarshal(userInfoBody, &qqUserInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if qqUserInfo.Ret != 0 {
|
||||
return nil, errors.New(fmt.Sprintf("ret expected 0, got %d", qqUserInfo.Ret))
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: openId,
|
||||
DisplayName: qqUserInfo.Nickname,
|
||||
AvatarUrl: qqUserInfo.FigureurlQq1,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type WeChatIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeChatIdProvider(clientId string, clientSecret string, redirectUrl string) *WeChatIdProvider {
|
||||
idp := &WeChatIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeChatIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *WeChatIdProvider) 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 WechatAccessToken struct {
|
||||
AccessToken string `json:"access_token"` //Interface call credentials
|
||||
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
|
||||
RefreshToken string `json:"refresh_token"` //User refresh access_token
|
||||
Openid string `json:"openid"` //Unique ID of authorized user
|
||||
Scope string `json:"scope"` //The scope of user authorization, separated by commas. (,)
|
||||
Unionid string `json:"unionid"` //This field will appear if and only if the website application has been authorized by the user's UserInfo.
|
||||
}
|
||||
|
||||
// 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 *WeChatIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("appid", idp.Config.ClientID)
|
||||
params.Add("secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?%s", params.Encode())
|
||||
tokenResponse, err := idp.Client.Get(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(tokenResponse.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(tokenResponse.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wechatAccessToken WechatAccessToken
|
||||
if err = json.Unmarshal([]byte(buf.String()), &wechatAccessToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: wechatAccessToken.AccessToken,
|
||||
TokenType: "WeChatAccessToken",
|
||||
RefreshToken: wechatAccessToken.RefreshToken,
|
||||
Expiry: time.Time{},
|
||||
}
|
||||
|
||||
raw := make(map[string]string)
|
||||
raw["Openid"] = wechatAccessToken.Openid
|
||||
token.WithExtra(raw)
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
//{
|
||||
// "openid": "of_Hl5zVpyj0vwzIlAyIlnXe1234",
|
||||
// "nickname": "飞翔的企鹅",
|
||||
// "sex": 1,
|
||||
// "language": "zh_CN",
|
||||
// "city": "Shanghai",
|
||||
// "province": "Shanghai",
|
||||
// "country": "CN",
|
||||
// "headimgurl": "https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/Q0j4TwGTfTK6xc7vGca4KtibJib5dslRianc9VHt9k2N7fewYOl8fak7grRM7nS5V6HcvkkIkGThWUXPjDbXkQFYA\/132",
|
||||
// "privilege": [],
|
||||
// "unionid": "oxW9O1VAL8x-zfWP2hrqW9c81234"
|
||||
//}
|
||||
|
||||
type WechatUserInfo struct {
|
||||
Openid string `json:"openid"` // The ID of an ordinary user, which is unique to the current developer account
|
||||
Nickname string `json:"nickname"` // Ordinary user nickname
|
||||
Sex int `json:"sex"` // Ordinary user gender, 1 is male, 2 is female
|
||||
Language string `json:"language"`
|
||||
City string `json:"city"` // City filled in by general user's personal data
|
||||
Province string `json:"province"` // Province filled in by ordinary user's personal information
|
||||
Country string `json:"country"` // Country, such as China is CN
|
||||
Headimgurl string `json:"headimgurl"` // User avatar, the last value represents the size of the square avatar (there are optional values of 0, 46, 64, 96, 132, 0 represents a 640*640 square avatar), this item is empty when the user does not have a avatar
|
||||
Privilege []string `json:"privilege"` // User Privilege information, json array, such as Wechat Woka user (chinaunicom)
|
||||
Unionid string `json:"unionid"` // Unified user identification. For an application under a WeChat open platform account, the unionid of the same user is unique.
|
||||
}
|
||||
|
||||
// GetUserInfo use WechatAccessToken gotten before return WechatUserInfo
|
||||
// get more detail via: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
|
||||
func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var wechatUserInfo WechatUserInfo
|
||||
accessToken := token.AccessToken
|
||||
openid := token.Extra("Openid")
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", accessToken, openid)
|
||||
resp, err := idp.Client.Get(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal([]byte(buf.String()), &wechatUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := wechatUserInfo.Unionid
|
||||
if id == "" {
|
||||
id = wechatUserInfo.Openid
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: id,
|
||||
DisplayName: wechatUserInfo.Nickname,
|
||||
AvatarUrl: wechatUserInfo.Headimgurl,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
// 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 (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type WeiBoIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewWeiBoIdProvider(clientId string, clientSecret string, redirectUrl string) *WeiBoIdProvider {
|
||||
idp := &WeiBoIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *WeiBoIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *WeiBoIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://api.weibo.com/oauth2/access_token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{""},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type WeiboAccessToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RemindIn string `json:"remind_in"` // This parameter is about to be obsolete, developers please use expires_in
|
||||
Uid string `json:"uid"`
|
||||
}
|
||||
|
||||
// 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 *WeiBoIdProvider) 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.PostForm(idp.Config.Endpoint.TokenURL, params)
|
||||
// resp, err := idp.GetUrlResp(accessTokenUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
bs, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var weiboAccessToken WeiboAccessToken
|
||||
if err = json.Unmarshal(bs, &weiboAccessToken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: weiboAccessToken.AccessToken,
|
||||
TokenType: "WeiboAccessToken",
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(weiboAccessToken.ExpiresIn), 0),
|
||||
}
|
||||
|
||||
idp.Config.Scopes[0] = weiboAccessToken.Uid
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"id": 1404376560,
|
||||
"screen_name": "zaku",
|
||||
"name": "zaku",
|
||||
"province": "11",
|
||||
"city": "5",
|
||||
"location": "北京 朝阳区",
|
||||
"description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
|
||||
"url": "http://blog.sina.com.cn/zaku",
|
||||
"profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
|
||||
"domain": "zaku",
|
||||
"gender": "m",
|
||||
"followers_count": 1204,
|
||||
"friends_count": 447,
|
||||
"statuses_count": 2908,
|
||||
"favourites_count": 0,
|
||||
"created_at": "Fri Aug 28 00:00:00 +0800 2009",
|
||||
"following": false,
|
||||
"allow_all_act_msg": false,
|
||||
"geo_enabled": true,
|
||||
"verified": false,
|
||||
"status": {
|
||||
"created_at": "Tue May 24 18:04:53 +0800 2011",
|
||||
"id": 11142488790,
|
||||
"text": "我的相机到了。",
|
||||
"source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>",
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"in_reply_to_status_id": "",
|
||||
"in_reply_to_user_id": "",
|
||||
"in_reply_to_screen_name": "",
|
||||
"geo": null,
|
||||
"mid": "5610221544300749636",
|
||||
"annotations": [],
|
||||
"reposts_count": 5,
|
||||
"comments_count": 8
|
||||
},
|
||||
"allow_all_comment": true,
|
||||
"avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
|
||||
"verified_reason": "",
|
||||
"follow_me": false,
|
||||
"online_status": 0,
|
||||
"bi_followers_count": 215
|
||||
}
|
||||
*/
|
||||
|
||||
type WeiboUserinfo struct {
|
||||
Id int `json:"id"`
|
||||
ScreenName string `json:"screen_name"`
|
||||
Name string `json:"name"`
|
||||
Province string `json:"province"`
|
||||
City string `json:"city"`
|
||||
Location string `json:"location"`
|
||||
Description string `json:"description"`
|
||||
Url string `json:"url"`
|
||||
ProfileImageUrl string `json:"profile_image_url"`
|
||||
Domain string `json:"domain"`
|
||||
Gender string `json:"gender"`
|
||||
FollowersCount int `json:"followers_count"`
|
||||
FriendsCount int `json:"friends_count"`
|
||||
StatusesCount int `json:"statuses_count"`
|
||||
FavouritesCount int `json:"favourites_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Following bool `json:"following"`
|
||||
AllowAllActMsg bool `json:"allow_all_act_msg"`
|
||||
GeoEnabled bool `json:"geo_enabled"`
|
||||
Verified bool `json:"verified"`
|
||||
Status struct {
|
||||
CreatedAt string `json:"created_at"`
|
||||
Id int64 `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Source string `json:"source"`
|
||||
Favorited bool `json:"favorited"`
|
||||
Truncated bool `json:"truncated"`
|
||||
InReplyToStatusId string `json:"in_reply_to_status_id"`
|
||||
InReplyToUserId string `json:"in_reply_to_user_id"`
|
||||
InReplyToScreenName string `json:"in_reply_to_screen_name"`
|
||||
Geo interface{} `json:"geo"`
|
||||
Mid string `json:"mid"`
|
||||
Annotations []interface{} `json:"annotations"`
|
||||
RepostsCount int `json:"reposts_count"`
|
||||
CommentsCount int `json:"comments_count"`
|
||||
} `json:"status"`
|
||||
AllowAllComment bool `json:"allow_all_comment"`
|
||||
AvatarLarge string `json:"avatar_large"`
|
||||
VerifiedReason string `json:"verified_reason"`
|
||||
FollowMe bool `json:"follow_me"`
|
||||
OnlineStatus int `json:"online_status"`
|
||||
BiFollowersCount int `json:"bi_followers_count"`
|
||||
}
|
||||
|
||||
// GetUserInfo use WeiboAccessToken gotten before return UserInfo
|
||||
// get more detail via: https://open.weibo.com/wiki/2/users/show
|
||||
func (idp *WeiBoIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
var weiboUserInfo WeiboUserinfo
|
||||
accessToken := token.AccessToken
|
||||
uid := idp.Config.Scopes[0]
|
||||
id, _ := strconv.Atoi(uid)
|
||||
|
||||
userInfoUrl := fmt.Sprintf("https://api.weibo.com/2/users/show.json?access_token=%s&uid=%d", accessToken, id)
|
||||
resp, err := idp.GetUrlResp(userInfoUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal([]byte(resp), &weiboUserInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// weibo user email need to get separately through this url, need user authorization.
|
||||
e := struct {
|
||||
Email string `json:"email"`
|
||||
}{}
|
||||
emailUrl := fmt.Sprintf("https://api.weibo.com/2/account/profile/email.json?access_token=%s", accessToken)
|
||||
resp, err = idp.GetUrlResp(emailUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = json.Unmarshal([]byte(resp), &e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(weiboUserInfo.Id),
|
||||
Username: weiboUserInfo.Name,
|
||||
DisplayName: weiboUserInfo.Name,
|
||||
AvatarUrl: weiboUserInfo.AvatarLarge,
|
||||
Email: e.Email,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
||||
|
||||
func (idp *WeiBoIdProvider) GetUrlResp(url string) (string, error) {
|
||||
resp, err := idp.Client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// 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 main
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/astaxie/beego/logs"
|
||||
"github.com/astaxie/beego/plugins/cors"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
|
||||
_ "github.com/casdoor/casdoor/routers"
|
||||
)
|
||||
|
||||
func main() {
|
||||
object.InitAdapter()
|
||||
object.InitDb()
|
||||
controllers.InitHttpClient()
|
||||
authz.InitAuthz()
|
||||
|
||||
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
|
||||
AllowOrigins: []string{"*"},
|
||||
AllowMethods: []string{"GET", "PUT", "PATCH"},
|
||||
AllowHeaders: []string{"Origin"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowCredentials: true,
|
||||
}))
|
||||
|
||||
//beego.DelStaticPath("/static")
|
||||
beego.SetStaticPath("/static", "web/build/static")
|
||||
beego.BConfig.WebConfig.DirectoryIndex = true
|
||||
beego.SetStaticPath("/swagger", "swagger")
|
||||
// https://studygolang.com/articles/2303
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||
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"
|
||||
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}`)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
logs.SetLevel(logs.LevelInformational)
|
||||
logs.SetLogFuncCall(false)
|
||||
|
||||
beego.Run()
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
// 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"
|
||||
"runtime"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var adapter *Adapter
|
||||
|
||||
func InitConfig() {
|
||||
err := beego.LoadAppConfig("ini", "../conf/app.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
InitAdapter()
|
||||
}
|
||||
|
||||
func InitAdapter() {
|
||||
adapter = NewAdapter(beego.AppConfig.String("driverName"), beego.AppConfig.String("dataSourceName"), beego.AppConfig.String("dbName"))
|
||||
adapter.createTable()
|
||||
}
|
||||
|
||||
// Adapter represents the MySQL adapter for policy storage.
|
||||
type Adapter struct {
|
||||
driverName string
|
||||
dataSourceName string
|
||||
dbName string
|
||||
Engine *xorm.Engine
|
||||
}
|
||||
|
||||
// finalizer is the destructor for Adapter.
|
||||
func finalizer(a *Adapter) {
|
||||
err := a.Engine.Close()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewAdapter is the constructor for Adapter.
|
||||
func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapter {
|
||||
a := &Adapter{}
|
||||
a.driverName = driverName
|
||||
a.dataSourceName = dataSourceName
|
||||
a.dbName = dbName
|
||||
|
||||
// Open the DB, create it if not existed.
|
||||
a.open()
|
||||
|
||||
// Call the destructor when the object is released.
|
||||
runtime.SetFinalizer(a, finalizer)
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Adapter) createDatabase() error {
|
||||
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer engine.Close()
|
||||
|
||||
_, err = engine.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s default charset utf8 COLLATE utf8_general_ci", a.dbName))
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Adapter) open() {
|
||||
if a.driverName != "postgres" {
|
||||
if err := a.createDatabase(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+a.dbName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
a.Engine = engine
|
||||
}
|
||||
|
||||
func (a *Adapter) close() {
|
||||
a.Engine.Close()
|
||||
a.Engine = nil
|
||||
}
|
||||
|
||||
func (a *Adapter) createTable() {
|
||||
err := a.Engine.Sync2(new(Organization))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(User))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Provider))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Token))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(VerificationRecord))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = a.Engine.Sync2(new(Records))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Ldap))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
// 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/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
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"`
|
||||
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 {
|
||||
applications := []*Application{}
|
||||
err := adapter.Engine.Desc("created_time").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{}
|
||||
for _, provider := range providers {
|
||||
//if provider.Category != "OAuth" {
|
||||
// continue
|
||||
//}
|
||||
|
||||
m[provider.Name] = getMaskedProvider(provider)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func extendApplicationWithProviders(application *Application) {
|
||||
m := getProviderMap(application.Owner)
|
||||
for _, providerItem := range application.Providers {
|
||||
if provider, ok := m[providerItem.Name]; ok {
|
||||
providerItem.Provider = provider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extendApplicationWithOrg(application *Application) {
|
||||
organization := getOrganization(application.Owner, application.Organization)
|
||||
application.OrganizationObj = organization
|
||||
}
|
||||
|
||||
func getApplication(owner string, name string) *Application {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
application := Application{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
extendApplicationWithProviders(&application)
|
||||
extendApplicationWithOrg(&application)
|
||||
return &application
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByOrganizationName(organization string) *Application {
|
||||
application := Application{}
|
||||
existed, err := adapter.Engine.Where("organization=?", organization).Get(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
extendApplicationWithProviders(&application)
|
||||
extendApplicationWithOrg(&application)
|
||||
return &application
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByUser(user *User) *Application {
|
||||
if user.SignupApplication != "" {
|
||||
return getApplication("admin", user.SignupApplication)
|
||||
} else {
|
||||
return GetApplicationByOrganizationName(user.Owner)
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByClientId(clientId string) *Application {
|
||||
application := Application{}
|
||||
existed, err := adapter.Engine.Where("client_id=?", clientId).Get(&application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
extendApplicationWithProviders(&application)
|
||||
extendApplicationWithOrg(&application)
|
||||
return &application
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplication(id string) *Application {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getApplication(owner, name)
|
||||
}
|
||||
|
||||
func UpdateApplication(id string, application *Application) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getApplication(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddApplication(application *Application) bool {
|
||||
application.ClientId = util.GenerateClientId()
|
||||
application.ClientSecret = util.GenerateClientSecret()
|
||||
for _, providerItem := range application.Providers {
|
||||
providerItem.Provider = nil
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(application)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteApplication(application *Application) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{application.Owner, application.Name}).Delete(&Application{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
// 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
|
||||
|
||||
func (application *Application) getProviderByCategory(category string) *Provider {
|
||||
providers := GetProviders(application.Owner)
|
||||
m := map[string]*Provider{}
|
||||
for _, provider := range providers {
|
||||
if provider.Category != category {
|
||||
continue
|
||||
}
|
||||
|
||||
m[provider.Name] = provider
|
||||
}
|
||||
|
||||
for _, providerItem := range application.Providers {
|
||||
if provider, ok := m[providerItem.Name]; ok {
|
||||
return provider
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (application *Application) GetEmailProvider() *Provider {
|
||||
return application.getProviderByCategory("Email")
|
||||
}
|
||||
|
||||
func (application *Application) GetSmsProvider() *Provider {
|
||||
return application.getProviderByCategory("SMS")
|
||||
}
|
||||
|
||||
func (application *Application) getSignupItem(itemName string) *SignupItem {
|
||||
for _, signupItem := range application.SignupItems {
|
||||
if signupItem.Name == itemName {
|
||||
return 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 {
|
||||
return false
|
||||
}
|
||||
|
||||
return signupItem.Visible
|
||||
}
|
||||
|
||||
func (application *Application) GetSignupItemRule(itemName string) string {
|
||||
signupItem := application.getSignupItem(itemName)
|
||||
if signupItem == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return signupItem.Rule
|
||||
}
|
||||
|
||||
func (application *Application) getAllPromptedProviderItems() []*ProviderItem {
|
||||
res := []*ProviderItem{}
|
||||
for _, providerItem := range application.Providers {
|
||||
if providerItem.isProviderPrompted() {
|
||||
res = append(res, providerItem)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (application *Application) isAffiliationPrompted() bool {
|
||||
signupItem := application.getSignupItem("Affiliation")
|
||||
if signupItem == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return signupItem.Prompted
|
||||
}
|
||||
|
||||
func (application *Application) HasPromptPage() bool {
|
||||
providerItems := application.getAllPromptedProviderItems()
|
||||
if len(providerItems) != 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return application.isAffiliationPrompted()
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// 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"
|
||||
|
||||
"github.com/dchest/captcha"
|
||||
)
|
||||
|
||||
func GetCaptcha() (string, []byte) {
|
||||
id := captcha.NewLen(5)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
err := captcha.WriteImage(&buffer, id, 200, 80)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return id, buffer.Bytes()
|
||||
}
|
||||
|
||||
func VerifyCaptcha(id string, digits string) bool {
|
||||
res := captcha.VerifyString(id, digits)
|
||||
|
||||
return res
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
// 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"
|
||||
"regexp"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
var reWhiteSpace *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
reWhiteSpace, _ = regexp.Compile("\\s")
|
||||
}
|
||||
|
||||
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, email string, phone string, affiliation string) string {
|
||||
if organization == nil {
|
||||
return "organization does not exist"
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Username") {
|
||||
if len(username) <= 1 {
|
||||
return "username must have at least 2 characters"
|
||||
} else if reWhiteSpace.MatchString(username) {
|
||||
return "username cannot contain white spaces"
|
||||
} else if HasUserByField(organization.Name, "name", username) {
|
||||
return "username already exists"
|
||||
}
|
||||
}
|
||||
|
||||
if len(password) <= 5 {
|
||||
return "password must have at least 6 characters"
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Email") {
|
||||
if HasUserByField(organization.Name, "email", email) {
|
||||
return "email already exists"
|
||||
} else if !util.IsEmailValid(email) {
|
||||
return "email is invalid"
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Phone") {
|
||||
if HasUserByField(organization.Name, "phone", phone) {
|
||||
return "phone already exists"
|
||||
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
|
||||
return "phone number is invalid"
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Display name") {
|
||||
if displayName == "" {
|
||||
return "displayName cannot be blank"
|
||||
} else if application.GetSignupItemRule("Display name") == "Personal" {
|
||||
if !isValidPersonalName(displayName) {
|
||||
return "displayName is not valid personal name"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if application.IsSignupItemVisible("Affiliation") {
|
||||
if affiliation == "" {
|
||||
return "affiliation cannot be blank"
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func CheckPassword(user *User, password string) string {
|
||||
organization := GetOrganizationByUser(user)
|
||||
|
||||
if organization.PasswordType == "plain" {
|
||||
if password == user.Password {
|
||||
return ""
|
||||
} else {
|
||||
return "password incorrect"
|
||||
}
|
||||
} else if organization.PasswordType == "salt" {
|
||||
if password == user.Password || getSaltedPassword(password, organization.PasswordSalt) == user.Password {
|
||||
return ""
|
||||
} else {
|
||||
return "password incorrect"
|
||||
}
|
||||
} else {
|
||||
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckUserLogin(organization string, username string, password string) (*User, string) {
|
||||
user := GetUserByFields(organization, username)
|
||||
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"
|
||||
}
|
||||
|
||||
msg := CheckPassword(user, password)
|
||||
if msg != "" {
|
||||
return nil, msg
|
||||
}
|
||||
|
||||
return user, ""
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// 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 "regexp"
|
||||
|
||||
var rePersonalName *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
rePersonalName, err = regexp.Compile("^[\u4E00-\u9FA5]{2,3}(?:·[\u4E00-\u9FA5]{2,3})*$")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func isValidPersonalName(s string) bool {
|
||||
return rePersonalName.MatchString(s)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// 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.
|
||||
|
||||
// modified from https://github.com/casbin/casnode/blob/master/service/mail.go
|
||||
|
||||
package object
|
||||
|
||||
import "github.com/go-gomail/gomail"
|
||||
|
||||
func SendEmail(provider *Provider, title, content, dest, sender string) string {
|
||||
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
|
||||
message := gomail.NewMessage()
|
||||
message.SetAddressHeader("From", provider.ClientId, sender)
|
||||
message.SetHeader("To", dest)
|
||||
message.SetHeader("Subject", title)
|
||||
message.SetBody("text/html", content)
|
||||
|
||||
err := dialer.DialAndSend(message)
|
||||
if err == nil {
|
||||
return ""
|
||||
} else {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// 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/casdoor/casdoor/util"
|
||||
|
||||
func InitDb() {
|
||||
initBuiltInOrganization()
|
||||
initBuiltInUser()
|
||||
initBuiltInApplication()
|
||||
initBuiltInLdap()
|
||||
}
|
||||
|
||||
func initBuiltInOrganization() {
|
||||
organization := getOrganization("admin", "built-in")
|
||||
if organization != nil {
|
||||
return
|
||||
}
|
||||
|
||||
organization = &Organization{
|
||||
Owner: "admin",
|
||||
Name: "built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Built-in Organization",
|
||||
WebsiteUrl: "https://example.com",
|
||||
Favicon: "https://cdn.casbin.com/static/favicon.ico",
|
||||
PhonePrefix: "86",
|
||||
DefaultAvatar: "https://casbin.org/img/casbin.svg",
|
||||
PasswordType: "plain",
|
||||
}
|
||||
AddOrganization(organization)
|
||||
}
|
||||
|
||||
func initBuiltInUser() {
|
||||
user := getUser("built-in", "admin")
|
||||
if user != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user = &User{
|
||||
Owner: "built-in",
|
||||
Name: "admin",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Type: "normal-user",
|
||||
Password: "123",
|
||||
DisplayName: "Admin",
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: "admin@example.com",
|
||||
Phone: "12345678910",
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
IsAdmin: true,
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
Properties: make(map[string]string),
|
||||
}
|
||||
AddUser(user)
|
||||
}
|
||||
|
||||
func initBuiltInApplication() {
|
||||
application := getApplication("admin", "app-built-in")
|
||||
if application != nil {
|
||||
return
|
||||
}
|
||||
|
||||
application = &Application{
|
||||
Owner: "admin",
|
||||
Name: "app-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
DisplayName: "Casdoor",
|
||||
Logo: "https://cdn.casbin.com/logo/logo_1024x256.png",
|
||||
HomepageUrl: "https://casdoor.org",
|
||||
Organization: "built-in",
|
||||
EnablePassword: true,
|
||||
EnableSignUp: true,
|
||||
Providers: []*ProviderItem{},
|
||||
SignupItems: []*SignupItem{},
|
||||
RedirectUris: []string{},
|
||||
ExpireInHours: 168,
|
||||
}
|
||||
AddApplication(application)
|
||||
}
|
||||
|
||||
func initBuiltInLdap() {
|
||||
ldap := GetLdap("ldap-built-in")
|
||||
if ldap != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ldap = &Ldap{
|
||||
Id: "ldap-built-in",
|
||||
Owner: "built-in",
|
||||
ServerName: "BuildIn LDAP Server",
|
||||
Host: "example.com",
|
||||
Port: 389,
|
||||
Admin: "cn=buildin,dc=example,dc=com",
|
||||
Passwd: "123",
|
||||
BaseDn: "ou=BuildIn,dc=example,dc=com",
|
||||
AutoSync: 0,
|
||||
LastSync: "",
|
||||
}
|
||||
AddLdap(ldap)
|
||||
}
|
|
@ -0,0 +1,381 @@
|
|||
// 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
goldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/thanhpk/randstr"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Ldap struct {
|
||||
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||
Owner string `xorm:"varchar(100)" json:"owner"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
ServerName string `xorm:"varchar(100)" json:"serverName"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
Admin string `xorm:"varchar(100)" json:"admin"`
|
||||
Passwd string `xorm:"varchar(100)" json:"passwd"`
|
||||
BaseDn string `xorm:"varchar(100)" json:"baseDn"`
|
||||
|
||||
AutoSync int `json:"autoSync"`
|
||||
LastSync string `xorm:"varchar(100)" json:"lastSync"`
|
||||
}
|
||||
|
||||
type ldapConn struct {
|
||||
Conn *goldap.Conn
|
||||
}
|
||||
|
||||
//type ldapGroup struct {
|
||||
// GidNumber string
|
||||
// Cn string
|
||||
//}
|
||||
|
||||
type ldapUser struct {
|
||||
UidNumber string
|
||||
Uid string
|
||||
Cn string
|
||||
GidNumber string
|
||||
//Gcn string
|
||||
Uuid string
|
||||
Mail string
|
||||
Email string
|
||||
EmailAddress string
|
||||
TelephoneNumber string
|
||||
Mobile string
|
||||
MobileTelephoneNumber string
|
||||
RegisteredAddress string
|
||||
PostalAddress string
|
||||
}
|
||||
|
||||
type LdapRespUser struct {
|
||||
UidNumber string `json:"uidNumber"`
|
||||
Uid string `json:"uid"`
|
||||
Cn string `json:"cn"`
|
||||
GroupId string `json:"groupId"`
|
||||
//GroupName string `json:"groupName"`
|
||||
Uuid string `json:"uuid"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conn.Bind(adminUser, adminPasswd)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("fail to login Ldap server with [%s]", adminUser)
|
||||
}
|
||||
|
||||
return &ldapConn{Conn: conn}, nil
|
||||
}
|
||||
|
||||
//FIXME: The Base DN does not necessarily contain the Group
|
||||
//func (l *ldapConn) GetLdapGroups(baseDn string) (map[string]ldapGroup, error) {
|
||||
// SearchFilter := "(objectClass=posixGroup)"
|
||||
// SearchAttributes := []string{"cn", "gidNumber"}
|
||||
// groupMap := make(map[string]ldapGroup)
|
||||
//
|
||||
// searchReq := goldap.NewSearchRequest(baseDn,
|
||||
// goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
// SearchFilter, SearchAttributes, nil)
|
||||
// searchResult, err := l.Conn.Search(searchReq)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
//
|
||||
// if len(searchResult.Entries) == 0 {
|
||||
// return nil, errors.New("no result")
|
||||
// }
|
||||
//
|
||||
// for _, entry := range searchResult.Entries {
|
||||
// var ldapGroupItem ldapGroup
|
||||
// for _, attribute := range entry.Attributes {
|
||||
// switch attribute.Name {
|
||||
// case "gidNumber":
|
||||
// ldapGroupItem.GidNumber = attribute.Values[0]
|
||||
// break
|
||||
// case "cn":
|
||||
// ldapGroupItem.Cn = attribute.Values[0]
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// groupMap[ldapGroupItem.GidNumber] = ldapGroupItem
|
||||
// }
|
||||
//
|
||||
// return groupMap, nil
|
||||
//}
|
||||
|
||||
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
|
||||
SearchFilter := "(objectClass=posixAccount)"
|
||||
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
|
||||
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
|
||||
|
||||
searchReq := goldap.NewSearchRequest(baseDn,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
SearchFilter, SearchAttributes, nil)
|
||||
searchResult, err := l.Conn.Search(searchReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("no result")
|
||||
}
|
||||
|
||||
var ldapUsers []ldapUser
|
||||
|
||||
for _, entry := range searchResult.Entries {
|
||||
var ldapUserItem ldapUser
|
||||
for _, attribute := range entry.Attributes {
|
||||
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)
|
||||
}
|
||||
|
||||
return ldapUsers, nil
|
||||
}
|
||||
|
||||
func AddLdap(ldap *Ldap) bool {
|
||||
if len(ldap.Id) == 0 {
|
||||
ldap.Id = util.GenerateId()
|
||||
}
|
||||
|
||||
if len(ldap.CreatedTime) == 0 {
|
||||
ldap.CreatedTime = util.GetCurrentTime()
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func CheckLdapExist(ldap *Ldap) bool {
|
||||
var result []*Ldap
|
||||
err := adapter.Engine.Find(&result, &Ldap{
|
||||
Owner: ldap.Owner,
|
||||
Host: ldap.Host,
|
||||
Port: ldap.Port,
|
||||
Admin: ldap.Admin,
|
||||
Passwd: ldap.Passwd,
|
||||
BaseDn: ldap.BaseDn,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(result) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func GetLdaps(owner string) []*Ldap {
|
||||
var ldaps []*Ldap
|
||||
err := adapter.Engine.Desc("created_time").Find(&ldaps, &Ldap{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ldaps
|
||||
}
|
||||
|
||||
func GetLdap(id string) *Ldap {
|
||||
if util.IsStrsEmpty(id) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ldap := Ldap{Id: id}
|
||||
existed, err := adapter.Engine.Get(&ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &ldap
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateLdap(ldap *Ldap) bool {
|
||||
if GetLdap(ldap.Id) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Cols("owner", "server_name", "host",
|
||||
"port", "admin", "passwd", "base_dn", "auto_sync").Update(ldap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteLdap(ldap *Ldap) bool {
|
||||
affected, err := adapter.Engine.ID(ldap.Id).Delete(&Ldap{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]LdapRespUser) {
|
||||
var existUsers []LdapRespUser
|
||||
var failedUsers []LdapRespUser
|
||||
var uuids []string
|
||||
|
||||
for _, user := range users {
|
||||
uuids = append(uuids, user.Uuid)
|
||||
}
|
||||
|
||||
existUuids := CheckLdapUuidExist(owner, uuids)
|
||||
|
||||
for _, user := range users {
|
||||
if len(existUuids) > 0 {
|
||||
for index, existUuid := range existUuids {
|
||||
if user.Uuid == existUuid {
|
||||
existUsers = append(existUsers, user)
|
||||
existUuids = append(existUuids[:index], existUuids[index+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !AddUser(&User{
|
||||
Owner: owner,
|
||||
Name: buildLdapUserName(user.Uid, user.UidNumber),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Password: "123",
|
||||
DisplayName: user.Cn,
|
||||
Avatar: "https://casbin.org/img/casbin.svg",
|
||||
Email: user.Email,
|
||||
Phone: user.Phone,
|
||||
Address: []string{user.Address},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Ldap: user.Uuid,
|
||||
}) {
|
||||
failedUsers = append(failedUsers, user)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return &existUsers, &failedUsers
|
||||
}
|
||||
|
||||
func UpdateLdapSyncTime(ldapId string) {
|
||||
_, err := adapter.Engine.ID(ldapId).Update(&Ldap{LastSync: util.GetCurrentTime()})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckLdapUuidExist(owner string, uuids []string) []string {
|
||||
var results []User
|
||||
var existUuids []string
|
||||
|
||||
//whereStr := ""
|
||||
//for i, uuid := range uuids {
|
||||
// if i == 0 {
|
||||
// whereStr = fmt.Sprintf("'%s'", uuid)
|
||||
// } else {
|
||||
// whereStr = fmt.Sprintf(",'%s'", uuid)
|
||||
// }
|
||||
//}
|
||||
|
||||
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 {
|
||||
existUuids = append(existUuids, result.Ldap)
|
||||
}
|
||||
}
|
||||
return existUuids
|
||||
}
|
||||
|
||||
func buildLdapUserName(uid, uidNum string) string {
|
||||
var result User
|
||||
uidWithNumber := fmt.Sprintf("%s_%s", uid, uidNum)
|
||||
|
||||
has, err := adapter.Engine.Where("name = ? or name = ?", uid, uidWithNumber).Get(&result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if has {
|
||||
if result.Name == uid {
|
||||
return uidWithNumber
|
||||
}
|
||||
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
|
||||
}
|
||||
|
||||
return uid
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
// 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/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Organization struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
|
||||
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
|
||||
}
|
||||
|
||||
func GetOrganizations(owner string) []*Organization {
|
||||
organizations := []*Organization{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&organizations, &Organization{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return organizations
|
||||
}
|
||||
|
||||
func getOrganization(owner string, name string) *Organization {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
organization := Organization{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &organization
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetOrganization(id string) *Organization {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getOrganization(owner, name)
|
||||
}
|
||||
|
||||
func UpdateOrganization(id string, organization *Organization) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getOrganization(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddOrganization(organization *Organization) bool {
|
||||
affected, err := adapter.Engine.Insert(organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteOrganization(organization *Organization) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{organization.Owner, organization.Name}).Delete(&Organization{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetOrganizationByUser(user *User) *Organization {
|
||||
return getOrganization("admin", user.Owner)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// 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/sha256"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func getSha256(data []byte) []byte {
|
||||
hash := sha256.Sum256(data)
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func getSha256HexDigest(s string) string {
|
||||
b := getSha256([]byte(s))
|
||||
res := hex.EncodeToString(b)
|
||||
return res
|
||||
}
|
||||
|
||||
func getSaltedPassword(password string, salt string) string {
|
||||
hash1 := getSha256HexDigest(password)
|
||||
res := getSha256HexDigest(hash1 + salt)
|
||||
return res
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
// 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/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Provider struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
Content string `xorm:"varchar(1000)" json:"content"`
|
||||
|
||||
RegionId string `xorm:"varchar(100)" json:"regionId"`
|
||||
SignName string `xorm:"varchar(100)" json:"signName"`
|
||||
TemplateCode string `xorm:"varchar(100)" json:"templateCode"`
|
||||
AppId string `xorm:"varchar(100)" json:"appId"`
|
||||
|
||||
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
|
||||
}
|
||||
|
||||
func getMaskedProvider(provider *Provider) *Provider {
|
||||
p := &Provider{
|
||||
Owner: provider.Owner,
|
||||
Name: provider.Name,
|
||||
CreatedTime: provider.CreatedTime,
|
||||
DisplayName: provider.DisplayName,
|
||||
Category: provider.Category,
|
||||
Type: provider.Type,
|
||||
ClientId: provider.ClientId,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func GetProviders(owner string) []*Provider {
|
||||
providers := []*Provider{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&providers, &Provider{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func getProvider(owner string, name string) *Provider {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
provider := Provider{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &provider
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetProvider(id string) *Provider {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getProvider(owner, name)
|
||||
}
|
||||
|
||||
func GetDefaultHumanCheckProvider() *Provider {
|
||||
provider := Provider{Owner: "admin", Category: "HumanCheck"}
|
||||
existed, err := adapter.Engine.Get(&provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !existed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &provider
|
||||
}
|
||||
|
||||
func UpdateProvider(id string, provider *Provider) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getProvider(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddProvider(provider *Provider) bool {
|
||||
affected, err := adapter.Engine.Insert(provider)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteProvider(provider *Provider) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{provider.Owner, provider.Name}).Delete(&Provider{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// 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
|
||||
|
||||
type ProviderItem struct {
|
||||
Name string `json:"name"`
|
||||
CanSignUp bool `json:"canSignUp"`
|
||||
CanSignIn bool `json:"canSignIn"`
|
||||
CanUnlink bool `json:"canUnlink"`
|
||||
Prompted bool `json:"prompted"`
|
||||
AlertType string `json:"alertType"`
|
||||
Provider *Provider `json:"provider"`
|
||||
}
|
||||
|
||||
func (application *Application) GetProviderItem(providerName string) *ProviderItem {
|
||||
for _, providerItem := range application.Providers {
|
||||
if providerItem.Name == providerName {
|
||||
return providerItem
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pi *ProviderItem) isProviderVisible() bool {
|
||||
return pi.Provider.Category == "OAuth"
|
||||
}
|
||||
|
||||
func (pi *ProviderItem) isProviderPrompted() bool {
|
||||
return pi.isProviderVisible() && pi.Prompted
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// 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/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type Records struct {
|
||||
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||
Record util.Record `xorm:"extends"`
|
||||
}
|
||||
|
||||
func AddRecord(record *util.Record) bool {
|
||||
records := new(Records)
|
||||
records.Record = *record
|
||||
|
||||
affected, err := adapter.Engine.Insert(records)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetRecordCount() int {
|
||||
count, err := adapter.Engine.Count(&Records{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(count)
|
||||
}
|
||||
|
||||
func GetRecords() []*Records {
|
||||
records := []*Records{}
|
||||
err := adapter.Engine.Desc("id").Find(&records)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
||||
|
||||
func GetRecordsByField(record *Records) []*Records {
|
||||
records := []*Records{}
|
||||
err := adapter.Engine.Find(&records, record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return records
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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
|
||||
|
||||
type SignupItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Rule string `json:"rule"`
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// 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/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)
|
||||
}
|
||||
|
||||
param := make(map[string]string)
|
||||
if provider.Type == "tencent" {
|
||||
param["0"] = code
|
||||
} else {
|
||||
param["code"] = code
|
||||
}
|
||||
client.SendMessage(param, phone)
|
||||
return ""
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
// 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 (
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Code struct {
|
||||
Message string `xorm:"varchar(100)" json:"message"`
|
||||
Code string `xorm:"varchar(100)" json:"code"`
|
||||
}
|
||||
|
||||
type Token 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"`
|
||||
|
||||
Application string `xorm:"varchar(100)" json:"application"`
|
||||
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"`
|
||||
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"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
func GetTokens(owner string) []*Token {
|
||||
tokens := []*Token{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&tokens, &Token{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
func getToken(owner string, name string) *Token {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
token := Token{Owner: owner, Name: name}
|
||||
existed, err := adapter.Engine.Get(&token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &token
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getTokenByCode(code string) *Token {
|
||||
token := Token{}
|
||||
existed, err := adapter.Engine.Where("code=?", code).Get(&token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &token
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetToken(id string) *Token {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
return getToken(owner, name)
|
||||
}
|
||||
|
||||
func UpdateToken(id string, token *Token) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getToken(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddToken(token *Token) bool {
|
||||
affected, err := adapter.Engine.Insert(token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func DeleteToken(token *Token) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{token.Owner, token.Name}).Delete(&Token{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func CheckOAuthLogin(clientId string, responseType string, redirectUri string, scope string, state string) (string, *Application) {
|
||||
if responseType != "code" {
|
||||
return "response_type should be \"code\"", nil
|
||||
}
|
||||
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
return "Invalid client_id", nil
|
||||
}
|
||||
|
||||
validUri := false
|
||||
for _, tmpUri := range application.RedirectUris {
|
||||
if strings.Contains(redirectUri, tmpUri) {
|
||||
validUri = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validUri {
|
||||
return "redirect_uri doesn't exist in the allowed Redirect URL list", application
|
||||
}
|
||||
|
||||
return "", application
|
||||
}
|
||||
|
||||
func GetOAuthCode(userId string, clientId string, responseType string, redirectUri string, scope string, state string) *Code {
|
||||
user := GetUser(userId)
|
||||
if user == nil {
|
||||
return &Code{
|
||||
Message: "Invalid user_id",
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
|
||||
msg, application := CheckOAuthLogin(clientId, responseType, redirectUri, scope, state)
|
||||
if msg != "" {
|
||||
return &Code{
|
||||
Message: msg,
|
||||
Code: "",
|
||||
}
|
||||
}
|
||||
|
||||
accessToken, err := generateJwtToken(application, user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
token := &Token{
|
||||
Owner: application.Owner,
|
||||
Name: util.GenerateId(),
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Application: application.Name,
|
||||
Organization: user.Owner,
|
||||
User: user.Name,
|
||||
Code: util.GenerateClientId(),
|
||||
AccessToken: accessToken,
|
||||
ExpiresIn: application.ExpireInHours * 60,
|
||||
Scope: scope,
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
AddToken(token)
|
||||
|
||||
return &Code{
|
||||
Message: "",
|
||||
Code: token.Code,
|
||||
}
|
||||
}
|
||||
|
||||
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string) *TokenWrapper {
|
||||
application := GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid client_id",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if grantType != "authorization_code" {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: grant_type should be \"authorization_code\"",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if code == "" {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: code should not be empty",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
token := getTokenByCode(code)
|
||||
if token == nil {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid code",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if application.Name != token.Application {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: the token is for wrong application (client_id)",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
if application.ClientSecret != clientSecret {
|
||||
return &TokenWrapper{
|
||||
AccessToken: "error: invalid client_secret",
|
||||
TokenType: "",
|
||||
ExpiresIn: 0,
|
||||
Scope: "",
|
||||
}
|
||||
}
|
||||
|
||||
tokenWrapper := &TokenWrapper{
|
||||
AccessToken: token.AccessToken,
|
||||
TokenType: token.TokenType,
|
||||
ExpiresIn: token.ExpiresIn,
|
||||
Scope: token.Scope,
|
||||
}
|
||||
|
||||
return tokenWrapper
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// 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 (
|
||||
"time"
|
||||
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
var jwtSecret = []byte("CasdoorSecret")
|
||||
|
||||
type Claims struct {
|
||||
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) (string, error) {
|
||||
nowTime := time.Now()
|
||||
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
|
||||
|
||||
claims := Claims{
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
token, err := tokenClaims.SignedString(jwtSecret)
|
||||
|
||||
return token, err
|
||||
}
|
||||
|
||||
func ParseJwtToken(token string) (*Claims, error) {
|
||||
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return jwtSecret, nil
|
||||
})
|
||||
|
||||
if tokenClaims != nil {
|
||||
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
|
@ -0,0 +1,261 @@
|
|||
// 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/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type User 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"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
|
||||
Id string `xorm:"varchar(100)" json:"id"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Avatar string `xorm:"varchar(255)" json:"avatar"`
|
||||
Email string `xorm:"varchar(100)" json:"email"`
|
||||
Phone string `xorm:"varchar(100)" json:"phone"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Language string `xorm:"varchar(100)" json:"language"`
|
||||
Score int `json:"score"`
|
||||
IsAdmin bool `json:"isAdmin"`
|
||||
IsGlobalAdmin bool `json:"isGlobalAdmin"`
|
||||
IsForbidden bool `json:"isForbidden"`
|
||||
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
||||
Hash string `xorm:"varchar(100)" json:"hash"`
|
||||
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
||||
|
||||
Github string `xorm:"varchar(100)" json:"github"`
|
||||
Google string `xorm:"varchar(100)" json:"google"`
|
||||
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
||||
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
||||
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
}
|
||||
|
||||
func GetGlobalUsers() []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func GetUsers(owner string) []*User {
|
||||
users := []*User{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&users, &User{Owner: owner})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
func getUser(owner string, name string) *User {
|
||||
if owner == "" || name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
user := User{Owner: owner, Name: name}
|
||||
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 GetMaskedUser(user *User) *User {
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if user.Password != "" {
|
||||
user.Password = "***"
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func GetMaskedUsers(users []*User) []*User {
|
||||
for _, user := range users {
|
||||
user = GetMaskedUser(user)
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func GetLastUser(owner string) *User {
|
||||
user := User{Owner: owner}
|
||||
existed, err := adapter.Engine.Desc("created_time", "id").Get(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &user
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateUser(id string, user *User) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getUser(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
user.UpdateUserHash()
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func UpdateUserInternal(id string, user *User) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getUser(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
user.UpdateUserHash()
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddUser(user *User) bool {
|
||||
if user.Id == "" {
|
||||
user.Id = util.GenerateId()
|
||||
}
|
||||
|
||||
organization := GetOrganizationByUser(user)
|
||||
user.UpdateUserPassword(organization)
|
||||
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
|
||||
affected, err := adapter.Engine.Insert(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddUsers(users []*User) bool {
|
||||
if len(users) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
organization := GetOrganizationByUser(users[0])
|
||||
for _, user := range users {
|
||||
user.UpdateUserPassword(organization)
|
||||
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(users)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func AddUsersSafe(users []*User) bool {
|
||||
batchSize := 1000
|
||||
|
||||
if len(users) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
affected := false
|
||||
for i := 0; i < (len(users)-1)/batchSize+1; i++ {
|
||||
start := i * batchSize
|
||||
end := (i + 1) * batchSize
|
||||
if end > len(users) {
|
||||
end = len(users)
|
||||
}
|
||||
|
||||
tmp := users[start:end]
|
||||
fmt.Printf("Add users: [%d - %d].\n", start, end)
|
||||
if AddUsers(tmp) {
|
||||
affected = true
|
||||
}
|
||||
}
|
||||
|
||||
return affected
|
||||
}
|
||||
|
||||
func DeleteUser(user *User) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func LinkUserAccount(user *User, field string, value string) bool {
|
||||
return SetUserField(user, field, value)
|
||||
}
|
||||
|
||||
func (user *User) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// 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 (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func calculateHash(user *User) string {
|
||||
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() {
|
||||
hash := calculateHash(user)
|
||||
user.Hash = hash
|
||||
}
|
||||
|
||||
func (user *User) UpdateUserPassword(organization *Organization) {
|
||||
if organization.PasswordType == "salt" {
|
||||
user.Password = getSaltedPassword(user.Password, organization.PasswordSalt)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
// 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/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
func updateUserColumn(column string, user *User) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols(column).Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func TestSyncAvatarsFromGitHub(t *testing.T) {
|
||||
InitConfig()
|
||||
|
||||
users := GetGlobalUsers()
|
||||
for _, user := range users {
|
||||
if user.Github == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.Github)
|
||||
updateUserColumn("avatar", user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncIds(t *testing.T) {
|
||||
InitConfig()
|
||||
|
||||
users := GetGlobalUsers()
|
||||
for _, user := range users {
|
||||
if user.Id != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
user.Id = util.GenerateId()
|
||||
updateUserColumn("id", user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncHashes(t *testing.T) {
|
||||
InitConfig()
|
||||
|
||||
users := GetGlobalUsers()
|
||||
for _, user := range users {
|
||||
if user.Hash != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
user.UpdateUserHash()
|
||||
updateUserColumn("hash", user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSaltedPassword(t *testing.T) {
|
||||
password := "123456"
|
||||
salt := "123"
|
||||
fmt.Printf("%s -> %s\n", password, getSaltedPassword(password, salt))
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
// 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"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
func GetUserByField(organizationName string, field string, value string) *User {
|
||||
if field == "" || value == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
user := User{Owner: organizationName}
|
||||
existed, err := adapter.Engine.Where(fmt.Sprintf("%s=?", field), value).Get(&user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &user
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func HasUserByField(organizationName string, field string, value string) bool {
|
||||
return GetUserByField(organizationName, field, value) != nil
|
||||
}
|
||||
|
||||
func GetUserByFields(organization string, field string) *User {
|
||||
// check username
|
||||
user := GetUserByField(organization, "name", field)
|
||||
if user != nil {
|
||||
return user
|
||||
}
|
||||
|
||||
// check email
|
||||
user = GetUserByField(organization, "email", field)
|
||||
if user != nil {
|
||||
return user
|
||||
}
|
||||
|
||||
// check phone
|
||||
user = GetUserByField(organization, "phone", field)
|
||||
if user != nil {
|
||||
return user
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetUserField(user *User, field string, value string) bool {
|
||||
if field == "password" {
|
||||
organization := GetOrganizationByUser(user)
|
||||
user.UpdateUserPassword(organization)
|
||||
value = user.Password
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(map[string]interface{}{field: value})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
user = getUser(user.Owner, user.Name)
|
||||
user.UpdateUserHash()
|
||||
_, err = adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("hash").Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetUserField(user *User, field string) string {
|
||||
// https://socketloop.com/tutorials/golang-how-to-get-struct-field-and-value-by-name
|
||||
u := reflect.ValueOf(user)
|
||||
f := reflect.Indirect(u).FieldByName(field)
|
||||
return f.String()
|
||||
}
|
||||
|
||||
func setUserProperty(user *User, field string, value string) {
|
||||
if value == "" {
|
||||
delete(user.Properties, field)
|
||||
} else {
|
||||
user.Properties[field] = value
|
||||
}
|
||||
}
|
||||
|
||||
func SetUserOAuthProperties(organization *Organization, user *User, providerType string, userInfo *idp.UserInfo) bool {
|
||||
if userInfo.Id != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_id", providerType)
|
||||
setUserProperty(user, propertyName, userInfo.Id)
|
||||
}
|
||||
if userInfo.Username != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_username", providerType)
|
||||
setUserProperty(user, propertyName, userInfo.Username)
|
||||
}
|
||||
if userInfo.DisplayName != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_displayName", providerType)
|
||||
setUserProperty(user, propertyName, userInfo.DisplayName)
|
||||
if user.DisplayName == "" {
|
||||
user.DisplayName = userInfo.DisplayName
|
||||
}
|
||||
}
|
||||
if userInfo.Email != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_email", providerType)
|
||||
setUserProperty(user, propertyName, userInfo.Email)
|
||||
if user.Email == "" {
|
||||
user.Email = userInfo.Email
|
||||
}
|
||||
}
|
||||
if userInfo.AvatarUrl != "" {
|
||||
propertyName := fmt.Sprintf("oauth_%s_avatarUrl", providerType)
|
||||
setUserProperty(user, propertyName, userInfo.AvatarUrl)
|
||||
if user.Avatar == "" || user.Avatar == organization.DefaultAvatar {
|
||||
user.Avatar = userInfo.AvatarUrl
|
||||
}
|
||||
}
|
||||
|
||||
affected := UpdateUserInternal(user.GetId(), user)
|
||||
return affected
|
||||
}
|
||||
|
||||
func ClearUserOAuthProperties(user *User, providerType string) bool {
|
||||
for k := range user.Properties {
|
||||
prefix := fmt.Sprintf("oauth_%s_", providerType)
|
||||
if strings.HasPrefix(k, prefix) {
|
||||
delete(user.Properties, k)
|
||||
}
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("properties").Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
// 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"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type VerificationRecord 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"`
|
||||
|
||||
RemoteAddr string `xorm:"varchar(100)"`
|
||||
Type string `xorm:"varchar(10)"`
|
||||
User string `xorm:"varchar(100) notnull"`
|
||||
Provider string `xorm:"varchar(100) notnull"`
|
||||
Receiver string `xorm:"varchar(100) notnull"`
|
||||
Code string `xorm:"varchar(10) notnull"`
|
||||
Time int64 `xorm:"notnull"`
|
||||
IsUsed bool
|
||||
}
|
||||
|
||||
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) string {
|
||||
if provider == nil {
|
||||
return "Please set an Email provider first"
|
||||
}
|
||||
|
||||
sender := organization.DisplayName
|
||||
title := provider.Title
|
||||
code := getRandomCode(5)
|
||||
// "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 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) string {
|
||||
if provider == nil {
|
||||
return "Please set a SMS provider first"
|
||||
}
|
||||
|
||||
code := getRandomCode(5)
|
||||
if result := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); len(result) != 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
return SendCodeToPhone(provider, dest, code)
|
||||
}
|
||||
|
||||
func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordType, dest, code string) string {
|
||||
var record VerificationRecord
|
||||
record.RemoteAddr = remoteAddr
|
||||
record.Type = recordType
|
||||
if user != nil {
|
||||
record.User = user.GetId()
|
||||
}
|
||||
has, err := adapter.Engine.Desc("created_time").Get(&record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
if has && now-record.Time < 60 {
|
||||
return "You can only send one code in 60s."
|
||||
}
|
||||
|
||||
record.Owner = provider.Owner
|
||||
record.Name = util.GenerateId()
|
||||
record.CreatedTime = util.GetCurrentTime()
|
||||
if user != nil {
|
||||
record.User = user.GetId()
|
||||
}
|
||||
record.Provider = provider.Name
|
||||
|
||||
record.Receiver = dest
|
||||
record.Code = code
|
||||
record.Time = now
|
||||
record.IsUsed = false
|
||||
|
||||
_, err = adapter.Engine.Insert(record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getVerificationRecord(dest string) *VerificationRecord {
|
||||
var record VerificationRecord
|
||||
record.Receiver = dest
|
||||
has, err := adapter.Engine.Desc("time").Where("is_used = 0").Get(&record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if !has {
|
||||
return nil
|
||||
}
|
||||
return &record
|
||||
}
|
||||
|
||||
func CheckVerificationCode(dest, code string) string {
|
||||
record := getVerificationRecord(dest)
|
||||
|
||||
if record == nil {
|
||||
return "Code has not been sent yet!"
|
||||
}
|
||||
|
||||
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
if now-record.Time > timeout*60 {
|
||||
return fmt.Sprintf("You should verify your code in %d min!", timeout)
|
||||
}
|
||||
|
||||
if record.Code != code {
|
||||
return "Wrong code!"
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func DisableVerificationCode(dest string) {
|
||||
record := getVerificationRecord(dest)
|
||||
if record == nil {
|
||||
return
|
||||
}
|
||||
|
||||
record.IsUsed = true
|
||||
_, err := adapter.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// from Casnode/object/validateCode.go line 116
|
||||
var stdNums = []byte("0123456789")
|
||||
|
||||
func getRandomCode(length int) string {
|
||||
var result []byte
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < length; i++ {
|
||||
result = append(result, stdNums[r.Intn(len(stdNums))])
|
||||
}
|
||||
return string(result)
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// 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 (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
_ "github.com/go-sql-driver/mysql" // db = mysql
|
||||
//_ "github.com/lib/pq" // db = postgres
|
||||
)
|
||||
|
||||
var adapter *object.Adapter
|
||||
|
||||
func initConfig() {
|
||||
err := beego.LoadAppConfig("ini", "../conf/app.conf")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
// 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
|
||||
|
||||
type Affiliation struct {
|
||||
Id int `xorm:"int notnull pk autoincr" json:"id"`
|
||||
Name string `xorm:"varchar(128)" json:"name"`
|
||||
}
|
||||
|
||||
func (Affiliation) TableName() string {
|
||||
return affiliationTableName
|
||||
}
|
||||
|
||||
func getAffiliations() []*Affiliation {
|
||||
affiliations := []*Affiliation{}
|
||||
err := adapter.Engine.Asc("id").Find(&affiliations)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affiliations
|
||||
}
|
||||
|
||||
func getAffiliationMap() ([]*Affiliation, map[int]string) {
|
||||
affiliations := getAffiliations()
|
||||
|
||||
m := map[int]string{}
|
||||
for _, affiliation := range affiliations {
|
||||
m[affiliation.Id] = affiliation.Name
|
||||
}
|
||||
return affiliations, m
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
// 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
|
||||
|
||||
var dbName = "dbName"
|
||||
var userTableName = "userTableName"
|
||||
var affiliationTableName = "affiliationTableName"
|
||||
var avatarBaseUrl = "https://cdn.example.com/"
|
||||
|
||||
var orgName = "orgName"
|
|
@ -0,0 +1,23 @@
|
|||
// 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 "github.com/mileusna/crontab"
|
||||
|
||||
var ctab *crontab.Crontab
|
||||
|
||||
func init() {
|
||||
ctab = crontab.New()
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// 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 "github.com/casdoor/casdoor/object"
|
||||
|
||||
func getUsers() []*object.User {
|
||||
users := object.GetUsers(orgName)
|
||||
return users
|
||||
}
|
||||
|
||||
func getUserMap() ([]*object.User, map[string]*object.User) {
|
||||
users := getUsers()
|
||||
|
||||
m := map[string]*object.User{}
|
||||
for _, user := range users {
|
||||
m[user.Name] = user
|
||||
}
|
||||
return users, m
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
// 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"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
func TestGetUsers(t *testing.T) {
|
||||
initConfig()
|
||||
initAdapter()
|
||||
|
||||
users := getUsersOriginal()
|
||||
for _, user := range users {
|
||||
fmt.Printf("%v\n", user)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncUsers(t *testing.T) {
|
||||
initConfig()
|
||||
initAdapter()
|
||||
object.InitAdapter()
|
||||
|
||||
syncUsers()
|
||||
|
||||
// run at every minute
|
||||
schedule := "* * * * *"
|
||||
err := ctab.AddJob(schedule, syncUsers)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(time.Duration(1<<63 - 1))
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
// 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 routers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/authz"
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
requestUri := ctx.Request.RequestURI
|
||||
clientId := parseQuery(requestUri, "clientId")
|
||||
clientSecret := parseQuery(requestUri, "clientSecret")
|
||||
if len(clientId) == 0 || len(clientSecret) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
app := object.GetApplicationByClientId(clientId)
|
||||
if app == nil || app.ClientSecret != clientSecret {
|
||||
return ""
|
||||
}
|
||||
return "built-in/service"
|
||||
}
|
||||
|
||||
func getUsername(ctx *context.Context) (username string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
// bug in Beego: this call will panic when file session store is empty
|
||||
// so we catch the panic
|
||||
username = ctx.Input.Session("username").(string)
|
||||
|
||||
if len(username) == 0 {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getSubject(ctx *context.Context) (string, string) {
|
||||
username := getUsername(ctx)
|
||||
if username == "" {
|
||||
return "anonymous", "anonymous"
|
||||
}
|
||||
|
||||
// username == "built-in/admin"
|
||||
tokens := strings.Split(username, "/")
|
||||
owner := tokens[0]
|
||||
name := tokens[1]
|
||||
return owner, name
|
||||
}
|
||||
|
||||
func getObject(ctx *context.Context) (string, string) {
|
||||
method := ctx.Request.Method
|
||||
if method == http.MethodGet {
|
||||
query := ctx.Request.URL.RawQuery
|
||||
// query == "?id=built-in/admin"
|
||||
idParamValue := parseQuery(query, "id")
|
||||
if idParamValue == "" {
|
||||
return "", ""
|
||||
}
|
||||
return parseSlash(idParamValue)
|
||||
} else {
|
||||
body := ctx.Input.RequestBody
|
||||
|
||||
if len(body) == 0 {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
var obj Object
|
||||
err := json.Unmarshal(body, &obj)
|
||||
if err != nil {
|
||||
//panic(err)
|
||||
return "", ""
|
||||
}
|
||||
return obj.Owner, obj.Name
|
||||
}
|
||||
}
|
||||
|
||||
func denyRequest(ctx *context.Context) {
|
||||
w := ctx.ResponseWriter
|
||||
w.WriteHeader(403)
|
||||
resp := &controllers.Response{Status: "error", Msg: "Unauthorized operation"}
|
||||
_, err := w.Write([]byte(util.StructToJson(resp)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func willLog(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||
if subOwner == "anonymous" && subName == "anonymous" && method == "GET" && (urlPath == "/api/get-account" || urlPath == "/api/get-app-login") && objOwner == "" && objName == "" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func AuthzFilter(ctx *context.Context) {
|
||||
subOwner, subName := getSubject(ctx)
|
||||
method := ctx.Request.Method
|
||||
urlPath := ctx.Request.URL.Path
|
||||
objOwner, objName := getObject(ctx)
|
||||
|
||||
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
|
||||
|
||||
result := "deny"
|
||||
if isAllowed {
|
||||
result = "allow"
|
||||
}
|
||||
|
||||
if willLog(subOwner, subName, method, urlPath, objOwner, objName) {
|
||||
logLine := fmt.Sprintf("subOwner = %s, subName = %s, method = %s, urlPath = %s, obj.Owner = %s, obj.Name = %s, result = %s",
|
||||
subOwner, subName, method, urlPath, objOwner, objName, result)
|
||||
fmt.Println(logLine)
|
||||
util.LogInfo(ctx, logLine)
|
||||
}
|
||||
|
||||
if !isAllowed {
|
||||
denyRequest(ctx)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
// 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 routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func getSessionUser(ctx *context.Context) string {
|
||||
user := ctx.Input.CruSession.Get("username")
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func setSessionUser(ctx *context.Context, user string) {
|
||||
err := ctx.Input.CruSession.Set("username", user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
|
||||
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||
}
|
||||
|
||||
func returnRequest(ctx *context.Context, msg string) {
|
||||
w := ctx.ResponseWriter
|
||||
w.WriteHeader(200)
|
||||
resp := &controllers.Response{Status: "error", Msg: msg}
|
||||
_, err := w.Write([]byte(util.StructToJson(resp)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func AutoLoginFilter(ctx *context.Context) {
|
||||
//if getSessionUser(ctx) != "" {
|
||||
// return
|
||||
//}
|
||||
|
||||
query := ctx.Request.URL.RawQuery
|
||||
queryMap, err := url.ParseQuery(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// "/page?access_token=123"
|
||||
accessToken := queryMap.Get("accessToken")
|
||||
if accessToken != "" {
|
||||
claims, err := object.ParseJwtToken(accessToken)
|
||||
if err != nil {
|
||||
returnRequest(ctx, "Invalid JWT token")
|
||||
return
|
||||
}
|
||||
|
||||
userId := fmt.Sprintf("%s/%s", claims.Organization, claims.Username)
|
||||
setSessionUser(ctx, userId)
|
||||
return
|
||||
}
|
||||
|
||||
// "/page?username=abc&password=123"
|
||||
userId := queryMap.Get("username")
|
||||
password := queryMap.Get("password")
|
||||
if userId != "" && password != "" {
|
||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||
_, msg := object.CheckUserLogin(owner, name, password)
|
||||
if msg != "" {
|
||||
returnRequest(ctx, msg)
|
||||
return
|
||||
}
|
||||
|
||||
setSessionUser(ctx, userId)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// 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 routers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func getUser(ctx *context.Context) (username string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
username = getUserByClientIdSecret(ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
username = ctx.Input.Session("username").(string)
|
||||
|
||||
if username == "" {
|
||||
username = getUserByClientIdSecret(ctx)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getUserByClientIdSecret(ctx *context.Context) string {
|
||||
requestUri := ctx.Request.RequestURI
|
||||
clientId := parseQuery(requestUri, "clientId")
|
||||
clientSecret := parseQuery(requestUri, "clientSecret")
|
||||
if len(clientId) == 0 || len(clientSecret) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
app := object.GetApplicationByClientId(clientId)
|
||||
if app == nil || app.ClientSecret != clientSecret {
|
||||
return ""
|
||||
}
|
||||
return app.Organization+"/"+app.Name
|
||||
}
|
||||
|
||||
func RecordMessage(ctx *context.Context) {
|
||||
if ctx.Request.URL.Path != "/api/login" {
|
||||
user := getUser(ctx)
|
||||
userinfo := strings.Split(user,"/")
|
||||
if user == "" {
|
||||
userinfo = append(userinfo,"")
|
||||
}
|
||||
record := util.Records(ctx)
|
||||
record.Organization = userinfo[0]
|
||||
record.Username = userinfo[1]
|
||||
|
||||
object.AddRecord(record)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
// 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.
|
||||
|
||||
// @APIVersion 1.0.0
|
||||
// @Title Casdoor API
|
||||
// @Description Documentation of Casdoor API
|
||||
// @Contact admin@casbin.org
|
||||
package routers
|
||||
|
||||
import (
|
||||
"github.com/astaxie/beego"
|
||||
|
||||
"github.com/casdoor/casdoor/controllers"
|
||||
)
|
||||
|
||||
func init() {
|
||||
initAPI()
|
||||
}
|
||||
|
||||
func initAPI() {
|
||||
ns :=
|
||||
beego.NewNamespace("/api",
|
||||
beego.NSNamespace("/api",
|
||||
beego.NSInclude(
|
||||
&controllers.ApiController{},
|
||||
),
|
||||
),
|
||||
)
|
||||
beego.AddNamespace(ns)
|
||||
|
||||
beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup")
|
||||
beego.Router("/api/login", &controllers.ApiController{}, "POST:Login")
|
||||
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
|
||||
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout")
|
||||
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
|
||||
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")
|
||||
|
||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||
beego.Router("/api/update-organization", &controllers.ApiController{}, "POST:UpdateOrganization")
|
||||
beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization")
|
||||
beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization")
|
||||
|
||||
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
|
||||
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
|
||||
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
|
||||
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
||||
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
||||
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
||||
beego.Router("/api/upload-avatar", &controllers.ApiController{}, "POST:UploadAvatar")
|
||||
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
|
||||
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "POST:GetEmailAndPhone")
|
||||
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
|
||||
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
|
||||
beego.Router("/api/get-human-check", &controllers.ApiController{}, "GET:GetHumanCheck")
|
||||
beego.Router("/api/get-ldap-user", &controllers.ApiController{}, "POST:GetLdapUser")
|
||||
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "POST:GetLdaps")
|
||||
beego.Router("/api/get-ldap", &controllers.ApiController{}, "POST:GetLdap")
|
||||
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
|
||||
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
|
||||
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
|
||||
beego.Router("/api/check-ldap-users-exist", &controllers.ApiController{}, "POST:CheckLdapUsersExist")
|
||||
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
|
||||
|
||||
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
|
||||
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
|
||||
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
|
||||
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
|
||||
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
|
||||
|
||||
beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")
|
||||
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
|
||||
beego.Router("/api/get-user-application", &controllers.ApiController{}, "GET:GetUserApplication")
|
||||
beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
|
||||
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
|
||||
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
|
||||
|
||||
beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens")
|
||||
beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken")
|
||||
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
|
||||
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
|
||||
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
|
||||
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
|
||||
|
||||
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
|
||||
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// 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 routers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func StaticFilter(ctx *context.Context) {
|
||||
urlPath := ctx.Request.URL.Path
|
||||
if strings.HasPrefix(urlPath, "/api/") {
|
||||
return
|
||||
}
|
||||
|
||||
path := "web/build"
|
||||
if urlPath == "/" {
|
||||
path += "/index.html"
|
||||
} else {
|
||||
path += urlPath
|
||||
}
|
||||
|
||||
if util.FileExist(path) {
|
||||
http.ServeFile(ctx.ResponseWriter, ctx.Request, path)
|
||||
} else {
|
||||
http.ServeFile(ctx.ResponseWriter, ctx.Request, "web/build/index.html")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// 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 routers
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseQuery(query string, key string) string {
|
||||
queryMap, err := url.ParseQuery(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return queryMap.Get(key)
|
||||
}
|
||||
|
||||
func parseSlash(s string) (string, string) {
|
||||
tokens := strings.Split(s, "/")
|
||||
return tokens[0], tokens[1]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 445 B |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,93 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
|
||||
<defs>
|
||||
<symbol viewBox="0 0 20 20" id="unlocked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="locked">
|
||||
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="close">
|
||||
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow">
|
||||
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow-down">
|
||||
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
|
||||
</symbol>
|
||||
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="jump-to">
|
||||
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="expand">
|
||||
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
|
||||
</symbol>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Build a system
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,53 @@
|
|||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var isValid, qp, arr;
|
||||
|
||||
qp = (window.location.hash || location.search).substring(1);
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if (oauth2.auth.schema.get("flow") === "accessCode" && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback(oauth2.auth);
|
||||
} else {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: "Authorization failed: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
</script>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;AAu/FA;AA6+FA;;;;;;;;;;;;;;;;;;;;;;;;;;AAyTA;;;;;;AAoIA;AAi7FA;AAmtCA;AAi0IA;AA0oJA;AAgwFA;AAyrGA;AA0lFA;AA4nFA;AA+9CA;AA+gDA;AAwrCA;AA60EA;;;;;AA6oCA;AAsyJA;;;;;;;;;;;;;;AA64EA;AA4mIA;AAquJA;AA2qHA;AA2mGA;AAiiEA;AAq4DA;AAg3DA;AAoPA;;;;;;AAk7FA;AA07FA;;;;;AAi8CA;AAgsFA;AAs2CA;AAglCA;AAu9CA;AAy8EA;AAsiCA;AA+yFA;;;;;;;;;AAgkDA;AA2zIA;AAu7FA;AAmrFA;AAu0EA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA8QA;AAmvGA;AAuxFA;;;;;;AAocA;AAkvFA;AAu+CA;AAo+CA;AAgrCA;AAuyEA","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AA0yCA;AAoyHA;AAmyHA;AAykGA;AA+9BA;AA6iCA;AAojCA;AAu5BA","sourceRoot":""}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,31 @@
|
|||
// 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 util
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func StructToJson(v interface{}) string {
|
||||
//data, err := json.MarshalIndent(v, "", " ")
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func JsonToStruct(data string, v interface{}) error {
|
||||
return json.Unmarshal([]byte(data), v)
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
// 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/astaxie/beego/logs"
|
||||
)
|
||||
|
||||
func GetIPInfo(clientIP string) string {
|
||||
if clientIP == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
ips := strings.Split(clientIP, ",")
|
||||
res := ""
|
||||
for i := range ips {
|
||||
ip := strings.TrimSpace(ips[i])
|
||||
//desc := GetDescFromIP(ip)
|
||||
ipstr := fmt.Sprintf("%s: %s", ip, "")
|
||||
if i != len(ips)-1 {
|
||||
res += ipstr + " -> "
|
||||
} else {
|
||||
res += ipstr
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func getIPFromRequest(req *http.Request) string {
|
||||
clientIP := req.Header.Get("x-forwarded-for")
|
||||
if clientIP == "" {
|
||||
ipPort := strings.Split(req.RemoteAddr, ":")
|
||||
if len(ipPort) >= 1 && len(ipPort) <= 2 {
|
||||
clientIP = ipPort[0]
|
||||
} else if len(ipPort) > 2 {
|
||||
idx := strings.LastIndex(req.RemoteAddr, ":")
|
||||
clientIP = req.RemoteAddr[0:idx]
|
||||
clientIP = strings.TrimLeft(clientIP, "[")
|
||||
clientIP = strings.TrimRight(clientIP, "]")
|
||||
}
|
||||
}
|
||||
|
||||
return GetIPInfo(clientIP)
|
||||
}
|
||||
|
||||
func LogInfo(ctx *context.Context, f string, v ...interface{}) {
|
||||
ipString := fmt.Sprintf("(%s) ", getIPFromRequest(ctx.Request))
|
||||
logs.Info(ipString+f, v...)
|
||||
}
|
||||
|
||||
func LogWarning(ctx *context.Context, f string, v ...interface{}) {
|
||||
ipString := fmt.Sprintf("(%s) ", getIPFromRequest(ctx.Request))
|
||||
logs.Warning(ipString+f, v...)
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// 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 util
|
||||
|
||||
import "os"
|
||||
|
||||
func FileExist(path string) bool {
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
// 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 util
|
||||
|
||||
import "github.com/thanhpk/randstr"
|
||||
|
||||
func GenerateClientId() string {
|
||||
return randstr.Hex(10)
|
||||
}
|
||||
|
||||
func GenerateClientSecret() string {
|
||||
return randstr.Hex(20)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// 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 util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
)
|
||||
|
||||
type Record struct {
|
||||
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
|
||||
Timestamp string `xorm:"varchar(100)" json:"timestamp"`
|
||||
Organization string `xorm:"varchar(100)" json:"organization"`
|
||||
Username string `xorm:"varchar(100)" json:"username"`
|
||||
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
|
||||
Action string `xorm:"varchar(1000)" json:"action"`
|
||||
}
|
||||
|
||||
func Records(ctx *context.Context) *Record {
|
||||
ip := strings.Replace(getIPFromRequest(ctx.Request), ": ", "", -1)
|
||||
currenttime := GetCurrentTime()
|
||||
requesturi := ctx.Request.RequestURI
|
||||
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
|
||||
|
||||
record := Record{
|
||||
ClientIp: ip,
|
||||
Timestamp: currenttime,
|
||||
RequestUri: requesturi,
|
||||
Username: "",
|
||||
Organization: "",
|
||||
Action: action,
|
||||
}
|
||||
return &record
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
// 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 util
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var rePhoneCn *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
// https://learnku.com/articles/31543
|
||||
rePhoneCn, _ = regexp.Compile("^1(3\\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$")
|
||||
}
|
||||
|
||||
func IsEmailValid(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func IsPhoneCnValid(phone string) bool {
|
||||
return rePhoneCn.MatchString(phone)
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// 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 util
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func ParseInt(s string) int {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func GetOwnerAndNameFromId(id string) (string, string) {
|
||||
tokens := strings.Split(id, "/")
|
||||
if len(tokens) != 2 {
|
||||
panic(errors.New("GetOwnerAndNameFromId() error, wrong token count for ID: " + id))
|
||||
}
|
||||
|
||||
return tokens[0], tokens[1]
|
||||
}
|
||||
|
||||
func GenerateId() string {
|
||||
return uuid.NewString()
|
||||
}
|
||||
|
||||
func GetId(name string) string {
|
||||
return fmt.Sprintf("admin/%s", name)
|
||||
}
|
||||
|
||||
func GetMd5Hash(text string) string {
|
||||
hash := md5.Sum([]byte(text))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func IsStrsEmpty(strs ...string) bool {
|
||||
r := false
|
||||
for _, str := range strs {
|
||||
if len(str) == 0 {
|
||||
r = true
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func GetMaxLenStr(strs ...string) string {
|
||||
m := 0
|
||||
i := 0
|
||||
for j, str := range strs {
|
||||
l := len(str)
|
||||
if l > m {
|
||||
m = l
|
||||
i = j
|
||||
}
|
||||
}
|
||||
return strs[i]
|
||||
}
|
||||
|
||||
func GetMinLenStr(strs ...string) string {
|
||||
m := int(^uint(0) >> 1)
|
||||
i := 0
|
||||
for j, str := range strs {
|
||||
l := len(str)
|
||||
if l > m {
|
||||
m = l
|
||||
i = j
|
||||
}
|
||||
}
|
||||
return strs[i]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// 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 util
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetCurrentTime() string {
|
||||
timestamp := time.Now().Unix()
|
||||
tm := time.Unix(timestamp, 0)
|
||||
return tm.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func GetCurrentUnixTime() string {
|
||||
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
|
@ -0,0 +1,17 @@
|
|||
const CracoLessPlugin = require('craco-less');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: CracoLessPlugin,
|
||||
options: {
|
||||
lessLoaderOptions: {
|
||||
lessOptions: {
|
||||
modifyVars: { '@primary-color': 'rgb(45,120,213)' },
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
project_id: '463556'
|
||||
api_token_env: 'CROWDIN_PERSONAL_TOKEN'
|
||||
preserve_hierarchy: true
|
||||
files: [
|
||||
# JSON translation files
|
||||
{
|
||||
source: '/web/src/locales/en/data.json',
|
||||
translation: '/web/src/locales/%two_letters_code%/data.json',
|
||||
},
|
||||
]
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"name": "web",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@craco/craco": "^6.1.1",
|
||||
"@crowdin/cli": "^3.6.4",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"antd": "^4.15.5",
|
||||
"codemirror": "^5.61.1",
|
||||
"copy-to-clipboard": "^3.3.1",
|
||||
"craco-less": "^1.17.1",
|
||||
"i18next": "^19.8.9",
|
||||
"moment": "^2.29.1",
|
||||
"react": "^17.0.2",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-cropper": "^2.1.7",
|
||||
"react-device-detect": "^1.14.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-github-corner": "^2.5.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^11.8.7",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
"react-social-login-buttons": "^3.4.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "cross-env PORT=7001 craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "craco eject",
|
||||
"crowdin:sync": "crowdin upload && crowdin download"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.3"
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue