mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-21 17:53:51 +08:00
Compare commits
104 Commits
Author | SHA1 | Date | |
---|---|---|---|
3215b88eae | |||
9703f3f712 | |||
140737b2f6 | |||
b285144a64 | |||
49c6ce2221 | |||
2398e69012 | |||
ade9de8256 | |||
1bf5497d08 | |||
cf10738f45 | |||
ac00713c20 | |||
febb27f765 | |||
49a981f787 | |||
34b1945180 | |||
b320cca789 | |||
b38654a45a | |||
f77fafae24 | |||
8b6b5ffe81 | |||
a147fa3e0b | |||
9d03665523 | |||
0106c7f7fa | |||
6713dad0af | |||
6ef2b51782 | |||
1732cd8538 | |||
a10548fe73 | |||
f6a7888f83 | |||
93efaa5459 | |||
0bfe683108 | |||
8a4758c22d | |||
ee3b46e91c | |||
37744d6cd7 | |||
98defe617b | |||
96cbf51ca0 | |||
22b57fdd23 | |||
b68e291f37 | |||
9960b4933b | |||
432a5496f2 | |||
45db4deb6b | |||
3f53591751 | |||
d7569684f6 | |||
a616127909 | |||
f2e2b960ff | |||
fbc603876f | |||
9ea77c63d1 | |||
53243a30f3 | |||
cbdeb91ee8 | |||
2dd1dc582f | |||
f3d4b45a0f | |||
2ee4aebd96 | |||
150e3e30d5 | |||
1055d7781b | |||
1c296e9b6f | |||
3d80ec721f | |||
43d849086f | |||
69b144d80f | |||
52a66ef044 | |||
ec0a8e16f7 | |||
80a8000057 | |||
77091a3ae5 | |||
983da685a2 | |||
3d567c3d45 | |||
440d87d70c | |||
e4208d7fd9 | |||
4de716fef3 | |||
070aa8a65f | |||
684cbdb951 | |||
9aec69ef47 | |||
98411ef67b | |||
71279f548d | |||
0096e47351 | |||
814d3f749b | |||
ec0f457c7f | |||
0033ae1ff1 | |||
d06d7c5c09 | |||
23c4fd8183 | |||
e3558894c3 | |||
2fd2d88d20 | |||
d0c424db0a | |||
6a9d1e0fe5 | |||
938e8e2699 | |||
620383cf33 | |||
de6cd380eb | |||
7e0bce2d0f | |||
1461268a51 | |||
5ec49dc883 | |||
5c89705d9e | |||
06e3b8481f | |||
81a8b91e3f | |||
56787fab90 | |||
1319216625 | |||
6fe5c44c1c | |||
981908b0b6 | |||
03a281cb5d | |||
a8e541159b | |||
577bf91d25 | |||
329a6a8132 | |||
fba0866cd6 | |||
aab6a799fe | |||
b94d06fb07 | |||
f9cc6ed064 | |||
4cc9137637 | |||
d145ab780c | |||
687830697e | |||
111d1a5786 | |||
775dd9eb57 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -195,7 +195,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
target: STANDARD
|
target: STANDARD
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||||
|
|
||||||
@ -205,6 +205,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
target: ALLINONE
|
target: ALLINONE
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
|
||||||
|
61
.github/workflows/migrate.yml
vendored
61
.github/workflows/migrate.yml
vendored
@ -1,61 +0,0 @@
|
|||||||
name: Migration Test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'object/migrator**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'object/migrator**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
db-migrator-test:
|
|
||||||
name: db-migrator-test
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
mysql:
|
|
||||||
image: mysql:5.7
|
|
||||||
env:
|
|
||||||
MYSQL_DATABASE: casdoor
|
|
||||||
MYSQL_ROOT_PASSWORD: 123456
|
|
||||||
ports:
|
|
||||||
- 3306:3306
|
|
||||||
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: '^1.16.5'
|
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
- name: pull casdoor-master-latest
|
|
||||||
run: |
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install git
|
|
||||||
sudo apt install net-tools
|
|
||||||
sudo mkdir tmp
|
|
||||||
cd tmp
|
|
||||||
sudo git clone https://github.com/casdoor/casdoor.git
|
|
||||||
cd ..
|
|
||||||
working-directory: ./
|
|
||||||
- name: run casdoor-master-latest
|
|
||||||
run: |
|
|
||||||
sudo nohup go run main.go &
|
|
||||||
sudo sleep 2m
|
|
||||||
working-directory: ./tmp/casdoor
|
|
||||||
- name: stop casdoor-master-latest
|
|
||||||
run: |
|
|
||||||
sudo kill -9 `sudo netstat -anltp | grep 8000 | awk '{print $7}' | cut -d / -f 1`
|
|
||||||
working-directory: ./
|
|
||||||
- name: run casdoor-current-version
|
|
||||||
run: |
|
|
||||||
sudo nohup go run ./main.go &
|
|
||||||
sudo sleep 2m
|
|
||||||
working-directory: ./
|
|
||||||
- name: test port-8000
|
|
||||||
run: |
|
|
||||||
if [[ `sudo netstat -anltp | grep 8000 | awk '{print $7}'` == "" ]];then echo 'db-migrator-test fail' && exit 1;fi;
|
|
||||||
echo 'db-migrator-test pass'
|
|
||||||
working-directory: ./
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -30,5 +30,4 @@ commentsRouter*.go
|
|||||||
|
|
||||||
# ignore build result
|
# ignore build result
|
||||||
casdoor
|
casdoor
|
||||||
server_linux_arm64
|
server
|
||||||
server_linux_amd64
|
|
||||||
|
11
Dockerfile
11
Dockerfile
@ -1,7 +1,6 @@
|
|||||||
FROM node:16.18.0 AS FRONT
|
FROM node:16.18.0 AS FRONT
|
||||||
WORKDIR /web
|
WORKDIR /web
|
||||||
COPY ./web .
|
COPY ./web .
|
||||||
RUN yarn config set registry https://registry.npmmirror.com
|
|
||||||
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
|
||||||
|
|
||||||
|
|
||||||
@ -14,9 +13,6 @@ RUN go test -v -run TestGetVersionInfo ./util/system_test.go ./util/system.go >
|
|||||||
FROM alpine:latest AS STANDARD
|
FROM alpine:latest AS STANDARD
|
||||||
LABEL MAINTAINER="https://casdoor.org/"
|
LABEL MAINTAINER="https://casdoor.org/"
|
||||||
ARG USER=casdoor
|
ARG USER=casdoor
|
||||||
ARG TARGETOS
|
|
||||||
ARG TARGETARCH
|
|
||||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
|
||||||
|
|
||||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||||
RUN apk add --update sudo
|
RUN apk add --update sudo
|
||||||
@ -31,7 +27,7 @@ RUN adduser -D $USER -u 1000 \
|
|||||||
|
|
||||||
USER 1000
|
USER 1000
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server ./server
|
||||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
|
||||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||||
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
|
||||||
@ -50,15 +46,12 @@ RUN apt update \
|
|||||||
|
|
||||||
FROM db AS ALLINONE
|
FROM db AS ALLINONE
|
||||||
LABEL MAINTAINER="https://casdoor.org/"
|
LABEL MAINTAINER="https://casdoor.org/"
|
||||||
ARG TARGETOS
|
|
||||||
ARG TARGETARCH
|
|
||||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
|
||||||
|
|
||||||
RUN apt update
|
RUN apt update
|
||||||
RUN apt install -y ca-certificates && update-ca-certificates
|
RUN apt install -y ca-certificates && update-ca-certificates
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
COPY --from=BACK /go/src/casdoor/server_${BUILDX_ARCH} ./server
|
COPY --from=BACK /go/src/casdoor/server ./server
|
||||||
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
COPY --from=BACK /go/src/casdoor/swagger ./swagger
|
||||||
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||||
|
@ -81,6 +81,7 @@ p, *, *, GET, /api/get-saml-login, *, *
|
|||||||
p, *, *, POST, /api/acs, *, *
|
p, *, *, POST, /api/acs, *, *
|
||||||
p, *, *, GET, /api/saml/metadata, *, *
|
p, *, *, GET, /api/saml/metadata, *, *
|
||||||
p, *, *, *, /cas, *, *
|
p, *, *, *, /cas, *, *
|
||||||
|
p, *, *, *, /scim, *, *
|
||||||
p, *, *, *, /api/webauthn, *, *
|
p, *, *, *, /api/webauthn, *, *
|
||||||
p, *, *, GET, /api/get-release, *, *
|
p, *, *, GET, /api/get-release, *, *
|
||||||
p, *, *, GET, /api/get-default-application, *, *
|
p, *, *, GET, /api/get-default-application, *, *
|
||||||
@ -126,8 +127,14 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
if user != nil {
|
||||||
return true
|
if user.IsDeleted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||||
@ -140,7 +147,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
|||||||
|
|
||||||
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
|
||||||
if method == "POST" {
|
if method == "POST" {
|
||||||
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" {
|
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" {
|
||||||
return true
|
return true
|
||||||
} else if urlPath == "/api/update-user" {
|
} else if urlPath == "/api/update-user" {
|
||||||
// Allow ordinary users to update their own information
|
// Allow ordinary users to update their own information
|
||||||
|
3
build.sh
3
build.sh
@ -8,5 +8,4 @@ else
|
|||||||
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
|
||||||
export GOPROXY="https://goproxy.cn,direct"
|
export GOPROXY="https://goproxy.cn,direct"
|
||||||
fi
|
fi
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .
|
|
||||||
|
@ -8,18 +8,23 @@ dbName = casdoor
|
|||||||
tableNamePrefix =
|
tableNamePrefix =
|
||||||
showSql = false
|
showSql = false
|
||||||
redisEndpoint =
|
redisEndpoint =
|
||||||
defaultStorageProvider =
|
defaultStorageProvider =
|
||||||
isCloudIntranet = false
|
isCloudIntranet = false
|
||||||
authState = "casdoor"
|
authState = "casdoor"
|
||||||
socks5Proxy = "127.0.0.1:10808"
|
socks5Proxy = "127.0.0.1:10808"
|
||||||
verificationCodeTimeout = 10
|
verificationCodeTimeout = 10
|
||||||
initScore = 2000
|
initScore = 0
|
||||||
logPostOnly = true
|
logPostOnly = true
|
||||||
origin =
|
origin =
|
||||||
|
originFrontend =
|
||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
isDemoMode = false
|
isDemoMode = false
|
||||||
batchSize = 100
|
batchSize = 100
|
||||||
|
enableGzip = true
|
||||||
ldapServerPort = 389
|
ldapServerPort = 389
|
||||||
|
radiusServerPort = 1812
|
||||||
|
radiusSecret = "secret"
|
||||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||||
initDataFile = "./init_data.json"
|
initDataFile = "./init_data.json"
|
||||||
|
frontendBaseDir = "../casdoor"
|
@ -477,11 +477,10 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
|
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userInfo := &idp.UserInfo{}
|
userInfo := &idp.UserInfo{}
|
||||||
if provider.Category == "SAML" {
|
if provider.Category == "SAML" {
|
||||||
// SAML
|
// SAML
|
||||||
userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
|
userInfo, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -489,7 +488,11 @@ func (c *ApiController) Login() {
|
|||||||
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
||||||
// OAuth
|
// OAuth
|
||||||
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
|
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
|
||||||
idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
idProvider, err := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
if idProvider == nil {
|
if idProvider == nil {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
||||||
return
|
return
|
||||||
@ -524,7 +527,8 @@ func (c *ApiController) Login() {
|
|||||||
if authForm.Method == "signup" {
|
if authForm.Method == "signup" {
|
||||||
user := &object.User{}
|
user := &object.User{}
|
||||||
if provider.Category == "SAML" {
|
if provider.Category == "SAML" {
|
||||||
user, err = object.GetUser(util.GetId(application.Organization, userInfo.Id))
|
// The userInfo.Id is the NameID in SAML response, it could be name / email / phone
|
||||||
|
user, err = object.GetUserByFields(application.Organization, userInfo.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -615,11 +619,16 @@ func (c *ApiController) Login() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userId := userInfo.Id
|
||||||
|
if userId == "" {
|
||||||
|
userId = util.GenerateId()
|
||||||
|
}
|
||||||
|
|
||||||
user = &object.User{
|
user = &object.User{
|
||||||
Owner: application.Organization,
|
Owner: application.Organization,
|
||||||
Name: userInfo.Username,
|
Name: userInfo.Username,
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
Id: util.GenerateId(),
|
Id: userId,
|
||||||
Type: "normal-user",
|
Type: "normal-user",
|
||||||
DisplayName: userInfo.DisplayName,
|
DisplayName: userInfo.DisplayName,
|
||||||
Avatar: userInfo.AvatarUrl,
|
Avatar: userInfo.AvatarUrl,
|
||||||
@ -646,6 +655,15 @@ func (c *ApiController) Login() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user)))
|
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if providerItem.SignupGroup != "" {
|
||||||
|
user.Groups = []string{providerItem.SignupGroup}
|
||||||
|
_, err = object.UpdateUser(user.GetId(), user, []string{"groups"}, false)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sync info from 3rd-party if possible
|
// sync info from 3rd-party if possible
|
||||||
@ -674,6 +692,7 @@ func (c *ApiController) Login() {
|
|||||||
record2.User = user.Name
|
record2.User = user.Name
|
||||||
util.SafeGoroutine(func() { object.AddRecord(record2) })
|
util.SafeGoroutine(func() { object.AddRecord(record2) })
|
||||||
} else if provider.Category == "SAML" {
|
} else if provider.Category == "SAML" {
|
||||||
|
// TODO: since we get the user info from SAML response, we can try to create the user
|
||||||
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
|
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
|
||||||
}
|
}
|
||||||
// resp = &Response{Status: "ok", Msg: "", Data: res}
|
// resp = &Response{Status: "ok", Msg: "", Data: res}
|
||||||
|
@ -37,6 +37,11 @@ func (c *ApiController) Enforce() {
|
|||||||
resourceId := c.Input().Get("resourceId")
|
resourceId := c.Input().Get("resourceId")
|
||||||
enforcerId := c.Input().Get("enforcerId")
|
enforcerId := c.Input().Get("enforcerId")
|
||||||
|
|
||||||
|
if len(c.Ctx.Input.RequestBody) == 0 {
|
||||||
|
c.ResponseError("The request body should not be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var request object.CasbinRequest
|
var request object.CasbinRequest
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -191,7 +191,7 @@ func (c *ApiController) UpdatePolicy() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := object.UpdatePolicy(id, util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]))
|
affected, err := object.UpdatePolicy(id, policies[0].Ptype, util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -210,7 +210,7 @@ func (c *ApiController) AddPolicy() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := object.AddPolicy(id, util.CasbinToSlice(policy))
|
affected, err := object.AddPolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
@ -229,7 +229,7 @@ func (c *ApiController) RemovePolicy() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := object.RemovePolicy(id, util.CasbinToSlice(policy))
|
affected, err := object.RemovePolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -16,7 +16,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -83,11 +82,8 @@ func (c *ApiController) GetPlan() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if plan == nil {
|
|
||||||
c.ResponseError(fmt.Sprintf(c.T("plan:The plan: %s does not exist"), id))
|
if plan != nil && includeOption {
|
||||||
return
|
|
||||||
}
|
|
||||||
if includeOption {
|
|
||||||
options, err := object.GetPermissionsByRole(plan.Role)
|
options, err := object.GetPermissionsByRole(plan.Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
@ -97,11 +93,9 @@ func (c *ApiController) GetPlan() {
|
|||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
plan.Options = append(plan.Options, option.DisplayName)
|
plan.Options = append(plan.Options, option.DisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(plan)
|
|
||||||
} else {
|
|
||||||
c.ResponseOk(plan)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(plan)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlan
|
// UpdatePlan
|
||||||
|
@ -16,7 +16,6 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@ -81,10 +80,7 @@ func (c *ApiController) GetPricing() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pricing == nil {
|
|
||||||
c.ResponseError(fmt.Sprintf(c.T("pricing:The pricing: %s does not exist"), id))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.ResponseOk(pricing)
|
c.ResponseOk(pricing)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,6 +272,11 @@ func (c *ApiController) UploadResource() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if username == "Built-in-Untracked" {
|
||||||
|
c.ResponseOk(fileUrl, objectKey)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if createdTime == "" {
|
if createdTime == "" {
|
||||||
createdTime = util.GetCurrentTime()
|
createdTime = util.GetCurrentTime()
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,13 @@ func (c *ApiController) GetSamlMeta() {
|
|||||||
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
metadata, _ := object.GetSamlMeta(application, host)
|
|
||||||
|
metadata, err := object.GetSamlMeta(application, host)
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.Data["xml"] = metadata
|
c.Data["xml"] = metadata
|
||||||
c.ServeXML()
|
c.ServeXML()
|
||||||
}
|
}
|
||||||
|
27
controllers/scim.go
Normal file
27
controllers/scim.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/scim"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *RootController) HandleScim() {
|
||||||
|
path := c.Ctx.Request.URL.Path
|
||||||
|
c.Ctx.Request.URL.Path = strings.TrimPrefix(path, "/scim")
|
||||||
|
scim.Server.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
|
||||||
|
}
|
@ -160,35 +160,51 @@ func (c *ApiController) GetUser() {
|
|||||||
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
|
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if owner == "" {
|
var user *object.User
|
||||||
owner = util.GetOwnerFromId(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
if id == "" && owner == "" {
|
||||||
if err != nil {
|
switch {
|
||||||
c.ResponseError(err.Error())
|
case email != "":
|
||||||
return
|
user, err = object.GetUserByEmailOnly(email)
|
||||||
}
|
case phone != "":
|
||||||
|
user, err = object.GetUserByPhoneOnly(phone)
|
||||||
|
case userId != "":
|
||||||
|
user, err = object.GetUserByUserIdOnly(userId)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if owner == "" {
|
||||||
|
owner = util.GetOwnerFromId(id)
|
||||||
|
}
|
||||||
|
|
||||||
if !organization.IsProfilePublic {
|
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
||||||
requestUserId := c.GetSessionUsername()
|
if err != nil {
|
||||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
|
||||||
if !hasPermission {
|
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
if organization == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var user *object.User
|
if !organization.IsProfilePublic {
|
||||||
switch {
|
requestUserId := c.GetSessionUsername()
|
||||||
case email != "":
|
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||||
user, err = object.GetUserByEmail(owner, email)
|
if !hasPermission {
|
||||||
case phone != "":
|
c.ResponseError(err.Error())
|
||||||
user, err = object.GetUserByPhone(owner, phone)
|
return
|
||||||
case userId != "":
|
}
|
||||||
user = userFromUserId
|
}
|
||||||
default:
|
|
||||||
user, err = object.GetUser(id)
|
switch {
|
||||||
|
case email != "":
|
||||||
|
user, err = object.GetUserByEmail(owner, email)
|
||||||
|
case phone != "":
|
||||||
|
user, err = object.GetUserByPhone(owner, phone)
|
||||||
|
case userId != "":
|
||||||
|
user = userFromUserId
|
||||||
|
default:
|
||||||
|
user, err = object.GetUser(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -466,7 +482,7 @@ func (c *ApiController) SetPassword() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if code == "" {
|
||||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
c.ResponseError(msg)
|
c.ResponseError(msg)
|
||||||
@ -560,11 +576,11 @@ func (c *ApiController) GetUserCount() {
|
|||||||
c.ResponseOk(count)
|
c.ResponseOk(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddUserkeys
|
// AddUserKeys
|
||||||
// @Title AddUserkeys
|
// @Title AddUserKeys
|
||||||
// @router /add-user-keys [post]
|
// @router /add-user-keys [post]
|
||||||
// @Tag User API
|
// @Tag User API
|
||||||
func (c *ApiController) AddUserkeys() {
|
func (c *ApiController) AddUserKeys() {
|
||||||
var user object.User
|
var user object.User
|
||||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -573,7 +589,7 @@ func (c *ApiController) AddUserkeys() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isAdmin := c.IsAdmin()
|
isAdmin := c.IsAdmin()
|
||||||
affected, err := object.AddUserkeys(&user, isAdmin)
|
affected, err := object.AddUserKeys(&user, isAdmin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
|
@ -96,6 +96,13 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(userId, "app/") {
|
||||||
|
tmpUserId := c.Input().Get("userId")
|
||||||
|
if tmpUserId != "" {
|
||||||
|
userId = tmpUserId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user, err := object.GetUser(userId)
|
user, err := object.GetUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
|
@ -142,6 +142,10 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("please add an Email provider to the \"Providers\" list for the application: %s", application.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
|
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
|
||||||
case object.VerifyTypePhone:
|
case object.VerifyTypePhone:
|
||||||
@ -184,6 +188,10 @@ func (c *ApiController) SendVerificationCode() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if provider == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf("please add a SMS provider to the \"Providers\" list for the application: %s", application.Name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
|
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
|
||||||
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))
|
||||||
|
@ -123,7 +123,9 @@ func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
|
|||||||
|
|
||||||
bodyBuffer := bytes.NewBuffer(postBody)
|
bodyBuffer := bytes.NewBuffer(postBody)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", a.Endpoint+sendEmailEndpoint+"?api-version="+apiVersion, bodyBuffer)
|
endpoint := strings.TrimSuffix(a.Endpoint, "/")
|
||||||
|
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
|
||||||
|
req, err := http.NewRequest("POST", url, bodyBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating AzureACS API request: %s", err)
|
return fmt.Errorf("error creating AzureACS API request: %s", err)
|
||||||
}
|
}
|
||||||
@ -149,7 +151,7 @@ func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Response error Handling
|
// Response error Handling
|
||||||
if resp.StatusCode == http.StatusBadRequest {
|
if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
|
||||||
commError := ErrorResponse{}
|
commError := ErrorResponse{}
|
||||||
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&commError)
|
err = json.NewDecoder(resp.Body).Decode(&commError)
|
||||||
|
@ -18,9 +18,9 @@ type EmailProvider interface {
|
|||||||
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetEmailProvider(typ string, clientId string, clientSecret string, appId string, host string, port int, disableSsl bool) EmailProvider {
|
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool) EmailProvider {
|
||||||
if typ == "Azure ACS" {
|
if typ == "Azure ACS" {
|
||||||
return NewAzureACSEmailProvider(appId, host)
|
return NewAzureACSEmailProvider(clientSecret, host)
|
||||||
} else {
|
} else {
|
||||||
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||||
}
|
}
|
||||||
|
7
go.mod
7
go.mod
@ -11,15 +11,16 @@ require (
|
|||||||
github.com/beevik/etree v1.1.0
|
github.com/beevik/etree v1.1.0
|
||||||
github.com/casbin/casbin v1.9.1 // indirect
|
github.com/casbin/casbin v1.9.1 // indirect
|
||||||
github.com/casbin/casbin/v2 v2.77.2
|
github.com/casbin/casbin/v2 v2.77.2
|
||||||
github.com/casdoor/go-sms-sender v0.14.0
|
github.com/casdoor/go-sms-sender v0.15.0
|
||||||
github.com/casdoor/gomail/v2 v2.0.1
|
github.com/casdoor/gomail/v2 v2.0.1
|
||||||
github.com/casdoor/notify v0.43.0
|
github.com/casdoor/notify v0.45.0
|
||||||
github.com/casdoor/oss v1.3.0
|
github.com/casdoor/oss v1.3.0
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
github.com/casdoor/xorm-adapter/v3 v3.0.4
|
||||||
github.com/casvisor/casvisor-go-sdk v1.0.3
|
github.com/casvisor/casvisor-go-sdk v1.0.3
|
||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/denisenkom/go-mssqldb v0.9.0
|
github.com/denisenkom/go-mssqldb v0.9.0
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
|
||||||
|
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
|
||||||
github.com/fogleman/gg v1.3.0
|
github.com/fogleman/gg v1.3.0
|
||||||
github.com/forestmgy/ldapserver v1.1.0
|
github.com/forestmgy/ldapserver v1.1.0
|
||||||
github.com/go-git/go-git/v5 v5.6.0
|
github.com/go-git/go-git/v5 v5.6.0
|
||||||
@ -63,9 +64,11 @@ require (
|
|||||||
golang.org/x/crypto v0.12.0
|
golang.org/x/crypto v0.12.0
|
||||||
golang.org/x/net v0.14.0
|
golang.org/x/net v0.14.0
|
||||||
golang.org/x/oauth2 v0.11.0
|
golang.org/x/oauth2 v0.11.0
|
||||||
|
golang.org/x/text v0.13.0 // indirect
|
||||||
google.golang.org/api v0.138.0
|
google.golang.org/api v0.138.0
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/square/go-jose.v2 v2.6.0
|
gopkg.in/square/go-jose.v2 v2.6.0
|
||||||
|
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
||||||
maunium.net/go/mautrix v0.16.0
|
maunium.net/go/mautrix v0.16.0
|
||||||
modernc.org/sqlite v1.18.2
|
modernc.org/sqlite v1.18.2
|
||||||
)
|
)
|
||||||
|
28
go.sum
28
go.sum
@ -917,16 +917,17 @@ github.com/casbin/casbin v1.9.1 h1:ucjbS5zTrmSLtH4XogqOG920Poe6QatdXtz1FEbApeM=
|
|||||||
github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
|
github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
|
||||||
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw=
|
|
||||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||||
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
|
||||||
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
|
||||||
github.com/casdoor/go-sms-sender v0.14.0 h1:yqrzWIHUg64OYPynzF5Fr0XDuCWIWxtXIjOQAAkRKuw=
|
github.com/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
||||||
github.com/casdoor/go-sms-sender v0.14.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs=
|
github.com/casdoor/go-reddit/v2 v2.1.0/go.mod h1:eagkvwlZ4Hcsuc/uQsLHYEulz5jN65SVSwV/AIE7zsc=
|
||||||
|
github.com/casdoor/go-sms-sender v0.15.0 h1:9SWj/jd5c7jIteTRUrqbkpWbtIXMDv+t1CEfDhO06m0=
|
||||||
|
github.com/casdoor/go-sms-sender v0.15.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs=
|
||||||
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
|
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
|
||||||
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
github.com/casdoor/gomail/v2 v2.0.1/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||||
github.com/casdoor/notify v0.43.0 h1:NukyVZ9l7d2TSlB5YWKJyDsPmHCvwKQVi9rWDprtcU4=
|
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||||
github.com/casdoor/notify v0.43.0/go.mod h1:qDmQM5vr2uU01BEuDC6pY6ryahSU11cXPqlHFW232Do=
|
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
|
||||||
github.com/casdoor/oss v1.3.0 h1:D5pcz65tJRqJrWY11Ks7D9LUsmlhqqMHugjDhSxWTvk=
|
github.com/casdoor/oss v1.3.0 h1:D5pcz65tJRqJrWY11Ks7D9LUsmlhqqMHugjDhSxWTvk=
|
||||||
github.com/casdoor/oss v1.3.0/go.mod h1:YOi6KpG1pZHTkiy9AYaqI0UaPfE7YkaA07d89f1idqY=
|
github.com/casdoor/oss v1.3.0/go.mod h1:YOi6KpG1pZHTkiy9AYaqI0UaPfE7YkaA07d89f1idqY=
|
||||||
github.com/casdoor/xorm-adapter/v3 v3.0.4 h1:vB04Ao8n2jA7aFBI9F+gGXo9+Aa1IQP6mTdo50913DM=
|
github.com/casdoor/xorm-adapter/v3 v3.0.4 h1:vB04Ao8n2jA7aFBI9F+gGXo9+Aa1IQP6mTdo50913DM=
|
||||||
@ -1011,6 +1012,10 @@ github.com/dghubble/sling v1.4.0/go.mod h1:0r40aNsU9EdDUVBNhfCstAtFgutjgJGYbO1oN
|
|||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/di-wu/parser v0.2.2 h1:I9oHJ8spBXOeL7Wps0ffkFFFiXJf/pk7NX9lcAMqRMU=
|
||||||
|
github.com/di-wu/parser v0.2.2/go.mod h1:SLp58pW6WamdmznrVRrw2NTyn4wAvT9rrEFynKX7nYo=
|
||||||
|
github.com/di-wu/xsd-datetime v1.0.0 h1:vZoGNkbzpBNoc+JyfVLEbutNDNydYV8XwHeV7eUJoxI=
|
||||||
|
github.com/di-wu/xsd-datetime v1.0.0/go.mod h1:i3iEhrP3WchwseOBeIdW/zxeoleXTOzx1WyDXgdmOww=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 h1:uh1GSejOhVPRQmoXZxY82TiewZB8QXiaP1skL7Nun3Y=
|
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 h1:uh1GSejOhVPRQmoXZxY82TiewZB8QXiaP1skL7Nun3Y=
|
||||||
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7/go.mod h1:ncTaGuXc5v7AuiVekeJ0Nwh8Bf4cudukoj0qM/15UZE=
|
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7/go.mod h1:ncTaGuXc5v7AuiVekeJ0Nwh8Bf4cudukoj0qM/15UZE=
|
||||||
@ -1027,6 +1032,8 @@ github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox
|
|||||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
|
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3 h1:+zrUtdBUJpY9qptMaaY3CA3T/lBI2+QqfUbzM2uxJss=
|
||||||
|
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3/go.mod h1:JkjcmqbLW+khwt2fmBPJFBhx2zGZ8XobRZ+O0VhlwWo=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
@ -1694,6 +1701,8 @@ github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZ
|
|||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/scim2/filter-parser/v2 v2.2.0 h1:QGadEcsmypxg8gYChRSM2j1edLyE/2j72j+hdmI4BJM=
|
||||||
|
github.com/scim2/filter-parser/v2 v2.2.0/go.mod h1:jWnkDToqX/Y0ugz0P5VvpVEUKcWcyHHj+X+je9ce5JA=
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||||
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
|
||||||
github.com/sendgrid/sendgrid-go v3.13.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
github.com/sendgrid/sendgrid-go v3.13.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
|
||||||
@ -1795,7 +1804,6 @@ github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg=
|
|||||||
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|
||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
@ -1823,8 +1831,6 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
|||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/utahta/go-linenotify v0.5.0 h1:E1tJaB/XhqRY/iz203FD0MaHm10DjQPOq5/Mem2A3Gs=
|
github.com/utahta/go-linenotify v0.5.0 h1:E1tJaB/XhqRY/iz203FD0MaHm10DjQPOq5/Mem2A3Gs=
|
||||||
github.com/utahta/go-linenotify v0.5.0/go.mod h1:KsvBXil2wx+ByaCR0e+IZKTbp4pDesc7yjzRigLf6pE=
|
github.com/utahta/go-linenotify v0.5.0/go.mod h1:KsvBXil2wx+ByaCR0e+IZKTbp4pDesc7yjzRigLf6pE=
|
||||||
github.com/vartanbeno/go-reddit/v2 v2.0.1 h1:P6ITpf5YHjdy7DHZIbUIDn/iNAoGcEoDQnMa+L4vutw=
|
|
||||||
github.com/vartanbeno/go-reddit/v2 v2.0.1/go.mod h1:758/S10hwZSLm43NPtwoNQdZFSg3sjB5745Mwjb0ANI=
|
|
||||||
github.com/volcengine/volc-sdk-golang v1.0.117 h1:ykFVSwsVq9qvIoWP9jeP+VKNAUjrblAdsZl46yVWiH8=
|
github.com/volcengine/volc-sdk-golang v1.0.117 h1:ykFVSwsVq9qvIoWP9jeP+VKNAUjrblAdsZl46yVWiH8=
|
||||||
github.com/volcengine/volc-sdk-golang v1.0.117/go.mod h1:ojXSFvj404o2UKnZR9k9LUUWIUU+9XtlRlzk2+UFc/M=
|
github.com/volcengine/volc-sdk-golang v1.0.117/go.mod h1:ojXSFvj404o2UKnZR9k9LUUWIUU+9XtlRlzk2+UFc/M=
|
||||||
github.com/wendal/errors v0.0.0-20181209125328-7f31f4b264ec/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
github.com/wendal/errors v0.0.0-20181209125328-7f31f4b264ec/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||||
@ -1912,6 +1918,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
@ -2300,8 +2307,9 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -2768,6 +2776,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
|
|||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||||
|
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 h1:2NDro2Jzkrqfngy/sA5GVnChs7fx8EzcQKFi/lI2cfg=
|
||||||
|
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68/go.mod h1:pFWM9De99EY9TPVyHIyA56QmoRViVck/x41WFkUlc9A=
|
||||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
|
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
|
||||||
"Unauthorized operation": "Opération non autorisée",
|
"Unauthorized operation": "Opération non autorisée",
|
||||||
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
|
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
|
||||||
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
|
"User's tag: %s is not listed in the application's tags": "Le tag de l’utilisateur %s n’est pas répertorié dans les tags de l’application"
|
||||||
},
|
},
|
||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
|
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
"Phone number is invalid": "Le numéro de téléphone est invalide",
|
||||||
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
|
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
|
||||||
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
|
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
|
||||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
"The user: %s doesn't exist in LDAP server": "L'utilisateur %s n'existe pas sur le serveur LDAP",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
|
||||||
"Username already exists": "Nom d'utilisateur existe déjà",
|
"Username already exists": "Nom d'utilisateur existe déjà",
|
||||||
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
|
||||||
@ -53,7 +53,7 @@
|
|||||||
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
|
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
|
||||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
|
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
|
||||||
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
|
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
|
||||||
"password or code is incorrect": "password or code is incorrect",
|
"password or code is incorrect": "mot de passe ou code invalide",
|
||||||
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
|
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
|
||||||
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
||||||
},
|
},
|
||||||
@ -61,8 +61,8 @@
|
|||||||
"Missing parameter": "Paramètre manquant",
|
"Missing parameter": "Paramètre manquant",
|
||||||
"Please login first": "Veuillez d'abord vous connecter",
|
"Please login first": "Veuillez d'abord vous connecter",
|
||||||
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
|
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
|
||||||
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider",
|
"don't support captchaProvider: ": "ne prend pas en charge captchaProvider: ",
|
||||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
"this operation is not allowed in demo mode": "cette opération n’est pas autorisée en mode démo"
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "Le serveur LDAP existe"
|
"Ldap server exist": "Le serveur LDAP existe"
|
||||||
|
@ -24,14 +24,6 @@
|
|||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
},
|
},
|
||||||
"chat": {
|
|
||||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
|
||||||
"The chat: %s is not found": "The chat: %s is not found",
|
|
||||||
"The message is invalid": "The message is invalid",
|
|
||||||
"The message: %s is not found": "The message: %s is not found",
|
|
||||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
|
||||||
"The provider: %s is not found": "The provider: %s is not found"
|
|
||||||
},
|
|
||||||
"check": {
|
"check": {
|
||||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
@ -24,14 +24,6 @@
|
|||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
},
|
},
|
||||||
"chat": {
|
|
||||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
|
||||||
"The chat: %s is not found": "The chat: %s is not found",
|
|
||||||
"The message is invalid": "The message is invalid",
|
|
||||||
"The message: %s is not found": "The message: %s is not found",
|
|
||||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
|
||||||
"The provider: %s is not found": "The provider: %s is not found"
|
|
||||||
},
|
|
||||||
"check": {
|
"check": {
|
||||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
@ -24,14 +24,6 @@
|
|||||||
"cas": {
|
"cas": {
|
||||||
"Service %s and %s do not match": "Service %s and %s do not match"
|
"Service %s and %s do not match": "Service %s and %s do not match"
|
||||||
},
|
},
|
||||||
"chat": {
|
|
||||||
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
|
|
||||||
"The chat: %s is not found": "The chat: %s is not found",
|
|
||||||
"The message is invalid": "The message is invalid",
|
|
||||||
"The message: %s is not found": "The message: %s is not found",
|
|
||||||
"The provider: %s is invalid": "The provider: %s is invalid",
|
|
||||||
"The provider: %s is not found": "The provider: %s is not found"
|
|
||||||
},
|
|
||||||
"check": {
|
"check": {
|
||||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
"Phone number is invalid": "无效手机号",
|
"Phone number is invalid": "无效手机号",
|
||||||
"Session outdated, please login again": "会话已过期,请重新登录",
|
"Session outdated, please login again": "会话已过期,请重新登录",
|
||||||
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
|
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
|
||||||
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
|
"The user: %s doesn't exist in LDAP server": "用户: %s 在LDAP服务器中未找到",
|
||||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
||||||
"Username already exists": "用户名已存在",
|
"Username already exists": "用户名已存在",
|
||||||
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
||||||
@ -62,7 +62,7 @@
|
|||||||
"Please login first": "请先登录",
|
"Please login first": "请先登录",
|
||||||
"The user: %s doesn't exist": "用户: %s不存在",
|
"The user: %s doesn't exist": "用户: %s不存在",
|
||||||
"don't support captchaProvider: ": "不支持验证码提供商: ",
|
"don't support captchaProvider: ": "不支持验证码提供商: ",
|
||||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
"this operation is not allowed in demo mode": "demo模式下不允许该操作"
|
||||||
},
|
},
|
||||||
"ldap": {
|
"ldap": {
|
||||||
"Ldap server exist": "LDAP服务器已存在"
|
"Ldap server exist": "LDAP服务器已存在"
|
||||||
|
20
idp/adfs.go
20
idp/adfs.go
@ -19,7 +19,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
@ -84,6 +83,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
|||||||
payload.Set("code", code)
|
payload.Set("code", code)
|
||||||
payload.Set("grant_type", "authorization_code")
|
payload.Set("grant_type", "authorization_code")
|
||||||
payload.Set("client_id", idp.Config.ClientID)
|
payload.Set("client_id", idp.Config.ClientID)
|
||||||
|
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||||
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||||
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -118,11 +118,25 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
keyset, err := jwk.ParseKey(body)
|
var respKeys struct {
|
||||||
|
Keys []interface{} `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(body, &respKeys); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respKey, err := json.Marshal(&(respKeys.Keys[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keyset, err := jwk.ParseKey(respKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
tokenSrc := []byte(token.AccessToken)
|
tokenSrc := []byte(token.AccessToken)
|
||||||
publicKey, _ := keyset.PublicKey()
|
publicKey, _ := keyset.PublicKey()
|
||||||
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))
|
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))
|
||||||
|
24
idp/goth.go
24
idp/goth.go
@ -89,7 +89,7 @@ type GothIdProvider struct {
|
|||||||
Session goth.Session
|
Session goth.Session
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string, hostUrl string) *GothIdProvider {
|
func NewGothIdProvider(providerType string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, redirectUrl string, hostUrl string) (*GothIdProvider, error) {
|
||||||
var idp GothIdProvider
|
var idp GothIdProvider
|
||||||
switch providerType {
|
switch providerType {
|
||||||
case "Amazon":
|
case "Amazon":
|
||||||
@ -101,8 +101,24 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
if !strings.Contains(redirectUrl, "/api/callback") {
|
if !strings.Contains(redirectUrl, "/api/callback") {
|
||||||
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
|
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iat := time.Now().Unix()
|
||||||
|
exp := iat + 60*60
|
||||||
|
sp := apple.SecretParams{
|
||||||
|
ClientId: clientId,
|
||||||
|
TeamId: clientSecret,
|
||||||
|
KeyId: clientId2,
|
||||||
|
PKCS8PrivateKey: clientSecret2,
|
||||||
|
Iat: int(iat),
|
||||||
|
Exp: int(exp),
|
||||||
|
}
|
||||||
|
secret, err := apple.MakeSecret(sp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
idp = GothIdProvider{
|
idp = GothIdProvider{
|
||||||
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
|
Provider: apple.New(clientId, *secret, redirectUrl, nil),
|
||||||
Session: &apple.Session{},
|
Session: &apple.Session{},
|
||||||
}
|
}
|
||||||
case "AzureAD":
|
case "AzureAD":
|
||||||
@ -386,10 +402,10 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
|||||||
Session: &zoom.Session{},
|
Session: &zoom.Session{},
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &idp
|
return &idp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHttpClient
|
// SetHttpClient
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package idp
|
package idp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -33,13 +34,15 @@ type UserInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ProviderInfo struct {
|
type ProviderInfo struct {
|
||||||
Type string
|
Type string
|
||||||
SubType string
|
SubType string
|
||||||
ClientId string
|
ClientId string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
AppId string
|
ClientId2 string
|
||||||
HostUrl string
|
ClientSecret2 string
|
||||||
RedirectUrl string
|
AppId string
|
||||||
|
HostUrl string
|
||||||
|
RedirectUrl string
|
||||||
|
|
||||||
TokenURL string
|
TokenURL string
|
||||||
AuthURL string
|
AuthURL string
|
||||||
@ -53,71 +56,71 @@ type IdProvider interface {
|
|||||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
|
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) {
|
||||||
switch idpInfo.Type {
|
switch idpInfo.Type {
|
||||||
case "GitHub":
|
case "GitHub":
|
||||||
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Google":
|
case "Google":
|
||||||
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "QQ":
|
case "QQ":
|
||||||
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "WeChat":
|
case "WeChat":
|
||||||
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Facebook":
|
case "Facebook":
|
||||||
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "DingTalk":
|
case "DingTalk":
|
||||||
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Weibo":
|
case "Weibo":
|
||||||
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Gitee":
|
case "Gitee":
|
||||||
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "LinkedIn":
|
case "LinkedIn":
|
||||||
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "WeCom":
|
case "WeCom":
|
||||||
if idpInfo.SubType == "Internal" {
|
if idpInfo.SubType == "Internal" {
|
||||||
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
} else if idpInfo.SubType == "Third-party" {
|
} else if idpInfo.SubType == "Third-party" {
|
||||||
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
|
||||||
}
|
}
|
||||||
case "Lark":
|
case "Lark":
|
||||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "GitLab":
|
case "GitLab":
|
||||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Adfs":
|
case "ADFS":
|
||||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Baidu":
|
case "Baidu":
|
||||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Alipay":
|
case "Alipay":
|
||||||
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Custom":
|
case "Custom":
|
||||||
return NewCustomIdProvider(idpInfo, redirectUrl)
|
return NewCustomIdProvider(idpInfo, redirectUrl), nil
|
||||||
case "Infoflow":
|
case "Infoflow":
|
||||||
if idpInfo.SubType == "Internal" {
|
if idpInfo.SubType == "Internal" {
|
||||||
return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
|
return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
|
||||||
} else if idpInfo.SubType == "Third-party" {
|
} else if idpInfo.SubType == "Third-party" {
|
||||||
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
|
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType)
|
||||||
}
|
}
|
||||||
case "Casdoor":
|
case "Casdoor":
|
||||||
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Okta":
|
case "Okta":
|
||||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||||
case "Douyin":
|
case "Douyin":
|
||||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "Bilibili":
|
case "Bilibili":
|
||||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||||
case "MetaMask":
|
case "MetaMask":
|
||||||
return NewMetaMaskIdProvider()
|
return NewMetaMaskIdProvider(), nil
|
||||||
case "Web3Onboard":
|
case "Web3Onboard":
|
||||||
return NewWeb3OnboardIdProvider()
|
return NewWeb3OnboardIdProvider(), nil
|
||||||
default:
|
default:
|
||||||
if isGothSupport(idpInfo.Type) {
|
if isGothSupport(idpInfo.Type) {
|
||||||
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.ClientId2, idpInfo.ClientSecret2, redirectUrl, idpInfo.HostUrl)
|
||||||
}
|
}
|
||||||
return nil
|
return nil, fmt.Errorf("OAuth provider type: %s is not supported", idpInfo.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"tags": [],
|
"tags": [],
|
||||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
|
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
|
||||||
"masterPassword": "",
|
"masterPassword": "",
|
||||||
|
"defaultPassword": "",
|
||||||
"initScore": 2000,
|
"initScore": 2000,
|
||||||
"enableSoftDeletion": false,
|
"enableSoftDeletion": false,
|
||||||
"isProfilePublic": true,
|
"isProfilePublic": true,
|
||||||
@ -176,9 +177,7 @@
|
|||||||
],
|
],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"actions": [
|
"actions": [],
|
||||||
""
|
|
||||||
],
|
|
||||||
"displayName": "",
|
"displayName": "",
|
||||||
"effect": "",
|
"effect": "",
|
||||||
"isEnabled": true,
|
"isEnabled": true,
|
||||||
@ -186,15 +185,9 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"owner": "",
|
"owner": "",
|
||||||
"resourceType": "",
|
"resourceType": "",
|
||||||
"resources": [
|
"resources": [],
|
||||||
""
|
"roles": [],
|
||||||
],
|
"users": []
|
||||||
"roles": [
|
|
||||||
""
|
|
||||||
],
|
|
||||||
"users": [
|
|
||||||
""
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"payments": [
|
"payments": [
|
||||||
@ -236,9 +229,7 @@
|
|||||||
"name": "",
|
"name": "",
|
||||||
"owner": "",
|
"owner": "",
|
||||||
"price": 0,
|
"price": 0,
|
||||||
"providers": [
|
"providers": [],
|
||||||
""
|
|
||||||
],
|
|
||||||
"quantity": 0,
|
"quantity": 0,
|
||||||
"returnUrl": "",
|
"returnUrl": "",
|
||||||
"sold": 0,
|
"sold": 0,
|
||||||
@ -268,12 +259,8 @@
|
|||||||
"isEnabled": true,
|
"isEnabled": true,
|
||||||
"name": "",
|
"name": "",
|
||||||
"owner": "",
|
"owner": "",
|
||||||
"roles": [
|
"roles": [],
|
||||||
""
|
"users": []
|
||||||
],
|
|
||||||
"users": [
|
|
||||||
""
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"syncers": [
|
"syncers": [
|
||||||
@ -284,7 +271,7 @@
|
|||||||
"databaseType": "",
|
"databaseType": "",
|
||||||
"errorText": "",
|
"errorText": "",
|
||||||
"host": "",
|
"host": "",
|
||||||
"isEnabled": true,
|
"isEnabled": false,
|
||||||
"name": "",
|
"name": "",
|
||||||
"organization": "",
|
"organization": "",
|
||||||
"owner": "",
|
"owner": "",
|
||||||
@ -298,9 +285,7 @@
|
|||||||
"isHashed": true,
|
"isHashed": true,
|
||||||
"name": "",
|
"name": "",
|
||||||
"type": "",
|
"type": "",
|
||||||
"values": [
|
"values": []
|
||||||
""
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tablePrimaryKey": "",
|
"tablePrimaryKey": "",
|
||||||
@ -330,9 +315,7 @@
|
|||||||
"webhooks": [
|
"webhooks": [
|
||||||
{
|
{
|
||||||
"contentType": "",
|
"contentType": "",
|
||||||
"events": [
|
"events": [],
|
||||||
""
|
|
||||||
],
|
|
||||||
"headers": [
|
"headers": [
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "",
|
||||||
|
@ -25,6 +25,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func StartLdapServer() {
|
func StartLdapServer() {
|
||||||
|
ldapServerPort := conf.GetConfigString("ldapServerPort")
|
||||||
|
if ldapServerPort == "" || ldapServerPort == "0" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
server := ldap.NewServer()
|
server := ldap.NewServer()
|
||||||
routes := ldap.NewRouteMux()
|
routes := ldap.NewRouteMux()
|
||||||
|
|
||||||
@ -32,9 +37,9 @@ func StartLdapServer() {
|
|||||||
routes.Search(handleSearch).Label(" SEARCH****")
|
routes.Search(handleSearch).Label(" SEARCH****")
|
||||||
|
|
||||||
server.Handle(routes)
|
server.Handle(routes)
|
||||||
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("StartLdapServer() failed, ErrMsg = %s", err.Error())
|
log.Printf("StartLdapServer() failed, err = %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
|
|||||||
|
|
||||||
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, "en")
|
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, "en")
|
||||||
if !hasPermission {
|
if !hasPermission {
|
||||||
log.Printf("ErrMsg = %v", err.Error())
|
log.Printf("err = %v", err.Error())
|
||||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/casdoor/casdoor/ldap"
|
"github.com/casdoor/casdoor/ldap"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
|
"github.com/casdoor/casdoor/radius"
|
||||||
"github.com/casdoor/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
@ -81,6 +82,7 @@ func main() {
|
|||||||
logs.SetLogFuncCall(false)
|
logs.SetLogFuncCall(false)
|
||||||
|
|
||||||
go ldap.StartLdapServer()
|
go ldap.StartLdapServer()
|
||||||
|
go radius.StartRadiusServer()
|
||||||
go object.ClearThroughputPerSecond()
|
go object.ClearThroughputPerSecond()
|
||||||
|
|
||||||
beego.Run(fmt.Sprintf(":%v", port))
|
beego.Run(fmt.Sprintf(":%v", port))
|
||||||
|
@ -22,14 +22,15 @@ config: |
|
|||||||
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
|
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
|
||||||
dbName = casdoor
|
dbName = casdoor
|
||||||
redisEndpoint =
|
redisEndpoint =
|
||||||
defaultStorageProvider =
|
defaultStorageProvider =
|
||||||
isCloudIntranet = false
|
isCloudIntranet = false
|
||||||
authState = "casdoor"
|
authState = "casdoor"
|
||||||
socks5Proxy = ""
|
socks5Proxy = ""
|
||||||
verificationCodeTimeout = 10
|
verificationCodeTimeout = 10
|
||||||
initScore = 2000
|
initScore = 0
|
||||||
logPostOnly = true
|
logPostOnly = true
|
||||||
origin = "https://door.casbin.com"
|
origin =
|
||||||
|
enableGzip = true
|
||||||
|
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewMatrixProvider(userId string, roomId string, accessToken string, homeServer string) (*notify.Notify, error) {
|
func NewMatrixProvider(userId string, accessToken string, roomId string, homeServer string) (*notify.Notify, error) {
|
||||||
matrixSrv, err := matrix.New(id.UserID(userId), id.RoomID(roomId), homeServer, accessToken)
|
matrixSrv, err := matrix.New(id.UserID(userId), id.RoomID(roomId), homeServer, accessToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -18,27 +18,27 @@ import "github.com/casdoor/notify"
|
|||||||
|
|
||||||
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
|
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
|
||||||
if typ == "Telegram" {
|
if typ == "Telegram" {
|
||||||
return NewTelegramProvider(appId, receiver)
|
return NewTelegramProvider(clientSecret, receiver)
|
||||||
} else if typ == "Custom HTTP" {
|
} else if typ == "Custom HTTP" {
|
||||||
return NewCustomHttpProvider(receiver, method, title)
|
return NewCustomHttpProvider(receiver, method, title)
|
||||||
} else if typ == "DingTalk" {
|
} else if typ == "DingTalk" {
|
||||||
return NewDingTalkProvider(appId, receiver)
|
return NewDingTalkProvider(clientId, clientSecret)
|
||||||
} else if typ == "Lark" {
|
} else if typ == "Lark" {
|
||||||
return NewLarkProvider(receiver)
|
return NewLarkProvider(clientSecret)
|
||||||
} else if typ == "Microsoft Teams" {
|
} else if typ == "Microsoft Teams" {
|
||||||
return NewMicrosoftTeamsProvider(receiver)
|
return NewMicrosoftTeamsProvider(clientSecret)
|
||||||
} else if typ == "Bark" {
|
} else if typ == "Bark" {
|
||||||
return NewBarkProvider(receiver)
|
return NewBarkProvider(clientSecret)
|
||||||
} else if typ == "Pushover" {
|
} else if typ == "Pushover" {
|
||||||
return NewPushoverProvider(appId, receiver)
|
return NewPushoverProvider(clientSecret, receiver)
|
||||||
} else if typ == "Pushbullet" {
|
} else if typ == "Pushbullet" {
|
||||||
return NewPushbulletProvider(appId, receiver)
|
return NewPushbulletProvider(clientSecret, receiver)
|
||||||
} else if typ == "Slack" {
|
} else if typ == "Slack" {
|
||||||
return NewSlackProvider(appId, receiver)
|
return NewSlackProvider(clientSecret, receiver)
|
||||||
} else if typ == "Webpush" {
|
} else if typ == "Webpush" {
|
||||||
return NewWebpushProvider(clientId, clientSecret, receiver)
|
return NewWebpushProvider(clientId, clientSecret, receiver)
|
||||||
} else if typ == "Discord" {
|
} else if typ == "Discord" {
|
||||||
return NewDiscordProvider(appId, receiver)
|
return NewDiscordProvider(clientSecret, receiver)
|
||||||
} else if typ == "Google Chat" {
|
} else if typ == "Google Chat" {
|
||||||
return NewGoogleChatProvider(metaData)
|
return NewGoogleChatProvider(metaData)
|
||||||
} else if typ == "Line" {
|
} else if typ == "Line" {
|
||||||
|
@ -30,15 +30,15 @@ type Adapter struct {
|
|||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||||
|
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Table string `xorm:"varchar(100)" json:"table"`
|
||||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
UseSameDb bool `json:"useSameDb"`
|
||||||
Host string `xorm:"varchar(100)" json:"host"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
Port int `json:"port"`
|
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||||
User string `xorm:"varchar(100)" json:"user"`
|
Host string `xorm:"varchar(100)" json:"host"`
|
||||||
Password string `xorm:"varchar(100)" json:"password"`
|
Port int `json:"port"`
|
||||||
Database string `xorm:"varchar(100)" json:"database"`
|
User string `xorm:"varchar(100)" json:"user"`
|
||||||
Table string `xorm:"varchar(100)" json:"table"`
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
TableNamePrefix string `xorm:"varchar(100)" json:"tableNamePrefix"`
|
Database string `xorm:"varchar(100)" json:"database"`
|
||||||
|
|
||||||
*xormadapter.Adapter `xorm:"-" json:"-"`
|
*xormadapter.Adapter `xorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
@ -139,63 +139,69 @@ func (adapter *Adapter) GetId() string {
|
|||||||
return fmt.Sprintf("%s/%s", adapter.Owner, adapter.Name)
|
return fmt.Sprintf("%s/%s", adapter.Owner, adapter.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (adapter *Adapter) getTable() string {
|
|
||||||
if adapter.DatabaseType == "mssql" {
|
|
||||||
return fmt.Sprintf("[%s]", adapter.Table)
|
|
||||||
} else {
|
|
||||||
return adapter.Table
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (adapter *Adapter) InitAdapter() error {
|
func (adapter *Adapter) InitAdapter() error {
|
||||||
if adapter.Adapter == nil {
|
if adapter.Adapter != nil {
|
||||||
var dataSourceName string
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if adapter.isBuiltIn() {
|
var driverName string
|
||||||
dataSourceName = conf.GetConfigString("dataSourceName")
|
var dataSourceName string
|
||||||
if adapter.DatabaseType == "mysql" {
|
if adapter.UseSameDb || adapter.isBuiltIn() {
|
||||||
dataSourceName = dataSourceName + adapter.Database
|
driverName = conf.GetConfigString("driverName")
|
||||||
}
|
dataSourceName = conf.GetConfigString("dataSourceName")
|
||||||
} else {
|
if conf.GetConfigString("driverName") == "mysql" {
|
||||||
switch adapter.DatabaseType {
|
dataSourceName = dataSourceName + conf.GetConfigString("dbName")
|
||||||
case "mssql":
|
|
||||||
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
|
|
||||||
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
|
||||||
case "mysql":
|
|
||||||
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
|
|
||||||
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
|
||||||
case "postgres":
|
|
||||||
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User,
|
|
||||||
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
|
||||||
case "CockroachDB":
|
|
||||||
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s serial_normalization=virtual_sequence",
|
|
||||||
adapter.User, adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
|
||||||
case "sqlite3":
|
|
||||||
dataSourceName = fmt.Sprintf("file:%s", adapter.Host)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported database type: %s", adapter.DatabaseType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if !isCloudIntranet {
|
driverName = adapter.DatabaseType
|
||||||
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
switch driverName {
|
||||||
}
|
case "mssql":
|
||||||
|
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
|
||||||
var err error
|
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
||||||
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
|
case "mysql":
|
||||||
|
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
|
||||||
if adapter.isBuiltIn() && adapter.DatabaseType == "postgres" {
|
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
||||||
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
case "postgres":
|
||||||
if schema != "" {
|
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User,
|
||||||
engine.SetSchema(schema)
|
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
||||||
}
|
case "CockroachDB":
|
||||||
}
|
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s serial_normalization=virtual_sequence",
|
||||||
|
adapter.User, adapter.Password, adapter.Host, adapter.Port, adapter.Database)
|
||||||
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
|
case "sqlite3":
|
||||||
if err != nil {
|
dataSourceName = fmt.Sprintf("file:%s", adapter.Host)
|
||||||
return err
|
default:
|
||||||
|
return fmt.Errorf("unsupported database type: %s", adapter.DatabaseType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !isCloudIntranet {
|
||||||
|
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||||
|
}
|
||||||
|
|
||||||
|
engine, err := xorm.NewEngine(driverName, dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adapter.UseSameDb || adapter.isBuiltIn()) && driverName == "postgres" {
|
||||||
|
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||||
|
if schema != "" {
|
||||||
|
engine.SetSchema(schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tableName string
|
||||||
|
if driverName == "mssql" {
|
||||||
|
tableName = fmt.Sprintf("[%s]", adapter.Table)
|
||||||
|
} else {
|
||||||
|
tableName = adapter.Table
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, tableName, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type SignupItem struct {
|
type SignupItem struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Visible bool `json:"visible"`
|
Visible bool `json:"visible"`
|
||||||
Required bool `json:"required"`
|
Required bool `json:"required"`
|
||||||
Prompted bool `json:"prompted"`
|
Prompted bool `json:"prompted"`
|
||||||
Rule string `json:"rule"`
|
Label string `json:"label"`
|
||||||
|
Placeholder string `json:"placeholder"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SamlItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
NameFormat string `json:"nameformat"`
|
||||||
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Application struct {
|
type Application struct {
|
||||||
@ -49,17 +57,19 @@ type Application struct {
|
|||||||
EnableAutoSignin bool `json:"enableAutoSignin"`
|
EnableAutoSignin bool `json:"enableAutoSignin"`
|
||||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||||
|
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||||
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
|
||||||
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
|
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
|
||||||
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
|
||||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||||
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
||||||
|
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||||
|
|
||||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||||
@ -306,6 +316,9 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
|||||||
if application.OrganizationObj.MasterPassword != "" {
|
if application.OrganizationObj.MasterPassword != "" {
|
||||||
application.OrganizationObj.MasterPassword = "***"
|
application.OrganizationObj.MasterPassword = "***"
|
||||||
}
|
}
|
||||||
|
if application.OrganizationObj.DefaultPassword != "" {
|
||||||
|
application.OrganizationObj.DefaultPassword = "***"
|
||||||
|
}
|
||||||
if application.OrganizationObj.PasswordType != "" {
|
if application.OrganizationObj.PasswordType != "" {
|
||||||
application.OrganizationObj.PasswordType = "***"
|
application.OrganizationObj.PasswordType = "***"
|
||||||
}
|
}
|
||||||
@ -428,7 +441,7 @@ func (application *Application) GetId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
|
||||||
redirectUris := append([]string{"http://localhost:"}, application.RedirectUris...)
|
redirectUris := append([]string{"http://localhost:", "https://localhost:", "http://127.0.0.1:", "http://casdoor-app"}, application.RedirectUris...)
|
||||||
for _, targetUri := range redirectUris {
|
for _, targetUri := range redirectUris {
|
||||||
targetUriRegex := regexp.MustCompile(targetUri)
|
targetUriRegex := regexp.MustCompile(targetUri)
|
||||||
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
|
||||||
|
@ -351,8 +351,8 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckLoginPermission(userId string, application *Application) (bool, error) {
|
func CheckLoginPermission(userId string, application *Application) (bool, error) {
|
||||||
var err error
|
owner, _ := util.GetOwnerAndNameFromId(userId)
|
||||||
if userId == "built-in/admin" {
|
if owner == "built-in" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,6 +361,8 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allowPermissionCount := 0
|
||||||
|
denyPermissionCount := 0
|
||||||
allowCount := 0
|
allowCount := 0
|
||||||
denyCount := 0
|
denyCount := 0
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
@ -368,11 +370,19 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if permission.isUserHit(userId) {
|
if !permission.isUserHit(userId) && !permission.isRoleHit(userId) {
|
||||||
allowCount += 1
|
if permission.Effect == "Allow" {
|
||||||
|
allowPermissionCount += 1
|
||||||
|
} else {
|
||||||
|
denyPermissionCount += 1
|
||||||
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
enforcer := getPermissionEnforcer(permission)
|
enforcer, err := getPermissionEnforcer(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
var isAllowed bool
|
var isAllowed bool
|
||||||
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
|
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
|
||||||
@ -391,8 +401,18 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deny-override, if one deny is found, then deny
|
||||||
if denyCount > 0 {
|
if denyCount > 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
} else if allowCount > 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// For no-allow and no-deny condition
|
||||||
|
// If only allow permissions exist, we suppose it's Deny-by-default, aka no-allow means deny
|
||||||
|
// Otherwise, it's Allow-by-default, aka no-deny means allow
|
||||||
|
if allowPermissionCount > 0 && denyPermissionCount == 0 {
|
||||||
|
return false, nil
|
||||||
}
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||||
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.AppId, provider.Host, provider.Port, provider.DisableSsl)
|
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl)
|
||||||
|
|
||||||
fromAddress := provider.ClientId2
|
fromAddress := provider.ClientId2
|
||||||
if fromAddress == "" {
|
if fromAddress == "" {
|
||||||
|
@ -18,7 +18,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/casbin/casbin/v2"
|
"github.com/casbin/casbin/v2"
|
||||||
"github.com/casbin/casbin/v2/config"
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
@ -191,39 +190,55 @@ func GetPolicies(id string) ([]*xormadapter.CasbinRule, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
policies := util.MatrixToCasbinRules("p", enforcer.GetPolicy())
|
pRules := enforcer.GetPolicy()
|
||||||
|
res := util.MatrixToCasbinRules("p", pRules)
|
||||||
|
|
||||||
if enforcer.GetModel()["g"] != nil {
|
if enforcer.GetModel()["g"] != nil {
|
||||||
policies = append(policies, util.MatrixToCasbinRules("g", enforcer.GetGroupingPolicy())...)
|
gRules := enforcer.GetGroupingPolicy()
|
||||||
|
res2 := util.MatrixToCasbinRules("g", gRules)
|
||||||
|
res = append(res, res2...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return policies, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdatePolicy(id string, oldPolicy, newPolicy []string) (bool, error) {
|
func UpdatePolicy(id string, ptype string, oldPolicy []string, newPolicy []string) (bool, error) {
|
||||||
enforcer, err := GetInitializedEnforcer(id)
|
enforcer, err := GetInitializedEnforcer(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return enforcer.UpdatePolicy(oldPolicy, newPolicy)
|
if ptype == "p" {
|
||||||
|
return enforcer.UpdatePolicy(oldPolicy, newPolicy)
|
||||||
|
} else {
|
||||||
|
return enforcer.UpdateGroupingPolicy(oldPolicy, newPolicy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddPolicy(id string, policy []string) (bool, error) {
|
func AddPolicy(id string, ptype string, policy []string) (bool, error) {
|
||||||
enforcer, err := GetInitializedEnforcer(id)
|
enforcer, err := GetInitializedEnforcer(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return enforcer.AddPolicy(policy)
|
if ptype == "p" {
|
||||||
|
return enforcer.AddPolicy(policy)
|
||||||
|
} else {
|
||||||
|
return enforcer.AddGroupingPolicy(policy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemovePolicy(id string, policy []string) (bool, error) {
|
func RemovePolicy(id string, ptype string, policy []string) (bool, error) {
|
||||||
enforcer, err := GetInitializedEnforcer(id)
|
enforcer, err := GetInitializedEnforcer(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return enforcer.RemovePolicy(policy)
|
if ptype == "p" {
|
||||||
|
return enforcer.RemovePolicy(policy)
|
||||||
|
} else {
|
||||||
|
return enforcer.RemoveGroupingPolicy(policy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (enforcer *Enforcer) LoadModelCfg() error {
|
func (enforcer *Enforcer) LoadModelCfg() error {
|
||||||
@ -231,23 +246,17 @@ func (enforcer *Enforcer) LoadModelCfg() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
model, err := GetModel(enforcer.Model)
|
model, err := GetModelEx(enforcer.Model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if model == nil {
|
} else if model == nil {
|
||||||
return fmt.Errorf("the model: %s for enforcer: %s is not found", enforcer.Model, enforcer.GetId())
|
return fmt.Errorf("the model: %s for enforcer: %s is not found", enforcer.Model, enforcer.GetId())
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := config.NewConfigFromText(model.ModelText)
|
enforcer.ModelCfg, err = getModelCfg(model)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
enforcer.ModelCfg = make(map[string]string)
|
|
||||||
enforcer.ModelCfg["p"] = cfg.String("policy_definition::p")
|
|
||||||
if cfg.String("role_definition::g") != "" {
|
|
||||||
enforcer.ModelCfg["g"] = cfg.String("role_definition::g")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -226,7 +226,7 @@ func GetGroupUserCount(groupId string, field, value string) (int64, error) {
|
|||||||
} else {
|
} else {
|
||||||
return ormer.Engine.Table("user").
|
return ormer.Engine.Table("user").
|
||||||
Where("owner = ?", owner).In("name", names).
|
Where("owner = ?", owner).In("name", names).
|
||||||
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
|
And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%").
|
||||||
Count()
|
Count()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -247,7 +247,7 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
|
|||||||
}
|
}
|
||||||
|
|
||||||
if field != "" && value != "" {
|
if field != "" && value != "" {
|
||||||
session = session.And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%")
|
session = session.And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%")
|
||||||
}
|
}
|
||||||
|
|
||||||
if sortField == "" || sortOrder == "" {
|
if sortField == "" || sortOrder == "" {
|
||||||
|
@ -178,7 +178,7 @@ func initBuiltInApplication() {
|
|||||||
EnablePassword: true,
|
EnablePassword: true,
|
||||||
EnableSignUp: true,
|
EnableSignUp: true,
|
||||||
Providers: []*ProviderItem{
|
Providers: []*ProviderItem{
|
||||||
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, AlertType: "None", Rule: "None", Provider: nil},
|
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, SignupGroup: "", Rule: "None", Provider: nil},
|
||||||
},
|
},
|
||||||
SignupItems: []*SignupItem{
|
SignupItems: []*SignupItem{
|
||||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||||
@ -423,14 +423,11 @@ func initBuiltInUserAdapter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
adapter = &Adapter{
|
adapter = &Adapter{
|
||||||
Owner: "built-in",
|
Owner: "built-in",
|
||||||
Name: "user-adapter-built-in",
|
Name: "user-adapter-built-in",
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
Type: "Database",
|
Table: "casbin_user_rule",
|
||||||
DatabaseType: conf.GetConfigString("driverName"),
|
UseSameDb: true,
|
||||||
TableNamePrefix: conf.GetConfigString("tableNamePrefix"),
|
|
||||||
Database: conf.GetConfigString("dbName"),
|
|
||||||
Table: "casbin_user_rule",
|
|
||||||
}
|
}
|
||||||
_, err = AddAdapter(adapter)
|
_, err = AddAdapter(adapter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -449,14 +446,11 @@ func initBuiltInApiAdapter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
adapter = &Adapter{
|
adapter = &Adapter{
|
||||||
Owner: "built-in",
|
Owner: "built-in",
|
||||||
Name: "api-adapter-built-in",
|
Name: "api-adapter-built-in",
|
||||||
CreatedTime: util.GetCurrentTime(),
|
CreatedTime: util.GetCurrentTime(),
|
||||||
Type: "Database",
|
Table: "casbin_api_rule",
|
||||||
DatabaseType: conf.GetConfigString("driverName"),
|
UseSameDb: true,
|
||||||
TableNamePrefix: conf.GetConfigString("tableNamePrefix"),
|
|
||||||
Database: conf.GetConfigString("dbName"),
|
|
||||||
Table: "casbin_api_rule",
|
|
||||||
}
|
}
|
||||||
_, err = AddAdapter(adapter)
|
_, err = AddAdapter(adapter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
121
object/init_data_dump.go
Normal file
121
object/init_data_dump.go
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import "github.com/casdoor/casdoor/util"
|
||||||
|
|
||||||
|
func DumpToFile(filePath string) error {
|
||||||
|
return writeInitDataToFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeInitDataToFile(filePath string) error {
|
||||||
|
organizations, err := GetOrganizations("admin")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
applications, err := GetApplications("admin")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
users, err := GetGlobalUsers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
certs, err := GetCerts("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
providers, err := GetGlobalProviders()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ldaps, err := GetLdaps("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
models, err := GetModels("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions, err := GetPermissions("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
payments, err := GetPayments("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
products, err := GetProducts("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resources, err := GetResources("", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
roles, err := GetRoles("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
syncers, err := GetSyncers("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens, err := GetTokens("", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
webhooks, err := GetWebhooks("", "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &InitData{
|
||||||
|
Organizations: organizations,
|
||||||
|
Applications: applications,
|
||||||
|
Users: users,
|
||||||
|
Certs: certs,
|
||||||
|
Providers: providers,
|
||||||
|
Ldaps: ldaps,
|
||||||
|
Models: models,
|
||||||
|
Permissions: permissions,
|
||||||
|
Payments: payments,
|
||||||
|
Products: products,
|
||||||
|
Resources: resources,
|
||||||
|
Roles: roles,
|
||||||
|
Syncers: syncers,
|
||||||
|
Tokens: tokens,
|
||||||
|
Webhooks: webhooks,
|
||||||
|
}
|
||||||
|
|
||||||
|
text := util.StructToJsonFormatted(data)
|
||||||
|
util.WriteStringToPath(text, filePath)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,26 +12,18 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
package object
|
package object
|
||||||
|
|
||||||
func (syncer *Syncer) getUsers() []*User {
|
import "testing"
|
||||||
users, err := GetUsers(syncer.Organization)
|
|
||||||
|
func TestDumpToFile(t *testing.T) {
|
||||||
|
InitConfig()
|
||||||
|
|
||||||
|
err := DumpToFile("./init_data_dump.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return users
|
|
||||||
}
|
|
||||||
|
|
||||||
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User, map[string]*User) {
|
|
||||||
users := syncer.getUsers()
|
|
||||||
|
|
||||||
m1 := map[string]*User{}
|
|
||||||
m2 := map[string]*User{}
|
|
||||||
for _, user := range users {
|
|
||||||
m1[user.Id] = user
|
|
||||||
m2[user.Name] = user
|
|
||||||
}
|
|
||||||
|
|
||||||
return users, m1, m2
|
|
||||||
}
|
}
|
@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casbin/casbin/v2/config"
|
||||||
"github.com/casbin/casbin/v2/model"
|
"github.com/casbin/casbin/v2/model"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
@ -83,6 +84,19 @@ func GetModel(id string) (*Model, error) {
|
|||||||
return getModel(owner, name)
|
return getModel(owner, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetModelEx(id string) (*Model, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
model, err := getModel(owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if model != nil {
|
||||||
|
return model, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return getModel("built-in", name)
|
||||||
|
}
|
||||||
|
|
||||||
func UpdateModelWithCheck(id string, modelObj *Model) error {
|
func UpdateModelWithCheck(id string, modelObj *Model) error {
|
||||||
// check model grammar
|
// check model grammar
|
||||||
_, err := model.NewModelFromString(modelObj.ModelText)
|
_, err := model.NewModelFromString(modelObj.ModelText)
|
||||||
@ -188,3 +202,17 @@ func (m *Model) initModel() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getModelCfg(m *Model) (map[string]string, error) {
|
||||||
|
cfg, err := config.NewConfigFromText(m.ModelText)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
modelCfg := make(map[string]string)
|
||||||
|
modelCfg["p"] = cfg.String("policy_definition::p")
|
||||||
|
if cfg.String("role_definition::g") != "" {
|
||||||
|
modelCfg["g"] = cfg.String("role_definition::g")
|
||||||
|
}
|
||||||
|
return modelCfg, nil
|
||||||
|
}
|
||||||
|
@ -59,7 +59,7 @@ func isIpAddress(host string) bool {
|
|||||||
return ip != nil
|
return ip != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOriginFromHost(host string) (string, string) {
|
func getOriginFromHostInternal(host string) (string, string) {
|
||||||
origin := conf.GetConfigString("origin")
|
origin := conf.GetConfigString("origin")
|
||||||
if origin != "" {
|
if origin != "" {
|
||||||
return origin, origin
|
return origin, origin
|
||||||
@ -82,6 +82,17 @@ func getOriginFromHost(host string) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOriginFromHost(host string) (string, string) {
|
||||||
|
originF, originB := getOriginFromHostInternal(host)
|
||||||
|
|
||||||
|
originFrontend := conf.GetConfigString("originFrontend")
|
||||||
|
if originFrontend != "" {
|
||||||
|
originF = originFrontend
|
||||||
|
}
|
||||||
|
|
||||||
|
return originF, originB
|
||||||
|
}
|
||||||
|
|
||||||
func GetOidcDiscovery(host string) OidcDiscovery {
|
func GetOidcDiscovery(host string) OidcDiscovery {
|
||||||
originFrontend, originBackend := getOriginFromHost(host)
|
originFrontend, originBackend := getOriginFromHost(host)
|
||||||
|
|
||||||
@ -127,9 +138,16 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cert.Certificate == "" {
|
||||||
|
return jwks, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
|
}
|
||||||
|
|
||||||
certPemBlock := []byte(cert.Certificate)
|
certPemBlock := []byte(cert.Certificate)
|
||||||
certDerBlock, _ := pem.Decode(certPemBlock)
|
certDerBlock, _ := pem.Decode(certPemBlock)
|
||||||
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)
|
x509Cert, err := x509.ParseCertificate(certDerBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return jwks, err
|
||||||
|
}
|
||||||
|
|
||||||
var jwk jose.JSONWebKey
|
var jwk jose.JSONWebKey
|
||||||
jwk.Key = x509Cert.PublicKey
|
jwk.Key = x509Cert.PublicKey
|
||||||
|
@ -64,6 +64,7 @@ type Organization struct {
|
|||||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||||
|
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||||
InitScore int `json:"initScore"`
|
InitScore int `json:"initScore"`
|
||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
@ -155,6 +156,9 @@ func GetMaskedOrganization(organization *Organization, errs ...error) (*Organiza
|
|||||||
if organization.MasterPassword != "" {
|
if organization.MasterPassword != "" {
|
||||||
organization.MasterPassword = "***"
|
organization.MasterPassword = "***"
|
||||||
}
|
}
|
||||||
|
if organization.DefaultPassword != "" {
|
||||||
|
organization.DefaultPassword = "***"
|
||||||
|
}
|
||||||
return organization, nil
|
return organization, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,9 +206,14 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
session := ormer.Engine.ID(core.PK{owner, name}).AllCols()
|
session := ormer.Engine.ID(core.PK{owner, name}).AllCols()
|
||||||
|
|
||||||
if organization.MasterPassword == "***" {
|
if organization.MasterPassword == "***" {
|
||||||
session.Omit("master_password")
|
session.Omit("master_password")
|
||||||
}
|
}
|
||||||
|
if organization.DefaultPassword == "***" {
|
||||||
|
session.Omit("default_password")
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := session.Update(organization)
|
affected, err := session.Update(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -86,7 +86,11 @@ func InitAdapter() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ormer = NewAdapter(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
|
var err error
|
||||||
|
ormer, err = NewAdapter(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
|
||||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||||
@ -121,19 +125,22 @@ func finalizer(a *Ormer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAdapter is the constructor for Ormer.
|
// NewAdapter is the constructor for Ormer.
|
||||||
func NewAdapter(driverName string, dataSourceName string, dbName string) *Ormer {
|
func NewAdapter(driverName string, dataSourceName string, dbName string) (*Ormer, error) {
|
||||||
a := &Ormer{}
|
a := &Ormer{}
|
||||||
a.driverName = driverName
|
a.driverName = driverName
|
||||||
a.dataSourceName = dataSourceName
|
a.dataSourceName = dataSourceName
|
||||||
a.dbName = dbName
|
a.dbName = dbName
|
||||||
|
|
||||||
// Open the DB, create it if not existed.
|
// Open the DB, create it if not existed.
|
||||||
a.open()
|
err := a.open()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Call the destructor when the object is released.
|
// Call the destructor when the object is released.
|
||||||
runtime.SetFinalizer(a, finalizer)
|
runtime.SetFinalizer(a, finalizer)
|
||||||
|
|
||||||
return a
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func refineDataSourceNameForPostgres(dataSourceName string) string {
|
func refineDataSourceNameForPostgres(dataSourceName string) string {
|
||||||
@ -192,7 +199,7 @@ func (a *Ormer) CreateDatabase() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Ormer) open() {
|
func (a *Ormer) open() error {
|
||||||
dataSourceName := a.dataSourceName + a.dbName
|
dataSourceName := a.dataSourceName + a.dbName
|
||||||
if a.driverName != "mysql" {
|
if a.driverName != "mysql" {
|
||||||
dataSourceName = a.dataSourceName
|
dataSourceName = a.dataSourceName
|
||||||
@ -200,8 +207,9 @@ func (a *Ormer) open() {
|
|||||||
|
|
||||||
engine, err := xorm.NewEngine(a.driverName, dataSourceName)
|
engine, err := xorm.NewEngine(a.driverName, dataSourceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.driverName == "postgres" {
|
if a.driverName == "postgres" {
|
||||||
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||||
if schema != "" {
|
if schema != "" {
|
||||||
@ -210,6 +218,7 @@ func (a *Ormer) open() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.Engine = engine
|
a.Engine = engine
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Ormer) close() {
|
func (a *Ormer) close() {
|
||||||
@ -316,6 +325,11 @@ func (a *Ormer) createTable() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = a.Engine.Sync2(new(RadiusAccounting))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
err = a.Engine.Sync2(new(PermissionRule))
|
err = a.Engine.Sync2(new(PermissionRule))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -54,7 +54,7 @@ type Payment struct {
|
|||||||
// Order Info
|
// Order Info
|
||||||
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
||||||
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
|
||||||
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl""` // `successUrl` is redirected from `payUrl` after pay success
|
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl"` // `successUrl` is redirected from `payUrl` after pay success
|
||||||
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
||||||
Message string `xorm:"varchar(2000)" json:"message"`
|
Message string `xorm:"varchar(2000)" json:"message"`
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package object
|
package object
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@ -112,11 +113,15 @@ func GetPermission(id string) (*Permission, error) {
|
|||||||
|
|
||||||
// checkPermissionValid verifies if the permission is valid
|
// checkPermissionValid verifies if the permission is valid
|
||||||
func checkPermissionValid(permission *Permission) error {
|
func checkPermissionValid(permission *Permission) error {
|
||||||
enforcer := getPermissionEnforcer(permission)
|
enforcer, err := getPermissionEnforcer(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
enforcer.EnableAutoSave(false)
|
enforcer.EnableAutoSave(false)
|
||||||
|
|
||||||
policies := getPolicies(permission)
|
policies := getPolicies(permission)
|
||||||
_, err := enforcer.AddPolicies(policies)
|
_, err = enforcer.AddPolicies(policies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -128,7 +133,7 @@ func checkPermissionValid(permission *Permission) error {
|
|||||||
|
|
||||||
groupingPolicies := getGroupingPolicies(permission)
|
groupingPolicies := getGroupingPolicies(permission)
|
||||||
if len(groupingPolicies) > 0 {
|
if len(groupingPolicies) > 0 {
|
||||||
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
|
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -149,14 +154,40 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if permission.ResourceType == "Application" && permission.Model != "" {
|
||||||
|
model, err := GetModelEx(util.GetId(owner, permission.Model))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
} else if model == nil {
|
||||||
|
return false, fmt.Errorf("the model: %s for permission: %s is not found", permission.Model, permission.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
|
modelCfg, err := getModelCfg(model)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(strings.Split(modelCfg["p"], ",")) != 3 {
|
||||||
|
return false, fmt.Errorf("the model: %s for permission: %s is not valid, Casbin model's [policy_defination] section should have 3 elements", permission.Model, permission.GetId())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(permission)
|
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(permission)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if affected != 0 {
|
if affected != 0 {
|
||||||
removeGroupingPolicies(oldPermission)
|
err = removeGroupingPolicies(oldPermission)
|
||||||
removePolicies(oldPermission)
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = removePolicies(oldPermission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
||||||
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
||||||
if isEmpty {
|
if isEmpty {
|
||||||
@ -166,8 +197,16 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addGroupingPolicies(permission)
|
|
||||||
addPolicies(permission)
|
err = addGroupingPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
@ -180,59 +219,78 @@ func AddPermission(permission *Permission) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if affected != 0 {
|
if affected != 0 {
|
||||||
addGroupingPolicies(permission)
|
err = addGroupingPolicies(permission)
|
||||||
addPolicies(permission)
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddPermissions(permissions []*Permission) bool {
|
func AddPermissions(permissions []*Permission) (bool, error) {
|
||||||
if len(permissions) == 0 {
|
if len(permissions) == 0 {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := ormer.Engine.Insert(permissions)
|
affected, err := ormer.Engine.Insert(permissions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !strings.Contains(err.Error(), "Duplicate entry") {
|
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||||
panic(err)
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
// add using for loop
|
// add using for loop
|
||||||
if affected != 0 {
|
if affected != 0 {
|
||||||
addGroupingPolicies(permission)
|
err = addGroupingPolicies(permission)
|
||||||
addPolicies(permission)
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return affected != 0
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddPermissionsInBatch(permissions []*Permission) bool {
|
func AddPermissionsInBatch(permissions []*Permission) (bool, error) {
|
||||||
batchSize := conf.GetConfigBatchSize()
|
batchSize := conf.GetConfigBatchSize()
|
||||||
|
|
||||||
if len(permissions) == 0 {
|
if len(permissions) == 0 {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
affected := false
|
affected := false
|
||||||
for i := 0; i < (len(permissions)-1)/batchSize+1; i++ {
|
for i := 0; i < len(permissions); i += batchSize {
|
||||||
start := i * batchSize
|
start := i
|
||||||
end := (i + 1) * batchSize
|
end := i + batchSize
|
||||||
if end > len(permissions) {
|
if end > len(permissions) {
|
||||||
end = len(permissions)
|
end = len(permissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := permissions[start:end]
|
tmp := permissions[start:end]
|
||||||
// TODO: save to log instead of standard output
|
fmt.Printf("The syncer adds permissions: [%d - %d]\n", start, end)
|
||||||
// fmt.Printf("Add Permissions: [%d - %d].\n", start, end)
|
|
||||||
if AddPermissions(tmp) {
|
b, err := AddPermissions(tmp)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b {
|
||||||
affected = true
|
affected = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected
|
return affected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeletePermission(permission *Permission) (bool, error) {
|
func DeletePermission(permission *Permission) (bool, error) {
|
||||||
@ -242,8 +300,16 @@ func DeletePermission(permission *Permission) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if affected != 0 {
|
if affected != 0 {
|
||||||
removeGroupingPolicies(permission)
|
err = removeGroupingPolicies(permission)
|
||||||
removePolicies(permission)
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = removePolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
||||||
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
||||||
if isEmpty {
|
if isEmpty {
|
||||||
@ -258,9 +324,59 @@ func DeletePermission(permission *Permission) (bool, error) {
|
|||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) {
|
func getPermissionsByUser(userId string) ([]*Permission, error) {
|
||||||
permissions := []*Permission{}
|
permissions := []*Permission{}
|
||||||
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&permissions)
|
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&permissions)
|
||||||
|
if err != nil {
|
||||||
|
return permissions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*Permission{}
|
||||||
|
for _, permission := range permissions {
|
||||||
|
if util.InSlice(permission.Users, userId) {
|
||||||
|
res = append(res, permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPermissionsByRole(roleId string) ([]*Permission, error) {
|
||||||
|
permissions := []*Permission{}
|
||||||
|
err := ormer.Engine.Where("roles like ?", "%"+roleId+"\"%").Find(&permissions)
|
||||||
|
if err != nil {
|
||||||
|
return permissions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*Permission{}
|
||||||
|
for _, permission := range permissions {
|
||||||
|
if util.InSlice(permission.Roles, roleId) {
|
||||||
|
res = append(res, permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPermissionsByResource(resourceId string) ([]*Permission, error) {
|
||||||
|
permissions := []*Permission{}
|
||||||
|
err := ormer.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
|
||||||
|
if err != nil {
|
||||||
|
return permissions, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*Permission{}
|
||||||
|
for _, permission := range permissions {
|
||||||
|
if util.InSlice(permission.Resources, resourceId) {
|
||||||
|
res = append(res, permission)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) {
|
||||||
|
permissions, err := getPermissionsByUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -277,14 +393,13 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
|
|||||||
|
|
||||||
permFromRoles := []*Permission{}
|
permFromRoles := []*Permission{}
|
||||||
|
|
||||||
roles, err := GetRolesByUser(userId)
|
roles, err := getRolesByUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
perms := []*Permission{}
|
perms, err := GetPermissionsByRole(role.GetId())
|
||||||
err := ormer.Engine.Where("roles like ?", "%"+role.GetId()+"\"%").Find(&perms)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -302,26 +417,6 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
|
|||||||
return permissions, roles, nil
|
return permissions, roles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPermissionsByRole(roleId string) ([]*Permission, error) {
|
|
||||||
permissions := []*Permission{}
|
|
||||||
err := ormer.Engine.Where("roles like ?", "%"+roleId+"\"%").Find(&permissions)
|
|
||||||
if err != nil {
|
|
||||||
return permissions, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPermissionsByResource(resourceId string) ([]*Permission, error) {
|
|
||||||
permissions := []*Permission{}
|
|
||||||
err := ormer.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
|
|
||||||
if err != nil {
|
|
||||||
return permissions, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPermissionsBySubmitter(owner string, submitter string) ([]*Permission, error) {
|
func GetPermissionsBySubmitter(owner string, submitter string) ([]*Permission, error) {
|
||||||
permissions := []*Permission{}
|
permissions := []*Permission{}
|
||||||
err := ormer.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})
|
err := ormer.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})
|
||||||
@ -377,19 +472,34 @@ func (p *Permission) GetId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permission) isUserHit(name string) bool {
|
func (p *Permission) isUserHit(name string) bool {
|
||||||
targetOrg, _ := util.GetOwnerAndNameFromId(name)
|
targetOrg, targetName := util.GetOwnerAndNameFromId(name)
|
||||||
for _, user := range p.Users {
|
for _, user := range p.Users {
|
||||||
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
||||||
if userOrg == targetOrg && userName == "*" {
|
if userOrg == targetOrg && (userName == "*" || userName == targetName) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Permission) isRoleHit(userId string) bool {
|
||||||
|
targetRoles, err := getRolesByUser(userId)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, role := range p.Roles {
|
||||||
|
for _, targetRole := range targetRoles {
|
||||||
|
if targetRole.GetId() == role {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Permission) isResourceHit(name string) bool {
|
func (p *Permission) isResourceHit(name string) bool {
|
||||||
for _, resource := range p.Resources {
|
for _, resource := range p.Resources {
|
||||||
if name == resource {
|
if resource == "*" || resource == name {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,23 +26,23 @@ import (
|
|||||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enforcer {
|
func getPermissionEnforcer(p *Permission, permissionIDs ...string) (*casbin.Enforcer, error) {
|
||||||
// Init an enforcer instance without specifying a model or adapter.
|
// Init an enforcer instance without specifying a model or adapter.
|
||||||
// If you specify an adapter, it will load all policies, which is a
|
// If you specify an adapter, it will load all policies, which is a
|
||||||
// heavy process that can slow down the application.
|
// heavy process that can slow down the application.
|
||||||
enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false)
|
enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.setEnforcerModel(enforcer)
|
err = p.setEnforcerModel(enforcer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = p.setEnforcerAdapter(enforcer)
|
err = p.setEnforcerAdapter(enforcer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
policyFilterV5 := []string{p.GetId()}
|
policyFilterV5 := []string{p.GetId()}
|
||||||
@ -60,10 +60,10 @@ func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enfor
|
|||||||
|
|
||||||
err = enforcer.LoadFilteredPolicy(policyFilter)
|
err = enforcer.LoadFilteredPolicy(policyFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return enforcer
|
return enforcer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
|
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
|
||||||
@ -201,72 +201,96 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
|||||||
return groupingPolicies
|
return groupingPolicies
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPolicies(permission *Permission) {
|
func addPolicies(permission *Permission) error {
|
||||||
enforcer := getPermissionEnforcer(permission)
|
enforcer, err := getPermissionEnforcer(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
policies := getPolicies(permission)
|
policies := getPolicies(permission)
|
||||||
|
|
||||||
_, err := enforcer.AddPolicies(policies)
|
_, err = enforcer.AddPolicies(policies)
|
||||||
if err != nil {
|
return err
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGroupingPolicies(permission *Permission) {
|
func removePolicies(permission *Permission) error {
|
||||||
enforcer := getPermissionEnforcer(permission)
|
enforcer, err := getPermissionEnforcer(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
policies := getPolicies(permission)
|
||||||
|
|
||||||
|
_, err = enforcer.RemovePolicies(policies)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func addGroupingPolicies(permission *Permission) error {
|
||||||
|
enforcer, err := getPermissionEnforcer(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
groupingPolicies := getGroupingPolicies(permission)
|
groupingPolicies := getGroupingPolicies(permission)
|
||||||
|
|
||||||
if len(groupingPolicies) > 0 {
|
if len(groupingPolicies) > 0 {
|
||||||
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
|
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeGroupingPolicies(permission *Permission) {
|
func removeGroupingPolicies(permission *Permission) error {
|
||||||
enforcer := getPermissionEnforcer(permission)
|
enforcer, err := getPermissionEnforcer(permission)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
groupingPolicies := getGroupingPolicies(permission)
|
groupingPolicies := getGroupingPolicies(permission)
|
||||||
|
|
||||||
if len(groupingPolicies) > 0 {
|
if len(groupingPolicies) > 0 {
|
||||||
_, err := enforcer.RemoveGroupingPolicies(groupingPolicies)
|
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func removePolicies(permission *Permission) {
|
return nil
|
||||||
enforcer := getPermissionEnforcer(permission)
|
|
||||||
policies := getPolicies(permission)
|
|
||||||
|
|
||||||
_, err := enforcer.RemovePolicies(policies)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CasbinRequest = []interface{}
|
type CasbinRequest = []interface{}
|
||||||
|
|
||||||
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
|
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
|
||||||
enforcer := getPermissionEnforcer(permission, permissionIds...)
|
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
return enforcer.Enforce(*request...)
|
return enforcer.Enforce(*request...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
|
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
|
||||||
enforcer := getPermissionEnforcer(permission, permissionIds...)
|
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return enforcer.BatchEnforce(*requests)
|
return enforcer.BatchEnforce(*requests)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
|
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([]string, error) {
|
||||||
permissions, _, err := GetPermissionsAndRolesByUser(userId)
|
permissions, _, err := getPermissionsAndRolesByUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, role := range GetAllRoles(userId) {
|
for _, role := range GetAllRoles(userId) {
|
||||||
permissionsByRole, err := GetPermissionsByRole(role)
|
permissionsByRole, err := GetPermissionsByRole(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
permissions = append(permissions, permissionsByRole...)
|
permissions = append(permissions, permissionsByRole...)
|
||||||
@ -274,26 +298,31 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []
|
|||||||
|
|
||||||
var values []string
|
var values []string
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
enforcer := getPermissionEnforcer(permission)
|
enforcer, err := getPermissionEnforcer(permission)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
values = append(values, fn(enforcer)...)
|
values = append(values, fn(enforcer)...)
|
||||||
}
|
}
|
||||||
return values
|
|
||||||
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllObjects(userId string) []string {
|
func GetAllObjects(userId string) ([]string, error) {
|
||||||
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
||||||
return enforcer.GetAllObjects()
|
return enforcer.GetAllObjects()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllActions(userId string) []string {
|
func GetAllActions(userId string) ([]string, error) {
|
||||||
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
||||||
return enforcer.GetAllActions()
|
return enforcer.GetAllActions()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllRoles(userId string) []string {
|
func GetAllRoles(userId string) []string {
|
||||||
roles, err := GetRolesByUser(userId)
|
roles, err := getRolesByUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -330,17 +359,23 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
|
|||||||
|
|
||||||
// load [policy_definition]
|
// load [policy_definition]
|
||||||
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
|
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
|
||||||
|
|
||||||
fieldsNum := len(policyDefinition)
|
fieldsNum := len(policyDefinition)
|
||||||
if fieldsNum > builtInAvailableField {
|
if fieldsNum > builtInAvailableField {
|
||||||
panic(fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum))
|
return nil, fmt.Errorf("the maximum policy_definition field number cannot exceed %d, got %d", builtInAvailableField, fieldsNum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// filled empty field with "" and V5 with "permissionId"
|
// filled empty field with "" and V5 with "permissionId"
|
||||||
for i := builtInAvailableField - fieldsNum; i > 0; i-- {
|
for i := builtInAvailableField - fieldsNum; i > 0; i-- {
|
||||||
policyDefinition = append(policyDefinition, "")
|
policyDefinition = append(policyDefinition, "")
|
||||||
}
|
}
|
||||||
policyDefinition = append(policyDefinition, "permissionId")
|
policyDefinition = append(policyDefinition, "permissionId")
|
||||||
|
|
||||||
m, _ := model.NewModelFromString(modelText)
|
m, err := model.NewModelFromString(modelText)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
m.AddDef("p", "p", strings.Join(policyDefinition, ","))
|
m.AddDef("p", "p", strings.Join(policyDefinition, ","))
|
||||||
|
|
||||||
return m, err
|
return m, err
|
||||||
|
@ -82,5 +82,11 @@ func UploadPermissions(owner string, path string) (bool, error) {
|
|||||||
if len(newPermissions) == 0 {
|
if len(newPermissions) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return AddPermissionsInBatch(newPermissions), nil
|
|
||||||
|
affected, err := AddPermissionsInBatch(newPermissions)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return affected, nil
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ type Provider struct {
|
|||||||
ClientId string `xorm:"varchar(200)" json:"clientId"`
|
ClientId string `xorm:"varchar(200)" json:"clientId"`
|
||||||
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||||
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
|
||||||
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
|
ClientSecret2 string `xorm:"varchar(500)" json:"clientSecret2"`
|
||||||
Cert string `xorm:"varchar(100)" json:"cert"`
|
Cert string `xorm:"varchar(100)" json:"cert"`
|
||||||
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
||||||
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
CustomTokenUrl string `xorm:"varchar(200)" json:"customTokenUrl"`
|
||||||
@ -398,16 +398,18 @@ func providerChangeTrigger(oldName string, newName string) error {
|
|||||||
|
|
||||||
func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo {
|
func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.ProviderInfo {
|
||||||
providerInfo := &idp.ProviderInfo{
|
providerInfo := &idp.ProviderInfo{
|
||||||
Type: provider.Type,
|
Type: provider.Type,
|
||||||
SubType: provider.SubType,
|
SubType: provider.SubType,
|
||||||
ClientId: provider.ClientId,
|
ClientId: provider.ClientId,
|
||||||
ClientSecret: provider.ClientSecret,
|
ClientSecret: provider.ClientSecret,
|
||||||
AppId: provider.AppId,
|
ClientId2: provider.ClientId2,
|
||||||
HostUrl: provider.Host,
|
ClientSecret2: provider.ClientSecret2,
|
||||||
TokenURL: provider.CustomTokenUrl,
|
AppId: provider.AppId,
|
||||||
AuthURL: provider.CustomAuthUrl,
|
HostUrl: provider.Host,
|
||||||
UserInfoURL: provider.CustomUserInfoUrl,
|
TokenURL: provider.CustomTokenUrl,
|
||||||
UserMapping: provider.UserMapping,
|
AuthURL: provider.CustomAuthUrl,
|
||||||
|
UserInfoURL: provider.CustomUserInfoUrl,
|
||||||
|
UserMapping: provider.UserMapping,
|
||||||
}
|
}
|
||||||
|
|
||||||
if provider.Type == "WeChat" {
|
if provider.Type == "WeChat" {
|
||||||
@ -415,7 +417,7 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
|||||||
providerInfo.ClientId = provider.ClientId2
|
providerInfo.ClientId = provider.ClientId2
|
||||||
providerInfo.ClientSecret = provider.ClientSecret2
|
providerInfo.ClientSecret = provider.ClientSecret2
|
||||||
}
|
}
|
||||||
} else if provider.Type == "AzureAD" {
|
} else if provider.Type == "AzureAD" || provider.Type == "ADFS" {
|
||||||
providerInfo.HostUrl = provider.Domain
|
providerInfo.HostUrl = provider.Domain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,13 +18,13 @@ type ProviderItem struct {
|
|||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
||||||
CanSignUp bool `json:"canSignUp"`
|
CanSignUp bool `json:"canSignUp"`
|
||||||
CanSignIn bool `json:"canSignIn"`
|
CanSignIn bool `json:"canSignIn"`
|
||||||
CanUnlink bool `json:"canUnlink"`
|
CanUnlink bool `json:"canUnlink"`
|
||||||
Prompted bool `json:"prompted"`
|
Prompted bool `json:"prompted"`
|
||||||
AlertType string `json:"alertType"`
|
SignupGroup string `json:"signupGroup"`
|
||||||
Rule string `json:"rule"`
|
Rule string `json:"rule"`
|
||||||
Provider *Provider `json:"provider"`
|
Provider *Provider `json:"provider"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (application *Application) GetProviderItem(providerName string) *ProviderItem {
|
func (application *Application) GetProviderItem(providerName string) *ProviderItem {
|
||||||
|
124
object/radius.go
Normal file
124
object/radius.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/xorm-io/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/sec_usr_radatt/configuration/xe-16/sec-usr-radatt-xe-16-book/sec-rad-ov-ietf-attr.html
|
||||||
|
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a
|
||||||
|
type RadiusAccounting struct {
|
||||||
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
|
CreatedTime time.Time `json:"createdTime"`
|
||||||
|
|
||||||
|
Username string `xorm:"index" json:"username"`
|
||||||
|
ServiceType int64 `json:"serviceType"` // e.g. LoginUser (1)
|
||||||
|
|
||||||
|
NasId string `json:"nasId"` // String identifying the network access server originating the Access-Request.
|
||||||
|
NasIpAddr string `json:"nasIpAddr"` // e.g. "192.168.0.10"
|
||||||
|
NasPortId string `json:"nasPortId"` // Contains a text string which identifies the port of the NAS that is authenticating the user. e.g."eth.0"
|
||||||
|
NasPortType int64 `json:"nasPortType"` // Indicates the type of physical port the network access server is using to authenticate the user. e.g.Ethernet(15)
|
||||||
|
NasPort int64 `json:"nasPort"` // Indicates the physical port number of the network access server that is authenticating the user. e.g. 233
|
||||||
|
|
||||||
|
FramedIpAddr string `json:"framedIpAddr"` // Indicates the IP address to be configured for the user by sending the IP address of a user to the RADIUS server.
|
||||||
|
FramedIpNetmask string `json:"framedIpNetmask"` // Indicates the IP netmask to be configured for the user when the user is using a device on a network.
|
||||||
|
|
||||||
|
AcctSessionId string `xorm:"index" json:"acctSessionId"`
|
||||||
|
AcctSessionTime int64 `json:"acctSessionTime"` // Indicates how long (in seconds) the user has received service.
|
||||||
|
AcctInputTotal int64 `json:"acctInputTotal"`
|
||||||
|
AcctOutputTotal int64 `json:"acctOutputTotal"`
|
||||||
|
AcctInputPackets int64 `json:"acctInputPackets"` // Indicates how many packets have been received from the port over the course of this service being provided to a framed user.
|
||||||
|
AcctOutputPackets int64 `json:"acctOutputPackets"` // Indicates how many packets have been sent to the port in the course of delivering this service to a framed user.
|
||||||
|
AcctTerminateCause int64 `json:"acctTerminateCause"` // e.g. Lost-Carrier (2)
|
||||||
|
LastUpdate time.Time `json:"lastUpdate"`
|
||||||
|
AcctStartTime time.Time `xorm:"index" json:"acctStartTime"`
|
||||||
|
AcctStopTime time.Time `xorm:"index" json:"acctStopTime"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ra *RadiusAccounting) GetId() string {
|
||||||
|
return util.GetId(ra.Owner, ra.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRadiusAccounting(owner, name string) (*RadiusAccounting, error) {
|
||||||
|
if owner == "" || name == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
ra := RadiusAccounting{Owner: owner, Name: name}
|
||||||
|
existed, err := ormer.Engine.Get(&ra)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if existed {
|
||||||
|
return &ra, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPaginationRadiusAccounting(owner, field, value, sortField, sortOrder string, offset, limit int) ([]*RadiusAccounting, error) {
|
||||||
|
ras := []*RadiusAccounting{}
|
||||||
|
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
|
||||||
|
err := session.Find(&ras)
|
||||||
|
if err != nil {
|
||||||
|
return ras, err
|
||||||
|
}
|
||||||
|
return ras, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRadiusAccounting(id string) (*RadiusAccounting, error) {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
return getRadiusAccounting(owner, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRadiusAccountingBySessionId(sessionId string) (*RadiusAccounting, error) {
|
||||||
|
ras, err := getPaginationRadiusAccounting("", "acct_session_id", sessionId, "created_time", "desc", 0, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(ras) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return ras[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddRadiusAccounting(ra *RadiusAccounting) error {
|
||||||
|
_, err := ormer.Engine.Insert(ra)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteRadiusAccounting(ra *RadiusAccounting) error {
|
||||||
|
_, err := ormer.Engine.ID(core.PK{ra.Owner, ra.Name}).Delete(&RadiusAccounting{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateRadiusAccounting(id string, ra *RadiusAccounting) error {
|
||||||
|
owner, name := util.GetOwnerAndNameFromId(id)
|
||||||
|
_, err := ormer.Engine.ID(core.PK{owner, name}).Update(ra)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func InterimUpdateRadiusAccounting(oldRa *RadiusAccounting, newRa *RadiusAccounting, stop bool) error {
|
||||||
|
if oldRa.AcctSessionId != newRa.AcctSessionId {
|
||||||
|
return fmt.Errorf("AcctSessionId is not equal, newRa = %s, oldRa = %s", newRa.AcctSessionId, oldRa.AcctSessionId)
|
||||||
|
}
|
||||||
|
oldRa.AcctInputTotal = newRa.AcctInputTotal
|
||||||
|
oldRa.AcctOutputTotal = newRa.AcctOutputTotal
|
||||||
|
oldRa.AcctInputPackets = newRa.AcctInputPackets
|
||||||
|
oldRa.AcctOutputPackets = newRa.AcctOutputPackets
|
||||||
|
oldRa.AcctSessionTime = newRa.AcctSessionTime
|
||||||
|
if stop {
|
||||||
|
oldRa.AcctStopTime = newRa.AcctStopTime
|
||||||
|
if oldRa.AcctStopTime.IsZero() {
|
||||||
|
oldRa.AcctStopTime = time.Now()
|
||||||
|
}
|
||||||
|
oldRa.AcctTerminateCause = newRa.AcctTerminateCause
|
||||||
|
} else {
|
||||||
|
oldRa.LastUpdate = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
return UpdateRadiusAccounting(oldRa.GetId(), oldRa)
|
||||||
|
}
|
@ -87,47 +87,71 @@ func AddRecord(record *casvisorsdk.Record) bool {
|
|||||||
|
|
||||||
affected, err := casvisorsdk.AddRecord(record)
|
affected, err := casvisorsdk.AddRecord(record)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
fmt.Printf("AddRecord() error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected
|
return affected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFilteredWebhooks(webhooks []*Webhook, action string) []*Webhook {
|
||||||
|
res := []*Webhook{}
|
||||||
|
for _, webhook := range webhooks {
|
||||||
|
if !webhook.IsEnabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
matched := false
|
||||||
|
for _, event := range webhook.Events {
|
||||||
|
if action == event {
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
res = append(res, webhook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func SendWebhooks(record *casvisorsdk.Record) error {
|
func SendWebhooks(record *casvisorsdk.Record) error {
|
||||||
webhooks, err := getWebhooksByOrganization(record.Organization)
|
webhooks, err := getWebhooksByOrganization(record.Organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errs := []error{}
|
||||||
|
webhooks = getFilteredWebhooks(webhooks, record.Action)
|
||||||
for _, webhook := range webhooks {
|
for _, webhook := range webhooks {
|
||||||
if !webhook.IsEnabled {
|
var user *User
|
||||||
continue
|
if webhook.IsUserExtended {
|
||||||
}
|
user, err = getUser(record.Organization, record.User)
|
||||||
|
|
||||||
matched := false
|
|
||||||
for _, event := range webhook.Events {
|
|
||||||
if record.Action == event {
|
|
||||||
matched = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
var user *User
|
|
||||||
if webhook.IsUserExtended {
|
|
||||||
user, err = getUser(record.Organization, record.User)
|
|
||||||
user, err = GetMaskedUser(user, false, err)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sendWebhook(webhook, record, user)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user, err = GetMaskedUser(user, false, err)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = sendWebhook(webhook, record, user)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
errStrings := []string{}
|
||||||
|
for _, err := range errs {
|
||||||
|
errStrings = append(errStrings, err.Error())
|
||||||
|
}
|
||||||
|
return fmt.Errorf(strings.Join(errStrings, " | "))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ type Role struct {
|
|||||||
Description string `xorm:"varchar(100)" json:"description"`
|
Description string `xorm:"varchar(100)" json:"description"`
|
||||||
|
|
||||||
Users []string `xorm:"mediumtext" json:"users"`
|
Users []string `xorm:"mediumtext" json:"users"`
|
||||||
|
Groups []string `xorm:"mediumtext" json:"groups"`
|
||||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||||
Domains []string `xorm:"mediumtext" json:"domains"`
|
Domains []string `xorm:"mediumtext" json:"domains"`
|
||||||
IsEnabled bool `json:"isEnabled"`
|
IsEnabled bool `json:"isEnabled"`
|
||||||
@ -150,8 +151,16 @@ func UpdateRole(id string, role *Role) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
addGroupingPolicies(permission)
|
err = addGroupingPolicies(permission)
|
||||||
addPolicies(permission)
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = addPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
visited[permission.GetId()] = struct{}{}
|
visited[permission.GetId()] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,10 +174,15 @@ func UpdateRole(id string, role *Role) (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, permission := range permissions {
|
for _, permission := range permissions {
|
||||||
permissionId := permission.GetId()
|
permissionId := permission.GetId()
|
||||||
if _, ok := visited[permissionId]; !ok {
|
if _, ok := visited[permissionId]; !ok {
|
||||||
addGroupingPolicies(permission)
|
err = addGroupingPolicies(permission)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
visited[permissionId] = struct{}{}
|
visited[permissionId] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,16 +221,15 @@ func AddRolesInBatch(roles []*Role) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
affected := false
|
affected := false
|
||||||
for i := 0; i < (len(roles)-1)/batchSize+1; i++ {
|
for i := 0; i < len(roles); i += batchSize {
|
||||||
start := i * batchSize
|
start := i
|
||||||
end := (i + 1) * batchSize
|
end := i + batchSize
|
||||||
if end > len(roles) {
|
if end > len(roles) {
|
||||||
end = len(roles)
|
end = len(roles)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := roles[start:end]
|
tmp := roles[start:end]
|
||||||
// TODO: save to log instead of standard output
|
fmt.Printf("The syncer adds roles: [%d - %d]\n", start, end)
|
||||||
// fmt.Printf("Add users: [%d - %d].\n", start, end)
|
|
||||||
if AddRoles(tmp) {
|
if AddRoles(tmp) {
|
||||||
affected = true
|
affected = true
|
||||||
}
|
}
|
||||||
@ -252,15 +265,40 @@ func (role *Role) GetId() string {
|
|||||||
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
|
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRolesByUser(userId string) ([]*Role, error) {
|
func getRolesByUserInternal(userId string) ([]*Role, error) {
|
||||||
roles := []*Role{}
|
roles := []*Role{}
|
||||||
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&roles)
|
user, err := GetUser(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return roles, err
|
return roles, err
|
||||||
}
|
}
|
||||||
|
|
||||||
allRolesIds := make([]string, 0, len(roles))
|
query := ormer.Engine.Alias("r").Where("r.users like ?", fmt.Sprintf("%%%s%%", userId))
|
||||||
|
for _, group := range user.Groups {
|
||||||
|
query = query.Or("r.groups like ?", fmt.Sprintf("%%%s%%", group))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = query.Find(&roles)
|
||||||
|
if err != nil {
|
||||||
|
return roles, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := []*Role{}
|
||||||
|
for _, role := range roles {
|
||||||
|
if util.InSlice(role.Users, userId) || util.HaveIntersection(role.Groups, user.Groups) {
|
||||||
|
res = append(res, role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRolesByUser(userId string) ([]*Role, error) {
|
||||||
|
roles, err := getRolesByUserInternal(userId)
|
||||||
|
if err != nil {
|
||||||
|
return roles, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allRolesIds := []string{}
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
allRolesIds = append(allRolesIds, role.GetId())
|
allRolesIds = append(allRolesIds, role.GetId())
|
||||||
}
|
}
|
||||||
@ -336,16 +374,6 @@ func GetMaskedRoles(roles []*Role) []*Role {
|
|||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRolesByNamePrefix(owner string, prefix string) ([]*Role, error) {
|
|
||||||
roles := []*Role{}
|
|
||||||
err := ormer.Engine.Where("owner=? and name like ?", owner, prefix+"%").Find(&roles)
|
|
||||||
if err != nil {
|
|
||||||
return roles, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAncestorRoles returns a list of roles that contain the given roleIds
|
// GetAncestorRoles returns a list of roles that contain the given roleIds
|
||||||
func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
|
func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
|
||||||
var (
|
var (
|
||||||
|
@ -68,5 +68,6 @@ func UploadRoles(owner string, path string) (bool, error) {
|
|||||||
if len(newRoles) == 0 {
|
if len(newRoles) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return AddRolesInBatch(newRoles), nil
|
return AddRolesInBatch(newRoles), nil
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ import (
|
|||||||
|
|
||||||
// NewSamlResponse
|
// NewSamlResponse
|
||||||
// returns a saml2 response
|
// returns a saml2 response
|
||||||
func NewSamlResponse(user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
|
func NewSamlResponse(application *Application, user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
|
||||||
samlResponse := &etree.Element{
|
samlResponse := &etree.Element{
|
||||||
Space: "samlp",
|
Space: "samlp",
|
||||||
Tag: "Response",
|
Tag: "Response",
|
||||||
@ -103,6 +103,13 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
|
|||||||
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
displayName.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||||
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
|
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
|
||||||
|
|
||||||
|
for _, item := range application.SamlAttributes {
|
||||||
|
role := attributes.CreateElement("saml:Attribute")
|
||||||
|
role.CreateAttr("Name", item.Name)
|
||||||
|
role.CreateAttr("NameFormat", item.NameFormat)
|
||||||
|
role.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(item.Value)
|
||||||
|
}
|
||||||
|
|
||||||
roles := attributes.CreateElement("saml:Attribute")
|
roles := attributes.CreateElement("saml:Attribute")
|
||||||
roles.CreateAttr("Name", "Roles")
|
roles.CreateAttr("Name", "Roles")
|
||||||
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||||
@ -184,10 +191,11 @@ type SingleSignOnService struct {
|
|||||||
|
|
||||||
type Attribute struct {
|
type Attribute struct {
|
||||||
XMLName xml.Name
|
XMLName xml.Name
|
||||||
Name string `xml:"Name,attr"`
|
Name string `xml:"Name,attr"`
|
||||||
NameFormat string `xml:"NameFormat,attr"`
|
NameFormat string `xml:"NameFormat,attr"`
|
||||||
FriendlyName string `xml:"FriendlyName,attr"`
|
FriendlyName string `xml:"FriendlyName,attr"`
|
||||||
Xmlns string `xml:"xmlns,attr"`
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Values []string `xml:"AttributeValue"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
|
||||||
@ -200,6 +208,10 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
|||||||
return nil, errors.New("please set a cert for the application first")
|
return nil, errors.New("please set a cert for the application first")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cert.Certificate == "" {
|
||||||
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
|
}
|
||||||
|
|
||||||
block, _ := pem.Decode([]byte(cert.Certificate))
|
block, _ := pem.Decode([]byte(cert.Certificate))
|
||||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||||
|
|
||||||
@ -288,6 +300,10 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
return "", "", "", err
|
return "", "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cert.Certificate == "" {
|
||||||
|
return "", "", "", fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
|
}
|
||||||
|
|
||||||
block, _ := pem.Decode([]byte(cert.Certificate))
|
block, _ := pem.Decode([]byte(cert.Certificate))
|
||||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||||
|
|
||||||
@ -301,13 +317,18 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
|||||||
|
|
||||||
_, originBackend := getOriginFromHost(host)
|
_, originBackend := getOriginFromHost(host)
|
||||||
// build signedResponse
|
// build signedResponse
|
||||||
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
samlResponse, _ := NewSamlResponse(application, user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
|
||||||
randomKeyStore := &X509Key{
|
randomKeyStore := &X509Key{
|
||||||
PrivateKey: cert.PrivateKey,
|
PrivateKey: cert.PrivateKey,
|
||||||
X509Certificate: certificate,
|
X509Certificate: certificate,
|
||||||
}
|
}
|
||||||
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
||||||
ctx.Hash = crypto.SHA1
|
ctx.Hash = crypto.SHA1
|
||||||
|
|
||||||
|
if application.EnableSamlC14n10 {
|
||||||
|
ctx.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
|
||||||
|
}
|
||||||
|
|
||||||
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
||||||
//if err != nil {
|
//if err != nil {
|
||||||
// return "", "", fmt.Errorf("err: %s", err.Error())
|
// return "", "", fmt.Errorf("err: %s", err.Error())
|
||||||
|
@ -23,23 +23,49 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/idp"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/i18n"
|
"github.com/casdoor/casdoor/i18n"
|
||||||
saml2 "github.com/russellhaering/gosaml2"
|
saml2 "github.com/russellhaering/gosaml2"
|
||||||
dsig "github.com/russellhaering/goxmldsig"
|
dsig "github.com/russellhaering/goxmldsig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (string, error) {
|
func ParseSamlResponse(samlResponse string, provider *Provider, host string) (*idp.UserInfo, error) {
|
||||||
samlResponse, _ = url.QueryUnescape(samlResponse)
|
samlResponse, _ = url.QueryUnescape(samlResponse)
|
||||||
sp, err := buildSp(provider, samlResponse, host)
|
sp, err := buildSp(provider, samlResponse, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
return assertionInfo.NameID, err
|
|
||||||
|
userInfoMap := make(map[string]string)
|
||||||
|
for spAttr, idpAttr := range provider.UserMapping {
|
||||||
|
for _, attr := range assertionInfo.Values {
|
||||||
|
if attr.Name == idpAttr {
|
||||||
|
userInfoMap[spAttr] = attr.Values[0].Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userInfoMap["id"] = assertionInfo.NameID
|
||||||
|
|
||||||
|
customUserInfo := &idp.CustomUserInfo{}
|
||||||
|
err = mapstructure.Decode(userInfoMap, customUserInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
userInfo := &idp.UserInfo{
|
||||||
|
Id: customUserInfo.Id,
|
||||||
|
Username: customUserInfo.Username,
|
||||||
|
DisplayName: customUserInfo.DisplayName,
|
||||||
|
Email: customUserInfo.Email,
|
||||||
|
AvatarUrl: customUserInfo.AvatarUrl,
|
||||||
|
}
|
||||||
|
return userInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
|
func GenerateSamlRequest(id, relayState, host, lang string) (auth string, method string, err error) {
|
||||||
@ -146,14 +172,24 @@ func getCertificateFromSamlResponse(samlResponse string, providerType string) (s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
deStr := strings.Replace(string(de), "\n", "", -1)
|
expression string
|
||||||
tagMap := map[string]string{
|
deStr = strings.Replace(string(de), "\n", "", -1)
|
||||||
"Aliyun IDaaS": "ds",
|
tagMap = map[string]string{
|
||||||
"Keycloak": "dsig",
|
"Aliyun IDaaS": "ds",
|
||||||
}
|
"Keycloak": "dsig",
|
||||||
|
}
|
||||||
|
)
|
||||||
tag := tagMap[providerType]
|
tag := tagMap[providerType]
|
||||||
expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
|
if tag == "" {
|
||||||
|
// <ds:X509Certificate>...</ds:X509Certificate>
|
||||||
|
// <dsig:X509Certificate>...</dsig:X509Certificate>
|
||||||
|
// <X509Certificate>...</X509Certificate>
|
||||||
|
// ...
|
||||||
|
expression = "<[^>]*:?X509Certificate>([\\s\\S]*?)<[^>]*:?X509Certificate>"
|
||||||
|
} else {
|
||||||
|
expression = fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
|
||||||
|
}
|
||||||
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
res := regexp.MustCompile(expression).FindStringSubmatch(deStr)
|
||||||
return res[1], nil
|
return res[1], nil
|
||||||
}
|
}
|
||||||
|
@ -230,28 +230,39 @@ func (syncer *Syncer) getTable() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) getKey() string {
|
func (syncer *Syncer) getKeyColumn() *TableColumn {
|
||||||
key := "id"
|
var column *TableColumn
|
||||||
hasKey := false
|
|
||||||
hasId := false
|
|
||||||
for _, tableColumn := range syncer.TableColumns {
|
for _, tableColumn := range syncer.TableColumns {
|
||||||
if tableColumn.IsKey {
|
if tableColumn.IsKey {
|
||||||
hasKey = true
|
column = tableColumn
|
||||||
key = tableColumn.Name
|
|
||||||
}
|
|
||||||
if tableColumn.Name == "id" {
|
|
||||||
hasId = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasKey && !hasId {
|
if column == nil {
|
||||||
key = syncer.TableColumns[0].Name
|
for _, tableColumn := range syncer.TableColumns {
|
||||||
|
if tableColumn.Name == "id" {
|
||||||
|
column = tableColumn
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return key
|
if column == nil {
|
||||||
|
column = syncer.TableColumns[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return column
|
||||||
|
}
|
||||||
|
|
||||||
|
func (syncer *Syncer) getKey() string {
|
||||||
|
column := syncer.getKeyColumn()
|
||||||
|
return util.CamelToSnakeCase(column.CasdoorName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunSyncer(syncer *Syncer) error {
|
func RunSyncer(syncer *Syncer) error {
|
||||||
syncer.initAdapter()
|
err := syncer.initAdapter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return syncer.syncUsers()
|
return syncer.syncUsers()
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,12 @@ func addSyncerJob(syncer *Syncer) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
syncer.initAdapter()
|
err := syncer.initAdapter()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err := syncer.syncUsers()
|
err = syncer.syncUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,11 @@ func getEnabledSyncerForOrganization(organization string) (*Syncer, error) {
|
|||||||
|
|
||||||
for _, syncer := range syncers {
|
for _, syncer := range syncers {
|
||||||
if syncer.Organization == organization && syncer.IsEnabled {
|
if syncer.Organization == organization && syncer.IsEnabled {
|
||||||
syncer.initAdapter()
|
err = syncer.initAdapter()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return syncer, nil
|
return syncer, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,6 +59,10 @@ func AddUserToOriginalDatabase(user *User) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if syncer.IsReadOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
updatedOUser := syncer.createOriginalUserFromUser(user)
|
updatedOUser := syncer.createOriginalUserFromUser(user)
|
||||||
_, err = syncer.addUser(updatedOUser)
|
_, err = syncer.addUser(updatedOUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -74,6 +82,10 @@ func UpdateUserToOriginalDatabase(user *User) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if syncer.IsReadOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
newUser, err := GetUser(user.GetId())
|
newUser, err := GetUser(user.GetId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -16,7 +16,8 @@ package object
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (syncer *Syncer) syncUsers() error {
|
func (syncer *Syncer) syncUsers() error {
|
||||||
@ -26,17 +27,26 @@ func (syncer *Syncer) syncUsers() error {
|
|||||||
|
|
||||||
fmt.Printf("Running syncUsers()..\n")
|
fmt.Printf("Running syncUsers()..\n")
|
||||||
|
|
||||||
users, _, _ := syncer.getUserMap()
|
users, err := GetUsers(syncer.Organization)
|
||||||
oUsers, _, err := syncer.getOriginalUserMap()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(err.Error())
|
line := fmt.Sprintf("[%s] %s\n", util.GetCurrentTime(), err.Error())
|
||||||
|
_, err2 := updateSyncerErrorText(syncer, line)
|
||||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
if err2 != nil {
|
||||||
line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error())
|
panic(err2)
|
||||||
_, err = updateSyncerErrorText(syncer, line)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
oUsers, err := syncer.getOriginalUsers()
|
||||||
|
if err != nil {
|
||||||
|
line := fmt.Sprintf("[%s] %s\n", util.GetCurrentTime(), err.Error())
|
||||||
|
_, err2 := updateSyncerErrorText(syncer, line)
|
||||||
|
if err2 != nil {
|
||||||
|
panic(err2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
|
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
|
||||||
@ -76,7 +86,7 @@ func (syncer *Syncer) syncUsers() error {
|
|||||||
updatedUser.PreHash = oHash
|
updatedUser.PreHash = oHash
|
||||||
|
|
||||||
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
|
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
|
||||||
_, err = syncer.updateUserForOriginalByFields(updatedUser, key)
|
_, err = syncer.updateUserForOriginalFields(updatedUser, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -113,7 +123,7 @@ func (syncer *Syncer) syncUsers() error {
|
|||||||
updatedUser.PreHash = oHash
|
updatedUser.PreHash = oHash
|
||||||
|
|
||||||
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
|
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
|
||||||
_, err = syncer.updateUserForOriginalByFields(updatedUser, key)
|
_, err = syncer.updateUserForOriginalFields(updatedUser, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -122,6 +132,7 @@ func (syncer *Syncer) syncUsers() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = AddUsersInBatch(newUsers)
|
_, err = AddUsersInBatch(newUsers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OriginalUser = User
|
type OriginalUser = User
|
||||||
@ -50,19 +49,6 @@ func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*OriginalUser, error) {
|
|
||||||
users, err := syncer.getOriginalUsers()
|
|
||||||
if err != nil {
|
|
||||||
return users, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := map[string]*OriginalUser{}
|
|
||||||
for _, user := range users {
|
|
||||||
m[user.Id] = user
|
|
||||||
}
|
|
||||||
return users, m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
|
func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
|
||||||
m := syncer.getMapFromOriginalUser(user)
|
m := syncer.getMapFromOriginalUser(user)
|
||||||
affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).Insert(m)
|
affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).Insert(m)
|
||||||
@ -89,38 +75,14 @@ func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) {
|
|||||||
pkValue := m[key]
|
pkValue := m[key]
|
||||||
delete(m, key)
|
delete(m, key)
|
||||||
|
|
||||||
affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).ID(pkValue).Update(&m)
|
affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).Where(fmt.Sprintf("%s = ?", key), pkValue).Update(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return affected != 0, nil
|
return affected != 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) updateUserForOriginalFields(user *User) (bool, error) {
|
func (syncer *Syncer) updateUserForOriginalFields(user *User, key string) (bool, error) {
|
||||||
var err error
|
|
||||||
owner, name := util.GetOwnerAndNameFromId(user.GetId())
|
|
||||||
oldUser, err := getUserById(owner, name)
|
|
||||||
if oldUser == nil || err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
|
||||||
user.PermanentAvatar, err = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar, true)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
columns := syncer.getCasdoorColumns()
|
|
||||||
columns = append(columns, "affiliation", "hash", "pre_hash")
|
|
||||||
affected, err := ormer.Engine.ID(core.PK{oldUser.Owner, oldUser.Name}).Cols(columns...).Update(user)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return affected != 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (syncer *Syncer) updateUserForOriginalByFields(user *User, key string) (bool, error) {
|
|
||||||
var err error
|
var err error
|
||||||
oldUser := User{}
|
oldUser := User{}
|
||||||
|
|
||||||
@ -162,27 +124,31 @@ func (syncer *Syncer) calculateHash(user *OriginalUser) string {
|
|||||||
return util.GetMd5Hash(s)
|
return util.GetMd5Hash(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (syncer *Syncer) initAdapter() {
|
func (syncer *Syncer) initAdapter() error {
|
||||||
if syncer.Ormer == nil {
|
if syncer.Ormer != nil {
|
||||||
var dataSourceName string
|
return nil
|
||||||
if syncer.DatabaseType == "mssql" {
|
|
||||||
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
|
|
||||||
} else if syncer.DatabaseType == "postgres" {
|
|
||||||
sslMode := "disable"
|
|
||||||
if syncer.SslMode != "" {
|
|
||||||
sslMode = syncer.SslMode
|
|
||||||
}
|
|
||||||
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, sslMode, syncer.Database)
|
|
||||||
} else {
|
|
||||||
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isCloudIntranet {
|
|
||||||
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
|
||||||
}
|
|
||||||
|
|
||||||
syncer.Ormer = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dataSourceName string
|
||||||
|
if syncer.DatabaseType == "mssql" {
|
||||||
|
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
|
||||||
|
} else if syncer.DatabaseType == "postgres" {
|
||||||
|
sslMode := "disable"
|
||||||
|
if syncer.SslMode != "" {
|
||||||
|
sslMode = syncer.SslMode
|
||||||
|
}
|
||||||
|
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, sslMode, syncer.Database)
|
||||||
|
} else {
|
||||||
|
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isCloudIntranet {
|
||||||
|
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
syncer.Ormer, err = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunSyncUsersJob() {
|
func RunSyncUsersJob() {
|
||||||
@ -192,7 +158,10 @@ func RunSyncUsersJob() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, syncer := range syncers {
|
for _, syncer := range syncers {
|
||||||
addSyncerJob(syncer)
|
err = addSyncerJob(syncer)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(time.Duration(1<<63 - 1))
|
time.Sleep(time.Duration(1<<63 - 1))
|
||||||
|
@ -195,6 +195,9 @@ func GenerateCasToken(userId string, service string) (string, error) {
|
|||||||
|
|
||||||
user, _ = GetMaskedUser(user, false)
|
user, _ = GetMaskedUser(user, false)
|
||||||
|
|
||||||
|
user.WebauthnCredentials = nil
|
||||||
|
user.Properties = nil
|
||||||
|
|
||||||
authenticationSuccess := CasAuthenticationSuccess{
|
authenticationSuccess := CasAuthenticationSuccess{
|
||||||
User: user.Name,
|
User: user.Name,
|
||||||
Attributes: &CasAttributes{
|
Attributes: &CasAttributes{
|
||||||
@ -286,6 +289,10 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cert.Certificate == "" {
|
||||||
|
return "", "", fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
|
}
|
||||||
|
|
||||||
block, _ := pem.Decode([]byte(cert.Certificate))
|
block, _ := pem.Decode([]byte(cert.Certificate))
|
||||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||||
randomKeyStore := &X509Key{
|
randomKeyStore := &X509Key{
|
||||||
|
@ -26,7 +26,7 @@ type Claims struct {
|
|||||||
*User
|
*User
|
||||||
TokenType string `json:"tokenType,omitempty"`
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
@ -37,56 +37,90 @@ type UserShort struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserWithoutThirdIdp struct {
|
type UserWithoutThirdIdp struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
CreatedTime string `xorm:"varchar(100) index" json:"createdTime"`
|
||||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||||
Id string `xorm:"varchar(100) index" json:"id"`
|
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Id string `xorm:"varchar(100) index" json:"id"`
|
||||||
Password string `xorm:"varchar(100)" json:"password"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||||
Email string `xorm:"varchar(100) index" json:"email"`
|
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||||
EmailVerified bool `json:"emailVerified"`
|
AvatarType string `xorm:"varchar(100)" json:"avatarType"`
|
||||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||||
Location string `xorm:"varchar(100)" json:"location"`
|
Email string `xorm:"varchar(100) index" json:"email"`
|
||||||
Address []string `json:"address"`
|
EmailVerified bool `json:"emailVerified"`
|
||||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||||
Title string `xorm:"varchar(100)" json:"title"`
|
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||||
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
Region string `xorm:"varchar(100)" json:"region"`
|
||||||
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
Location string `xorm:"varchar(100)" json:"location"`
|
||||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
Address []string `json:"address"`
|
||||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
Title string `xorm:"varchar(100)" json:"title"`
|
||||||
Region string `xorm:"varchar(100)" json:"region"`
|
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
||||||
Language string `xorm:"varchar(100)" json:"language"`
|
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
||||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||||
Education string `xorm:"varchar(100)" json:"education"`
|
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||||
Score int `json:"score"`
|
Language string `xorm:"varchar(100)" json:"language"`
|
||||||
Karma int `json:"karma"`
|
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||||
Ranking int `json:"ranking"`
|
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||||
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
Education string `xorm:"varchar(100)" json:"education"`
|
||||||
IsOnline bool `json:"isOnline"`
|
Score int `json:"score"`
|
||||||
IsAdmin bool `json:"isAdmin"`
|
Karma int `json:"karma"`
|
||||||
IsForbidden bool `json:"isForbidden"`
|
Ranking int `json:"ranking"`
|
||||||
IsDeleted bool `json:"isDeleted"`
|
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
||||||
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
IsOnline bool `json:"isOnline"`
|
||||||
Hash string `xorm:"varchar(100)" json:"hash"`
|
IsAdmin bool `json:"isAdmin"`
|
||||||
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
IsForbidden bool `json:"isForbidden"`
|
||||||
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
|
IsDeleted bool `json:"isDeleted"`
|
||||||
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
||||||
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
Hash string `xorm:"varchar(100)" json:"hash"`
|
||||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
||||||
Properties map[string]string `json:"properties"`
|
AccessKey string `xorm:"varchar(100)" json:"accessKey"`
|
||||||
Roles []*Role `xorm:"-" json:"roles"`
|
AccessSecret string `xorm:"varchar(100)" json:"accessSecret"`
|
||||||
Permissions []*Permission `xorm:"-" json:"permissions"`
|
|
||||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
GitHub string `xorm:"github varchar(100)" json:"github"`
|
||||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
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"`
|
||||||
|
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||||
|
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||||
|
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||||
|
|
||||||
|
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
|
||||||
|
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
||||||
|
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
||||||
|
|
||||||
|
// WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
|
||||||
|
PreferredMfaType string `xorm:"varchar(100)" json:"preferredMfaType"`
|
||||||
|
RecoveryCodes []string `xorm:"varchar(1000)" json:"recoveryCodes"`
|
||||||
|
TotpSecret string `xorm:"varchar(100)" json:"totpSecret"`
|
||||||
|
MfaPhoneEnabled bool `json:"mfaPhoneEnabled"`
|
||||||
|
MfaEmailEnabled bool `json:"mfaEmailEnabled"`
|
||||||
|
// MultiFactorAuths []*MfaProps `xorm:"-" json:"multiFactorAuths,omitempty"`
|
||||||
|
|
||||||
|
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||||
|
Properties map[string]string `json:"properties"`
|
||||||
|
|
||||||
|
Roles []*Role `json:"roles"`
|
||||||
|
Permissions []*Permission `json:"permissions"`
|
||||||
|
Groups []string `xorm:"groups varchar(1000)" json:"groups"`
|
||||||
|
|
||||||
|
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||||
|
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||||
|
|
||||||
|
// ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimsShort struct {
|
type ClaimsShort struct {
|
||||||
@ -101,7 +135,7 @@ type ClaimsWithoutThirdIdp struct {
|
|||||||
*UserWithoutThirdIdp
|
*UserWithoutThirdIdp
|
||||||
TokenType string `json:"tokenType,omitempty"`
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag"`
|
||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
@ -125,14 +159,18 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
|||||||
Type: user.Type,
|
Type: user.Type,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
PasswordSalt: user.PasswordSalt,
|
PasswordSalt: user.PasswordSalt,
|
||||||
|
PasswordType: user.PasswordType,
|
||||||
DisplayName: user.DisplayName,
|
DisplayName: user.DisplayName,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Avatar: user.Avatar,
|
Avatar: user.Avatar,
|
||||||
|
AvatarType: user.AvatarType,
|
||||||
PermanentAvatar: user.PermanentAvatar,
|
PermanentAvatar: user.PermanentAvatar,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
EmailVerified: user.EmailVerified,
|
EmailVerified: user.EmailVerified,
|
||||||
Phone: user.Phone,
|
Phone: user.Phone,
|
||||||
|
CountryCode: user.CountryCode,
|
||||||
|
Region: user.Region,
|
||||||
Location: user.Location,
|
Location: user.Location,
|
||||||
Address: user.Address,
|
Address: user.Address,
|
||||||
Affiliation: user.Affiliation,
|
Affiliation: user.Affiliation,
|
||||||
@ -142,7 +180,6 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
|||||||
Homepage: user.Homepage,
|
Homepage: user.Homepage,
|
||||||
Bio: user.Bio,
|
Bio: user.Bio,
|
||||||
Tag: user.Tag,
|
Tag: user.Tag,
|
||||||
Region: user.Region,
|
|
||||||
Language: user.Language,
|
Language: user.Language,
|
||||||
Gender: user.Gender,
|
Gender: user.Gender,
|
||||||
Birthday: user.Birthday,
|
Birthday: user.Birthday,
|
||||||
@ -158,16 +195,38 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
|||||||
SignupApplication: user.SignupApplication,
|
SignupApplication: user.SignupApplication,
|
||||||
Hash: user.Hash,
|
Hash: user.Hash,
|
||||||
PreHash: user.PreHash,
|
PreHash: user.PreHash,
|
||||||
|
AccessKey: user.AccessKey,
|
||||||
|
AccessSecret: user.AccessSecret,
|
||||||
|
|
||||||
|
GitHub: user.GitHub,
|
||||||
|
Google: user.Google,
|
||||||
|
QQ: user.QQ,
|
||||||
|
WeChat: user.WeChat,
|
||||||
|
Facebook: user.Facebook,
|
||||||
|
DingTalk: user.DingTalk,
|
||||||
|
Weibo: user.Weibo,
|
||||||
|
Gitee: user.Gitee,
|
||||||
|
LinkedIn: user.LinkedIn,
|
||||||
|
Wecom: user.Wecom,
|
||||||
|
Lark: user.Lark,
|
||||||
|
Gitlab: user.Gitlab,
|
||||||
|
|
||||||
CreatedIp: user.CreatedIp,
|
CreatedIp: user.CreatedIp,
|
||||||
LastSigninTime: user.LastSigninTime,
|
LastSigninTime: user.LastSigninTime,
|
||||||
LastSigninIp: user.LastSigninIp,
|
LastSigninIp: user.LastSigninIp,
|
||||||
|
|
||||||
|
PreferredMfaType: user.PreferredMfaType,
|
||||||
|
RecoveryCodes: user.RecoveryCodes,
|
||||||
|
TotpSecret: user.TotpSecret,
|
||||||
|
MfaPhoneEnabled: user.MfaPhoneEnabled,
|
||||||
|
MfaEmailEnabled: user.MfaEmailEnabled,
|
||||||
|
|
||||||
Ldap: user.Ldap,
|
Ldap: user.Ldap,
|
||||||
Properties: user.Properties,
|
Properties: user.Properties,
|
||||||
|
|
||||||
Roles: user.Roles,
|
Roles: user.Roles,
|
||||||
Permissions: user.Permissions,
|
Permissions: user.Permissions,
|
||||||
|
Groups: user.Groups,
|
||||||
|
|
||||||
LastSigninWrongTime: user.LastSigninWrongTime,
|
LastSigninWrongTime: user.LastSigninWrongTime,
|
||||||
SigninWrongTimes: user.SigninWrongTimes,
|
SigninWrongTimes: user.SigninWrongTimes,
|
||||||
@ -309,6 +368,10 @@ func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
|
|||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cert.Certificate == "" {
|
||||||
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
|
}
|
||||||
|
|
||||||
// RSA certificate
|
// RSA certificate
|
||||||
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
107
object/user.go
107
object/user.go
@ -50,6 +50,7 @@ type User struct {
|
|||||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||||
|
|
||||||
Id string `xorm:"varchar(100) index" json:"id"`
|
Id string `xorm:"varchar(100) index" json:"id"`
|
||||||
|
ExternalId string `xorm:"varchar(100) index" json:"externalId"`
|
||||||
Type string `xorm:"varchar(100)" json:"type"`
|
Type string `xorm:"varchar(100)" json:"type"`
|
||||||
Password string `xorm:"varchar(100)" json:"password"`
|
Password string `xorm:"varchar(100)" json:"password"`
|
||||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||||
@ -371,6 +372,24 @@ func GetUserByEmail(owner string, email string) (*User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserByEmailOnly(email string) (*User, error) {
|
||||||
|
if email == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := User{Email: email}
|
||||||
|
existed, err := ormer.Engine.Get(&user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &user, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserByPhone(owner string, phone string) (*User, error) {
|
func GetUserByPhone(owner string, phone string) (*User, error) {
|
||||||
if owner == "" || phone == "" {
|
if owner == "" || phone == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -389,6 +408,24 @@ func GetUserByPhone(owner string, phone string) (*User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserByPhoneOnly(phone string) (*User, error) {
|
||||||
|
if phone == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := User{Phone: phone}
|
||||||
|
existed, err := ormer.Engine.Get(&user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &user, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserByUserId(owner string, userId string) (*User, error) {
|
func GetUserByUserId(owner string, userId string) (*User, error) {
|
||||||
if owner == "" || userId == "" {
|
if owner == "" || userId == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -407,6 +444,24 @@ func GetUserByUserId(owner string, userId string) (*User, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserByUserIdOnly(userId string) (*User, error) {
|
||||||
|
if userId == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
user := User{Id: userId}
|
||||||
|
existed, err := ormer.Engine.Get(&user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if existed {
|
||||||
|
return &user, nil
|
||||||
|
} else {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func GetUserByAccessKey(accessKey string) (*User, error) {
|
func GetUserByAccessKey(accessKey string) (*User, error) {
|
||||||
if accessKey == "" {
|
if accessKey == "" {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -506,7 +561,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if oldUser == nil {
|
if oldUser == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user: %s is not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if name != user.Name {
|
if name != user.Name {
|
||||||
@ -529,7 +584,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
|
|
||||||
if len(columns) == 0 {
|
if len(columns) == 0 {
|
||||||
columns = []string{
|
columns = []string{
|
||||||
"owner", "display_name", "avatar",
|
"owner", "display_name", "avatar", "first_name", "last_name",
|
||||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
|
||||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret",
|
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret",
|
||||||
@ -546,6 +601,9 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
|||||||
columns = append(columns, "name", "email", "phone", "country_code", "type")
|
columns = append(columns, "name", "email", "phone", "country_code", "type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
columns = append(columns, "updated_time")
|
||||||
|
user.UpdatedTime = util.GetCurrentTime()
|
||||||
|
|
||||||
if util.ContainsString(columns, "groups") {
|
if util.ContainsString(columns, "groups") {
|
||||||
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -584,7 +642,7 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if oldUser == nil {
|
if oldUser == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user: %s is not found", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if name != user.Name {
|
if name != user.Name {
|
||||||
@ -630,19 +688,26 @@ func AddUser(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user.Owner == "" || user.Name == "" {
|
if user.Owner == "" || user.Name == "" {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user's owner and name should not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
organization, _ := GetOrganizationByUser(user)
|
organization, err := GetOrganizationByUser(user)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
if organization == nil {
|
if organization == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the organization: %s is not found", user.Owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
if organization.DefaultPassword != "" && user.Password == "123" {
|
||||||
|
user.Password = organization.DefaultPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.PasswordType == "" || user.PasswordType == "plain" {
|
if user.PasswordType == "" || user.PasswordType == "plain" {
|
||||||
user.UpdateUserPassword(organization)
|
user.UpdateUserPassword(organization)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := user.UpdateUserHash()
|
err = user.UpdateUserHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -676,9 +741,8 @@ func AddUser(user *User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddUsers(users []*User) (bool, error) {
|
func AddUsers(users []*User) (bool, error) {
|
||||||
var err error
|
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
return false, nil
|
return false, fmt.Errorf("no users are provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
// organization := GetOrganizationByUser(users[0])
|
// organization := GetOrganizationByUser(users[0])
|
||||||
@ -686,7 +750,7 @@ func AddUsers(users []*User) (bool, error) {
|
|||||||
// this function is only used for syncer or batch upload, so no need to encrypt the password
|
// this function is only used for syncer or batch upload, so no need to encrypt the password
|
||||||
// user.UpdateUserPassword(organization)
|
// user.UpdateUserPassword(organization)
|
||||||
|
|
||||||
err = user.UpdateUserHash()
|
err := user.UpdateUserHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -710,23 +774,22 @@ func AddUsers(users []*User) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AddUsersInBatch(users []*User) (bool, error) {
|
func AddUsersInBatch(users []*User) (bool, error) {
|
||||||
batchSize := conf.GetConfigBatchSize()
|
|
||||||
|
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
return false, nil
|
return false, fmt.Errorf("no users are provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
batchSize := conf.GetConfigBatchSize()
|
||||||
|
|
||||||
affected := false
|
affected := false
|
||||||
for i := 0; i < (len(users)-1)/batchSize+1; i++ {
|
for i := 0; i < len(users); i += batchSize {
|
||||||
start := i * batchSize
|
start := i
|
||||||
end := (i + 1) * batchSize
|
end := i + batchSize
|
||||||
if end > len(users) {
|
if end > len(users) {
|
||||||
end = len(users)
|
end = len(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := users[start:end]
|
tmp := users[start:end]
|
||||||
// TODO: save to log instead of standard output
|
fmt.Printf("The syncer adds users: [%d - %d]\n", start, end)
|
||||||
// fmt.Printf("Add users: [%d - %d].\n", start, end)
|
|
||||||
if ok, err := AddUsers(tmp); err != nil {
|
if ok, err := AddUsers(tmp); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
} else if ok {
|
} else if ok {
|
||||||
@ -796,7 +859,7 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Permissions, user.Roles, err = GetPermissionsAndRolesByUser(user.GetId())
|
user.Permissions, user.Roles, err = getPermissionsAndRolesByUser(user.GetId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -884,9 +947,9 @@ func (user *User) GetPreferredMfaProps(masked bool) *MfaProps {
|
|||||||
return user.GetMfaProps(user.PreferredMfaType, masked)
|
return user.GetMfaProps(user.PreferredMfaType, masked)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddUserkeys(user *User, isAdmin bool) (bool, error) {
|
func AddUserKeys(user *User, isAdmin bool) (bool, error) {
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return false, nil
|
return false, fmt.Errorf("the user is not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
user.AccessKey = util.GenerateId()
|
user.AccessKey = util.GenerateId()
|
||||||
@ -912,7 +975,7 @@ func (user *User) IsGlobalAdmin() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GenerateIdForNewUser(application *Application) (string, error) {
|
func GenerateIdForNewUser(application *Application) (string, error) {
|
||||||
if application.GetSignupItemRule("ID") != "Incremental" {
|
if application == nil || application.GetSignupItemRule("ID") != "Incremental" {
|
||||||
return util.GenerateId(), nil
|
return util.GenerateId(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
|||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
|
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
|
||||||
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") {
|
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") || strings.Contains(err.Error(), "unrecognized name") {
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
} else {
|
} else {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
@ -144,5 +144,6 @@ func UploadUsers(owner string, path string) (bool, error) {
|
|||||||
if len(newUsers) == 0 {
|
if len(newUsers) == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return AddUsersInBatch(newUsers)
|
return AddUsersInBatch(newUsers)
|
||||||
}
|
}
|
||||||
|
@ -80,10 +80,6 @@ func IsAllowSend(user *User, remoteAddr, recordType string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
|
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
|
||||||
if provider == nil {
|
|
||||||
return fmt.Errorf("please set an Email provider first")
|
|
||||||
}
|
|
||||||
|
|
||||||
sender := organization.DisplayName
|
sender := organization.DisplayName
|
||||||
title := provider.Title
|
title := provider.Title
|
||||||
code := getRandomCode(6)
|
code := getRandomCode(6)
|
||||||
@ -106,10 +102,6 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SendVerificationCodeToPhone(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
|
func SendVerificationCodeToPhone(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
|
||||||
if provider == nil {
|
|
||||||
return errors.New("please set a SMS provider first")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := IsAllowSend(user, remoteAddr, provider.Category); err != nil {
|
if err := IsAllowSend(user, remoteAddr, provider.Category); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
109
radius/server.go
Normal file
109
radius/server.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package radius
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"layeh.com/radius"
|
||||||
|
"layeh.com/radius/rfc2865"
|
||||||
|
"layeh.com/radius/rfc2866"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3
|
||||||
|
func StartRadiusServer() {
|
||||||
|
secret := conf.GetConfigString("radiusSecret")
|
||||||
|
server := radius.PacketServer{
|
||||||
|
Addr: "0.0.0.0:" + conf.GetConfigString("radiusServerPort"),
|
||||||
|
Handler: radius.HandlerFunc(handlerRadius),
|
||||||
|
SecretSource: radius.StaticSecretSource([]byte(secret)),
|
||||||
|
}
|
||||||
|
log.Printf("Starting Radius server on %s", server.Addr)
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
log.Printf("StartRadiusServer() failed, err = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlerRadius(w radius.ResponseWriter, r *radius.Request) {
|
||||||
|
switch r.Code {
|
||||||
|
case radius.CodeAccessRequest:
|
||||||
|
handleAccessRequest(w, r)
|
||||||
|
case radius.CodeAccountingRequest:
|
||||||
|
handleAccountingRequest(w, r)
|
||||||
|
default:
|
||||||
|
log.Printf("radius message, code = %d", r.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
|
||||||
|
username := rfc2865.UserName_GetString(r.Packet)
|
||||||
|
password := rfc2865.UserPassword_GetString(r.Packet)
|
||||||
|
organization := rfc2865.Class_GetString(r.Packet)
|
||||||
|
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
|
||||||
|
if organization == "" {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, msg := object.CheckUserPassword(organization, username, password, "en")
|
||||||
|
if msg != "" {
|
||||||
|
w.Write(r.Response(radius.CodeAccessReject))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(r.Response(radius.CodeAccessAccept))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAccountingRequest(w radius.ResponseWriter, r *radius.Request) {
|
||||||
|
statusType := rfc2866.AcctStatusType_Get(r.Packet)
|
||||||
|
username := rfc2865.UserName_GetString(r.Packet)
|
||||||
|
organization := rfc2865.Class_GetString(r.Packet)
|
||||||
|
log.Printf("handleAccountingRequest() username=%v, org=%v, statusType=%v", username, organization, statusType)
|
||||||
|
w.Write(r.Response(radius.CodeAccountingResponse))
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("handleAccountingRequest() failed, err = %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
switch statusType {
|
||||||
|
case rfc2866.AcctStatusType_Value_Start:
|
||||||
|
// Start an accounting session
|
||||||
|
ra := GetAccountingFromRequest(r)
|
||||||
|
err = object.AddRadiusAccounting(ra)
|
||||||
|
case rfc2866.AcctStatusType_Value_InterimUpdate, rfc2866.AcctStatusType_Value_Stop:
|
||||||
|
// Interim update to an accounting session | Stop an accounting session
|
||||||
|
var (
|
||||||
|
newRa = GetAccountingFromRequest(r)
|
||||||
|
oldRa *object.RadiusAccounting
|
||||||
|
)
|
||||||
|
oldRa, err = object.GetRadiusAccountingBySessionId(newRa.AcctSessionId)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if oldRa == nil {
|
||||||
|
if err = object.AddRadiusAccounting(newRa); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stop := statusType == rfc2866.AcctStatusType_Value_Stop
|
||||||
|
err = object.InterimUpdateRadiusAccounting(oldRa, newRa, stop)
|
||||||
|
case rfc2866.AcctStatusType_Value_AccountingOn, rfc2866.AcctStatusType_Value_AccountingOff:
|
||||||
|
// By default, no Accounting-On or Accounting-Off messages are sent (no acct-on-off).
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupport statusType = %v", statusType)
|
||||||
|
}
|
||||||
|
}
|
54
radius/server_test.go
Normal file
54
radius/server_test.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package radius
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"layeh.com/radius"
|
||||||
|
"layeh.com/radius/rfc2865"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccessRequestRejected(t *testing.T) {
|
||||||
|
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
|
||||||
|
rfc2865.UserName_SetString(packet, "admin")
|
||||||
|
rfc2865.UserPassword_SetString(packet, "12345")
|
||||||
|
rfc2865.Class_SetString(packet, "built-in")
|
||||||
|
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if response.Code != radius.CodeAccessReject {
|
||||||
|
t.Fatalf("Expected %v, got %v", radius.CodeAccessReject, response.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccessRequestAccepted(t *testing.T) {
|
||||||
|
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
|
||||||
|
rfc2865.UserName_SetString(packet, "admin")
|
||||||
|
rfc2865.UserPassword_SetString(packet, "123")
|
||||||
|
rfc2865.Class_SetString(packet, "built-in")
|
||||||
|
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if response.Code != radius.CodeAccessAccept {
|
||||||
|
t.Fatalf("Expected %v, got %v", radius.CodeAccessAccept, response.Code)
|
||||||
|
}
|
||||||
|
}
|
67
radius/util.go
Normal file
67
radius/util.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package radius
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"layeh.com/radius"
|
||||||
|
"layeh.com/radius/rfc2865"
|
||||||
|
"layeh.com/radius/rfc2866"
|
||||||
|
"layeh.com/radius/rfc2869"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetAccountingFromRequest(r *radius.Request) *object.RadiusAccounting {
|
||||||
|
acctInputOctets := int(rfc2866.AcctInputOctets_Get(r.Packet))
|
||||||
|
acctInputGigawords := int(rfc2869.AcctInputGigawords_Get(r.Packet))
|
||||||
|
acctOutputOctets := int(rfc2866.AcctOutputOctets_Get(r.Packet))
|
||||||
|
acctOutputGigawords := int(rfc2869.AcctOutputGigawords_Get(r.Packet))
|
||||||
|
organization := rfc2865.Class_GetString(r.Packet)
|
||||||
|
getAcctStartTime := func(sessionTime int) time.Time {
|
||||||
|
m, _ := time.ParseDuration(fmt.Sprintf("-%ds", sessionTime))
|
||||||
|
return time.Now().Add(m)
|
||||||
|
}
|
||||||
|
ra := &object.RadiusAccounting{
|
||||||
|
Owner: organization,
|
||||||
|
Name: "ra_" + util.GenerateId()[:6],
|
||||||
|
CreatedTime: time.Now(),
|
||||||
|
|
||||||
|
Username: rfc2865.UserName_GetString(r.Packet),
|
||||||
|
ServiceType: int64(rfc2865.ServiceType_Get(r.Packet)),
|
||||||
|
|
||||||
|
NasId: rfc2865.NASIdentifier_GetString(r.Packet),
|
||||||
|
NasIpAddr: rfc2865.NASIPAddress_Get(r.Packet).String(),
|
||||||
|
NasPortId: rfc2869.NASPortID_GetString(r.Packet),
|
||||||
|
NasPortType: int64(rfc2865.NASPortType_Get(r.Packet)),
|
||||||
|
NasPort: int64(rfc2865.NASPort_Get(r.Packet)),
|
||||||
|
|
||||||
|
FramedIpAddr: rfc2865.FramedIPAddress_Get(r.Packet).String(),
|
||||||
|
FramedIpNetmask: rfc2865.FramedIPNetmask_Get(r.Packet).String(),
|
||||||
|
|
||||||
|
AcctSessionId: rfc2866.AcctSessionID_GetString(r.Packet),
|
||||||
|
AcctSessionTime: int64(rfc2866.AcctSessionTime_Get(r.Packet)),
|
||||||
|
AcctInputTotal: int64(acctInputOctets) + int64(acctInputGigawords)*4*1024*1024*1024,
|
||||||
|
AcctOutputTotal: int64(acctOutputOctets) + int64(acctOutputGigawords)*4*1024*1024*1024,
|
||||||
|
AcctInputPackets: int64(rfc2866.AcctInputPackets_Get(r.Packet)),
|
||||||
|
AcctOutputPackets: int64(rfc2866.AcctInputPackets_Get(r.Packet)),
|
||||||
|
AcctStartTime: getAcctStartTime(int(rfc2866.AcctSessionTime_Get(r.Packet))),
|
||||||
|
AcctTerminateCause: int64(rfc2866.AcctTerminateCause_Get(r.Packet)),
|
||||||
|
LastUpdate: time.Now(),
|
||||||
|
}
|
||||||
|
return ra
|
||||||
|
}
|
@ -35,14 +35,14 @@ type Object struct {
|
|||||||
func getUsername(ctx *context.Context) (username string) {
|
func getUsername(ctx *context.Context) (username string) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
username = getUsernameByClientIdSecret(ctx)
|
username, _ = getUsernameByClientIdSecret(ctx)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
username = ctx.Input.Session("username").(string)
|
username = ctx.Input.Session("username").(string)
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = getUsernameByClientIdSecret(ctx)
|
username, _ = getUsernameByClientIdSecret(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
@ -66,6 +66,13 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
path := ctx.Request.URL.Path
|
path := ctx.Request.URL.Path
|
||||||
|
|
||||||
if method == http.MethodGet {
|
if method == http.MethodGet {
|
||||||
|
if ctx.Request.URL.Path == "/api/get-policies" && ctx.Input.Query("id") == "/" {
|
||||||
|
adapterId := ctx.Input.Query("adapterId")
|
||||||
|
if adapterId != "" {
|
||||||
|
return util.GetOwnerAndNameFromIdNoCheck(adapterId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// query == "?id=built-in/admin"
|
// query == "?id=built-in/admin"
|
||||||
id := ctx.Input.Query("id")
|
id := ctx.Input.Query("id")
|
||||||
if id != "" {
|
if id != "" {
|
||||||
@ -79,8 +86,14 @@ func getObject(ctx *context.Context) (string, string) {
|
|||||||
|
|
||||||
return "", ""
|
return "", ""
|
||||||
} else {
|
} else {
|
||||||
body := ctx.Input.RequestBody
|
if path == "/api/add-policy" || path == "/api/remove-policy" || path == "/api/update-policy" {
|
||||||
|
id := ctx.Input.Query("id")
|
||||||
|
if id != "" {
|
||||||
|
return util.GetOwnerAndNameFromIdNoCheck(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body := ctx.Input.RequestBody
|
||||||
if len(body) == 0 {
|
if len(body) == 0 {
|
||||||
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name")
|
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name")
|
||||||
}
|
}
|
||||||
@ -139,6 +152,10 @@ func getUrlPath(urlPath string) string {
|
|||||||
return "/cas"
|
return "/cas"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(urlPath, "/scim") {
|
||||||
|
return "/scim"
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(urlPath, "/api/login/oauth") {
|
if strings.HasPrefix(urlPath, "/api/login/oauth") {
|
||||||
return "/api/login/oauth"
|
return "/api/login/oauth"
|
||||||
}
|
}
|
||||||
|
@ -45,19 +45,21 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if token == nil {
|
if token == nil {
|
||||||
responseError(ctx, "Access token doesn't exist")
|
responseError(ctx, "Access token doesn't exist in database")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.IsTokenExpired(token.CreatedTime, token.ExpiresIn) {
|
isExpired, expireTime := util.IsTokenExpired(token.CreatedTime, token.ExpiresIn)
|
||||||
responseError(ctx, "Access token has expired")
|
if isExpired {
|
||||||
|
responseError(ctx, fmt.Sprintf("Access token has expired, expireTime = %s", expireTime))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userId := util.GetId(token.Organization, token.User)
|
userId := util.GetId(token.Organization, token.User)
|
||||||
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setSessionUser(ctx, userId)
|
setSessionUser(ctx, userId)
|
||||||
@ -66,7 +68,11 @@ func AutoSigninFilter(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// "/page?clientId=123&clientSecret=456"
|
// "/page?clientId=123&clientSecret=456"
|
||||||
userId := getUsernameByClientIdSecret(ctx)
|
userId, err := getUsernameByClientIdSecret(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
if userId != "" {
|
if userId != "" {
|
||||||
setSessionUser(ctx, userId)
|
setSessionUser(ctx, userId)
|
||||||
return
|
return
|
||||||
|
@ -16,6 +16,9 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
@ -33,6 +36,8 @@ type Response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func responseError(ctx *context.Context, error string, data ...interface{}) {
|
func responseError(ctx *context.Context, error string, data ...interface{}) {
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||||
|
|
||||||
resp := Response{Status: "error", Msg: error}
|
resp := Response{Status: "error", Msg: error}
|
||||||
switch len(data) {
|
switch len(data) {
|
||||||
case 2:
|
case 2:
|
||||||
@ -61,7 +66,7 @@ func denyRequest(ctx *context.Context) {
|
|||||||
responseError(ctx, T(ctx, "auth:Unauthorized operation"))
|
responseError(ctx, T(ctx, "auth:Unauthorized operation"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUsernameByClientIdSecret(ctx *context.Context) string {
|
func getUsernameByClientIdSecret(ctx *context.Context) (string, error) {
|
||||||
clientId, clientSecret, ok := ctx.Request.BasicAuth()
|
clientId, clientSecret, ok := ctx.Request.BasicAuth()
|
||||||
if !ok {
|
if !ok {
|
||||||
clientId = ctx.Input.Query("clientId")
|
clientId = ctx.Input.Query("clientId")
|
||||||
@ -69,19 +74,22 @@ func getUsernameByClientIdSecret(ctx *context.Context) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if clientId == "" || clientSecret == "" {
|
if clientId == "" || clientSecret == "" {
|
||||||
return ""
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
application, err := object.GetApplicationByClientId(clientId)
|
application, err := object.GetApplicationByClientId(clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return "", err
|
||||||
|
}
|
||||||
|
if application == nil {
|
||||||
|
return "", fmt.Errorf("Application not found for client ID: %s", clientId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if application == nil || application.ClientSecret != clientSecret {
|
if application.ClientSecret != clientSecret {
|
||||||
return ""
|
return "", fmt.Errorf("Incorrect client secret for application: %s", application.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("app/%s", application.Name)
|
return fmt.Sprintf("app/%s", application.Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUsernameByKeys(ctx *context.Context) string {
|
func getUsernameByKeys(ctx *context.Context) string {
|
||||||
@ -151,3 +159,39 @@ func parseBearerToken(ctx *context.Context) string {
|
|||||||
|
|
||||||
return tokens[1]
|
return tokens[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getHostname(s string) string {
|
||||||
|
if s == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := l.Hostname()
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func removePort(s string) string {
|
||||||
|
ipStr, _, err := net.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
ipStr = s
|
||||||
|
}
|
||||||
|
return ipStr
|
||||||
|
}
|
||||||
|
|
||||||
|
func isHostIntranet(s string) bool {
|
||||||
|
ipStr, _, err := net.SplitHostPort(s)
|
||||||
|
if err != nil {
|
||||||
|
ipStr = s
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
@ -29,42 +30,61 @@ const (
|
|||||||
headerAllowHeaders = "Access-Control-Allow-Headers"
|
headerAllowHeaders = "Access-Control-Allow-Headers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func setCorsHeaders(ctx *context.Context, origin string) {
|
||||||
|
ctx.Output.Header(headerAllowOrigin, origin)
|
||||||
|
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
||||||
|
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||||
|
|
||||||
|
if ctx.Input.Method() == "OPTIONS" {
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func CorsFilter(ctx *context.Context) {
|
func CorsFilter(ctx *context.Context) {
|
||||||
origin := ctx.Input.Header(headerOrigin)
|
origin := ctx.Input.Header(headerOrigin)
|
||||||
originConf := conf.GetConfigString("origin")
|
originConf := conf.GetConfigString("origin")
|
||||||
|
originHostname := getHostname(origin)
|
||||||
|
host := removePort(ctx.Request.Host)
|
||||||
|
|
||||||
|
if strings.HasPrefix(origin, "http://localhost") || strings.HasPrefix(origin, "https://localhost") || strings.HasPrefix(origin, "http://127.0.0.1") || strings.HasPrefix(origin, "http://casdoor-app") {
|
||||||
|
setCorsHeaders(ctx, origin)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if originHostname == "appleid.apple.com" {
|
||||||
|
setCorsHeaders(ctx, origin)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Request.Method == "POST" && ctx.Request.RequestURI == "/api/login/oauth/access_token" {
|
if ctx.Request.Method == "POST" && ctx.Request.RequestURI == "/api/login/oauth/access_token" {
|
||||||
ctx.Output.Header(headerAllowOrigin, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
|
||||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Request.RequestURI == "/api/userinfo" {
|
if ctx.Request.RequestURI == "/api/userinfo" {
|
||||||
ctx.Output.Header(headerAllowOrigin, origin)
|
setCorsHeaders(ctx, origin)
|
||||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
|
||||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if origin != "" && originConf != "" && origin != originConf {
|
if origin != "" {
|
||||||
ok, err := object.IsOriginAllowed(origin)
|
if origin == originConf {
|
||||||
if err != nil {
|
setCorsHeaders(ctx, origin)
|
||||||
panic(err)
|
} else if originHostname == host {
|
||||||
}
|
setCorsHeaders(ctx, origin)
|
||||||
|
} else if isHostIntranet(host) {
|
||||||
if ok {
|
setCorsHeaders(ctx, origin)
|
||||||
ctx.Output.Header(headerAllowOrigin, origin)
|
|
||||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
|
||||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
|
||||||
} else {
|
} else {
|
||||||
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
ok, err := object.IsOriginAllowed(origin)
|
||||||
return
|
if err != nil {
|
||||||
}
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Input.Method() == "OPTIONS" {
|
if ok {
|
||||||
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
setCorsHeaders(ctx, origin)
|
||||||
return
|
} else {
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func initAPI() {
|
|||||||
beego.Router("/api/get-user-count", &controllers.ApiController{}, "GET:GetUserCount")
|
beego.Router("/api/get-user-count", &controllers.ApiController{}, "GET:GetUserCount")
|
||||||
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
|
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
|
||||||
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
|
||||||
beego.Router("/api/add-user-keys", &controllers.ApiController{}, "POST:AddUserkeys")
|
beego.Router("/api/add-user-keys", &controllers.ApiController{}, "POST:AddUserKeys")
|
||||||
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
|
||||||
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
||||||
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
||||||
@ -277,4 +277,6 @@ func initAPI() {
|
|||||||
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceValidate")
|
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceValidate")
|
||||||
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ProxyValidate")
|
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ProxyValidate")
|
||||||
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
|
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
|
||||||
|
|
||||||
|
beego.Router("/scim/*", &controllers.RootController{}, "*:HandleScim")
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/beego/beego/context"
|
"github.com/beego/beego/context"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,8 +34,63 @@ var (
|
|||||||
oldStaticBaseUrl = "https://cdn.casbin.org"
|
oldStaticBaseUrl = "https://cdn.casbin.org"
|
||||||
newStaticBaseUrl = conf.GetConfigString("staticBaseUrl")
|
newStaticBaseUrl = conf.GetConfigString("staticBaseUrl")
|
||||||
enableGzip = conf.GetConfigBool("enableGzip")
|
enableGzip = conf.GetConfigBool("enableGzip")
|
||||||
|
frontendBaseDir = conf.GetConfigString("frontendBaseDir")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func getWebBuildFolder() string {
|
||||||
|
path := "web/build"
|
||||||
|
if util.FileExist(filepath.Join(path, "index.html")) || frontendBaseDir == "" {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(frontendBaseDir, "web/build")
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func fastAutoSignin(ctx *context.Context) (string, error) {
|
||||||
|
userId := getSessionUser(ctx)
|
||||||
|
if userId == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
clientId := ctx.Input.Query("client_id")
|
||||||
|
responseType := ctx.Input.Query("response_type")
|
||||||
|
redirectUri := ctx.Input.Query("redirect_uri")
|
||||||
|
scope := ctx.Input.Query("scope")
|
||||||
|
state := ctx.Input.Query("state")
|
||||||
|
nonce := ""
|
||||||
|
codeChallenge := ""
|
||||||
|
if clientId == "" || responseType != "code" || redirectUri == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
application, err := object.GetApplicationByClientId(clientId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if application == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !application.EnableAutoSignin {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, ctx.Request.Host, getAcceptLanguage(ctx))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
} else if code.Message != "" {
|
||||||
|
return "", fmt.Errorf(code.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
sep := "?"
|
||||||
|
if strings.Contains(redirectUri, "?") {
|
||||||
|
sep = "&"
|
||||||
|
}
|
||||||
|
res := fmt.Sprintf("%s%scode=%s&state=%s", redirectUri, sep, code.Code, state)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
func StaticFilter(ctx *context.Context) {
|
func StaticFilter(ctx *context.Context) {
|
||||||
urlPath := ctx.Request.URL.Path
|
urlPath := ctx.Request.URL.Path
|
||||||
|
|
||||||
@ -48,8 +104,25 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
|
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate") || strings.HasSuffix(urlPath, "/p3/serviceValidate") || strings.HasSuffix(urlPath, "/p3/proxyValidate") || strings.HasSuffix(urlPath, "/samlValidate")) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(urlPath, "/scim") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
path := "web/build"
|
if urlPath == "/login/oauth/authorize" {
|
||||||
|
redirectUrl, err := fastAutoSignin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
responseError(ctx, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectUrl != "" {
|
||||||
|
http.Redirect(ctx.ResponseWriter, ctx.Request, redirectUrl, http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webBuildFolder := getWebBuildFolder()
|
||||||
|
path := webBuildFolder
|
||||||
if urlPath == "/" {
|
if urlPath == "/" {
|
||||||
path += "/index.html"
|
path += "/index.html"
|
||||||
} else {
|
} else {
|
||||||
@ -57,7 +130,7 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !util.FileExist(path) {
|
if !util.FileExist(path) {
|
||||||
path = "web/build/index.html"
|
path = webBuildFolder + "/index.html"
|
||||||
}
|
}
|
||||||
if !util.FileExist(path) {
|
if !util.FileExist(path) {
|
||||||
dir, err := os.Getwd()
|
dir, err := os.Getwd()
|
||||||
@ -65,6 +138,7 @@ func StaticFilter(ctx *context.Context) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
dir = strings.ReplaceAll(dir, "\\", "/")
|
dir = strings.ReplaceAll(dir, "\\", "/")
|
||||||
|
ctx.ResponseWriter.WriteHeader(http.StatusNotFound)
|
||||||
errorText := fmt.Sprintf("The Casdoor frontend HTML file: \"index.html\" was not found, it should be placed at: \"%s/web/build/index.html\". For more information, see: https://casdoor.org/docs/basic/server-installation/#frontend-1", dir)
|
errorText := fmt.Sprintf("The Casdoor frontend HTML file: \"index.html\" was not found, it should be placed at: \"%s/web/build/index.html\". For more information, see: https://casdoor.org/docs/basic/server-installation/#frontend-1", dir)
|
||||||
http.ServeContent(ctx.ResponseWriter, ctx.Request, "Casdoor frontend has encountered error...", time.Now(), strings.NewReader(errorText))
|
http.ServeContent(ctx.ResponseWriter, ctx.Request, "Casdoor frontend has encountered error...", time.Now(), strings.NewReader(errorText))
|
||||||
return
|
return
|
||||||
|
154
scim/server.go
Normal file
154
scim/server.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package scim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/elimity-com/scim"
|
||||||
|
"github.com/elimity-com/scim/optional"
|
||||||
|
"github.com/elimity-com/scim/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Example JSON user resource
|
||||||
|
{
|
||||||
|
"schemas": [
|
||||||
|
"urn:ietf:params:scim:schemas:core:2.0:User",
|
||||||
|
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
|
||||||
|
],
|
||||||
|
"addresses": [
|
||||||
|
{
|
||||||
|
"country": "US",
|
||||||
|
"locality": "San Fransisco",
|
||||||
|
"region": "US West"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"displayName": "Hello, Scim",
|
||||||
|
"name": {
|
||||||
|
"familyName": "Bob",
|
||||||
|
"givenName": "Alice"
|
||||||
|
},
|
||||||
|
"phoneNumbers": [
|
||||||
|
{
|
||||||
|
"value": "46407568879"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"photos": [
|
||||||
|
{
|
||||||
|
"value": "https://cdn.casbin.org/img/casbin.svg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"emails": [
|
||||||
|
{
|
||||||
|
"value": "cbvdho@example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"profileUrl": "https://door.casdoor.com/users/build-in/scim_test_user2",
|
||||||
|
"userName": "scim_test_user2",
|
||||||
|
"userType": "normal-user",
|
||||||
|
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
|
||||||
|
"organization": "built-in"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserExtensionKey = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
UserStringField = []schema.SimpleParams{
|
||||||
|
newStringParams("externalId", false, true),
|
||||||
|
newStringParams("userName", true, true),
|
||||||
|
newStringParams("password", false, false),
|
||||||
|
newStringParams("displayName", false, false),
|
||||||
|
newStringParams("profileUrl", false, false),
|
||||||
|
newStringParams("userType", false, false),
|
||||||
|
}
|
||||||
|
UserComplexField = []schema.ComplexParams{
|
||||||
|
newComplexParams("name", false, false, []schema.SimpleParams{
|
||||||
|
newStringParams("givenName", false, false),
|
||||||
|
newStringParams("familyName", false, false),
|
||||||
|
}),
|
||||||
|
newComplexParams("emails", false, true, []schema.SimpleParams{
|
||||||
|
newStringParams("value", true, false),
|
||||||
|
}),
|
||||||
|
newComplexParams("phoneNumbers", false, true, []schema.SimpleParams{
|
||||||
|
newStringParams("value", true, false),
|
||||||
|
}),
|
||||||
|
newComplexParams("photos", false, true, []schema.SimpleParams{
|
||||||
|
newStringParams("value", true, false),
|
||||||
|
}),
|
||||||
|
newComplexParams("addresses", false, true, []schema.SimpleParams{
|
||||||
|
newStringParams("locality", false, false),
|
||||||
|
newStringParams("region", false, false),
|
||||||
|
newStringParams("country", false, false),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
Server = GetScimServer()
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetScimServer() scim.Server {
|
||||||
|
config := scim.ServiceProviderConfig{
|
||||||
|
// DocumentationURI: optional.NewString("www.example.com/scim"),
|
||||||
|
SupportPatch: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
codeAttrs := make([]schema.CoreAttribute, 0, len(UserStringField)+len(UserComplexField))
|
||||||
|
for _, field := range UserStringField {
|
||||||
|
codeAttrs = append(codeAttrs, schema.SimpleCoreAttribute(field))
|
||||||
|
}
|
||||||
|
for _, field := range UserComplexField {
|
||||||
|
codeAttrs = append(codeAttrs, schema.ComplexCoreAttribute(field))
|
||||||
|
}
|
||||||
|
|
||||||
|
userSchema := schema.Schema{
|
||||||
|
ID: schema.UserSchema,
|
||||||
|
Name: optional.NewString("User"),
|
||||||
|
Description: optional.NewString("User Account"),
|
||||||
|
Attributes: codeAttrs,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := schema.Schema{
|
||||||
|
ID: UserExtensionKey,
|
||||||
|
Name: optional.NewString("EnterpriseUser"),
|
||||||
|
Description: optional.NewString("Enterprise User"),
|
||||||
|
Attributes: []schema.CoreAttribute{
|
||||||
|
schema.SimpleCoreAttribute(schema.SimpleStringParams(schema.StringParams{
|
||||||
|
Name: "organization",
|
||||||
|
Required: true,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resourceTypes := []scim.ResourceType{
|
||||||
|
{
|
||||||
|
ID: optional.NewString("User"),
|
||||||
|
Name: "User",
|
||||||
|
Endpoint: "/Users",
|
||||||
|
Description: optional.NewString("User Account in Casdoor"),
|
||||||
|
Schema: userSchema,
|
||||||
|
SchemaExtensions: []scim.SchemaExtension{
|
||||||
|
{Schema: extension},
|
||||||
|
},
|
||||||
|
Handler: UserResourceHandler{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
server := scim.Server{
|
||||||
|
Config: config,
|
||||||
|
ResourceTypes: resourceTypes,
|
||||||
|
}
|
||||||
|
return server
|
||||||
|
}
|
260
scim/user_handler.go
Normal file
260
scim/user_handler.go
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package scim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/elimity-com/scim"
|
||||||
|
"github.com/elimity-com/scim/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserResourceHandler struct{}
|
||||||
|
|
||||||
|
// https://github.com/elimity-com/scim/blob/master/resource_handler_test.go Example in-memory resource handler
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7644#section-3.4 How to query/update resources
|
||||||
|
|
||||||
|
func (h UserResourceHandler) Create(r *http.Request, attrs scim.ResourceAttributes) (scim.Resource, error) {
|
||||||
|
resource := &scim.Resource{Attributes: attrs}
|
||||||
|
err := AddScimUser(resource)
|
||||||
|
return *resource, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserResourceHandler) Get(r *http.Request, id string) (scim.Resource, error) {
|
||||||
|
resource, err := GetScimUser(id)
|
||||||
|
if err != nil {
|
||||||
|
return scim.Resource{}, err
|
||||||
|
}
|
||||||
|
if resource == nil {
|
||||||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||||||
|
}
|
||||||
|
return *resource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserResourceHandler) Delete(r *http.Request, id string) error {
|
||||||
|
user, err := object.GetUserByUserIdOnly(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return errors.ScimErrorResourceNotFound(id)
|
||||||
|
}
|
||||||
|
_, err = object.DeleteUser(user)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserResourceHandler) GetAll(r *http.Request, params scim.ListRequestParams) (scim.Page, error) {
|
||||||
|
if params.Count == 0 {
|
||||||
|
count, err := object.GetGlobalUserCount("", "")
|
||||||
|
if err != nil {
|
||||||
|
return scim.Page{}, err
|
||||||
|
}
|
||||||
|
return scim.Page{TotalResults: int(count)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resources := make([]scim.Resource, 0)
|
||||||
|
// startIndex is 1-based index
|
||||||
|
users, err := object.GetPaginationGlobalUsers(params.StartIndex-1, params.Count, "", "", "", "")
|
||||||
|
if err != nil {
|
||||||
|
return scim.Page{}, err
|
||||||
|
}
|
||||||
|
for _, user := range users {
|
||||||
|
resources = append(resources, *user2resource(user))
|
||||||
|
}
|
||||||
|
return scim.Page{
|
||||||
|
TotalResults: len(resources),
|
||||||
|
Resources: resources,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserResourceHandler) Patch(r *http.Request, id string, operations []scim.PatchOperation) (scim.Resource, error) {
|
||||||
|
user, err := object.GetUserByUserIdOnly(id)
|
||||||
|
if err != nil {
|
||||||
|
return scim.Resource{}, err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||||||
|
}
|
||||||
|
return UpdateScimUserByPatchOperation(id, operations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserResourceHandler) Replace(r *http.Request, id string, attrs scim.ResourceAttributes) (scim.Resource, error) {
|
||||||
|
user, err := object.GetUserByUserIdOnly(id)
|
||||||
|
if err != nil {
|
||||||
|
return scim.Resource{}, err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||||||
|
}
|
||||||
|
resource := &scim.Resource{Attributes: attrs}
|
||||||
|
err = UpdateScimUser(id, resource)
|
||||||
|
return *resource, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetScimUser(id string) (*scim.Resource, error) {
|
||||||
|
user, err := object.GetUserByUserIdOnly(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
r := user2resource(user)
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddScimUser(r *scim.Resource) error {
|
||||||
|
newUser, err := resource2user(r.Attributes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the user exists.
|
||||||
|
oldUser, err := object.GetUser(newUser.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oldUser != nil {
|
||||||
|
return errors.ScimErrorUniqueness
|
||||||
|
}
|
||||||
|
|
||||||
|
affect, err := object.AddUser(newUser)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !affect {
|
||||||
|
return fmt.Errorf("add new user failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Attributes = user2resource(newUser).Attributes
|
||||||
|
r.ID = newUser.Id
|
||||||
|
r.ExternalID = buildExternalId(newUser)
|
||||||
|
r.Meta = buildMeta(newUser)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateScimUser(id string, r *scim.Resource) error {
|
||||||
|
oldUser, err := object.GetUserByUserIdOnly(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if oldUser == nil {
|
||||||
|
return errors.ScimErrorResourceNotFound(id)
|
||||||
|
}
|
||||||
|
newUser, err := resource2user(r.Attributes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = object.UpdateUser(oldUser.GetId(), newUser, nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ID = newUser.Id
|
||||||
|
r.ExternalID = buildExternalId(newUser)
|
||||||
|
r.Meta = buildMeta(newUser)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2 Modifying with PATCH
|
||||||
|
func UpdateScimUserByPatchOperation(id string, ops []scim.PatchOperation) (r scim.Resource, err error) {
|
||||||
|
user, err := object.GetUserByUserIdOnly(id)
|
||||||
|
if err != nil {
|
||||||
|
return scim.Resource{}, err
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return scim.Resource{}, errors.ScimErrorResourceNotFound(id)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("invalid patch op value: %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
old := user.GetId()
|
||||||
|
for _, op := range ops {
|
||||||
|
value := op.Value
|
||||||
|
if op.Op == scim.PatchOperationRemove {
|
||||||
|
value = nil
|
||||||
|
}
|
||||||
|
// PatchOperationAdd and PatchOperationReplace is same in Casdoor, just replace the value
|
||||||
|
switch op.Path.String() {
|
||||||
|
case "userName":
|
||||||
|
user.Name = ToString(value, "")
|
||||||
|
case "password":
|
||||||
|
user.Password = ToString(value, "")
|
||||||
|
case "externalId":
|
||||||
|
user.ExternalId = ToString(value, "")
|
||||||
|
case "displayName":
|
||||||
|
user.DisplayName = ToString(value, "")
|
||||||
|
case "profileUrl":
|
||||||
|
user.Homepage = ToString(value, "")
|
||||||
|
case "userType":
|
||||||
|
user.Type = ToString(value, "")
|
||||||
|
case "name.givenName":
|
||||||
|
user.FirstName = ToString(value, "")
|
||||||
|
case "name.familyName":
|
||||||
|
user.LastName = ToString(value, "")
|
||||||
|
case "name":
|
||||||
|
defaultV := AnyMap{"givenName": "", "familyName": ""}
|
||||||
|
v := ToAnyMap(value, defaultV) // e.g. {"givenName": "AA", "familyName": "BB"}
|
||||||
|
user.FirstName = ToString(v["givenName"], user.FirstName)
|
||||||
|
user.LastName = ToString(v["familyName"], user.LastName)
|
||||||
|
case "emails":
|
||||||
|
defaultV := AnyArray{AnyMap{"value": ""}}
|
||||||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"value": "test@casdoor"}]
|
||||||
|
if len(vs) > 0 {
|
||||||
|
v := ToAnyMap(vs[0])
|
||||||
|
user.Email = ToString(v["value"], user.Email)
|
||||||
|
}
|
||||||
|
case "phoneNumbers":
|
||||||
|
defaultV := AnyArray{AnyMap{"value": ""}}
|
||||||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"value": "18750004417"}]
|
||||||
|
if len(vs) > 0 {
|
||||||
|
v := ToAnyMap(vs[0])
|
||||||
|
user.Phone = ToString(v["value"], user.Phone)
|
||||||
|
}
|
||||||
|
case "photos":
|
||||||
|
defaultV := AnyArray{AnyMap{"value": ""}}
|
||||||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"value": "https://cdn.casbin.org/img/casbin.svg"}]
|
||||||
|
if len(vs) > 0 {
|
||||||
|
v := ToAnyMap(vs[0])
|
||||||
|
user.Avatar = ToString(v["value"], user.Avatar)
|
||||||
|
}
|
||||||
|
case "addresses":
|
||||||
|
defaultV := AnyArray{AnyMap{"locality": "", "region": "", "country": ""}}
|
||||||
|
vs := ToAnyArray(value, defaultV) // e.g. [{"locality": "Hollywood", "region": "CN", "country": "USA"}]
|
||||||
|
if len(vs) > 0 {
|
||||||
|
v := ToAnyMap(vs[0])
|
||||||
|
user.Location = ToString(v["locality"], user.Location)
|
||||||
|
user.Region = ToString(v["region"], user.Region)
|
||||||
|
user.CountryCode = ToString(v["country"], user.CountryCode)
|
||||||
|
}
|
||||||
|
case UserExtensionKey:
|
||||||
|
defaultV := AnyMap{"organization": user.Owner}
|
||||||
|
v := ToAnyMap(value, defaultV) // e.g. {"organization": "org1"}
|
||||||
|
user.Owner = ToString(v["organization"], user.Owner)
|
||||||
|
case fmt.Sprintf("%v.%v", UserExtensionKey, "organization"):
|
||||||
|
user.Owner = ToString(value, user.Owner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = object.UpdateUser(old, user, nil, true)
|
||||||
|
if err != nil {
|
||||||
|
return scim.Resource{}, err
|
||||||
|
}
|
||||||
|
r = *user2resource(user)
|
||||||
|
return r, nil
|
||||||
|
}
|
238
scim/util.go
Normal file
238
scim/util.go
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package scim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
"github.com/elimity-com/scim"
|
||||||
|
"github.com/elimity-com/scim/optional"
|
||||||
|
"github.com/elimity-com/scim/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnyMap map[string]interface{}
|
||||||
|
|
||||||
|
type AnyArray []interface{}
|
||||||
|
|
||||||
|
func ToString(v interface{}, defaultV ...interface{}) string {
|
||||||
|
if v == nil {
|
||||||
|
if len(defaultV) > 0 {
|
||||||
|
v = defaultV[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToAnyMap(v interface{}, defaultV ...interface{}) AnyMap {
|
||||||
|
if v == nil {
|
||||||
|
if len(defaultV) > 0 {
|
||||||
|
v = defaultV[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
m = v.(AnyMap)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToAnyArray(v interface{}, defaultV ...interface{}) AnyArray {
|
||||||
|
if v == nil {
|
||||||
|
if len(defaultV) > 0 {
|
||||||
|
v = defaultV[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m, ok := v.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
m = v.(AnyArray)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStringParams(name string, required, unique bool) schema.SimpleParams {
|
||||||
|
uniqueness := schema.AttributeUniquenessNone()
|
||||||
|
if unique {
|
||||||
|
uniqueness = schema.AttributeUniquenessServer()
|
||||||
|
}
|
||||||
|
return schema.SimpleStringParams(schema.StringParams{
|
||||||
|
Name: name,
|
||||||
|
Required: required,
|
||||||
|
Uniqueness: uniqueness,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newComplexParams(name string, required bool, multi bool, subAttributes []schema.SimpleParams) schema.ComplexParams {
|
||||||
|
return schema.ComplexParams{
|
||||||
|
Name: name,
|
||||||
|
Required: required,
|
||||||
|
MultiValued: multi,
|
||||||
|
SubAttributes: subAttributes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExternalId(user *object.User) optional.String {
|
||||||
|
if user.ExternalId != "" {
|
||||||
|
return optional.NewString(user.ExternalId)
|
||||||
|
} else {
|
||||||
|
return optional.String{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMeta(user *object.User) scim.Meta {
|
||||||
|
createdTime := util.String2Time(user.CreatedTime)
|
||||||
|
updatedTime := util.String2Time(user.UpdatedTime)
|
||||||
|
if user.UpdatedTime == "" {
|
||||||
|
updatedTime = createdTime
|
||||||
|
}
|
||||||
|
return scim.Meta{
|
||||||
|
Created: &createdTime,
|
||||||
|
LastModified: &updatedTime,
|
||||||
|
Version: util.Time2String(updatedTime),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAttrString(attrs scim.ResourceAttributes, key string) string {
|
||||||
|
if attrs[key] == nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return attrs[key].(string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAttrJson(attrs scim.ResourceAttributes, key string) scim.ResourceAttributes {
|
||||||
|
if attrs[key] == nil {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
if v, ok := attrs[key].(map[string]interface{}); ok {
|
||||||
|
return v
|
||||||
|
} else if v, ok := attrs[key].([]interface{}); ok {
|
||||||
|
if len(v) > 0 {
|
||||||
|
return v[0].(map[string]interface{})
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic("invalid attribute type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAttrJsonValue(attrs scim.ResourceAttributes, key1 string, key2 string) string {
|
||||||
|
attr := getAttrJson(attrs, key1)
|
||||||
|
if attr == nil {
|
||||||
|
return ""
|
||||||
|
} else {
|
||||||
|
return getAttrString(attr, key2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func user2resource(user *object.User) *scim.Resource {
|
||||||
|
attrs := make(map[string]interface{})
|
||||||
|
// Singular attributes
|
||||||
|
attrs["userName"] = user.Name
|
||||||
|
// The cleartext value or the hashed value of a password SHALL NOT be returnable by a service provider.
|
||||||
|
// attrs["password"] = user.Password
|
||||||
|
formatted := fmt.Sprintf("%s %s", user.FirstName, user.LastName)
|
||||||
|
if user.FirstName == "" {
|
||||||
|
formatted = user.LastName
|
||||||
|
}
|
||||||
|
if user.LastName == "" {
|
||||||
|
formatted = user.FirstName
|
||||||
|
}
|
||||||
|
attrs["name"] = scim.ResourceAttributes{
|
||||||
|
"formatted": formatted,
|
||||||
|
"familyName": user.LastName,
|
||||||
|
"givenName": user.FirstName,
|
||||||
|
}
|
||||||
|
attrs["displayName"] = user.DisplayName
|
||||||
|
attrs["nickName"] = user.DisplayName
|
||||||
|
attrs["userType"] = user.Type
|
||||||
|
attrs["profileUrl"] = user.Homepage
|
||||||
|
attrs["active"] = !user.IsForbidden && !user.IsDeleted
|
||||||
|
|
||||||
|
// Multi-Valued attributes
|
||||||
|
attrs["emails"] = []scim.ResourceAttributes{
|
||||||
|
{
|
||||||
|
"value": user.Email,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
attrs["phoneNumbers"] = []scim.ResourceAttributes{
|
||||||
|
{
|
||||||
|
"value": user.Phone,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
attrs["photos"] = []scim.ResourceAttributes{
|
||||||
|
{
|
||||||
|
"value": user.Avatar,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
attrs["addresses"] = []scim.ResourceAttributes{
|
||||||
|
{
|
||||||
|
"locality": user.Location, // e.g. Hollywood
|
||||||
|
"region": user.Region, // e.g. CN
|
||||||
|
"country": user.CountryCode, // e.g. USA
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enterprise user schema extension
|
||||||
|
attrs[UserExtensionKey] = scim.ResourceAttributes{
|
||||||
|
"organization": user.Owner,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &scim.Resource{
|
||||||
|
ID: user.Id,
|
||||||
|
ExternalID: buildExternalId(user),
|
||||||
|
Attributes: attrs,
|
||||||
|
Meta: buildMeta(user),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource2user(attrs scim.ResourceAttributes) (user *object.User, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Printf("failed to parse attrs: %v", r)
|
||||||
|
err = fmt.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
user = &object.User{
|
||||||
|
ExternalId: getAttrString(attrs, "externalId"),
|
||||||
|
Name: getAttrString(attrs, "userName"),
|
||||||
|
Password: getAttrString(attrs, "password"),
|
||||||
|
DisplayName: getAttrString(attrs, "displayName"),
|
||||||
|
Homepage: getAttrString(attrs, "profileUrl"),
|
||||||
|
Type: getAttrString(attrs, "userType"),
|
||||||
|
|
||||||
|
Owner: getAttrJsonValue(attrs, UserExtensionKey, "organization"),
|
||||||
|
FirstName: getAttrJsonValue(attrs, "name", "givenName"),
|
||||||
|
LastName: getAttrJsonValue(attrs, "name", "familyName"),
|
||||||
|
Email: getAttrJsonValue(attrs, "emails", "value"),
|
||||||
|
Phone: getAttrJsonValue(attrs, "phoneNumbers", "value"),
|
||||||
|
Avatar: getAttrJsonValue(attrs, "photos", "value"),
|
||||||
|
Location: getAttrJsonValue(attrs, "addresses", "locality"),
|
||||||
|
Region: getAttrJsonValue(attrs, "addresses", "region"),
|
||||||
|
CountryCode: getAttrJsonValue(attrs, "addresses", "country"),
|
||||||
|
|
||||||
|
CreatedTime: util.GetCurrentTime(),
|
||||||
|
UpdatedTime: util.GetCurrentTime(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.Owner == "" {
|
||||||
|
err = fmt.Errorf("organization in %s is required", UserExtensionKey)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
@ -36,7 +36,12 @@ func (db *Database) onDDL(header *replication.EventHeader, nextPos mysql.Positio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) OnRow(e *canal.RowsEvent) error {
|
func (db *Database) OnRow(e *canal.RowsEvent) error {
|
||||||
log.Info("serverId: ", e.Header.ServerID)
|
if e.Header != nil {
|
||||||
|
log.Info("serverId: ", e.Header.ServerID)
|
||||||
|
} else {
|
||||||
|
log.Info("serverId: e.Header == nil")
|
||||||
|
}
|
||||||
|
|
||||||
if strings.Contains(db.Gtid, db.serverUuid) {
|
if strings.Contains(db.Gtid, db.serverUuid) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -87,11 +92,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
|||||||
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||||
updateSql, args, err := getUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
|
updateSql, args, err := getUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := db.engine.DB().Exec(updateSql, args...)
|
res, err := db.engine.DB().Exec(updateSql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info(updateSql, args, res)
|
log.Info(updateSql, args, res)
|
||||||
@ -113,11 +120,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
|||||||
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||||
deleteSql, args, err := getDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
|
deleteSql, args, err := getDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := db.engine.DB().Exec(deleteSql, args...)
|
res, err := db.engine.DB().Exec(deleteSql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info(deleteSql, args, res)
|
log.Info(deleteSql, args, res)
|
||||||
@ -141,11 +150,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
|||||||
|
|
||||||
insertSql, args, err := getInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
|
insertSql, args, err := getInsertSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := db.engine.DB().Exec(insertSql, args...)
|
res, err := db.engine.DB().Exec(insertSql, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Info(insertSql, args, res)
|
log.Info(insertSql, args, res)
|
||||||
|
14
sync/sync.go
14
sync/sync.go
@ -20,11 +20,21 @@ func startSyncJob(db1 *Database, db2 *Database) error {
|
|||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
// start canal1 replication
|
// start canal1 replication
|
||||||
go db1.startCanal(db2)
|
go func(db1 *Database, db2 *Database) {
|
||||||
|
err := db1.startCanal(db2)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}(db1, db2)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
// start canal2 replication
|
// start canal2 replication
|
||||||
go db2.startCanal(db1)
|
go func(db1 *Database, db2 *Database) {
|
||||||
|
err := db2.startCanal(db1)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}(db1, db2)
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
@ -24,7 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestStartSyncJob(t *testing.T) {
|
func TestStartSyncJob(t *testing.T) {
|
||||||
db1 := newDatabase("127.0.0.1", 3306, "casdoor", "root", "123456")
|
db1 := newDatabase("localhost", 3306, "casdoor", "root", "123456")
|
||||||
db2 := newDatabase("127.0.0.1", 3306, "casdoor2", "root", "123456")
|
db2 := newDatabase("localhost", 3306, "casdoor2", "root", "123456")
|
||||||
startSyncJob(db1, db2)
|
err := startSyncJob(db1, db2)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
16
sync/util.go
16
sync/util.go
@ -15,9 +15,7 @@
|
|||||||
package sync
|
package sync
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Masterminds/squirrel"
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/xorm-io/xorm"
|
"github.com/xorm-io/xorm"
|
||||||
@ -74,21 +72,23 @@ func createEngine(dataSourceName string) (*xorm.Engine, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getServerId(engin *xorm.Engine) (uint32, error) {
|
func getServerId(engin *xorm.Engine) (uint32, error) {
|
||||||
res, err := engin.QueryInterface("SELECT @@server_id")
|
record, err := engin.QueryInterface("SELECT @@server_id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
serverId, _ := strconv.ParseUint(fmt.Sprintf("%s", res[0]["@@server_id"]), 10, 32)
|
|
||||||
return uint32(serverId), nil
|
res := uint32(record[0]["@@server_id"].(int64))
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getServerUuid(engin *xorm.Engine) (string, error) {
|
func getServerUuid(engin *xorm.Engine) (string, error) {
|
||||||
res, err := engin.QueryString("show variables like 'server_uuid'")
|
record, err := engin.QueryString("show variables like 'server_uuid'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
serverUuid := fmt.Sprintf("%s", res[0]["Value"])
|
|
||||||
return serverUuid, err
|
res := record[0]["Value"]
|
||||||
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPkColumnNames(columnNames []string, PKColumns []int) []string {
|
func getPkColumnNames(columnNames []string, PKColumns []int) []string {
|
||||||
|
116
sync_v2/cmd_test.go
Normal file
116
sync_v2/cmd_test.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "github.com/go-sql-driver/mysql"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
The following config should be added to my.cnf:
|
||||||
|
|
||||||
|
gtid_mode=on
|
||||||
|
enforce_gtid_consistency=on
|
||||||
|
binlog-format=ROW
|
||||||
|
server-id = 1 # this should be different for each mysql instance (1,2)
|
||||||
|
auto_increment_offset = 1 # this is same as server-id
|
||||||
|
auto_increment_increment = 2 # this is same as the number of mysql instances (2)
|
||||||
|
log-bin = mysql-bin
|
||||||
|
replicate-do-db = casdoor # this is the database name
|
||||||
|
binlog-do-db = casdoor # this is the database name
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Configs = []Database{
|
||||||
|
{
|
||||||
|
host: "test-db.v2tl.com",
|
||||||
|
port: 3306,
|
||||||
|
username: "root",
|
||||||
|
password: "password",
|
||||||
|
database: "casdoor",
|
||||||
|
// the following two fields are used to create replication user, you don't need to change them
|
||||||
|
slaveUser: "repl_user",
|
||||||
|
slavePassword: "repl_user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host: "localhost",
|
||||||
|
port: 3306,
|
||||||
|
username: "root",
|
||||||
|
password: "password",
|
||||||
|
database: "casdoor",
|
||||||
|
// the following two fields are used to create replication user, you don't need to change them
|
||||||
|
slaveUser: "repl_user",
|
||||||
|
slavePassword: "repl_user",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartMasterSlaveSync(t *testing.T) {
|
||||||
|
// for example, this is aliyun rds
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
// for example, this is local mysql instance
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
|
||||||
|
createSlaveUser(db0)
|
||||||
|
// db0 is master, db1 is slave
|
||||||
|
startSlave(db0, db1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopMasterSlaveSync(t *testing.T) {
|
||||||
|
// for example, this is aliyun rds
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
// for example, this is local mysql instance
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
|
||||||
|
stopSlave(db1)
|
||||||
|
deleteSlaveUser(db0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartMasterMasterSync(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
createSlaveUser(db0)
|
||||||
|
createSlaveUser(db1)
|
||||||
|
// db0 is master, db1 is slave
|
||||||
|
startSlave(db0, db1)
|
||||||
|
// db1 is master, db0 is slave
|
||||||
|
startSlave(db1, db0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStopMasterMasterSync(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
stopSlave(db0)
|
||||||
|
stopSlave(db1)
|
||||||
|
deleteSlaveUser(db0)
|
||||||
|
deleteSlaveUser(db1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShowSlaveStatus(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
slaveStatus(db0)
|
||||||
|
slaveStatus(db1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShowMasterStatus(t *testing.T) {
|
||||||
|
db0 := newDatabase(&Configs[0])
|
||||||
|
db1 := newDatabase(&Configs[1])
|
||||||
|
masterStatus(db0)
|
||||||
|
masterStatus(db1)
|
||||||
|
}
|
70
sync_v2/db.go
Normal file
70
sync_v2/db.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/xorm-io/xorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
database string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
slaveUser string
|
||||||
|
slavePassword string
|
||||||
|
engine *xorm.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) exec(format string, args ...interface{}) []map[string]string {
|
||||||
|
sql := fmt.Sprintf(format, args...)
|
||||||
|
res, err := db.engine.QueryString(sql)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEngine(dataSourceName string) (*xorm.Engine, error) {
|
||||||
|
engine, err := xorm.NewEngine("mysql", dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ping mysql
|
||||||
|
err = engine.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.ShowSQL(true)
|
||||||
|
log.Println("mysql connection success")
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDatabase(db *Database) *Database {
|
||||||
|
dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", db.username, db.password, db.host, db.port, db.database)
|
||||||
|
engine, err := createEngine(dataSourceName)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.engine = engine
|
||||||
|
return db
|
||||||
|
}
|
89
sync_v2/master.go
Normal file
89
sync_v2/master.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deleteSlaveUser(masterdb *Database) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
masterdb.exec("delete from mysql.user where user = '%v'", masterdb.slaveUser)
|
||||||
|
masterdb.exec("flush privileges")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSlaveUser(masterdb *Database) {
|
||||||
|
res := make([]map[string]string, 0)
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
res = masterdb.exec("show databases")
|
||||||
|
dbNames := make([]string, 0, len(res))
|
||||||
|
for _, dbInfo := range res {
|
||||||
|
dbName := dbInfo["Database"]
|
||||||
|
dbNames = append(dbNames, dbName)
|
||||||
|
}
|
||||||
|
log.Println("dbs in mysql: ", dbNames)
|
||||||
|
res = masterdb.exec("show tables")
|
||||||
|
tableNames := make([]string, 0, len(res))
|
||||||
|
for _, table := range res {
|
||||||
|
tableName := table[fmt.Sprintf("Tables_in_%v", masterdb.database)]
|
||||||
|
tableNames = append(tableNames, tableName)
|
||||||
|
}
|
||||||
|
log.Printf("tables in %v: %v", masterdb.database, tableNames)
|
||||||
|
|
||||||
|
// delete user to prevent user already exists
|
||||||
|
res = masterdb.exec("delete from mysql.user where user = '%v'", masterdb.slaveUser)
|
||||||
|
res = masterdb.exec("flush privileges")
|
||||||
|
|
||||||
|
// create replication user
|
||||||
|
res = masterdb.exec("create user '%s'@'%s' identified by '%s'", masterdb.slaveUser, "%", masterdb.slavePassword)
|
||||||
|
res = masterdb.exec("select host, user from mysql.user where user = '%v'", masterdb.slaveUser)
|
||||||
|
log.Println("user: ", res[0])
|
||||||
|
res = masterdb.exec("grant replication slave on *.* to '%s'@'%s'", masterdb.slaveUser, "%")
|
||||||
|
res = masterdb.exec("flush privileges")
|
||||||
|
res = masterdb.exec("show grants for '%s'@'%s'", masterdb.slaveUser, "%")
|
||||||
|
log.Println("grants: ", res[0])
|
||||||
|
|
||||||
|
// check env
|
||||||
|
res = masterdb.exec("show variables like 'server_id'")
|
||||||
|
log.Println("server_id: ", res[0]["Value"])
|
||||||
|
res = masterdb.exec("show variables like 'log_bin'")
|
||||||
|
log.Println("log_bin: ", res[0]["Value"])
|
||||||
|
res = masterdb.exec("show variables like 'binlog_format'")
|
||||||
|
log.Println("binlog_format: ", res[0]["Value"])
|
||||||
|
res = masterdb.exec("show variables like 'binlog_row_image'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func masterStatus(masterdb *Database) {
|
||||||
|
res := masterdb.exec("show master status")
|
||||||
|
if len(res) == 0 {
|
||||||
|
log.Printf("no master status for master [%v:%v]\n", masterdb.host, masterdb.port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pos := res[0]["Position"]
|
||||||
|
file := res[0]["File"]
|
||||||
|
log.Println("*****check master status*****")
|
||||||
|
log.Println("master:", masterdb.host, ":", masterdb.port)
|
||||||
|
log.Println("file:", file, ", position:", pos, ", master status:", res)
|
||||||
|
log.Println("*****************************")
|
||||||
|
}
|
84
sync_v2/slave.go
Normal file
84
sync_v2/slave.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package sync_v2
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
// slaveStatus shows slave status
|
||||||
|
func slaveStatus(slavedb *Database) {
|
||||||
|
res := slavedb.exec("show slave status")
|
||||||
|
if len(res) == 0 {
|
||||||
|
log.Printf("no slave status for slave [%v:%v]\n", slavedb.host, slavedb.port)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("*****check slave status*****")
|
||||||
|
log.Println("slave:", slavedb.host, ":", slavedb.port)
|
||||||
|
masterServerId := res[0]["Master_Server_Id"]
|
||||||
|
log.Println("master server id:", masterServerId)
|
||||||
|
lastError := res[0]["Last_Error"]
|
||||||
|
log.Println("last error:", lastError) // this should be empty
|
||||||
|
lastIoError := res[0]["Last_IO_Error"]
|
||||||
|
log.Println("last io error:", lastIoError) // this should be empty
|
||||||
|
slaveIoState := res[0]["Slave_IO_State"]
|
||||||
|
log.Println("slave io state:", slaveIoState)
|
||||||
|
slaveIoRunning := res[0]["Slave_IO_Running"]
|
||||||
|
log.Println("slave io running:", slaveIoRunning) // this should be Yes
|
||||||
|
slaveSqlRunning := res[0]["Slave_SQL_Running"]
|
||||||
|
log.Println("slave sql running:", slaveSqlRunning) // this should be Yes
|
||||||
|
slaveSqlRunningState := res[0]["Slave_SQL_Running_State"]
|
||||||
|
log.Println("slave sql running state:", slaveSqlRunningState)
|
||||||
|
slaveSecondsBehindMaster := res[0]["Seconds_Behind_Master"]
|
||||||
|
log.Println("seconds behind master:", slaveSecondsBehindMaster) // this should be 0, if not, it means the slave is behind the master
|
||||||
|
log.Println("slave status:", res)
|
||||||
|
log.Println("****************************")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopSlave stops slave
|
||||||
|
func stopSlave(slavedb *Database) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
slavedb.exec("stop slave")
|
||||||
|
slaveStatus(slavedb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// startSlave starts slave
|
||||||
|
func startSlave(masterdb *Database, slavedb *Database) {
|
||||||
|
res := make([]map[string]string, 0)
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
stopSlave(slavedb)
|
||||||
|
// get the info about master
|
||||||
|
res = masterdb.exec("show master status")
|
||||||
|
if len(res) == 0 {
|
||||||
|
log.Println("no master status")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pos := res[0]["Position"]
|
||||||
|
file := res[0]["File"]
|
||||||
|
log.Println("file:", file, ", position:", pos, ", master status:", res)
|
||||||
|
res = slavedb.exec("stop slave")
|
||||||
|
res = slavedb.exec(
|
||||||
|
"change master to master_host='%v', master_port=%v, master_user='%v', master_password='%v', master_log_file='%v', master_log_pos=%v;",
|
||||||
|
masterdb.host, masterdb.port, masterdb.slaveUser, masterdb.slavePassword, file, pos,
|
||||||
|
)
|
||||||
|
res = slavedb.exec("start slave")
|
||||||
|
slaveStatus(slavedb)
|
||||||
|
}
|
66
sync_v2/table_test.go
Normal file
66
sync_v2/table_test.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !skipCi
|
||||||
|
// +build !skipCi
|
||||||
|
|
||||||
|
package sync_v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestUser struct {
|
||||||
|
Id int64 `xorm:"pk autoincr"`
|
||||||
|
Username string `xorm:"varchar(50)"`
|
||||||
|
Address string `xorm:"varchar(50)"`
|
||||||
|
Card string `xorm:"varchar(50)"`
|
||||||
|
Age int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateUserTable(t *testing.T) {
|
||||||
|
db := newDatabase(&Configs[0])
|
||||||
|
err := db.engine.Sync2(new(TestUser))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInsertUser(t *testing.T) {
|
||||||
|
db := newDatabase(&Configs[0])
|
||||||
|
// random generate user
|
||||||
|
user := &TestUser{
|
||||||
|
Username: util.GetRandomName(),
|
||||||
|
Age: rand.Intn(100) + 10,
|
||||||
|
}
|
||||||
|
_, err := db.engine.Insert(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeleteUser(t *testing.T) {
|
||||||
|
db := newDatabase(&Configs[0])
|
||||||
|
user := &TestUser{
|
||||||
|
Id: 10,
|
||||||
|
}
|
||||||
|
_, err := db.engine.Delete(user)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func FileExist(path string) bool {
|
func FileExist(path string) bool {
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
_, err := os.Stat(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
} else if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -60,3 +60,19 @@ func ReturnAnyNotEmpty(strs ...string) string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HaveIntersection(arr1 []string, arr2 []string) bool {
|
||||||
|
elements := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, str := range arr1 {
|
||||||
|
elements[str] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, str := range arr2 {
|
||||||
|
if elements[str] {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
21
util/time.go
21
util/time.go
@ -43,8 +43,25 @@ func GetCurrentUnixTime() string {
|
|||||||
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsTokenExpired(createdTime string, expiresIn int) bool {
|
func String2Time(timestamp string) time.Time {
|
||||||
|
if timestamp == "" {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
parseTime, err := time.Parse(time.RFC3339, timestamp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return parseTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func Time2String(timestamp time.Time) string {
|
||||||
|
return timestamp.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsTokenExpired(createdTime string, expiresIn int) (bool, string) {
|
||||||
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
|
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
|
||||||
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Second)
|
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Second)
|
||||||
return time.Now().After(expiresAtObj)
|
isExpired := time.Now().After(expiresAtObj)
|
||||||
|
expireTime := expiresAtObj.Local().Format(time.RFC3339)
|
||||||
|
return isExpired, expireTime
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ func Test_IsTokenExpired(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(scenario.description, func(t *testing.T) {
|
t.Run(scenario.description, func(t *testing.T) {
|
||||||
result := IsTokenExpired(scenario.input.createdTime, scenario.input.expiresIn)
|
result, _ := IsTokenExpired(scenario.input.createdTime, scenario.input.expiresIn)
|
||||||
assert.Equal(t, scenario.expected, result, fmt.Sprintf("Expected %t, but was founded %t", scenario.expected, result))
|
assert.Equal(t, scenario.expected, result, fmt.Sprintf("Expected %t, but was founded %t", scenario.expected, result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@ module.exports = {
|
|||||||
target: "http://localhost:8000",
|
target: "http://localhost:8000",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
|
"/scim": {
|
||||||
|
target: "http://localhost:8000",
|
||||||
|
changeOrigin: true,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
|
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||||
import * as AdapterBackend from "./backend/AdapterBackend";
|
import * as AdapterBackend from "./backend/AdapterBackend";
|
||||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
@ -81,56 +81,6 @@ class AdapterEditPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDataSourceNameConfig() {
|
|
||||||
if (Setting.builtInObject(this.state.adapter)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Input value={this.state.adapter.host} onChange={e => {
|
|
||||||
this.updateAdapterField("host", e.target.value);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<InputNumber value={this.state.adapter.port} min={0} max={65535} onChange={value => {
|
|
||||||
this.updateAdapterField("port", value);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Input value={this.state.adapter.user} onChange={e => {
|
|
||||||
this.updateAdapterField("user", e.target.value);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Input value={this.state.adapter.password} onChange={e => {
|
|
||||||
this.updateAdapterField("password", e.target.value);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderAdapter() {
|
renderAdapter() {
|
||||||
return (
|
return (
|
||||||
<Card size="small" title={
|
<Card size="small" title={
|
||||||
@ -165,55 +115,6 @@ class AdapterEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.type} onChange={(value => {
|
|
||||||
this.updateAdapterField("type", value);
|
|
||||||
const adapter = this.state.adapter;
|
|
||||||
// adapter["tableColumns"] = Setting.getAdapterTableColumns(this.state.adapter);
|
|
||||||
this.setState({
|
|
||||||
adapter: adapter,
|
|
||||||
});
|
|
||||||
})}>
|
|
||||||
{
|
|
||||||
["Database"]
|
|
||||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.databaseType} onChange={(value => {this.updateAdapterField("databaseType", value);})}>
|
|
||||||
{
|
|
||||||
[
|
|
||||||
{id: "mysql", name: "MySQL"},
|
|
||||||
{id: "postgres", name: "PostgreSQL"},
|
|
||||||
{id: "mssql", name: "SQL Server"},
|
|
||||||
{id: "oracle", name: "Oracle"},
|
|
||||||
{id: "sqlite3", name: "Sqlite 3"},
|
|
||||||
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
|
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
{this.state.adapter.type === "Database" ? this.renderDataSourceNameConfig() : null}
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
|
||||||
{Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
|
|
||||||
</Col>
|
|
||||||
<Col span={22} >
|
|
||||||
<Input disabled={Setting.builtInObject(this.state.adapter)} value={this.state.adapter.database} onChange={e => {
|
|
||||||
this.updateAdapterField("database", e.target.value);
|
|
||||||
}} />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
||||||
@ -225,9 +126,130 @@ class AdapterEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("adapter:Use same DB"), i18next.t("adapter:Use same DB - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch disabled={Setting.builtInObject(this.state.adapter)} checked={this.state.adapter.useSameDb || Setting.builtInObject(this.state.adapter)} onChange={checked => {
|
||||||
|
this.updateAdapterField("useSameDb", checked);
|
||||||
|
if (checked) {
|
||||||
|
this.updateAdapterField("type", "");
|
||||||
|
this.updateAdapterField("databaseType", "");
|
||||||
|
this.updateAdapterField("host", "");
|
||||||
|
this.updateAdapterField("port", 0);
|
||||||
|
this.updateAdapterField("user", "");
|
||||||
|
this.updateAdapterField("password", "");
|
||||||
|
this.updateAdapterField("database", "");
|
||||||
|
} else {
|
||||||
|
this.updateAdapterField("type", "Database");
|
||||||
|
this.updateAdapterField("databaseType", "mysql");
|
||||||
|
this.updateAdapterField("host", "localhost");
|
||||||
|
this.updateAdapterField("port", 3306);
|
||||||
|
this.updateAdapterField("user", "root");
|
||||||
|
this.updateAdapterField("password", "123456");
|
||||||
|
this.updateAdapterField("database", "dbName");
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{
|
||||||
|
(this.state.adapter.useSameDb || Setting.builtInObject(this.state.adapter)) ? null : (
|
||||||
|
<React.Fragment>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.type} onChange={(value => {
|
||||||
|
this.updateAdapterField("type", value);
|
||||||
|
const adapter = this.state.adapter;
|
||||||
|
// adapter["tableColumns"] = Setting.getAdapterTableColumns(this.state.adapter);
|
||||||
|
this.setState({
|
||||||
|
adapter: adapter,
|
||||||
|
});
|
||||||
|
})}>
|
||||||
|
{
|
||||||
|
["Database"]
|
||||||
|
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.databaseType} onChange={(value => {this.updateAdapterField("databaseType", value);})}>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
{id: "mysql", name: "MySQL"},
|
||||||
|
{id: "postgres", name: "PostgreSQL"},
|
||||||
|
{id: "mssql", name: "SQL Server"},
|
||||||
|
{id: "oracle", name: "Oracle"},
|
||||||
|
{id: "sqlite3", name: "Sqlite 3"},
|
||||||
|
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.host} onChange={e => {
|
||||||
|
this.updateAdapterField("host", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<InputNumber value={this.state.adapter.port} min={0} max={65535} onChange={value => {
|
||||||
|
this.updateAdapterField("port", value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.user} onChange={e => {
|
||||||
|
this.updateAdapterField("user", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input value={this.state.adapter.password} onChange={e => {
|
||||||
|
this.updateAdapterField("password", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<Input disabled={Setting.builtInObject(this.state.adapter)} value={this.state.adapter.database} onChange={e => {
|
||||||
|
this.updateAdapterField("database", e.target.value);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("provider:DB Test"), i18next.t("provider:DB Test - Tooltip"))} :
|
{Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={2} >
|
<Col span={2} >
|
||||||
<Button type={"primary"} onClick={() => {
|
<Button type={"primary"} onClick={() => {
|
||||||
@ -250,7 +272,7 @@ class AdapterEditPage extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
submitAdapterEdit(willExist) {
|
submitAdapterEdit(exitAfterSave) {
|
||||||
const adapter = Setting.deepCopy(this.state.adapter);
|
const adapter = Setting.deepCopy(this.state.adapter);
|
||||||
AdapterBackend.updateAdapter(this.state.organizationName, this.state.adapterName, adapter)
|
AdapterBackend.updateAdapter(this.state.organizationName, this.state.adapterName, adapter)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
@ -260,7 +282,7 @@ class AdapterEditPage extends React.Component {
|
|||||||
adapterName: this.state.adapter.name,
|
adapterName: this.state.adapter.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (willExist) {
|
if (exitAfterSave) {
|
||||||
this.props.history.push("/adapters");
|
this.props.history.push("/adapters");
|
||||||
} else {
|
} else {
|
||||||
this.props.history.push(`/adapters/${this.state.organizationName}/${this.state.adapter.name}`);
|
this.props.history.push(`/adapters/${this.state.organizationName}/${this.state.adapter.name}`);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user