mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-14 16:13:24 +08:00
Compare commits
92 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
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
|
||||
|
@ -81,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, *, *
|
||||
@ -126,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)
|
||||
|
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
|
||||
|
@ -477,11 +477,10 @@ func (c *ApiController) Login() {
|
||||
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
|
||||
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
|
||||
@ -524,7 +523,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
|
||||
@ -615,11 +615,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,
|
||||
@ -646,6 +651,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
|
||||
@ -674,6 +688,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}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
package controllers
|
||||
|
||||
func (syncer *Syncer) getUsers() []*User {
|
||||
users, err := GetUsers(syncer.Organization)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
import (
|
||||
"strings"
|
||||
|
||||
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
|
||||
"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,47 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
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 +478,7 @@ func (c *ApiController) SetPassword() {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if code == "" {
|
||||
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
|
||||
if msg != "" {
|
||||
c.ResponseError(msg)
|
||||
|
@ -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=
|
||||
|
@ -85,7 +85,7 @@ func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
|
||||
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
case "GitLab":
|
||||
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
case "Adfs":
|
||||
case "ADFS":
|
||||
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
|
||||
case "Baidu":
|
||||
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
|
||||
|
@ -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": "",
|
||||
|
@ -34,7 +34,7 @@ func StartLdapServer() {
|
||||
server.Handle(routes)
|
||||
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("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) {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -415,6 +415,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" {
|
||||
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.Where("users like ?", fmt.Sprintf("%%%s%%", userId))
|
||||
for _, group := range user.Groups {
|
||||
query = query.Or("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
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -286,6 +286,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 {
|
||||
|
112
object/user.go
112
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 {
|
||||
@ -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 {
|
||||
@ -614,9 +673,18 @@ 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 == "" {
|
||||
@ -628,11 +696,15 @@ func AddUser(user *User) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if organization.DefaultPassword != "" && user.Password == "123" {
|
||||
user.Password = organization.DefaultPassword
|
||||
}
|
||||
|
||||
if user.PasswordType == "" || user.PasswordType == "plain" {
|
||||
user.UpdateUserPassword(organization)
|
||||
}
|
||||
|
||||
err = user.UpdateUserHash()
|
||||
err := user.UpdateUserHash()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -707,16 +779,15 @@ func AddUsersInBatch(users []*User) (bool, error) {
|
||||
}
|
||||
|
||||
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 +857,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
|
||||
}
|
||||
@ -900,3 +971,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") || strings.Contains(err.Error(), "did not properly respond after a period of time") {
|
||||
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") || strings.Contains(err.Error(), "unrecognized name") {
|
||||
return nil, "", nil
|
||||
} 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)
|
||||
}
|
||||
|
@ -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,56 @@ 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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,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,59 @@ 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)
|
||||
}
|
||||
|
||||
res := fmt.Sprintf("%s?code=%s&state=%s", redirectUri, code.Code, state)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func StaticFilter(ctx *context.Context) {
|
||||
urlPath := ctx.Request.URL.Path
|
||||
|
||||
@ -48,8 +100,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 +126,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 +134,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 {
|
||||
|
@ -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
|
||||
|
@ -60,3 +60,19 @@ func ReturnAnyNotEmpty(strs ...string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func HaveIntersection(arr1 []string, arr2 []string) bool {
|
||||
elements := make(map[string]bool)
|
||||
|
||||
for _, str := range arr1 {
|
||||
elements[str] = true
|
||||
}
|
||||
|
||||
for _, str := range arr2 {
|
||||
if elements[str] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
21
util/time.go
21
util/time.go
@ -43,8 +43,25 @@ func GetCurrentUnixTime() string {
|
||||
return strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
||||
|
||||
func IsTokenExpired(createdTime string, expiresIn int) bool {
|
||||
func String2Time(timestamp string) time.Time {
|
||||
if timestamp == "" {
|
||||
return time.Now()
|
||||
}
|
||||
parseTime, err := time.Parse(time.RFC3339, timestamp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return parseTime
|
||||
}
|
||||
|
||||
func Time2String(timestamp time.Time) string {
|
||||
return timestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func IsTokenExpired(createdTime string, expiresIn int) (bool, string) {
|
||||
createdTimeObj, _ := time.Parse(time.RFC3339, createdTime)
|
||||
expiresAtObj := createdTimeObj.Add(time.Duration(expiresIn) * time.Second)
|
||||
return time.Now().After(expiresAtObj)
|
||||
isExpired := time.Now().After(expiresAtObj)
|
||||
expireTime := expiresAtObj.Local().Format(time.RFC3339)
|
||||
return isExpired, expireTime
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ func Test_IsTokenExpired(t *testing.T) {
|
||||
},
|
||||
} {
|
||||
t.Run(scenario.description, func(t *testing.T) {
|
||||
result := IsTokenExpired(scenario.input.createdTime, scenario.input.expiresIn)
|
||||
result, _ := IsTokenExpired(scenario.input.createdTime, scenario.input.expiresIn)
|
||||
assert.Equal(t, scenario.expected, result, fmt.Sprintf("Expected %t, but was founded %t", scenario.expected, result))
|
||||
})
|
||||
}
|
||||
|
@ -35,6 +35,10 @@ module.exports = {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
},
|
||||
"/scim": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
|
||||
import {Button, Card, Col, Input, InputNumber, Row, Select, Switch} from "antd";
|
||||
import * as AdapterBackend from "./backend/AdapterBackend";
|
||||
import * as OrganizationBackend from "./backend/OrganizationBackend";
|
||||
import * as Setting from "./Setting";
|
||||
@ -81,56 +81,6 @@ class AdapterEditPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
renderDataSourceNameConfig() {
|
||||
if (Setting.builtInObject(this.state.adapter)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.adapter.host} onChange={e => {
|
||||
this.updateAdapterField("host", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.adapter.port} min={0} max={65535} onChange={value => {
|
||||
this.updateAdapterField("port", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.adapter.user} onChange={e => {
|
||||
this.updateAdapterField("user", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.adapter.password} onChange={e => {
|
||||
this.updateAdapterField("password", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
renderAdapter() {
|
||||
return (
|
||||
<Card size="small" title={
|
||||
@ -165,55 +115,6 @@ class AdapterEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.type} onChange={(value => {
|
||||
this.updateAdapterField("type", value);
|
||||
const adapter = this.state.adapter;
|
||||
// adapter["tableColumns"] = Setting.getAdapterTableColumns(this.state.adapter);
|
||||
this.setState({
|
||||
adapter: adapter,
|
||||
});
|
||||
})}>
|
||||
{
|
||||
["Database"]
|
||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.databaseType} onChange={(value => {this.updateAdapterField("databaseType", value);})}>
|
||||
{
|
||||
[
|
||||
{id: "mysql", name: "MySQL"},
|
||||
{id: "postgres", name: "PostgreSQL"},
|
||||
{id: "mssql", name: "SQL Server"},
|
||||
{id: "oracle", name: "Oracle"},
|
||||
{id: "sqlite3", name: "Sqlite 3"},
|
||||
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{this.state.adapter.type === "Database" ? this.renderDataSourceNameConfig() : null}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input disabled={Setting.builtInObject(this.state.adapter)} value={this.state.adapter.database} onChange={e => {
|
||||
this.updateAdapterField("database", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
|
||||
@ -225,9 +126,130 @@ class AdapterEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("adapter:Use same DB"), i18next.t("adapter:Use same DB - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch disabled={Setting.builtInObject(this.state.adapter)} checked={this.state.adapter.useSameDb || Setting.builtInObject(this.state.adapter)} onChange={checked => {
|
||||
this.updateAdapterField("useSameDb", checked);
|
||||
if (checked) {
|
||||
this.updateAdapterField("type", "");
|
||||
this.updateAdapterField("databaseType", "");
|
||||
this.updateAdapterField("host", "");
|
||||
this.updateAdapterField("port", 0);
|
||||
this.updateAdapterField("user", "");
|
||||
this.updateAdapterField("password", "");
|
||||
this.updateAdapterField("database", "");
|
||||
} else {
|
||||
this.updateAdapterField("type", "Database");
|
||||
this.updateAdapterField("databaseType", "mysql");
|
||||
this.updateAdapterField("host", "localhost");
|
||||
this.updateAdapterField("port", 3306);
|
||||
this.updateAdapterField("user", "root");
|
||||
this.updateAdapterField("password", "123456");
|
||||
this.updateAdapterField("database", "dbName");
|
||||
}
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
(this.state.adapter.useSameDb || Setting.builtInObject(this.state.adapter)) ? null : (
|
||||
<React.Fragment>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.type} onChange={(value => {
|
||||
this.updateAdapterField("type", value);
|
||||
const adapter = this.state.adapter;
|
||||
// adapter["tableColumns"] = Setting.getAdapterTableColumns(this.state.adapter);
|
||||
this.setState({
|
||||
adapter: adapter,
|
||||
});
|
||||
})}>
|
||||
{
|
||||
["Database"]
|
||||
.map((item, index) => <Option key={index} value={item}>{item}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Database type"), i18next.t("syncer:Database type - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.databaseType} onChange={(value => {this.updateAdapterField("databaseType", value);})}>
|
||||
{
|
||||
[
|
||||
{id: "mysql", name: "MySQL"},
|
||||
{id: "postgres", name: "PostgreSQL"},
|
||||
{id: "mssql", name: "SQL Server"},
|
||||
{id: "oracle", name: "Oracle"},
|
||||
{id: "sqlite3", name: "Sqlite 3"},
|
||||
].map((databaseType, index) => <Option key={index} value={databaseType.id}>{databaseType.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Host"), i18next.t("provider:Host - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.adapter.host} onChange={e => {
|
||||
this.updateAdapterField("host", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:Port"), i18next.t("provider:Port - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<InputNumber value={this.state.adapter.port} min={0} max={65535} onChange={value => {
|
||||
this.updateAdapterField("port", value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:User"), i18next.t("general:User - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.adapter.user} onChange={e => {
|
||||
this.updateAdapterField("user", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.adapter.password} onChange={e => {
|
||||
this.updateAdapterField("password", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("syncer:Database"), i18next.t("syncer:Database - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input disabled={Setting.builtInObject(this.state.adapter)} value={this.state.adapter.database} onChange={e => {
|
||||
this.updateAdapterField("database", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("provider:DB Test"), i18next.t("provider:DB Test - Tooltip"))} :
|
||||
{Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={2} >
|
||||
<Button type={"primary"} onClick={() => {
|
||||
@ -250,7 +272,7 @@ class AdapterEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitAdapterEdit(willExist) {
|
||||
submitAdapterEdit(exitAfterSave) {
|
||||
const adapter = Setting.deepCopy(this.state.adapter);
|
||||
AdapterBackend.updateAdapter(this.state.organizationName, this.state.adapterName, adapter)
|
||||
.then((res) => {
|
||||
@ -260,7 +282,7 @@ class AdapterEditPage extends React.Component {
|
||||
adapterName: this.state.adapter.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/adapters");
|
||||
} else {
|
||||
this.props.history.push(`/adapters/${this.state.organizationName}/${this.state.adapter.name}`);
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Table} from "antd";
|
||||
import {Button, Switch, Table} from "antd";
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as AdapterBackend from "./backend/AdapterBackend";
|
||||
@ -30,14 +30,8 @@ class AdapterListPage extends BaseListPage {
|
||||
owner: owner,
|
||||
name: `adapter_${randomName}`,
|
||||
createdTime: moment().format(),
|
||||
type: "Database",
|
||||
host: "localhost",
|
||||
port: 3306,
|
||||
user: "root",
|
||||
password: "123456",
|
||||
databaseType: "mysql",
|
||||
database: "dbName",
|
||||
table: "tableName",
|
||||
table: "table_name",
|
||||
useSameDb: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -118,6 +112,25 @@ class AdapterListPage extends BaseListPage {
|
||||
return Setting.getFormattedDate(text);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("syncer:Table"),
|
||||
dataIndex: "table",
|
||||
key: "table",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: i18next.t("adapter:Use same DB"),
|
||||
dataIndex: "useSameDb",
|
||||
key: "useSameDb",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:Type"),
|
||||
dataIndex: "type",
|
||||
@ -129,6 +142,13 @@ class AdapterListPage extends BaseListPage {
|
||||
{text: "Database", value: "Database"},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: i18next.t("syncer:Database type"),
|
||||
dataIndex: "databaseType",
|
||||
key: "databaseType",
|
||||
width: "120px",
|
||||
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
|
||||
},
|
||||
{
|
||||
title: i18next.t("provider:Host"),
|
||||
dataIndex: "host",
|
||||
@ -144,6 +164,12 @@ class AdapterListPage extends BaseListPage {
|
||||
width: "100px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("port"),
|
||||
render: (text, record, index) => {
|
||||
if (text === 0) {
|
||||
return "";
|
||||
}
|
||||
return text;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:User"),
|
||||
@ -161,13 +187,6 @@ class AdapterListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("password"),
|
||||
},
|
||||
{
|
||||
title: i18next.t("syncer:Database type"),
|
||||
dataIndex: "databaseType",
|
||||
key: "databaseType",
|
||||
width: "120px",
|
||||
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
|
||||
},
|
||||
{
|
||||
title: i18next.t("syncer:Database"),
|
||||
dataIndex: "database",
|
||||
@ -175,13 +194,6 @@ class AdapterListPage extends BaseListPage {
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: i18next.t("syncer:Table"),
|
||||
dataIndex: "table",
|
||||
key: "table",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: "",
|
||||
|
@ -28,12 +28,13 @@ import i18next from "i18next";
|
||||
import UrlTable from "./table/UrlTable";
|
||||
import ProviderTable from "./table/ProviderTable";
|
||||
import SignupTable from "./table/SignupTable";
|
||||
import SamlAttributeTable from "./table/SamlAttributeTable";
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import copy from "copy-to-clipboard";
|
||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/htmlmixed/htmlmixed");
|
||||
@ -104,6 +105,7 @@ class ApplicationEditPage extends React.Component {
|
||||
providers: [],
|
||||
uploading: false,
|
||||
mode: props.location.mode !== undefined ? props.location.mode : "edit",
|
||||
samlAttributes: [],
|
||||
samlMetadata: null,
|
||||
isAuthorized: true,
|
||||
};
|
||||
@ -638,6 +640,29 @@ class ApplicationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:Enable SAML C14N10"), i18next.t("application:Enable SAML C14N10 - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={1} >
|
||||
<Switch checked={this.state.application.enableSamlC14n10} onChange={checked => {
|
||||
this.updateApplicationField("enableSamlC14n10", checked);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:SAML attributes"), i18next.t("general:SAML attributes - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<SamlAttributeTable
|
||||
title={i18next.t("general:SAML attributes")}
|
||||
table={this.state.application.samlAttributes}
|
||||
application={this.state.application}
|
||||
onUpdateTable={(value) => {this.updateApplicationField("samlAttributes", value);}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("application:SAML metadata"), i18next.t("application:SAML metadata - Tooltip"))} :
|
||||
@ -1001,7 +1026,7 @@ class ApplicationEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitApplicationEdit(willExist) {
|
||||
submitApplicationEdit(exitAfterSave) {
|
||||
const application = Setting.deepCopy(this.state.application);
|
||||
application.providers = application.providers?.filter(provider => this.state.providers.map(provider => provider.name).includes(provider.name));
|
||||
|
||||
@ -1013,7 +1038,7 @@ class ApplicationEditPage extends React.Component {
|
||||
applicationName: this.state.application.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/applications");
|
||||
} else {
|
||||
this.props.history.push(`/applications/${this.state.application.organization}/${this.state.application.name}`);
|
||||
|
@ -44,7 +44,7 @@ class ApplicationListPage extends BaseListPage {
|
||||
enableCodeSignin: false,
|
||||
enableSamlCompress: false,
|
||||
providers: [
|
||||
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, alertType: "None"},
|
||||
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, signupGroup: "", rule: ""},
|
||||
],
|
||||
signupItems: [
|
||||
{name: "ID", visible: false, required: true, rule: "Random"},
|
||||
|
@ -251,7 +251,7 @@ class CertEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitCertEdit(willExist) {
|
||||
submitCertEdit(exitAfterSave) {
|
||||
const cert = Setting.deepCopy(this.state.cert);
|
||||
CertBackend.updateCert(this.state.owner, this.state.certName, cert)
|
||||
.then((res) => {
|
||||
@ -261,7 +261,7 @@ class CertEditPage extends React.Component {
|
||||
certName: this.state.cert.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/certs");
|
||||
} else {
|
||||
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
|
||||
|
@ -198,7 +198,7 @@ class EnforcerEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitEnforcerEdit(willExist) {
|
||||
submitEnforcerEdit(exitAfterSave) {
|
||||
const enforcer = Setting.deepCopy(this.state.enforcer);
|
||||
EnforcerBackend.updateEnforcer(this.state.organizationName, this.state.enforcerName, enforcer)
|
||||
.then((res) => {
|
||||
@ -208,7 +208,7 @@ class EnforcerEditPage extends React.Component {
|
||||
enforcerName: this.state.enforcer.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/enforcers");
|
||||
} else {
|
||||
this.props.history.push(`/enforcers/${this.state.enforcer.owner}/${this.state.enforcer.name}`);
|
||||
|
@ -191,7 +191,7 @@ class GroupEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitGroupEdit(willExist) {
|
||||
submitGroupEdit(exitAfterSave) {
|
||||
const group = Setting.deepCopy(this.state.group);
|
||||
group["isTopGroup"] = this.state.organizations.some((organization) => organization.name === group.parentId);
|
||||
|
||||
@ -203,7 +203,7 @@ class GroupEditPage extends React.Component {
|
||||
groupName: this.state.group.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
const groupTreeUrl = sessionStorage.getItem("groupTreeUrl");
|
||||
if (groupTreeUrl !== null) {
|
||||
sessionStorage.removeItem("groupTreeUrl");
|
||||
|
@ -230,7 +230,7 @@ class LdapEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitLdapEdit(willExist) {
|
||||
submitLdapEdit(exitAfterSave) {
|
||||
LddpBackend.updateLdap(this.state.ldap)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
@ -239,7 +239,7 @@ class LdapEditPage extends React.Component {
|
||||
organizationName: this.state.ldap.owner,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push(`/organizations/${this.state.organizationName}`);
|
||||
}
|
||||
} else {
|
||||
|
@ -165,7 +165,7 @@ class ModelEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitModelEdit(willExist) {
|
||||
submitModelEdit(exitAfterSave) {
|
||||
const model = Setting.deepCopy(this.state.model);
|
||||
ModelBackend.updateModel(this.state.organizationName, this.state.modelName, model)
|
||||
.then((res) => {
|
||||
@ -175,7 +175,7 @@ class ModelEditPage extends React.Component {
|
||||
modelName: this.state.model.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/models");
|
||||
} else {
|
||||
this.props.history.push(`/models/${this.state.model.owner}/${this.state.model.name}`);
|
||||
|
@ -313,6 +313,16 @@ class OrganizationEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("general:Default password"), i18next.t("general:Default password - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.organization.defaultPassword} onChange={e => {
|
||||
this.updateOrganizationField("defaultPassword", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :
|
||||
@ -411,7 +421,7 @@ class OrganizationEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitOrganizationEdit(willExist) {
|
||||
submitOrganizationEdit(exitAfterSave) {
|
||||
const organization = Setting.deepCopy(this.state.organization);
|
||||
organization.accountItems = organization.accountItems?.filter(accountItem => accountItem.name !== "Please select an account item");
|
||||
|
||||
@ -429,7 +439,7 @@ class OrganizationEditPage extends React.Component {
|
||||
});
|
||||
window.dispatchEvent(new Event("storageOrganizationsChanged"));
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/organizations");
|
||||
} else {
|
||||
this.props.history.push(`/organizations/${this.state.organization.name}`);
|
||||
|
@ -41,6 +41,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
tags: [],
|
||||
languages: Setting.Countries.map(item => item.key),
|
||||
masterPassword: "",
|
||||
defaultPassword: "",
|
||||
enableSoftDeletion: false,
|
||||
isProfilePublic: true,
|
||||
accountItems: [
|
||||
|
@ -438,7 +438,7 @@ class PaymentEditPage extends React.Component {
|
||||
return "";
|
||||
}
|
||||
|
||||
submitPaymentEdit(willExist) {
|
||||
submitPaymentEdit(exitAfterSave) {
|
||||
const errorText = this.checkError();
|
||||
if (errorText !== "") {
|
||||
Setting.showMessage("error", errorText);
|
||||
@ -454,7 +454,7 @@ class PaymentEditPage extends React.Component {
|
||||
paymentName: this.state.payment.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/payments");
|
||||
} else {
|
||||
this.props.history.push(`/payments/${this.state.payment.name}`);
|
||||
|
@ -277,7 +277,10 @@ class PermissionEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.users}
|
||||
onChange={(value => {this.updatePermissionField("users", value);})}
|
||||
options={this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`))}
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
...this.state.users.map((user) => Setting.getOption(`${user.owner}/${user.name}`, `${user.owner}/${user.name}`)),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -288,7 +291,10 @@ class PermissionEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.groups}
|
||||
onChange={(value => {this.updatePermissionField("groups", value);})}
|
||||
options={this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`))}
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
...this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`)),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@ -299,8 +305,11 @@ class PermissionEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select disabled={!this.hasRoleDefinition(this.state.model)} virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.permission.roles}
|
||||
onChange={(value => {this.updatePermissionField("roles", value);})}
|
||||
options={this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission) => Setting.getOption(`${permission.owner}/${permission.name}`, `${permission.owner}/${permission.name}`))
|
||||
} />
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
...this.state.roles.filter(roles => (roles.owner !== this.state.roles.owner || roles.name !== this.state.roles.name)).map((permission) => Setting.getOption(`${permission.owner}/${permission.name}`, `${permission.owner}/${permission.name}`)),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -312,8 +321,11 @@ class PermissionEditPage extends React.Component {
|
||||
onChange={(value => {
|
||||
this.updatePermissionField("domains", value);
|
||||
})}
|
||||
options={this.state.permission.domains.map((domain) => Setting.getOption(domain, domain))
|
||||
} />
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
...this.state.permission.domains.map((domain) => Setting.getOption(domain, domain)),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -340,8 +352,11 @@ class PermissionEditPage extends React.Component {
|
||||
<Col span={22} >
|
||||
<Select virtual={false} mode={(this.state.permission.resourceType === "Custom") ? "tags" : "multiple"} style={{width: "100%"}} value={this.state.permission.resources}
|
||||
onChange={(value => {this.updatePermissionField("resources", value);})}
|
||||
options={this.state.resources.map((resource) => Setting.getOption(`${resource.name}`, `${resource.name}`))
|
||||
} />
|
||||
options={[
|
||||
Setting.getOption(i18next.t("organization:All"), "*"),
|
||||
...this.state.resources.map((resource) => Setting.getOption(`${resource.name}`, `${resource.name}`)),
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
@ -444,7 +459,7 @@ class PermissionEditPage extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
submitPermissionEdit(willExist) {
|
||||
submitPermissionEdit(exitAfterSave) {
|
||||
if (this.state.permission.users.length === 0 && this.state.permission.roles.length === 0) {
|
||||
Setting.showMessage("error", "The users and roles cannot be empty at the same time");
|
||||
return;
|
||||
@ -475,7 +490,7 @@ class PermissionEditPage extends React.Component {
|
||||
permissionName: this.state.permission.name,
|
||||
});
|
||||
|
||||
if (willExist) {
|
||||
if (exitAfterSave) {
|
||||
this.props.history.push("/permissions");
|
||||
} else {
|
||||
this.props.history.push(`/permissions/${this.state.permission.owner}/${encodeURIComponent(this.state.permission.name)}`);
|
||||
|
@ -298,6 +298,13 @@ class PermissionListPage extends BaseListPage {
|
||||
filterMultiple: false,
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/users/${record.owner}/${encodeURIComponent(text)}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("permission:Approver"),
|
||||
@ -306,6 +313,13 @@ class PermissionListPage extends BaseListPage {
|
||||
filterMultiple: false,
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/users/${record.owner}/${encodeURIComponent(text)}`}>
|
||||
{text}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: i18next.t("permission:Approve time"),
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user