mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-14 08:03:23 +08:00
Compare commits
116 Commits
Author | SHA1 | Date | |
---|---|---|---|
e082cf10e0 | |||
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 | |||
8f6c295c40 | |||
2f31e35315 | |||
b6d6aa9d04 | |||
f40d44fa1c | |||
3b2820cbe3 | |||
764e88f603 | |||
7f298efebc | |||
0fc48bb6cd | |||
c3b3840994 | |||
eacc3fae5a | |||
ce7a2e924b |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -195,7 +195,7 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
target: STANDARD
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
|
||||
|
||||
@ -205,6 +205,6 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
target: ALLINONE
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
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
|
||||
casdoor
|
||||
server_linux_arm64
|
||||
server_linux_amd64
|
||||
server
|
||||
|
11
Dockerfile
11
Dockerfile
@ -1,7 +1,6 @@
|
||||
FROM node:16.18.0 AS FRONT
|
||||
WORKDIR /web
|
||||
COPY ./web .
|
||||
RUN yarn config set registry https://registry.npmmirror.com
|
||||
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
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
ARG USER=casdoor
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
||||
|
||||
RUN sed -i 's/https/http/' /etc/apk/repositories
|
||||
RUN apk add --update sudo
|
||||
@ -31,7 +27,7 @@ RUN adduser -D $USER -u 1000 \
|
||||
|
||||
USER 1000
|
||||
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/conf/app.conf ./conf/app.conf
|
||||
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
|
||||
LABEL MAINTAINER="https://casdoor.org/"
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
|
||||
|
||||
RUN apt update
|
||||
RUN apt install -y ca-certificates && update-ca-certificates
|
||||
|
||||
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/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
|
||||
|
@ -46,6 +46,7 @@ p, *, *, POST, /api/login, *, *
|
||||
p, *, *, GET, /api/get-app-login, *, *
|
||||
p, *, *, POST, /api/logout, *, *
|
||||
p, *, *, GET, /api/logout, *, *
|
||||
p, *, *, POST, /api/callback, *, *
|
||||
p, *, *, GET, /api/get-account, *, *
|
||||
p, *, *, GET, /api/userinfo, *, *
|
||||
p, *, *, GET, /api/user, *, *
|
||||
@ -80,6 +81,7 @@ p, *, *, GET, /api/get-saml-login, *, *
|
||||
p, *, *, POST, /api/acs, *, *
|
||||
p, *, *, GET, /api/saml/metadata, *, *
|
||||
p, *, *, *, /cas, *, *
|
||||
p, *, *, *, /scim, *, *
|
||||
p, *, *, *, /api/webauthn, *, *
|
||||
p, *, *, GET, /api/get-release, *, *
|
||||
p, *, *, GET, /api/get-default-application, *, *
|
||||
@ -125,8 +127,14 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
|
||||
return true
|
||||
}
|
||||
|
||||
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||
return true
|
||||
if user != nil {
|
||||
if user.IsDeleted {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
|
||||
@ -139,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 {
|
||||
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
|
||||
} else if urlPath == "/api/update-user" {
|
||||
// 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"
|
||||
export GOPROXY="https://goproxy.cn,direct"
|
||||
fi
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .
|
||||
|
@ -8,18 +8,23 @@ dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
origin =
|
||||
originFrontend =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
enableGzip = true
|
||||
ldapServerPort = 389
|
||||
radiusServerPort = 1812
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataFile = "./init_data.json"
|
||||
initDataFile = "./init_data.json"
|
||||
frontendBaseDir = "../casdoor"
|
@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/form"
|
||||
@ -119,20 +118,10 @@ func (c *ApiController) Signup() {
|
||||
}
|
||||
}
|
||||
|
||||
id := util.GenerateId()
|
||||
if application.GetSignupItemRule("ID") == "Incremental" {
|
||||
lastUser, err := object.GetLastUser(authForm.Organization)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
lastIdInt := -1
|
||||
if lastUser != nil {
|
||||
lastIdInt = util.ParseInt(lastUser.Id)
|
||||
}
|
||||
|
||||
id = strconv.Itoa(lastIdInt + 1)
|
||||
id, err := object.GenerateIdForNewUser(application)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
username := authForm.Username
|
||||
@ -309,27 +298,32 @@ func (c *ApiController) Logout() {
|
||||
return
|
||||
}
|
||||
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
if user == "" {
|
||||
user = util.GetId(token.Organization, token.User)
|
||||
}
|
||||
if user == "" {
|
||||
user = util.GetId(token.Organization, token.User)
|
||||
}
|
||||
|
||||
c.ClearUserSession()
|
||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||
owner, username := util.GetOwnerAndNameFromId(user)
|
||||
c.ClearUserSession()
|
||||
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
|
||||
owner, username := util.GetOwnerAndNameFromId(user)
|
||||
|
||||
_, err := object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
_, err = object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
if redirectUri == "" {
|
||||
c.ResponseOk()
|
||||
return
|
||||
} else {
|
||||
if application.IsRedirectUriValid(redirectUri) {
|
||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
}
|
||||
|
||||
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
|
||||
|
||||
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
|
||||
} else {
|
||||
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -476,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))
|
||||
return
|
||||
}
|
||||
|
||||
userInfo := &idp.UserInfo{}
|
||||
if provider.Category == "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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -488,7 +488,11 @@ func (c *ApiController) Login() {
|
||||
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
|
||||
// OAuth
|
||||
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 {
|
||||
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
|
||||
return
|
||||
@ -523,7 +527,8 @@ func (c *ApiController) Login() {
|
||||
if authForm.Method == "signup" {
|
||||
user := &object.User{}
|
||||
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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -614,11 +619,16 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
userId := userInfo.Id
|
||||
if userId == "" {
|
||||
userId = util.GenerateId()
|
||||
}
|
||||
|
||||
user = &object.User{
|
||||
Owner: application.Organization,
|
||||
Name: userInfo.Username,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Id: util.GenerateId(),
|
||||
Id: userId,
|
||||
Type: "normal-user",
|
||||
DisplayName: userInfo.DisplayName,
|
||||
Avatar: userInfo.AvatarUrl,
|
||||
@ -645,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)))
|
||||
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
|
||||
@ -673,6 +692,7 @@ func (c *ApiController) Login() {
|
||||
record2.User = user.Name
|
||||
util.SafeGoroutine(func() { object.AddRecord(record2) })
|
||||
} 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: "ok", Msg: "", Data: res}
|
||||
@ -896,3 +916,16 @@ func (c *ApiController) GetCaptchaStatus() {
|
||||
}
|
||||
c.ResponseOk(captchaEnabled)
|
||||
}
|
||||
|
||||
// Callback
|
||||
// @Title Callback
|
||||
// @Tag Callback API
|
||||
// @Description Get Login Error Counts
|
||||
// @router /api/Callback [post]
|
||||
func (c *ApiController) Callback() {
|
||||
code := c.GetString("code")
|
||||
state := c.GetString("state")
|
||||
|
||||
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
|
||||
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
|
||||
}
|
||||
|
@ -37,6 +37,11 @@ func (c *ApiController) Enforce() {
|
||||
resourceId := c.Input().Get("resourceId")
|
||||
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
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
|
||||
if err != nil {
|
||||
|
@ -191,7 +191,7 @@ func (c *ApiController) UpdatePolicy() {
|
||||
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 {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -210,7 +210,7 @@ func (c *ApiController) AddPolicy() {
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.AddPolicy(id, util.CasbinToSlice(policy))
|
||||
affected, err := object.AddPolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@ -229,7 +229,7 @@ func (c *ApiController) RemovePolicy() {
|
||||
return
|
||||
}
|
||||
|
||||
affected, err := object.RemovePolicy(id, util.CasbinToSlice(policy))
|
||||
affected, err := object.RemovePolicy(id, policy.Ptype, util.CasbinToSlice(policy))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -16,7 +16,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -83,11 +82,8 @@ func (c *ApiController) GetPlan() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if plan == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("plan:The plan: %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
if includeOption {
|
||||
|
||||
if plan != nil && includeOption {
|
||||
options, err := object.GetPermissionsByRole(plan.Role)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
@ -97,11 +93,9 @@ func (c *ApiController) GetPlan() {
|
||||
for _, option := range options {
|
||||
plan.Options = append(plan.Options, option.DisplayName)
|
||||
}
|
||||
|
||||
c.ResponseOk(plan)
|
||||
} else {
|
||||
c.ResponseOk(plan)
|
||||
}
|
||||
|
||||
c.ResponseOk(plan)
|
||||
}
|
||||
|
||||
// UpdatePlan
|
||||
|
@ -16,7 +16,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/beego/beego/utils/pagination"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
@ -81,10 +80,7 @@ func (c *ApiController) GetPricing() {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if pricing == nil {
|
||||
c.ResponseError(fmt.Sprintf(c.T("pricing:The pricing: %s does not exist"), id))
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk(pricing)
|
||||
}
|
||||
|
||||
|
@ -272,6 +272,11 @@ func (c *ApiController) UploadResource() {
|
||||
return
|
||||
}
|
||||
|
||||
if username == "Built-in-Untracked" {
|
||||
c.ResponseOk(fileUrl, objectKey)
|
||||
return
|
||||
}
|
||||
|
||||
if createdTime == "" {
|
||||
createdTime = util.GetCurrentTime()
|
||||
}
|
||||
|
@ -33,7 +33,13 @@ func (c *ApiController) GetSamlMeta() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
|
||||
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.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)
|
||||
}
|
||||
|
||||
if owner == "" {
|
||||
owner = util.GetOwnerFromId(id)
|
||||
}
|
||||
var user *object.User
|
||||
|
||||
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
if id == "" && owner == "" {
|
||||
switch {
|
||||
case email != "":
|
||||
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 {
|
||||
requestUserId := c.GetSessionUsername()
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
organization, err := object.GetOrganization(util.GetId("admin", owner))
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if organization == nil {
|
||||
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
|
||||
return
|
||||
}
|
||||
|
||||
var user *object.User
|
||||
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 !organization.IsProfilePublic {
|
||||
requestUserId := c.GetSessionUsername()
|
||||
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
|
||||
if !hasPermission {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -466,7 +482,7 @@ func (c *ApiController) SetPassword() {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if code == "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
@ -560,11 +576,11 @@ func (c *ApiController) GetUserCount() {
|
||||
c.ResponseOk(count)
|
||||
}
|
||||
|
||||
// AddUserkeys
|
||||
// @Title AddUserkeys
|
||||
// AddUserKeys
|
||||
// @Title AddUserKeys
|
||||
// @router /add-user-keys [post]
|
||||
// @Tag User API
|
||||
func (c *ApiController) AddUserkeys() {
|
||||
func (c *ApiController) AddUserKeys() {
|
||||
var user object.User
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
|
||||
if err != nil {
|
||||
@ -573,7 +589,7 @@ func (c *ApiController) AddUserkeys() {
|
||||
}
|
||||
|
||||
isAdmin := c.IsAdmin()
|
||||
affected, err := object.AddUserkeys(&user, isAdmin)
|
||||
affected, err := object.AddUserKeys(&user, isAdmin)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@ -96,6 +96,13 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if strings.HasPrefix(userId, "app/") {
|
||||
tmpUserId := c.Input().Get("userId")
|
||||
if tmpUserId != "" {
|
||||
userId = tmpUserId
|
||||
}
|
||||
}
|
||||
|
||||
user, err := object.GetUser(userId)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
|
@ -142,6 +142,10 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.ResponseError(err.Error())
|
||||
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)
|
||||
case object.VerifyTypePhone:
|
||||
@ -184,6 +188,10 @@ func (c *ApiController) SendVerificationCode() {
|
||||
c.ResponseError(err.Error())
|
||||
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 {
|
||||
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)
|
||||
|
||||
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 {
|
||||
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()
|
||||
|
||||
// Response error Handling
|
||||
if resp.StatusCode == http.StatusBadRequest {
|
||||
if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
|
||||
commError := ErrorResponse{}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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" {
|
||||
return NewAzureACSEmailProvider(appId, host)
|
||||
return NewAzureACSEmailProvider(clientSecret, host)
|
||||
} else {
|
||||
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
|
||||
}
|
||||
|
10
go.mod
10
go.mod
@ -10,16 +10,17 @@ require (
|
||||
github.com/beego/beego v1.12.12
|
||||
github.com/beevik/etree v1.1.0
|
||||
github.com/casbin/casbin v1.9.1 // indirect
|
||||
github.com/casbin/casbin/v2 v2.37.0
|
||||
github.com/casdoor/go-sms-sender v0.14.0
|
||||
github.com/casbin/casbin/v2 v2.77.2
|
||||
github.com/casdoor/go-sms-sender v0.15.0
|
||||
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/xorm-adapter/v3 v3.0.4
|
||||
github.com/casvisor/casvisor-go-sdk v1.0.3
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/denisenkom/go-mssqldb v0.9.0
|
||||
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/forestmgy/ldapserver v1.1.0
|
||||
github.com/go-git/go-git/v5 v5.6.0
|
||||
@ -54,6 +55,7 @@ require (
|
||||
github.com/stripe/stripe-go/v74 v74.29.0
|
||||
github.com/tealeg/xlsx v1.0.5
|
||||
github.com/thanhpk/randstr v1.0.4
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||
github.com/xorm-io/builder v0.3.13
|
||||
github.com/xorm-io/core v0.7.4
|
||||
@ -62,9 +64,11 @@ require (
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/api v0.138.0
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
|
||||
maunium.net/go/mautrix v0.16.0
|
||||
modernc.org/sqlite v1.18.2
|
||||
)
|
||||
|
32
go.sum
32
go.sum
@ -917,14 +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/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.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw=
|
||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casdoor/go-sms-sender v0.14.0 h1:yqrzWIHUg64OYPynzF5Fr0XDuCWIWxtXIjOQAAkRKuw=
|
||||
github.com/casdoor/go-sms-sender v0.14.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs=
|
||||
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/casdoor/go-reddit/v2 v2.1.0 h1:kIbfdJ7AA7H0uTQ8s0q4GGZqSS5V9wVE74RrXyD9XPs=
|
||||
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/go.mod h1:VnGPslEAtpix5FjHisR/WKB1qvZDBaujbikxDe9d+2Q=
|
||||
github.com/casdoor/notify v0.43.0 h1:NukyVZ9l7d2TSlB5YWKJyDsPmHCvwKQVi9rWDprtcU4=
|
||||
github.com/casdoor/notify v0.43.0/go.mod h1:qDmQM5vr2uU01BEuDC6pY6ryahSU11cXPqlHFW232Do=
|
||||
github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk=
|
||||
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/go.mod h1:YOi6KpG1pZHTkiy9AYaqI0UaPfE7YkaA07d89f1idqY=
|
||||
github.com/casdoor/xorm-adapter/v3 v3.0.4 h1:vB04Ao8n2jA7aFBI9F+gGXo9+Aa1IQP6mTdo50913DM=
|
||||
@ -1009,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/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/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/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=
|
||||
@ -1025,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.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
|
||||
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/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@ -1692,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/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
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/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=
|
||||
@ -1793,8 +1804,9 @@ 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/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
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.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
|
||||
@ -1819,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/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/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/go.mod h1:ojXSFvj404o2UKnZR9k9LUUWIUU+9XtlRlzk2+UFc/M=
|
||||
github.com/wendal/errors v0.0.0-20181209125328-7f31f4b264ec/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
|
||||
@ -1908,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-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-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-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@ -2296,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.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.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.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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@ -2764,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.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
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.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
||||
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",
|
||||
"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",
|
||||
"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": {
|
||||
"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",
|
||||
"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: %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é.",
|
||||
"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",
|
||||
@ -53,7 +53,7 @@
|
||||
"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",
|
||||
"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",
|
||||
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
|
||||
},
|
||||
@ -61,8 +61,8 @@
|
||||
"Missing parameter": "Paramètre manquant",
|
||||
"Please login first": "Veuillez d'abord vous connecter",
|
||||
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
|
||||
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider",
|
||||
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
|
||||
"don't support captchaProvider: ": "ne prend pas en charge captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "cette opération n’est pas autorisée en mode démo"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "Le serveur LDAP existe"
|
||||
|
@ -24,14 +24,6 @@
|
||||
"cas": {
|
||||
"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": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
|
@ -24,14 +24,6 @@
|
||||
"cas": {
|
||||
"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": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
|
@ -24,14 +24,6 @@
|
||||
"cas": {
|
||||
"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": {
|
||||
"Affiliation cannot be blank": "Affiliation cannot be blank",
|
||||
"DisplayName cannot be blank": "DisplayName cannot be blank",
|
||||
|
@ -43,7 +43,7 @@
|
||||
"Phone number is invalid": "无效手机号",
|
||||
"Session outdated, please login again": "会话已过期,请重新登录",
|
||||
"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.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
|
||||
"Username already exists": "用户名已存在",
|
||||
"Username cannot be an email address": "用户名不可以是邮箱地址",
|
||||
@ -62,7 +62,7 @@
|
||||
"Please login first": "请先登录",
|
||||
"The user: %s doesn't exist": "用户: %s不存在",
|
||||
"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 server exist": "LDAP服务器已存在"
|
||||
|
20
idp/adfs.go
20
idp/adfs.go
@ -19,7 +19,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
@ -84,6 +83,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
payload.Set("code", code)
|
||||
payload.Set("grant_type", "authorization_code")
|
||||
payload.Set("client_id", idp.Config.ClientID)
|
||||
payload.Set("client_secret", idp.Config.ClientSecret)
|
||||
payload.Set("redirect_uri", idp.Config.RedirectURL)
|
||||
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
|
||||
if err != nil {
|
||||
@ -118,11 +118,25 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
keyset, err := jwk.ParseKey(body)
|
||||
body, err := io.ReadAll(resp.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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyset, err := jwk.ParseKey(respKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenSrc := []byte(token.AccessToken)
|
||||
publicKey, _ := keyset.PublicKey()
|
||||
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))
|
||||
|
34
idp/goth.go
34
idp/goth.go
@ -19,6 +19,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
@ -88,7 +89,7 @@ type GothIdProvider struct {
|
||||
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
|
||||
switch providerType {
|
||||
case "Amazon":
|
||||
@ -97,8 +98,27 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
Session: &amazon.Session{},
|
||||
}
|
||||
case "Apple":
|
||||
if !strings.Contains(redirectUrl, "/api/callback") {
|
||||
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{
|
||||
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
|
||||
Provider: apple.New(clientId, *secret, redirectUrl, nil),
|
||||
Session: &apple.Session{},
|
||||
}
|
||||
case "AzureAD":
|
||||
@ -382,17 +402,19 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
|
||||
Session: &zoom.Session{},
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType)
|
||||
}
|
||||
|
||||
return &idp
|
||||
return &idp, nil
|
||||
}
|
||||
|
||||
// SetHttpClient
|
||||
// Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
|
||||
func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
|
||||
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
|
||||
idpClient.Set(reflect.ValueOf(client))
|
||||
if idpClient.IsValid() {
|
||||
idpClient.Set(reflect.ValueOf(client))
|
||||
}
|
||||
}
|
||||
|
||||
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
@ -468,6 +490,8 @@ func getUser(gothUser goth.User, provider string) *UserInfo {
|
||||
if provider == "steam" {
|
||||
user.Username = user.Id
|
||||
user.Email = ""
|
||||
} else if provider == "apple" {
|
||||
user.Username = util.GetUsernameFromEmail(user.Email)
|
||||
}
|
||||
return &user
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package idp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -33,13 +34,15 @@ type UserInfo struct {
|
||||
}
|
||||
|
||||
type ProviderInfo struct {
|
||||
Type string
|
||||
SubType string
|
||||
ClientId string
|
||||
ClientSecret string
|
||||
AppId string
|
||||
HostUrl string
|
||||
RedirectUrl string
|
||||
Type string
|
||||
SubType string
|
||||
ClientId string
|
||||
ClientSecret string
|
||||
ClientId2 string
|
||||
ClientSecret2 string
|
||||
AppId string
|
||||
HostUrl string
|
||||
RedirectUrl string
|
||||
|
||||
TokenURL string
|
||||
AuthURL string
|
||||
@ -53,71 +56,71 @@ type IdProvider interface {
|
||||
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
|
||||
}
|
||||
|
||||
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
|
||||
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) {
|
||||
switch idpInfo.Type {
|
||||
case "GitHub":
|
||||
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Google":
|
||||
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "QQ":
|
||||
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "WeChat":
|
||||
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Facebook":
|
||||
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "DingTalk":
|
||||
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Weibo":
|
||||
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Gitee":
|
||||
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "LinkedIn":
|
||||
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "WeCom":
|
||||
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" {
|
||||
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
} else {
|
||||
return nil
|
||||
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
|
||||
}
|
||||
case "Lark":
|
||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "GitLab":
|
||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
case "Adfs":
|
||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "ADFS":
|
||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||
case "Baidu":
|
||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Alipay":
|
||||
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Custom":
|
||||
return NewCustomIdProvider(idpInfo, redirectUrl)
|
||||
return NewCustomIdProvider(idpInfo, redirectUrl), nil
|
||||
case "Infoflow":
|
||||
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" {
|
||||
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
|
||||
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
|
||||
} else {
|
||||
return nil
|
||||
return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType)
|
||||
}
|
||||
case "Casdoor":
|
||||
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
||||
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||
case "Okta":
|
||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
||||
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
|
||||
case "Douyin":
|
||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "Bilibili":
|
||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
|
||||
case "MetaMask":
|
||||
return NewMetaMaskIdProvider()
|
||||
return NewMetaMaskIdProvider(), nil
|
||||
case "Web3Onboard":
|
||||
return NewWeb3OnboardIdProvider()
|
||||
return NewWeb3OnboardIdProvider(), nil
|
||||
default:
|
||||
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": [],
|
||||
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
|
||||
"masterPassword": "",
|
||||
"defaultPassword": "",
|
||||
"initScore": 2000,
|
||||
"enableSoftDeletion": false,
|
||||
"isProfilePublic": true,
|
||||
@ -176,9 +177,7 @@
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"actions": [
|
||||
""
|
||||
],
|
||||
"actions": [],
|
||||
"displayName": "",
|
||||
"effect": "",
|
||||
"isEnabled": true,
|
||||
@ -186,15 +185,9 @@
|
||||
"name": "",
|
||||
"owner": "",
|
||||
"resourceType": "",
|
||||
"resources": [
|
||||
""
|
||||
],
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"users": [
|
||||
""
|
||||
]
|
||||
"resources": [],
|
||||
"roles": [],
|
||||
"users": []
|
||||
}
|
||||
],
|
||||
"payments": [
|
||||
@ -236,9 +229,7 @@
|
||||
"name": "",
|
||||
"owner": "",
|
||||
"price": 0,
|
||||
"providers": [
|
||||
""
|
||||
],
|
||||
"providers": [],
|
||||
"quantity": 0,
|
||||
"returnUrl": "",
|
||||
"sold": 0,
|
||||
@ -268,12 +259,8 @@
|
||||
"isEnabled": true,
|
||||
"name": "",
|
||||
"owner": "",
|
||||
"roles": [
|
||||
""
|
||||
],
|
||||
"users": [
|
||||
""
|
||||
]
|
||||
"roles": [],
|
||||
"users": []
|
||||
}
|
||||
],
|
||||
"syncers": [
|
||||
@ -284,7 +271,7 @@
|
||||
"databaseType": "",
|
||||
"errorText": "",
|
||||
"host": "",
|
||||
"isEnabled": true,
|
||||
"isEnabled": false,
|
||||
"name": "",
|
||||
"organization": "",
|
||||
"owner": "",
|
||||
@ -298,9 +285,7 @@
|
||||
"isHashed": true,
|
||||
"name": "",
|
||||
"type": "",
|
||||
"values": [
|
||||
""
|
||||
]
|
||||
"values": []
|
||||
}
|
||||
],
|
||||
"tablePrimaryKey": "",
|
||||
@ -330,9 +315,7 @@
|
||||
"webhooks": [
|
||||
{
|
||||
"contentType": "",
|
||||
"events": [
|
||||
""
|
||||
],
|
||||
"events": [],
|
||||
"headers": [
|
||||
{
|
||||
"name": "",
|
||||
|
@ -25,6 +25,11 @@ import (
|
||||
)
|
||||
|
||||
func StartLdapServer() {
|
||||
ldapServerPort := conf.GetConfigString("ldapServerPort")
|
||||
if ldapServerPort == "" || ldapServerPort == "0" {
|
||||
return
|
||||
}
|
||||
|
||||
server := ldap.NewServer()
|
||||
routes := ldap.NewRouteMux()
|
||||
|
||||
@ -32,9 +37,9 @@ func StartLdapServer() {
|
||||
routes.Search(handleSearch).Label(" SEARCH****")
|
||||
|
||||
server.Handle(routes)
|
||||
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
||||
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
|
||||
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")
|
||||
if !hasPermission {
|
||||
log.Printf("ErrMsg = %v", err.Error())
|
||||
log.Printf("err = %v", err.Error())
|
||||
return nil, ldap.LDAPResultInsufficientAccessRights
|
||||
}
|
||||
|
||||
|
2
main.go
2
main.go
@ -25,6 +25,7 @@ import (
|
||||
"github.com/casdoor/casdoor/ldap"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/proxy"
|
||||
"github.com/casdoor/casdoor/radius"
|
||||
"github.com/casdoor/casdoor/routers"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
@ -81,6 +82,7 @@ func main() {
|
||||
logs.SetLogFuncCall(false)
|
||||
|
||||
go ldap.StartLdapServer()
|
||||
go radius.StartRadiusServer()
|
||||
go object.ClearThroughputPerSecond()
|
||||
|
||||
beego.Run(fmt.Sprintf(":%v", port))
|
||||
|
@ -22,14 +22,15 @@ config: |
|
||||
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
|
||||
dbName = casdoor
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = ""
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 2000
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
origin = "https://door.casbin.com"
|
||||
origin =
|
||||
enableGzip = true
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
"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)
|
||||
if err != nil {
|
||||
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) {
|
||||
if typ == "Telegram" {
|
||||
return NewTelegramProvider(appId, receiver)
|
||||
return NewTelegramProvider(clientSecret, receiver)
|
||||
} else if typ == "Custom HTTP" {
|
||||
return NewCustomHttpProvider(receiver, method, title)
|
||||
} else if typ == "DingTalk" {
|
||||
return NewDingTalkProvider(appId, receiver)
|
||||
return NewDingTalkProvider(clientId, clientSecret)
|
||||
} else if typ == "Lark" {
|
||||
return NewLarkProvider(receiver)
|
||||
return NewLarkProvider(clientSecret)
|
||||
} else if typ == "Microsoft Teams" {
|
||||
return NewMicrosoftTeamsProvider(receiver)
|
||||
return NewMicrosoftTeamsProvider(clientSecret)
|
||||
} else if typ == "Bark" {
|
||||
return NewBarkProvider(receiver)
|
||||
return NewBarkProvider(clientSecret)
|
||||
} else if typ == "Pushover" {
|
||||
return NewPushoverProvider(appId, receiver)
|
||||
return NewPushoverProvider(clientSecret, receiver)
|
||||
} else if typ == "Pushbullet" {
|
||||
return NewPushbulletProvider(appId, receiver)
|
||||
return NewPushbulletProvider(clientSecret, receiver)
|
||||
} else if typ == "Slack" {
|
||||
return NewSlackProvider(appId, receiver)
|
||||
return NewSlackProvider(clientSecret, receiver)
|
||||
} else if typ == "Webpush" {
|
||||
return NewWebpushProvider(clientId, clientSecret, receiver)
|
||||
} else if typ == "Discord" {
|
||||
return NewDiscordProvider(appId, receiver)
|
||||
return NewDiscordProvider(clientSecret, receiver)
|
||||
} else if typ == "Google Chat" {
|
||||
return NewGoogleChatProvider(metaData)
|
||||
} else if typ == "Line" {
|
||||
|
@ -30,15 +30,15 @@ type Adapter struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
TableNamePrefix string `xorm:"varchar(100)" json:"tableNamePrefix"`
|
||||
Table string `xorm:"varchar(100)" json:"table"`
|
||||
UseSameDb bool `json:"useSameDb"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
|
||||
Host string `xorm:"varchar(100)" json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
Database string `xorm:"varchar(100)" json:"database"`
|
||||
|
||||
*xormadapter.Adapter `xorm:"-" json:"-"`
|
||||
}
|
||||
@ -139,63 +139,69 @@ func (adapter *Adapter) GetId() string {
|
||||
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 {
|
||||
if adapter.Adapter == nil {
|
||||
var dataSourceName string
|
||||
if adapter.Adapter != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if adapter.isBuiltIn() {
|
||||
dataSourceName = conf.GetConfigString("dataSourceName")
|
||||
if adapter.DatabaseType == "mysql" {
|
||||
dataSourceName = dataSourceName + adapter.Database
|
||||
}
|
||||
} else {
|
||||
switch adapter.DatabaseType {
|
||||
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)
|
||||
}
|
||||
var driverName string
|
||||
var dataSourceName string
|
||||
if adapter.UseSameDb || adapter.isBuiltIn() {
|
||||
driverName = conf.GetConfigString("driverName")
|
||||
dataSourceName = conf.GetConfigString("dataSourceName")
|
||||
if conf.GetConfigString("driverName") == "mysql" {
|
||||
dataSourceName = dataSourceName + conf.GetConfigString("dbName")
|
||||
}
|
||||
|
||||
if !isCloudIntranet {
|
||||
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
|
||||
}
|
||||
|
||||
var err error
|
||||
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
|
||||
|
||||
if adapter.isBuiltIn() && adapter.DatabaseType == "postgres" {
|
||||
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||
if schema != "" {
|
||||
engine.SetSchema(schema)
|
||||
}
|
||||
}
|
||||
|
||||
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
driverName = adapter.DatabaseType
|
||||
switch driverName {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -25,11 +25,19 @@ import (
|
||||
)
|
||||
|
||||
type SignupItem struct {
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
Rule string `json:"rule"`
|
||||
Name string `json:"name"`
|
||||
Visible bool `json:"visible"`
|
||||
Required bool `json:"required"`
|
||||
Prompted bool `json:"prompted"`
|
||||
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 {
|
||||
@ -49,17 +57,19 @@ type Application struct {
|
||||
EnableAutoSignin bool `json:"enableAutoSignin"`
|
||||
EnableCodeSignin bool `json:"enableCodeSignin"`
|
||||
EnableSamlCompress bool `json:"enableSamlCompress"`
|
||||
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
|
||||
EnableWebAuthn bool `json:"enableWebAuthn"`
|
||||
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
|
||||
OrgChoiceMode string `json:"orgChoiceMode"`
|
||||
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
|
||||
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"`
|
||||
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
|
||||
CertPublicKey string `xorm:"-" json:"certPublicKey"`
|
||||
Tags []string `xorm:"mediumtext" json:"tags"`
|
||||
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
|
||||
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
|
||||
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
@ -306,6 +316,9 @@ func GetMaskedApplication(application *Application, userId string) *Application
|
||||
if application.OrganizationObj.MasterPassword != "" {
|
||||
application.OrganizationObj.MasterPassword = "***"
|
||||
}
|
||||
if application.OrganizationObj.DefaultPassword != "" {
|
||||
application.OrganizationObj.DefaultPassword = "***"
|
||||
}
|
||||
if application.OrganizationObj.PasswordType != "" {
|
||||
application.OrganizationObj.PasswordType = "***"
|
||||
}
|
||||
@ -428,7 +441,7 @@ func (application *Application) GetId() string {
|
||||
}
|
||||
|
||||
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 {
|
||||
targetUriRegex := regexp.MustCompile(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) {
|
||||
var err error
|
||||
if userId == "built-in/admin" {
|
||||
owner, _ := util.GetOwnerAndNameFromId(userId)
|
||||
if owner == "built-in" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@ -361,6 +361,8 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
||||
return false, err
|
||||
}
|
||||
|
||||
allowPermissionCount := 0
|
||||
denyPermissionCount := 0
|
||||
allowCount := 0
|
||||
denyCount := 0
|
||||
for _, permission := range permissions {
|
||||
@ -368,11 +370,19 @@ func CheckLoginPermission(userId string, application *Application) (bool, error)
|
||||
continue
|
||||
}
|
||||
|
||||
if permission.isUserHit(userId) {
|
||||
allowCount += 1
|
||||
if !permission.isUserHit(userId) && !permission.isRoleHit(userId) {
|
||||
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
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
|
||||
}
|
||||
|
||||
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
|
||||
if fromAddress == "" {
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/casbin/casbin/v2/config"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
xormadapter "github.com/casdoor/xorm-adapter/v3"
|
||||
"github.com/xorm-io/core"
|
||||
@ -191,39 +190,55 @@ func GetPolicies(id string) ([]*xormadapter.CasbinRule, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policies := util.MatrixToCasbinRules("p", enforcer.GetPolicy())
|
||||
pRules := enforcer.GetPolicy()
|
||||
res := util.MatrixToCasbinRules("p", pRules)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return enforcer.RemovePolicy(policy)
|
||||
if ptype == "p" {
|
||||
return enforcer.RemovePolicy(policy)
|
||||
} else {
|
||||
return enforcer.RemoveGroupingPolicy(policy)
|
||||
}
|
||||
}
|
||||
|
||||
func (enforcer *Enforcer) LoadModelCfg() error {
|
||||
@ -231,23 +246,17 @@ func (enforcer *Enforcer) LoadModelCfg() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
model, err := GetModel(enforcer.Model)
|
||||
model, err := GetModelEx(enforcer.Model)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if model == nil {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ func GetGroupUserCount(groupId string, field, value string) (int64, error) {
|
||||
} else {
|
||||
return ormer.Engine.Table("user").
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -247,7 +247,7 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
|
||||
}
|
||||
|
||||
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 == "" {
|
||||
|
@ -178,7 +178,7 @@ func initBuiltInApplication() {
|
||||
EnablePassword: true,
|
||||
EnableSignUp: true,
|
||||
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{
|
||||
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
|
||||
@ -423,14 +423,11 @@ func initBuiltInUserAdapter() {
|
||||
}
|
||||
|
||||
adapter = &Adapter{
|
||||
Owner: "built-in",
|
||||
Name: "user-adapter-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Type: "Database",
|
||||
DatabaseType: conf.GetConfigString("driverName"),
|
||||
TableNamePrefix: conf.GetConfigString("tableNamePrefix"),
|
||||
Database: conf.GetConfigString("dbName"),
|
||||
Table: "casbin_user_rule",
|
||||
Owner: "built-in",
|
||||
Name: "user-adapter-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Table: "casbin_user_rule",
|
||||
UseSameDb: true,
|
||||
}
|
||||
_, err = AddAdapter(adapter)
|
||||
if err != nil {
|
||||
@ -449,14 +446,11 @@ func initBuiltInApiAdapter() {
|
||||
}
|
||||
|
||||
adapter = &Adapter{
|
||||
Owner: "built-in",
|
||||
Name: "api-adapter-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Type: "Database",
|
||||
DatabaseType: conf.GetConfigString("driverName"),
|
||||
TableNamePrefix: conf.GetConfigString("tableNamePrefix"),
|
||||
Database: conf.GetConfigString("dbName"),
|
||||
Table: "casbin_api_rule",
|
||||
Owner: "built-in",
|
||||
Name: "api-adapter-built-in",
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
Table: "casbin_api_rule",
|
||||
UseSameDb: true,
|
||||
}
|
||||
_, err = AddAdapter(adapter)
|
||||
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");
|
||||
// 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
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !skipCi
|
||||
// +build !skipCi
|
||||
|
||||
package object
|
||||
|
||||
func (syncer *Syncer) getUsers() []*User {
|
||||
users, err := GetUsers(syncer.Organization)
|
||||
import "testing"
|
||||
|
||||
func TestDumpToFile(t *testing.T) {
|
||||
InitConfig()
|
||||
|
||||
err := DumpToFile("./init_data_dump.json")
|
||||
if err != nil {
|
||||
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
|
||||
}
|
@ -42,7 +42,7 @@ func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, er
|
||||
//if issuer == "" {
|
||||
// issuer = "casdoor"
|
||||
//}
|
||||
issuer := "casdoor"
|
||||
issuer := "Casdoor"
|
||||
|
||||
key, err := totp.Generate(totp.GenerateOpts{
|
||||
Issuer: issuer,
|
||||
|
@ -17,6 +17,7 @@ package object
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casbin/v2/config"
|
||||
"github.com/casbin/casbin/v2/model"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
@ -83,6 +84,19 @@ func GetModel(id string) (*Model, error) {
|
||||
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 {
|
||||
// check model grammar
|
||||
_, err := model.NewModelFromString(modelObj.ModelText)
|
||||
@ -188,3 +202,17 @@ func (m *Model) initModel() error {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func getOriginFromHost(host string) (string, string) {
|
||||
func getOriginFromHostInternal(host string) (string, string) {
|
||||
origin := conf.GetConfigString("origin")
|
||||
if 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 {
|
||||
originFrontend, originBackend := getOriginFromHost(host)
|
||||
|
||||
@ -127,9 +138,16 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if cert.Certificate == "" {
|
||||
return jwks, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||
}
|
||||
|
||||
certPemBlock := []byte(cert.Certificate)
|
||||
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
|
||||
jwk.Key = x509Cert.PublicKey
|
||||
|
@ -64,6 +64,7 @@ type Organization struct {
|
||||
Languages []string `xorm:"varchar(255)" json:"languages"`
|
||||
ThemeData *ThemeData `xorm:"json" json:"themeData"`
|
||||
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
|
||||
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
|
||||
InitScore int `json:"initScore"`
|
||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||
IsProfilePublic bool `json:"isProfilePublic"`
|
||||
@ -155,6 +156,9 @@ func GetMaskedOrganization(organization *Organization, errs ...error) (*Organiza
|
||||
if organization.MasterPassword != "" {
|
||||
organization.MasterPassword = "***"
|
||||
}
|
||||
if organization.DefaultPassword != "" {
|
||||
organization.DefaultPassword = "***"
|
||||
}
|
||||
return organization, nil
|
||||
}
|
||||
|
||||
@ -202,9 +206,14 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
|
||||
}
|
||||
|
||||
session := ormer.Engine.ID(core.PK{owner, name}).AllCols()
|
||||
|
||||
if organization.MasterPassword == "***" {
|
||||
session.Omit("master_password")
|
||||
}
|
||||
if organization.DefaultPassword == "***" {
|
||||
session.Omit("default_password")
|
||||
}
|
||||
|
||||
affected, err := session.Update(organization)
|
||||
if err != nil {
|
||||
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")
|
||||
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
|
||||
@ -121,19 +125,22 @@ func finalizer(a *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.driverName = driverName
|
||||
a.dataSourceName = dataSourceName
|
||||
a.dbName = dbName
|
||||
|
||||
// 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.
|
||||
runtime.SetFinalizer(a, finalizer)
|
||||
|
||||
return a
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func refineDataSourceNameForPostgres(dataSourceName string) string {
|
||||
@ -192,7 +199,7 @@ func (a *Ormer) CreateDatabase() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Ormer) open() {
|
||||
func (a *Ormer) open() error {
|
||||
dataSourceName := a.dataSourceName + a.dbName
|
||||
if a.driverName != "mysql" {
|
||||
dataSourceName = a.dataSourceName
|
||||
@ -200,8 +207,9 @@ func (a *Ormer) open() {
|
||||
|
||||
engine, err := xorm.NewEngine(a.driverName, dataSourceName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if a.driverName == "postgres" {
|
||||
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
|
||||
if schema != "" {
|
||||
@ -210,6 +218,7 @@ func (a *Ormer) open() {
|
||||
}
|
||||
|
||||
a.Engine = engine
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Ormer) close() {
|
||||
@ -316,6 +325,11 @@ func (a *Ormer) createTable() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(RadiusAccounting))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(PermissionRule))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -54,7 +54,7 @@ type Payment struct {
|
||||
// Order Info
|
||||
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
|
||||
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"`
|
||||
Message string `xorm:"varchar(2000)" json:"message"`
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -112,11 +113,15 @@ func GetPermission(id string) (*Permission, error) {
|
||||
|
||||
// checkPermissionValid verifies if the permission is valid
|
||||
func checkPermissionValid(permission *Permission) error {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enforcer.EnableAutoSave(false)
|
||||
|
||||
policies := getPolicies(permission)
|
||||
_, err := enforcer.AddPolicies(policies)
|
||||
_, err = enforcer.AddPolicies(policies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -128,7 +133,7 @@ func checkPermissionValid(permission *Permission) error {
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -149,14 +154,40 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
removeGroupingPolicies(oldPermission)
|
||||
removePolicies(oldPermission)
|
||||
err = removeGroupingPolicies(oldPermission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = removePolicies(oldPermission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
||||
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
||||
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
|
||||
@ -180,59 +219,78 @@ func AddPermission(permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
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
|
||||
}
|
||||
|
||||
func AddPermissions(permissions []*Permission) bool {
|
||||
func AddPermissions(permissions []*Permission) (bool, error) {
|
||||
if len(permissions) == 0 {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
affected, err := ormer.Engine.Insert(permissions)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "Duplicate entry") {
|
||||
panic(err)
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
// add using for loop
|
||||
if affected != 0 {
|
||||
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
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func AddPermissionsInBatch(permissions []*Permission) bool {
|
||||
func AddPermissionsInBatch(permissions []*Permission) (bool, error) {
|
||||
batchSize := conf.GetConfigBatchSize()
|
||||
|
||||
if len(permissions) == 0 {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
affected := false
|
||||
for i := 0; i < (len(permissions)-1)/batchSize+1; i++ {
|
||||
start := i * batchSize
|
||||
end := (i + 1) * batchSize
|
||||
for i := 0; i < len(permissions); i += batchSize {
|
||||
start := i
|
||||
end := i + batchSize
|
||||
if end > len(permissions) {
|
||||
end = len(permissions)
|
||||
}
|
||||
|
||||
tmp := permissions[start:end]
|
||||
// TODO: save to log instead of standard output
|
||||
// fmt.Printf("Add Permissions: [%d - %d].\n", start, end)
|
||||
if AddPermissions(tmp) {
|
||||
fmt.Printf("The syncer adds permissions: [%d - %d]\n", start, end)
|
||||
|
||||
b, err := AddPermissions(tmp)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if b {
|
||||
affected = true
|
||||
}
|
||||
}
|
||||
|
||||
return affected
|
||||
return affected, nil
|
||||
}
|
||||
|
||||
func DeletePermission(permission *Permission) (bool, error) {
|
||||
@ -242,8 +300,16 @@ func DeletePermission(permission *Permission) (bool, error) {
|
||||
}
|
||||
|
||||
if affected != 0 {
|
||||
removeGroupingPolicies(permission)
|
||||
removePolicies(permission)
|
||||
err = removeGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = removePolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
||||
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
||||
if isEmpty {
|
||||
@ -258,9 +324,59 @@ func DeletePermission(permission *Permission) (bool, error) {
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) {
|
||||
func getPermissionsByUser(userId string) ([]*Permission, error) {
|
||||
permissions := []*Permission{}
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -277,14 +393,13 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
|
||||
|
||||
permFromRoles := []*Permission{}
|
||||
|
||||
roles, err := GetRolesByUser(userId)
|
||||
roles, err := getRolesByUser(userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
perms := []*Permission{}
|
||||
err := ormer.Engine.Where("roles like ?", "%"+role.GetId()+"\"%").Find(&perms)
|
||||
perms, err := GetPermissionsByRole(role.GetId())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -302,26 +417,6 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
|
||||
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) {
|
||||
permissions := []*Permission{}
|
||||
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 {
|
||||
targetOrg, _ := util.GetOwnerAndNameFromId(name)
|
||||
targetOrg, targetName := util.GetOwnerAndNameFromId(name)
|
||||
for _, user := range p.Users {
|
||||
userOrg, userName := util.GetOwnerAndNameFromId(user)
|
||||
if userOrg == targetOrg && userName == "*" {
|
||||
if userOrg == targetOrg && (userName == "*" || userName == targetName) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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 {
|
||||
for _, resource := range p.Resources {
|
||||
if name == resource {
|
||||
if resource == "*" || resource == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -26,23 +26,23 @@ import (
|
||||
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.
|
||||
// If you specify an adapter, it will load all policies, which is a
|
||||
// heavy process that can slow down the application.
|
||||
enforcer, err := casbin.NewEnforcer(&log.DefaultLogger{}, false)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.setEnforcerModel(enforcer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = p.setEnforcerAdapter(enforcer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policyFilterV5 := []string{p.GetId()}
|
||||
@ -60,10 +60,10 @@ func getPermissionEnforcer(p *Permission, permissionIDs ...string) *casbin.Enfor
|
||||
|
||||
err = enforcer.LoadFilteredPolicy(policyFilter)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return enforcer
|
||||
return enforcer, nil
|
||||
}
|
||||
|
||||
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
|
||||
@ -201,72 +201,96 @@ func getGroupingPolicies(permission *Permission) [][]string {
|
||||
return groupingPolicies
|
||||
}
|
||||
|
||||
func addPolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
func addPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policies := getPolicies(permission)
|
||||
|
||||
_, err := enforcer.AddPolicies(policies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, err = enforcer.AddPolicies(policies)
|
||||
return err
|
||||
}
|
||||
|
||||
func addGroupingPolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
func removePolicies(permission *Permission) error {
|
||||
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)
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err := enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
_, err = enforcer.AddGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeGroupingPolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
func removeGroupingPolicies(permission *Permission) error {
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
groupingPolicies := getGroupingPolicies(permission)
|
||||
|
||||
if len(groupingPolicies) > 0 {
|
||||
_, err := enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
_, err = enforcer.RemoveGroupingPolicies(groupingPolicies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removePolicies(permission *Permission) {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
policies := getPolicies(permission)
|
||||
|
||||
_, err := enforcer.RemovePolicies(policies)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CasbinRequest = []interface{}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
|
||||
permissions, _, err := GetPermissionsAndRolesByUser(userId)
|
||||
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([]string, error) {
|
||||
permissions, _, err := getPermissionsAndRolesByUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range GetAllRoles(userId) {
|
||||
permissionsByRole, err := GetPermissionsByRole(role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
permissions = append(permissions, permissionsByRole...)
|
||||
@ -274,26 +298,31 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []
|
||||
|
||||
var values []string
|
||||
for _, permission := range permissions {
|
||||
enforcer := getPermissionEnforcer(permission)
|
||||
enforcer, err := getPermissionEnforcer(permission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 enforcer.GetAllObjects()
|
||||
})
|
||||
}
|
||||
|
||||
func GetAllActions(userId string) []string {
|
||||
func GetAllActions(userId string) ([]string, error) {
|
||||
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
|
||||
return enforcer.GetAllActions()
|
||||
})
|
||||
}
|
||||
|
||||
func GetAllRoles(userId string) []string {
|
||||
roles, err := GetRolesByUser(userId)
|
||||
roles, err := getRolesByUser(userId)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -330,17 +359,23 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
|
||||
|
||||
// load [policy_definition]
|
||||
policyDefinition := strings.Split(cfg.String("policy_definition::p"), ",")
|
||||
|
||||
fieldsNum := len(policyDefinition)
|
||||
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"
|
||||
for i := builtInAvailableField - fieldsNum; i > 0; i-- {
|
||||
policyDefinition = append(policyDefinition, "")
|
||||
}
|
||||
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, ","))
|
||||
|
||||
return m, err
|
||||
|
@ -82,5 +82,11 @@ func UploadPermissions(owner string, path string) (bool, error) {
|
||||
if len(newPermissions) == 0 {
|
||||
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"`
|
||||
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
|
||||
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"`
|
||||
CustomAuthUrl string `xorm:"varchar(200)" json:"customAuthUrl"`
|
||||
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 {
|
||||
providerInfo := &idp.ProviderInfo{
|
||||
Type: provider.Type,
|
||||
SubType: provider.SubType,
|
||||
ClientId: provider.ClientId,
|
||||
ClientSecret: provider.ClientSecret,
|
||||
AppId: provider.AppId,
|
||||
HostUrl: provider.Host,
|
||||
TokenURL: provider.CustomTokenUrl,
|
||||
AuthURL: provider.CustomAuthUrl,
|
||||
UserInfoURL: provider.CustomUserInfoUrl,
|
||||
UserMapping: provider.UserMapping,
|
||||
Type: provider.Type,
|
||||
SubType: provider.SubType,
|
||||
ClientId: provider.ClientId,
|
||||
ClientSecret: provider.ClientSecret,
|
||||
ClientId2: provider.ClientId2,
|
||||
ClientSecret2: provider.ClientSecret2,
|
||||
AppId: provider.AppId,
|
||||
HostUrl: provider.Host,
|
||||
TokenURL: provider.CustomTokenUrl,
|
||||
AuthURL: provider.CustomAuthUrl,
|
||||
UserInfoURL: provider.CustomUserInfoUrl,
|
||||
UserMapping: provider.UserMapping,
|
||||
}
|
||||
|
||||
if provider.Type == "WeChat" {
|
||||
@ -415,6 +417,8 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
|
||||
providerInfo.ClientId = provider.ClientId2
|
||||
providerInfo.ClientSecret = provider.ClientSecret2
|
||||
}
|
||||
} else if provider.Type == "AzureAD" || provider.Type == "ADFS" || provider.Type == "Okta" {
|
||||
providerInfo.HostUrl = provider.Domain
|
||||
}
|
||||
|
||||
return providerInfo
|
||||
|
@ -18,13 +18,13 @@ type ProviderItem struct {
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
|
||||
CanSignUp bool `json:"canSignUp"`
|
||||
CanSignIn bool `json:"canSignIn"`
|
||||
CanUnlink bool `json:"canUnlink"`
|
||||
Prompted bool `json:"prompted"`
|
||||
AlertType string `json:"alertType"`
|
||||
Rule string `json:"rule"`
|
||||
Provider *Provider `json:"provider"`
|
||||
CanSignUp bool `json:"canSignUp"`
|
||||
CanSignIn bool `json:"canSignIn"`
|
||||
CanUnlink bool `json:"canUnlink"`
|
||||
Prompted bool `json:"prompted"`
|
||||
SignupGroup string `json:"signupGroup"`
|
||||
Rule string `json:"rule"`
|
||||
Provider *Provider `json:"provider"`
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
fmt.Printf("AddRecord() error: %s", err.Error())
|
||||
}
|
||||
|
||||
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 {
|
||||
webhooks, err := getWebhooksByOrganization(record.Organization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
errs := []error{}
|
||||
webhooks = getFilteredWebhooks(webhooks, record.Action)
|
||||
for _, webhook := range webhooks {
|
||||
if !webhook.IsEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
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)
|
||||
var user *User
|
||||
if webhook.IsUserExtended {
|
||||
user, err = getUser(record.Organization, record.User)
|
||||
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
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ type Role struct {
|
||||
Description string `xorm:"varchar(100)" json:"description"`
|
||||
|
||||
Users []string `xorm:"mediumtext" json:"users"`
|
||||
Groups []string `xorm:"mediumtext" json:"groups"`
|
||||
Roles []string `xorm:"mediumtext" json:"roles"`
|
||||
Domains []string `xorm:"mediumtext" json:"domains"`
|
||||
IsEnabled bool `json:"isEnabled"`
|
||||
@ -150,8 +151,16 @@ func UpdateRole(id string, role *Role) (bool, error) {
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
addGroupingPolicies(permission)
|
||||
addPolicies(permission)
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = addPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
visited[permission.GetId()] = struct{}{}
|
||||
}
|
||||
|
||||
@ -165,10 +174,15 @@ func UpdateRole(id string, role *Role) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, permission := range permissions {
|
||||
permissionId := permission.GetId()
|
||||
if _, ok := visited[permissionId]; !ok {
|
||||
addGroupingPolicies(permission)
|
||||
err = addGroupingPolicies(permission)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
visited[permissionId] = struct{}{}
|
||||
}
|
||||
}
|
||||
@ -207,16 +221,15 @@ func AddRolesInBatch(roles []*Role) bool {
|
||||
}
|
||||
|
||||
affected := false
|
||||
for i := 0; i < (len(roles)-1)/batchSize+1; i++ {
|
||||
start := i * batchSize
|
||||
end := (i + 1) * batchSize
|
||||
for i := 0; i < len(roles); i += batchSize {
|
||||
start := i
|
||||
end := i + batchSize
|
||||
if end > len(roles) {
|
||||
end = len(roles)
|
||||
}
|
||||
|
||||
tmp := roles[start:end]
|
||||
// TODO: save to log instead of standard output
|
||||
// fmt.Printf("Add users: [%d - %d].\n", start, end)
|
||||
fmt.Printf("The syncer adds roles: [%d - %d]\n", start, end)
|
||||
if AddRoles(tmp) {
|
||||
affected = true
|
||||
}
|
||||
@ -252,15 +265,40 @@ func (role *Role) GetId() string {
|
||||
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
|
||||
}
|
||||
|
||||
func GetRolesByUser(userId string) ([]*Role, error) {
|
||||
func getRolesByUserInternal(userId string) ([]*Role, error) {
|
||||
roles := []*Role{}
|
||||
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&roles)
|
||||
user, err := GetUser(userId)
|
||||
if err != nil {
|
||||
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 {
|
||||
allRolesIds = append(allRolesIds, role.GetId())
|
||||
}
|
||||
@ -336,16 +374,6 @@ func GetMaskedRoles(roles []*Role) []*Role {
|
||||
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
|
||||
func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
|
||||
var (
|
||||
|
@ -68,5 +68,6 @@ func UploadRoles(owner string, path string) (bool, error) {
|
||||
if len(newRoles) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return AddRolesInBatch(newRoles), nil
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ import (
|
||||
|
||||
// NewSamlResponse
|
||||
// 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{
|
||||
Space: "samlp",
|
||||
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.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.CreateAttr("Name", "Roles")
|
||||
roles.CreateAttr("NameFormat", "urn:oasis:names:tc:SAML:2.0:attrname-format:basic")
|
||||
@ -184,10 +191,11 @@ type SingleSignOnService struct {
|
||||
|
||||
type Attribute struct {
|
||||
XMLName xml.Name
|
||||
Name string `xml:"Name,attr"`
|
||||
NameFormat string `xml:"NameFormat,attr"`
|
||||
FriendlyName string `xml:"FriendlyName,attr"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Name string `xml:"Name,attr"`
|
||||
NameFormat string `xml:"NameFormat,attr"`
|
||||
FriendlyName string `xml:"FriendlyName,attr"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Values []string `xml:"AttributeValue"`
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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))
|
||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||
|
||||
@ -288,6 +300,10 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
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))
|
||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||
|
||||
@ -301,13 +317,18 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
|
||||
_, originBackend := getOriginFromHost(host)
|
||||
// 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{
|
||||
PrivateKey: cert.PrivateKey,
|
||||
X509Certificate: certificate,
|
||||
}
|
||||
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
|
||||
ctx.Hash = crypto.SHA1
|
||||
|
||||
if application.EnableSamlC14n10 {
|
||||
ctx.Canonicalizer = dsig.MakeC14N10ExclusiveCanonicalizerWithPrefixList("")
|
||||
}
|
||||
|
||||
//signedXML, err := ctx.SignEnvelopedLimix(samlResponse)
|
||||
//if err != nil {
|
||||
// return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
|
@ -23,23 +23,49 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/idp"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
saml2 "github.com/russellhaering/gosaml2"
|
||||
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)
|
||||
sp, err := buildSp(provider, samlResponse, host)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
assertionInfo, err := sp.RetrieveAssertionInfo(samlResponse)
|
||||
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) {
|
||||
@ -146,14 +172,24 @@ func getCertificateFromSamlResponse(samlResponse string, providerType string) (s
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deStr := strings.Replace(string(de), "\n", "", -1)
|
||||
tagMap := map[string]string{
|
||||
"Aliyun IDaaS": "ds",
|
||||
"Keycloak": "dsig",
|
||||
}
|
||||
var (
|
||||
expression string
|
||||
deStr = strings.Replace(string(de), "\n", "", -1)
|
||||
tagMap = map[string]string{
|
||||
"Aliyun IDaaS": "ds",
|
||||
"Keycloak": "dsig",
|
||||
}
|
||||
)
|
||||
tag := tagMap[providerType]
|
||||
expression := fmt.Sprintf("<%s:X509Certificate>([\\s\\S]*?)</%s:X509Certificate>", tag, tag)
|
||||
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)
|
||||
return res[1], nil
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func GetTruncatedPath(provider *Provider, fullFilePath string, limit int) string
|
||||
}
|
||||
|
||||
func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
|
||||
escapedPath := util.UrlJoin(provider.PathPrefix, escapePath(fullFilePath))
|
||||
escapedPath := util.UrlJoin(provider.PathPrefix, fullFilePath)
|
||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
|
||||
|
||||
host := ""
|
||||
|
@ -230,28 +230,39 @@ func (syncer *Syncer) getTable() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (syncer *Syncer) getKey() string {
|
||||
key := "id"
|
||||
hasKey := false
|
||||
hasId := false
|
||||
func (syncer *Syncer) getKeyColumn() *TableColumn {
|
||||
var column *TableColumn
|
||||
for _, tableColumn := range syncer.TableColumns {
|
||||
if tableColumn.IsKey {
|
||||
hasKey = true
|
||||
key = tableColumn.Name
|
||||
}
|
||||
if tableColumn.Name == "id" {
|
||||
hasId = true
|
||||
column = tableColumn
|
||||
}
|
||||
}
|
||||
|
||||
if !hasKey && !hasId {
|
||||
key = syncer.TableColumns[0].Name
|
||||
if column == nil {
|
||||
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 {
|
||||
syncer.initAdapter()
|
||||
err := syncer.initAdapter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return syncer.syncUsers()
|
||||
}
|
||||
|
@ -50,9 +50,12 @@ func addSyncerJob(syncer *Syncer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
syncer.initAdapter()
|
||||
err := syncer.initAdapter()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := syncer.syncUsers()
|
||||
err = syncer.syncUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -38,7 +38,11 @@ func getEnabledSyncerForOrganization(organization string) (*Syncer, error) {
|
||||
|
||||
for _, syncer := range syncers {
|
||||
if syncer.Organization == organization && syncer.IsEnabled {
|
||||
syncer.initAdapter()
|
||||
err = syncer.initAdapter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return syncer, nil
|
||||
}
|
||||
}
|
||||
@ -55,6 +59,10 @@ func AddUserToOriginalDatabase(user *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if syncer.IsReadOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
updatedOUser := syncer.createOriginalUserFromUser(user)
|
||||
_, err = syncer.addUser(updatedOUser)
|
||||
if err != nil {
|
||||
@ -74,6 +82,10 @@ func UpdateUserToOriginalDatabase(user *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if syncer.IsReadOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
newUser, err := GetUser(user.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -16,7 +16,8 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func (syncer *Syncer) syncUsers() error {
|
||||
@ -26,17 +27,26 @@ func (syncer *Syncer) syncUsers() error {
|
||||
|
||||
fmt.Printf("Running syncUsers()..\n")
|
||||
|
||||
users, _, _ := syncer.getUserMap()
|
||||
oUsers, _, err := syncer.getOriginalUserMap()
|
||||
users, err := GetUsers(syncer.Organization)
|
||||
if err != nil {
|
||||
fmt.Printf(err.Error())
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error())
|
||||
_, err = updateSyncerErrorText(syncer, line)
|
||||
if err != nil {
|
||||
return err
|
||||
line := fmt.Sprintf("[%s] %s\n", util.GetCurrentTime(), err.Error())
|
||||
_, err2 := updateSyncerErrorText(syncer, line)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
|
||||
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))
|
||||
@ -76,7 +86,7 @@ func (syncer *Syncer) syncUsers() error {
|
||||
updatedUser.PreHash = oHash
|
||||
|
||||
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
|
||||
_, err = syncer.updateUserForOriginalByFields(updatedUser, key)
|
||||
_, err = syncer.updateUserForOriginalFields(updatedUser, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -113,7 +123,7 @@ func (syncer *Syncer) syncUsers() error {
|
||||
updatedUser.PreHash = oHash
|
||||
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -122,6 +132,7 @@ func (syncer *Syncer) syncUsers() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = AddUsersInBatch(newUsers)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/util"
|
||||
"github.com/xorm-io/core"
|
||||
)
|
||||
|
||||
type OriginalUser = User
|
||||
@ -50,19 +49,6 @@ func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
|
||||
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) {
|
||||
m := syncer.getMapFromOriginalUser(user)
|
||||
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]
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
return affected != 0, nil
|
||||
}
|
||||
|
||||
func (syncer *Syncer) updateUserForOriginalFields(user *User) (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) {
|
||||
func (syncer *Syncer) updateUserForOriginalFields(user *User, key string) (bool, error) {
|
||||
var err error
|
||||
oldUser := User{}
|
||||
|
||||
@ -162,27 +124,31 @@ func (syncer *Syncer) calculateHash(user *OriginalUser) string {
|
||||
return util.GetMd5Hash(s)
|
||||
}
|
||||
|
||||
func (syncer *Syncer) initAdapter() {
|
||||
if syncer.Ormer == nil {
|
||||
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.")
|
||||
}
|
||||
|
||||
syncer.Ormer = NewAdapter(syncer.DatabaseType, dataSourceName, syncer.Database)
|
||||
func (syncer *Syncer) initAdapter() error {
|
||||
if syncer.Ormer != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -192,7 +158,10 @@ func RunSyncUsersJob() {
|
||||
}
|
||||
|
||||
for _, syncer := range syncers {
|
||||
addSyncerJob(syncer)
|
||||
err = addSyncerJob(syncer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(time.Duration(1<<63 - 1))
|
||||
|
@ -195,6 +195,9 @@ func GenerateCasToken(userId string, service string) (string, error) {
|
||||
|
||||
user, _ = GetMaskedUser(user, false)
|
||||
|
||||
user.WebauthnCredentials = nil
|
||||
user.Properties = nil
|
||||
|
||||
authenticationSuccess := CasAuthenticationSuccess{
|
||||
User: user.Name,
|
||||
Attributes: &CasAttributes{
|
||||
@ -216,10 +219,15 @@ func GenerateCasToken(userId string, service string) (string, error) {
|
||||
}
|
||||
|
||||
for k, v := range tmp {
|
||||
if v != "" {
|
||||
value := fmt.Sprintf("%v", v)
|
||||
if value == "<nil>" || value == "[]" || value == "map[]" {
|
||||
value = ""
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
|
||||
Name: k,
|
||||
Value: fmt.Sprintf("%v", v),
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -281,6 +289,10 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
|
||||
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))
|
||||
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
|
||||
randomKeyStore := &X509Key{
|
||||
|
@ -26,7 +26,7 @@ type Claims struct {
|
||||
*User
|
||||
TokenType string `json:"tokenType,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Tag string `json:"tag"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
@ -37,56 +37,90 @@ type UserShort struct {
|
||||
}
|
||||
|
||||
type UserWithoutThirdIdp struct {
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
Id string `xorm:"varchar(100) index" json:"id"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(100) index" json:"phone"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
||||
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Language string `xorm:"varchar(100)" json:"language"`
|
||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||
Education string `xorm:"varchar(100)" json:"education"`
|
||||
Score int `json:"score"`
|
||||
Karma int `json:"karma"`
|
||||
Ranking int `json:"ranking"`
|
||||
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
||||
IsOnline bool `json:"isOnline"`
|
||||
IsAdmin bool `json:"isAdmin"`
|
||||
IsForbidden bool `json:"isForbidden"`
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
||||
Hash string `xorm:"varchar(100)" json:"hash"`
|
||||
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
||||
CreatedIp string `xorm:"varchar(100)" json:"createdIp"`
|
||||
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
|
||||
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
Roles []*Role `xorm:"-" json:"roles"`
|
||||
Permissions []*Permission `xorm:"-" json:"permissions"`
|
||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100) index" json:"createdTime"`
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
|
||||
Id string `xorm:"varchar(100) index" json:"id"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
FirstName string `xorm:"varchar(100)" json:"firstName"`
|
||||
LastName string `xorm:"varchar(100)" json:"lastName"`
|
||||
Avatar string `xorm:"varchar(500)" json:"avatar"`
|
||||
AvatarType string `xorm:"varchar(100)" json:"avatarType"`
|
||||
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100) index" json:"email"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
Phone string `xorm:"varchar(20) index" json:"phone"`
|
||||
CountryCode string `xorm:"varchar(6)" json:"countryCode"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
IdCardType string `xorm:"varchar(100)" json:"idCardType"`
|
||||
IdCard string `xorm:"varchar(100) index" json:"idCard"`
|
||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Language string `xorm:"varchar(100)" json:"language"`
|
||||
Gender string `xorm:"varchar(100)" json:"gender"`
|
||||
Birthday string `xorm:"varchar(100)" json:"birthday"`
|
||||
Education string `xorm:"varchar(100)" json:"education"`
|
||||
Score int `json:"score"`
|
||||
Karma int `json:"karma"`
|
||||
Ranking int `json:"ranking"`
|
||||
IsDefaultAvatar bool `json:"isDefaultAvatar"`
|
||||
IsOnline bool `json:"isOnline"`
|
||||
IsAdmin bool `json:"isAdmin"`
|
||||
IsForbidden bool `json:"isForbidden"`
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
SignupApplication string `xorm:"varchar(100)" json:"signupApplication"`
|
||||
Hash string `xorm:"varchar(100)" json:"hash"`
|
||||
PreHash string `xorm:"varchar(100)" json:"preHash"`
|
||||
AccessKey string `xorm:"varchar(100)" json:"accessKey"`
|
||||
AccessSecret string `xorm:"varchar(100)" json:"accessSecret"`
|
||||
|
||||
GitHub string `xorm:"github varchar(100)" json:"github"`
|
||||
Google string `xorm:"varchar(100)" json:"google"`
|
||||
QQ string `xorm:"qq varchar(100)" json:"qq"`
|
||||
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
|
||||
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
|
||||
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
|
||||
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
|
||||
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
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 {
|
||||
@ -101,7 +135,7 @@ type ClaimsWithoutThirdIdp struct {
|
||||
*UserWithoutThirdIdp
|
||||
TokenType string `json:"tokenType,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Tag string `json:"tag"`
|
||||
Scope string `json:"scope,omitempty"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
@ -125,14 +159,18 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
||||
Type: user.Type,
|
||||
Password: user.Password,
|
||||
PasswordSalt: user.PasswordSalt,
|
||||
PasswordType: user.PasswordType,
|
||||
DisplayName: user.DisplayName,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Avatar: user.Avatar,
|
||||
AvatarType: user.AvatarType,
|
||||
PermanentAvatar: user.PermanentAvatar,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Phone: user.Phone,
|
||||
CountryCode: user.CountryCode,
|
||||
Region: user.Region,
|
||||
Location: user.Location,
|
||||
Address: user.Address,
|
||||
Affiliation: user.Affiliation,
|
||||
@ -142,7 +180,6 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
||||
Homepage: user.Homepage,
|
||||
Bio: user.Bio,
|
||||
Tag: user.Tag,
|
||||
Region: user.Region,
|
||||
Language: user.Language,
|
||||
Gender: user.Gender,
|
||||
Birthday: user.Birthday,
|
||||
@ -158,16 +195,38 @@ func getUserWithoutThirdIdp(user *User) *UserWithoutThirdIdp {
|
||||
SignupApplication: user.SignupApplication,
|
||||
Hash: user.Hash,
|
||||
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,
|
||||
LastSigninTime: user.LastSigninTime,
|
||||
LastSigninIp: user.LastSigninIp,
|
||||
|
||||
PreferredMfaType: user.PreferredMfaType,
|
||||
RecoveryCodes: user.RecoveryCodes,
|
||||
TotpSecret: user.TotpSecret,
|
||||
MfaPhoneEnabled: user.MfaPhoneEnabled,
|
||||
MfaEmailEnabled: user.MfaEmailEnabled,
|
||||
|
||||
Ldap: user.Ldap,
|
||||
Properties: user.Properties,
|
||||
|
||||
Roles: user.Roles,
|
||||
Permissions: user.Permissions,
|
||||
Groups: user.Groups,
|
||||
|
||||
LastSigninWrongTime: user.LastSigninWrongTime,
|
||||
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"])
|
||||
}
|
||||
|
||||
if cert.Certificate == "" {
|
||||
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||
}
|
||||
|
||||
// RSA certificate
|
||||
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
||||
if err != nil {
|
||||
|
138
object/user.go
138
object/user.go
@ -16,6 +16,7 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -49,6 +50,7 @@ type User struct {
|
||||
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
|
||||
|
||||
Id string `xorm:"varchar(100) index" json:"id"`
|
||||
ExternalId string `xorm:"varchar(100) index" json:"externalId"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
@ -370,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) {
|
||||
if owner == "" || phone == "" {
|
||||
return nil, nil
|
||||
@ -388,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) {
|
||||
if owner == "" || userId == "" {
|
||||
return nil, nil
|
||||
@ -406,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) {
|
||||
if accessKey == "" {
|
||||
return nil, nil
|
||||
@ -483,7 +539,7 @@ func GetMaskedUsers(users []*User, errs ...error) ([]*User, error) {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func GetLastUser(owner string) (*User, error) {
|
||||
func getLastUser(owner string) (*User, error) {
|
||||
user := User{Owner: owner}
|
||||
existed, err := ormer.Engine.Desc("created_time", "id").Get(&user)
|
||||
if err != nil {
|
||||
@ -505,7 +561,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
return false, err
|
||||
}
|
||||
if oldUser == nil {
|
||||
return false, nil
|
||||
return false, fmt.Errorf("the user: %s is not found", id)
|
||||
}
|
||||
|
||||
if name != user.Name {
|
||||
@ -528,7 +584,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
|
||||
if len(columns) == 0 {
|
||||
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",
|
||||
"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",
|
||||
@ -545,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, "updated_time")
|
||||
user.UpdatedTime = util.GetCurrentTime()
|
||||
|
||||
if util.ContainsString(columns, "groups") {
|
||||
_, err := userEnforcer.UpdateGroupsForUser(user.GetId(), user.Groups)
|
||||
if err != nil {
|
||||
@ -583,7 +642,7 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
||||
}
|
||||
|
||||
if oldUser == nil {
|
||||
return false, nil
|
||||
return false, fmt.Errorf("the user: %s is not found", id)
|
||||
}
|
||||
|
||||
if name != user.Name {
|
||||
@ -614,18 +673,34 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
|
||||
}
|
||||
|
||||
func AddUser(user *User) (bool, error) {
|
||||
var err error
|
||||
if user.Id == "" {
|
||||
user.Id = util.GenerateId()
|
||||
application, err := GetApplicationByUser(user)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
id, err := GenerateIdForNewUser(application)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
user.Id = id
|
||||
}
|
||||
|
||||
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 {
|
||||
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" {
|
||||
@ -666,9 +741,8 @@ func AddUser(user *User) (bool, error) {
|
||||
}
|
||||
|
||||
func AddUsers(users []*User) (bool, error) {
|
||||
var err error
|
||||
if len(users) == 0 {
|
||||
return false, nil
|
||||
return false, fmt.Errorf("no users are provided")
|
||||
}
|
||||
|
||||
// organization := GetOrganizationByUser(users[0])
|
||||
@ -676,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
|
||||
// user.UpdateUserPassword(organization)
|
||||
|
||||
err = user.UpdateUserHash()
|
||||
err := user.UpdateUserHash()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -700,23 +774,22 @@ func AddUsers(users []*User) (bool, error) {
|
||||
}
|
||||
|
||||
func AddUsersInBatch(users []*User) (bool, error) {
|
||||
batchSize := conf.GetConfigBatchSize()
|
||||
|
||||
if len(users) == 0 {
|
||||
return false, nil
|
||||
return false, fmt.Errorf("no users are provided")
|
||||
}
|
||||
|
||||
batchSize := conf.GetConfigBatchSize()
|
||||
|
||||
affected := false
|
||||
for i := 0; i < (len(users)-1)/batchSize+1; i++ {
|
||||
start := i * batchSize
|
||||
end := (i + 1) * batchSize
|
||||
for i := 0; i < len(users); i += batchSize {
|
||||
start := i
|
||||
end := i + batchSize
|
||||
if end > len(users) {
|
||||
end = len(users)
|
||||
}
|
||||
|
||||
tmp := users[start:end]
|
||||
// TODO: save to log instead of standard output
|
||||
// fmt.Printf("Add users: [%d - %d].\n", start, end)
|
||||
fmt.Printf("The syncer adds users: [%d - %d]\n", start, end)
|
||||
if ok, err := AddUsers(tmp); err != nil {
|
||||
return false, err
|
||||
} else if ok {
|
||||
@ -786,7 +859,7 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
user.Permissions, user.Roles, err = GetPermissionsAndRolesByUser(user.GetId())
|
||||
user.Permissions, user.Roles, err = getPermissionsAndRolesByUser(user.GetId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -874,9 +947,9 @@ func (user *User) GetPreferredMfaProps(masked bool) *MfaProps {
|
||||
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 {
|
||||
return false, nil
|
||||
return false, fmt.Errorf("the user is not found")
|
||||
}
|
||||
|
||||
user.AccessKey = util.GenerateId()
|
||||
@ -900,3 +973,22 @@ func (user *User) IsGlobalAdmin() bool {
|
||||
|
||||
return user.Owner == "built-in"
|
||||
}
|
||||
|
||||
func GenerateIdForNewUser(application *Application) (string, error) {
|
||||
if application == nil || application.GetSignupItemRule("ID") != "Incremental" {
|
||||
return util.GenerateId(), nil
|
||||
}
|
||||
|
||||
lastUser, err := getLastUser(application.Organization)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
lastUserId := -1
|
||||
if lastUser != nil {
|
||||
lastUserId = util.ParseInt(lastUser.Id)
|
||||
}
|
||||
|
||||
res := strconv.Itoa(lastUserId + 1)
|
||||
return res, nil
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
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") {
|
||||
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
|
||||
} else {
|
||||
return nil, "", err
|
||||
|
@ -87,7 +87,7 @@ func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
|
||||
|
||||
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
|
||||
if err != nil {
|
||||
if err == errors.ERR_NAME_NOT_FOUND {
|
||||
if err == errors.ErrNameNotFound {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, err
|
||||
|
@ -144,5 +144,6 @@ func UploadUsers(owner string, path string) (bool, error) {
|
||||
if len(newUsers) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return AddUsersInBatch(newUsers)
|
||||
}
|
||||
|
@ -320,6 +320,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
if oldUser.Score != newUser.Score {
|
||||
item := GetAccountItemByName("Score", organization)
|
||||
itemsChanged = append(itemsChanged, item)
|
||||
}
|
||||
|
||||
for i := range itemsChanged {
|
||||
if pass, err := CheckAccountItemModifyRule(itemsChanged[i], isAdmin, lang); !pass {
|
||||
return pass, err
|
||||
|
@ -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 {
|
||||
if provider == nil {
|
||||
return fmt.Errorf("please set an Email provider first")
|
||||
}
|
||||
|
||||
sender := organization.DisplayName
|
||||
title := provider.Title
|
||||
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 {
|
||||
if provider == nil {
|
||||
return errors.New("please set a SMS provider first")
|
||||
}
|
||||
|
||||
if err := IsAllowSend(user, remoteAddr, provider.Category); err != nil {
|
||||
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) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
username, _ = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
}()
|
||||
|
||||
username = ctx.Input.Session("username").(string)
|
||||
|
||||
if username == "" {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
username, _ = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
@ -66,6 +66,13 @@ func getObject(ctx *context.Context) (string, string) {
|
||||
path := ctx.Request.URL.Path
|
||||
|
||||
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"
|
||||
id := ctx.Input.Query("id")
|
||||
if id != "" {
|
||||
@ -79,8 +86,14 @@ func getObject(ctx *context.Context) (string, string) {
|
||||
|
||||
return "", ""
|
||||
} 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 {
|
||||
return ctx.Request.Form.Get("owner"), ctx.Request.Form.Get("name")
|
||||
}
|
||||
@ -139,6 +152,10 @@ func getUrlPath(urlPath string) string {
|
||||
return "/cas"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(urlPath, "/scim") {
|
||||
return "/scim"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(urlPath, "/api/login/oauth") {
|
||||
return "/api/login/oauth"
|
||||
}
|
||||
|
@ -45,19 +45,21 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if token == nil {
|
||||
responseError(ctx, "Access token doesn't exist")
|
||||
responseError(ctx, "Access token doesn't exist in database")
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsTokenExpired(token.CreatedTime, token.ExpiresIn) {
|
||||
responseError(ctx, "Access token has expired")
|
||||
isExpired, expireTime := util.IsTokenExpired(token.CreatedTime, token.ExpiresIn)
|
||||
if isExpired {
|
||||
responseError(ctx, fmt.Sprintf("Access token has expired, expireTime = %s", expireTime))
|
||||
return
|
||||
}
|
||||
|
||||
userId := util.GetId(token.Organization, token.User)
|
||||
application, err := object.GetApplicationByUserId(fmt.Sprintf("app/%s", token.Application))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
responseError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
setSessionUser(ctx, userId)
|
||||
@ -66,7 +68,11 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
}
|
||||
|
||||
// "/page?clientId=123&clientSecret=456"
|
||||
userId := getUsernameByClientIdSecret(ctx)
|
||||
userId, err := getUsernameByClientIdSecret(ctx)
|
||||
if err != nil {
|
||||
responseError(ctx, err.Error())
|
||||
return
|
||||
}
|
||||
if userId != "" {
|
||||
setSessionUser(ctx, userId)
|
||||
return
|
||||
|
@ -16,6 +16,9 @@ package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
@ -33,6 +36,8 @@ type Response struct {
|
||||
}
|
||||
|
||||
func responseError(ctx *context.Context, error string, data ...interface{}) {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
|
||||
resp := Response{Status: "error", Msg: error}
|
||||
switch len(data) {
|
||||
case 2:
|
||||
@ -61,7 +66,7 @@ func denyRequest(ctx *context.Context) {
|
||||
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()
|
||||
if !ok {
|
||||
clientId = ctx.Input.Query("clientId")
|
||||
@ -69,19 +74,22 @@ func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
}
|
||||
|
||||
if clientId == "" || clientSecret == "" {
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
application, err := object.GetApplicationByClientId(clientId)
|
||||
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 {
|
||||
return ""
|
||||
if application.ClientSecret != clientSecret {
|
||||
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 {
|
||||
@ -151,3 +159,39 @@ func parseBearerToken(ctx *context.Context) string {
|
||||
|
||||
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 (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
@ -29,42 +30,61 @@ const (
|
||||
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) {
|
||||
origin := ctx.Input.Header(headerOrigin)
|
||||
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" {
|
||||
ctx.Output.Header(headerAllowOrigin, origin)
|
||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||
setCorsHeaders(ctx, origin)
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Request.RequestURI == "/api/userinfo" {
|
||||
ctx.Output.Header(headerAllowOrigin, origin)
|
||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||
setCorsHeaders(ctx, origin)
|
||||
return
|
||||
}
|
||||
|
||||
if origin != "" && originConf != "" && origin != originConf {
|
||||
ok, err := object.IsOriginAllowed(origin)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if ok {
|
||||
ctx.Output.Header(headerAllowOrigin, origin)
|
||||
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
|
||||
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
|
||||
if origin != "" {
|
||||
if origin == originConf {
|
||||
setCorsHeaders(ctx, origin)
|
||||
} else if originHostname == host {
|
||||
setCorsHeaders(ctx, origin)
|
||||
} else if isHostIntranet(host) {
|
||||
setCorsHeaders(ctx, origin)
|
||||
} else {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
ok, err := object.IsOriginAllowed(origin)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if ctx.Input.Method() == "OPTIONS" {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||
return
|
||||
if ok {
|
||||
setCorsHeaders(ctx, origin)
|
||||
} else {
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,7 @@ func initAPI() {
|
||||
beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
|
||||
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
|
||||
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
|
||||
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
|
||||
|
||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||
@ -78,7 +79,7 @@ func initAPI() {
|
||||
beego.Router("/api/get-user-count", &controllers.ApiController{}, "GET:GetUserCount")
|
||||
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
|
||||
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/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
|
||||
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
|
||||
@ -276,4 +277,6 @@ func initAPI() {
|
||||
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/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
|
||||
|
||||
beego.Router("/scim/*", &controllers.RootController{}, "*:HandleScim")
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
"github.com/casdoor/casdoor/object"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
@ -33,8 +34,63 @@ var (
|
||||
oldStaticBaseUrl = "https://cdn.casbin.org"
|
||||
newStaticBaseUrl = conf.GetConfigString("staticBaseUrl")
|
||||
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) {
|
||||
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")) {
|
||||
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 == "/" {
|
||||
path += "/index.html"
|
||||
} else {
|
||||
@ -57,7 +130,7 @@ func StaticFilter(ctx *context.Context) {
|
||||
}
|
||||
|
||||
if !util.FileExist(path) {
|
||||
path = "web/build/index.html"
|
||||
path = webBuildFolder + "/index.html"
|
||||
}
|
||||
if !util.FileExist(path) {
|
||||
dir, err := os.Getwd()
|
||||
@ -65,6 +138,7 @@ func StaticFilter(ctx *context.Context) {
|
||||
panic(err)
|
||||
}
|
||||
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)
|
||||
http.ServeContent(ctx.ResponseWriter, ctx.Request, "Casdoor frontend has encountered error...", time.Now(), strings.NewReader(errorText))
|
||||
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 {
|
||||
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) {
|
||||
return nil
|
||||
}
|
||||
@ -87,11 +92,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
||||
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||
updateSql, args, err := getUpdateSql(e.Table.Schema, e.Table.Name, columnNames, newColumnValue, pkColumnNames, pkColumnValue)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := db.engine.DB().Exec(updateSql, args...)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
log.Info(updateSql, args, res)
|
||||
@ -113,11 +120,13 @@ func (db *Database) OnRow(e *canal.RowsEvent) error {
|
||||
pkColumnValue := getPkColumnValues(oldColumnValue, e.Table.PKColumns)
|
||||
deleteSql, args, err := getDeleteSql(e.Table.Schema, e.Table.Name, pkColumnNames, pkColumnValue)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := db.engine.DB().Exec(deleteSql, args...)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := db.engine.DB().Exec(insertSql, args...)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
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
|
||||
|
||||
// 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)
|
||||
|
||||
// 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.Wait()
|
||||
|
@ -24,7 +24,10 @@ import (
|
||||
)
|
||||
|
||||
func TestStartSyncJob(t *testing.T) {
|
||||
db1 := newDatabase("127.0.0.1", 3306, "casdoor", "root", "123456")
|
||||
db2 := newDatabase("127.0.0.1", 3306, "casdoor2", "root", "123456")
|
||||
startSyncJob(db1, db2)
|
||||
db1 := newDatabase("localhost", 3306, "casdoor", "root", "123456")
|
||||
db2 := newDatabase("localhost", 3306, "casdoor2", "root", "123456")
|
||||
err := startSyncJob(db1, db2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
16
sync/util.go
16
sync/util.go
@ -15,9 +15,7 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/xorm-io/xorm"
|
||||
@ -74,21 +72,23 @@ func createEngine(dataSourceName string) (*xorm.Engine, 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 {
|
||||
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) {
|
||||
res, err := engin.QueryString("show variables like 'server_uuid'")
|
||||
record, err := engin.QueryString("show variables like 'server_uuid'")
|
||||
if err != nil {
|
||||
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 {
|
||||
|
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 {
|
||||
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 true
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user