Compare commits

...

33 Commits

Author SHA1 Message Date
Yaodong Yu
65dcbd2236 feat: compatible different uid of LDAP server (#1860)
* feat: compatible different uid of LDAP server

* Update organization.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-19 02:34:25 +08:00
Yaodong Yu
6455734807 fix: fix incorrect LDAP sync status (#1859) 2023-05-18 22:03:53 +08:00
Trần Thanh Tịnh
2eefeaffa7 feat: enforce by using resourceId (#1855)
* feat: enforce by using resourceId

* Update permission.go

* chore: fix cilint for enforcer.go

---------

Co-authored-by: tinhtt4 <tinhtt4@vng.com.vn>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-18 16:36:03 +08:00
Yang Luo
04eaad1c80 Fix getCertByApplication() 2023-05-18 16:32:43 +08:00
Yang Luo
9f084a0799 Can update user with OAuth values 2023-05-18 15:58:41 +08:00
Yang Luo
293b9f1036 Remove languages in app.conf 2023-05-18 15:44:11 +08:00
Yang Luo
437376c472 Fix CheckAccessPermission() 2023-05-18 13:36:16 +08:00
Yang Luo
cc528c5d8c Add object to webhook 2023-05-17 23:57:14 +08:00
Yang Luo
54e2055ffb Fix Beego filter: RecordMessage 2023-05-17 23:01:59 +08:00
Yang Luo
983a30a2e0 Dingtalk now supports linking with corpMobile 2023-05-17 22:14:57 +08:00
Yang Luo
37d0157d41 Fix application.EnableSignUp bug 2023-05-17 21:56:36 +08:00
Yang Luo
d4dc236770 Fix refreshExpireInHours zero value issue 2023-05-17 20:47:59 +08:00
Yang Luo
596742d782 Show org column better for admin (shared) 2023-05-17 17:30:47 +08:00
XDTD
ce921c00cd fix: resolve the problem of cert being unable to be accessed properly (#1850)
* fix: resolve the problem of cert being unable to be accessed properly

* Update CertEditPage.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-17 17:17:58 +08:00
Yang Luo
3830e443b0 Put webhook's RecordMessage() to FinishRouter stage 2023-05-17 16:32:12 +08:00
Yaodong Yu
9092cad631 feat: support forced binding MFA after login (#1845) 2023-05-17 01:13:13 +08:00
Yang Luo
0b5ecca5c8 Support empty application in page 2023-05-16 22:17:39 +08:00
Yang Luo
3d9b305bbb Add /api/health API 2023-05-16 21:47:34 +08:00
Yang Luo
0217e359e7 Update to Go 1.19.9 and Node 16.18.0 in Dockerfile 2023-05-16 20:33:31 +08:00
Yang Luo
695a612e77 Improve passwordType in CheckPassword() 2023-05-16 20:14:05 +08:00
Alexander Egorov
645d53e2c6 feat: User should have PasswordType like Organization (#1841)
* fixes #1840: [backend] User should have PasswordType like Organization is

* Update migrator.go

* Update and rename migrator_1_314_0_PR_1838.go to migrator_1_314_0_PR_1841.go

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-16 20:11:19 +08:00
Yang Luo
73b9d73f64 Add CustomFooter to Conf.js 2023-05-15 16:49:45 +08:00
1307
c6675ee4e6 feat: AI responses support streaming (#1826)
Is an AI response that supports streaming return
2023-05-13 11:31:20 +08:00
Yang Luo
6f0b7f3f24 Support modelId arg in Enforce() API 2023-05-12 21:39:57 +08:00
Yang Luo
776a682fae Improve args of Enforce() API 2023-05-12 21:32:48 +08:00
Yang Luo
96a3db21a1 Support LDAP search by user tag 2023-05-12 13:03:43 +08:00
Yang Luo
c33d537ac1 Add formCssMobile to application 2023-05-12 12:16:03 +08:00
Yang Luo
5214d48486 Fix authorized issue of UploadResource() API 2023-05-12 01:00:06 +08:00
Yang Luo
e360b06d12 Fix termsOfUse upload in application edit page 2023-05-10 23:57:03 +08:00
Yang Luo
3c871c38df Fix message and chat owner bug 2023-05-10 22:32:32 +08:00
jakiuncle
7df043fb15 fix: fix cypress error (#1817)
* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error
2023-05-09 20:51:07 +08:00
XDTD
cb542ae46a feat: fix org admin permissions (#1822) 2023-05-09 00:06:52 +08:00
imp2002
3699177837 fix: fix URL path in MinIO storage provider(#1818) 2023-05-08 16:48:56 +08:00
126 changed files with 1189 additions and 453 deletions

View File

@@ -1,11 +1,11 @@
FROM node:16.13.0 AS FRONT
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
FROM golang:1.17.5 AS BACK
FROM golang:1.19.9 AS BACK
WORKDIR /go/src/casdoor
COPY . .
RUN ./build.sh

View File

@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"io"
"net/http"
"strings"
"time"
@@ -78,7 +79,10 @@ func QueryAnswerStream(authToken string, question string, writer io.Writer, buil
client := getProxyClientFromToken(authToken)
ctx := context.Background()
flusher, ok := writer.(http.Flusher)
if !ok {
return fmt.Errorf("writer does not implement http.Flusher")
}
// https://platform.openai.com/tokenizer
// https://github.com/pkoukk/tiktoken-go#available-encodings
promptTokens, err := getTokenSize(openai.GPT3TextDavinci003, question)
@@ -122,11 +126,13 @@ func QueryAnswerStream(authToken string, question string, writer io.Writer, buil
}
}
fmt.Printf("%s", data)
// Write the streamed data as Server-Sent Events
if _, err = fmt.Fprintf(writer, "data: %s\n\n", data); err != nil {
return err
}
flusher.Flush()
// Append the response to the strings.Builder
builder.WriteString(data)
}

View File

@@ -88,6 +88,7 @@ p, *, *, GET, /api/logout, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, GET, /api/user, *, *
p, *, *, GET, /api/health, *, *
p, *, *, POST, /api/webhook, *, *
p, *, *, GET, /api/get-webhook-event, *, *
p, *, *, GET, /api/get-captcha-status, *, *
@@ -151,7 +152,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
userId := fmt.Sprintf("%s/%s", subOwner, subName)
user := object.GetUser(userId)
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin" && subOwner == objName)) {
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
return true
}

View File

@@ -20,5 +20,4 @@ staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false
batchSize = 100
ldapServerPort = 389
languages = en,zh,es,fr,de,id,ja,ko,ru,vi
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}

View File

@@ -112,13 +112,8 @@ func GetLanguage(language string) string {
if len(language) < 2 {
return "en"
}
language = language[0:2]
if strings.Contains(GetConfigString("languages"), language) {
return language
} else {
return "en"
return language[0:2]
}
}

View File

@@ -126,7 +126,7 @@ func (c *ApiController) Signup() {
username = id
}
initScore, err := getInitScore(organization)
initScore, err := organization.GetInitScore()
if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return

View File

@@ -141,7 +141,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
c.setExpireForSession()
}
if resp.Status == "ok" && user.Owner == object.CasdoorOrganization && application.Name == object.CasdoorApplication {
if resp.Status == "ok" {
object.AddSession(&object.Session{
Owner: user.Owner,
Name: user.Name,
@@ -312,6 +312,11 @@ func (c *ApiController) Login() {
resp = c.HandleLoggedIn(application, user, &authForm)
organization := object.GetOrganizationByUser(user)
if user != nil && organization.HasRequiredMfa() && !user.IsMfaEnabled() {
resp.Msg = object.RequiredMfa
}
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
@@ -411,22 +416,29 @@ func (c *ApiController) Login() {
util.SafeGoroutine(func() { object.AddRecord(record) })
} else if provider.Category == "OAuth" {
// Sign up via OAuth
if !application.EnableSignUp {
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support"), provider.Type, userInfo.Username, userInfo.DisplayName))
return
}
if !providerItem.CanSignUp {
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up"), provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
return
}
if application.EnableLinkWithEmail {
// find user that has the same email
user = object.GetUserByField(application.Organization, "email", userInfo.Email)
if userInfo.Email != "" {
// Find existing user with Email
user = object.GetUserByField(application.Organization, "email", userInfo.Email)
}
if user == nil && userInfo.Phone != "" {
// Find existing user with phone number
user = object.GetUserByField(application.Organization, "phone", userInfo.Phone)
}
}
if user == nil || user.IsDeleted {
if !application.EnableSignUp {
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support"), provider.Type, userInfo.Username, userInfo.DisplayName))
return
}
if !providerItem.CanSignUp {
c.ResponseError(fmt.Sprintf(c.T("auth:The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up"), provider.Type, userInfo.Username, userInfo.DisplayName, provider.Type))
return
}
// Handle username conflicts
tmpUser := object.GetUser(fmt.Sprintf("%s/%s", application.Organization, userInfo.Username))
if tmpUser != nil {
@@ -442,7 +454,7 @@ func (c *ApiController) Login() {
properties := map[string]string{}
properties["no"] = strconv.Itoa(object.GetUserCount(application.Organization, "", "") + 2)
initScore, err := getInitScore(organization)
initScore, err := organization.GetInitScore()
if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return

View File

@@ -31,13 +31,14 @@ func (c *ApiController) GetCasbinAdapters() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
adapters := object.GetCasbinAdapters(owner)
adapters := object.GetCasbinAdapters(owner, organization)
c.ResponseOk(adapters)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
adapters := object.GetPaginationCasbinAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, organization, field, value)))
adapters := object.GetPaginationCasbinAdapters(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(adapters, paginator.Nums())
}
}

View File

@@ -48,6 +48,30 @@ func (c *ApiController) GetCerts() {
}
}
// GetGlobleCerts
// @Title GetGlobleCerts
// @Tag Cert API
// @Description get globle certs
// @Success 200 {array} object.Cert The Response object
// @router /get-globle-certs [get]
func (c *ApiController) GetGlobleCerts() {
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedCerts(object.GetGlobleCerts())
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalCertsCount(field, value)))
certs := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
c.ResponseOk(certs, paginator.Nums())
}
}
// GetCert
// @Title GetCert
// @Tag Cert API

View File

@@ -31,6 +31,7 @@ import (
// @router /get-chats [get]
func (c *ApiController) GetChats() {
owner := c.Input().Get("owner")
owner = "admin"
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")

View File

@@ -18,30 +18,69 @@ import (
"encoding/json"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) Enforce() {
var permissionRule object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRule)
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
resourceId := c.Input().Get("resourceId")
var request object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.Enforce(&permissionRule)
if permissionId != "" {
c.Data["json"] = object.Enforce(permissionId, &request)
c.ServeJSON()
return
}
permissions := make([]*object.Permission, 0)
res := []bool{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions = object.GetPermissionsByModel(owner, modelName)
} else {
permissions = object.GetPermissionsByResource(resourceId)
}
for _, permission := range permissions {
res = append(res, object.Enforce(permission.GetId(), &request))
}
c.Data["json"] = res
c.ServeJSON()
}
func (c *ApiController) BatchEnforce() {
var permissionRules []object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRules)
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
var requests []object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.BatchEnforce(permissionRules)
c.ServeJSON()
if permissionId != "" {
c.Data["json"] = object.BatchEnforce(permissionId, &requests)
c.ServeJSON()
} else {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions := object.GetPermissionsByModel(owner, modelName)
res := [][]bool{}
for _, permission := range permissions {
res = append(res, object.BatchEnforce(permission.GetId(), &requests))
}
c.Data["json"] = res
c.ServeJSON()
}
}
func (c *ApiController) GetAllObjects() {

View File

@@ -23,7 +23,8 @@ import (
type LdapResp struct {
// Groups []LdapRespGroup `json:"groups"`
Users []object.LdapRespUser `json:"users"`
Users []object.LdapUser `json:"users"`
ExistUuids []string `json:"existUuids"`
}
//type LdapRespGroup struct {
@@ -32,8 +33,8 @@ type LdapResp struct {
//}
type LdapSyncResp struct {
Exist []object.LdapRespUser `json:"exist"`
Failed []object.LdapRespUser `json:"failed"`
Exist []object.LdapUser `json:"exist"`
Failed []object.LdapUser `json:"failed"`
}
// GetLdapUsers
@@ -71,27 +72,17 @@ func (c *ApiController) GetLdapUsers() {
return
}
var resp LdapResp
uuids := make([]string, len(users))
for _, user := range users {
resp.Users = append(resp.Users, object.LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
// GroupName: groupsMap[user.GidNumber].Cn,
Uuid: user.Uuid,
DisplayName: user.DisplayName,
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
})
uuids = append(uuids, user.Uuid)
for i, user := range users {
uuids[i] = user.GetLdapUuid()
}
existUuids := object.GetExistUuids(ldapServer.Owner, uuids)
c.ResponseOk(resp, existUuids)
resp := LdapResp{
Users: object.AutoAdjustLdapUser(users),
ExistUuids: existUuids,
}
c.ResponseOk(resp)
}
// GetLdaps
@@ -206,7 +197,7 @@ func (c *ApiController) DeleteLdap() {
func (c *ApiController) SyncLdapUsers() {
owner := c.Input().Get("owner")
ldapId := c.Input().Get("ldapId")
var users []object.LdapRespUser
var users []object.LdapUser
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
if err != nil {
c.ResponseError(err.Error())
@@ -215,10 +206,10 @@ func (c *ApiController) SyncLdapUsers() {
object.UpdateLdapSyncTime(ldapId)
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
exist, failed, _ := object.SyncLdapUsers(owner, users, ldapId)
c.ResponseOk(&LdapSyncResp{
Exist: *exist,
Failed: *failed,
Exist: exist,
Failed: failed,
})
}

View File

@@ -41,7 +41,7 @@ func (c *ApiController) GetMessages() {
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
chat := c.Input().Get("chat")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
var messages []*object.Message
if chat == "" {
@@ -54,8 +54,8 @@ func (c *ApiController) GetMessages() {
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetMessageCount(owner, field, value)))
messages := object.GetMaskedMessages(object.GetPaginationMessages(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetMessageCount(owner, organization, field, value)))
messages := object.GetMaskedMessages(object.GetPaginationMessages(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder))
c.ResponseOk(messages, paginator.Nums())
}
}
@@ -107,9 +107,9 @@ func (c *ApiController) GetMessageAnswer() {
return
}
chatId := util.GetId(message.Owner, message.Chat)
chatId := util.GetId("admin", message.Chat)
chat := object.GetChat(chatId)
if chat == nil {
if chat == nil || chat.Organization != message.Organization {
c.ResponseErrorStream(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
return
}
@@ -144,12 +144,18 @@ func (c *ApiController) GetMessageAnswer() {
authToken := provider.ClientSecret
question := questionMessage.Text
var stringBuilder strings.Builder
fmt.Printf("Question: [%s]\n", questionMessage.Text)
fmt.Printf("Answer: [")
err := ai.QueryAnswerStream(authToken, question, c.Ctx.ResponseWriter, &stringBuilder)
if err != nil {
c.ResponseErrorStream(err.Error())
return
}
fmt.Printf("]\n")
event := fmt.Sprintf("event: end\ndata: %s\n\n", "end")
_, err = c.Ctx.ResponseWriter.Write([]byte(event))
if err != nil {
@@ -158,9 +164,6 @@ func (c *ApiController) GetMessageAnswer() {
answer := stringBuilder.String()
fmt.Printf("Question: [%s]\n", questionMessage.Text)
fmt.Printf("Answer: [%s]\n", answer)
message.Text = answer
object.UpdateMessage(message.GetId(), message)
}
@@ -202,10 +205,18 @@ func (c *ApiController) AddMessage() {
return
}
var chat *object.Chat
if message.Chat != "" {
chatId := util.GetId("admin", message.Chat)
chat = object.GetChat(chatId)
if chat == nil || chat.Organization != message.Organization {
c.ResponseError(fmt.Sprintf(c.T("chat:The chat: %s is not found"), chatId))
return
}
}
affected := object.AddMessage(&message)
if affected {
chatId := util.GetId(message.Owner, message.Chat)
chat := object.GetChat(chatId)
if chat != nil && chat.Type == "AI" {
answerMessage := &object.Message{
Owner: message.Owner,

View File

@@ -21,6 +21,7 @@ import (
"io"
"mime"
"path/filepath"
"strings"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@@ -235,10 +236,21 @@ func (c *ApiController) UploadResource() {
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
case "termsOfUse":
applicationId := fmt.Sprintf("admin/%s", parent)
app := object.GetApplication(applicationId)
app.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, app)
user := object.GetUserNoCheck(util.GetId(owner, username))
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(owner, username)))
return
}
if !user.IsAdminUser() {
c.ResponseError(c.T("auth:Unauthorized operation"))
return
}
_, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimRight(fullFilePath, ".html"))
applicationObj := object.GetApplication(applicationId)
applicationObj.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, applicationObj)
}
c.ResponseOk(fileUrl, objectKey)

View File

@@ -37,13 +37,14 @@ func (c *ApiController) GetSyncers() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
c.Data["json"] = object.GetSyncers(owner)
c.Data["json"] = object.GetOrganizationSyncers(owner, organization)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value)))
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, organization, field, value)))
syncers := object.GetPaginationSyncers(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(syncers, paginator.Nums())
}
}

View File

@@ -59,3 +59,13 @@ func (c *ApiController) GetVersionInfo() {
c.ResponseOk(versionInfo)
}
// Health
// @Title Health
// @Tag System API
// @Description check if the system is live
// @Success 200 {object} controllers.Response The Response object
// @router /health [get]
func (c *ApiController) Health() {
c.ResponseOk()
}

View File

@@ -39,13 +39,14 @@ func (c *ApiController) GetTokens() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
c.Data["json"] = object.GetTokens(owner)
c.Data["json"] = object.GetTokens(owner, organization)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value)))
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, organization, field, value)))
tokens := object.GetPaginationTokens(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(tokens, paginator.Nums())
}
}

View File

@@ -16,7 +16,6 @@ package controllers
import (
"fmt"
"strconv"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/i18n"
@@ -115,14 +114,6 @@ func (c *ApiController) RequireAdmin() (string, bool) {
return user.Owner, true
}
func getInitScore(organization *object.Organization) (int, error) {
if organization != nil {
return organization.InitScore, nil
} else {
return strconv.Atoi(conf.GetConfigString("initScore"))
}
}
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
providerName := c.Input().Get("provider")
if providerName != "" {

View File

@@ -37,13 +37,14 @@ func (c *ApiController) GetWebhooks() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
c.Data["json"] = object.GetWebhooks(owner)
c.Data["json"] = object.GetWebhooks(owner, organization)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value)))
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, organization, field, value)))
webhooks := object.GetPaginationWebhooks(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(webhooks, paginator.Nums())
}
}

View File

@@ -73,23 +73,27 @@ func applyData(data1 *I18nData, data2 *I18nData) {
}
}
func Translate(lang string, error string) string {
tokens := strings.SplitN(error, ":", 2)
if !strings.Contains(error, ":") || len(tokens) != 2 {
return "Translate Error: " + error
func Translate(language string, errorText string) string {
tokens := strings.SplitN(errorText, ":", 2)
if !strings.Contains(errorText, ":") || len(tokens) != 2 {
return fmt.Sprintf("Translate error: the error text doesn't contain \":\", errorText = %s", errorText)
}
if langMap[lang] == nil {
file, _ := f.ReadFile("locales/" + lang + "/data.json")
if langMap[language] == nil {
file, err := f.ReadFile(fmt.Sprintf("locales/%s/data.json", language))
if err != nil {
return fmt.Sprintf("Translate error: the language \"%s\" is not supported, err = %s", language, err.Error())
}
data := I18nData{}
err := util.JsonToStruct(string(file), &data)
err = util.JsonToStruct(string(file), &data)
if err != nil {
panic(err)
}
langMap[lang] = data
langMap[language] = data
}
res := langMap[lang][tokens[0]][tokens[1]]
res := langMap[language][tokens[0]][tokens[1]]
if res == "" {
res = tokens[1]
}

View File

@@ -179,8 +179,12 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
corpEmail, jobNumber, err := idp.getUserCorpEmail(userId, corpAccessToken)
corpMobile, corpEmail, jobNumber, err := idp.getUserCorpEmail(userId, corpAccessToken)
if err == nil {
if corpMobile != "" {
userInfo.Phone = corpMobile
}
if corpEmail != "" {
userInfo.Email = corpEmail
}
@@ -264,27 +268,29 @@ func (idp *DingTalkIdProvider) getUserId(unionId string, accessToken string) (st
return data.Result.UserId, nil
}
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, string, error) {
func (idp *DingTalkIdProvider) getUserCorpEmail(userId string, accessToken string) (string, string, string, error) {
// https://open.dingtalk.com/document/isvapp/query-user-details
body := make(map[string]string)
body["userid"] = userId
respBytes, err := idp.postWithBody(body, "https://oapi.dingtalk.com/topapi/v2/user/get?access_token="+accessToken)
if err != nil {
return "", "", err
return "", "", "", err
}
var data struct {
ErrMessage string `json:"errmsg"`
Result struct {
Mobile string `json:"mobile"`
Email string `json:"email"`
JobNumber string `json:"job_number"`
} `json:"result"`
}
err = json.Unmarshal(respBytes, &data)
if err != nil {
return "", "", err
return "", "", "", err
}
if data.ErrMessage != "ok" {
return "", "", fmt.Errorf(data.ErrMessage)
return "", "", "", fmt.Errorf(data.ErrMessage)
}
return data.Result.Email, data.Result.JobNumber, nil
return data.Result.Mobile, data.Result.Email, data.Result.JobNumber, nil
}

View File

@@ -113,6 +113,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
for _, attr := range r.Attributes() {
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
if string(attr) == "cn" {
e.AddAttribute(message.AttributeDescription(attr), getAttribute("title", user))
}
}
w.Write(e)

View File

@@ -74,6 +74,15 @@ func getUsername(filter string) string {
return name
}
func stringInSlice(value string, list []string) bool {
for _, item := range list {
if item == value {
return true
}
}
return false
}
func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int) {
r := m.GetSearchRequest()
@@ -94,13 +103,32 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
return nil, ldap.LDAPResultInsufficientAccessRights
}
} else {
hasPermission, err := object.CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), true, "en")
requestUserId := util.GetId(m.Client.OrgName, m.Client.UserName)
userId := util.GetId(org, name)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, "en")
if !hasPermission {
log.Printf("ErrMsg = %v", err.Error())
return nil, ldap.LDAPResultInsufficientAccessRights
}
user := object.GetUser(util.GetId(org, name))
filteredUsers = append(filteredUsers, user)
user := object.GetUser(userId)
if user != nil {
filteredUsers = append(filteredUsers, user)
return filteredUsers, ldap.LDAPResultSuccess
}
organization := object.GetOrganization(util.GetId("admin", org))
if organization == nil {
return nil, ldap.LDAPResultNoSuchObject
}
if !stringInSlice(name, organization.Tags) {
return nil, ldap.LDAPResultNoSuchObject
}
users := object.GetUsersByTag(org, name)
filteredUsers = append(filteredUsers, users...)
return filteredUsers, ldap.LDAPResultSuccess
}
}
@@ -130,12 +158,16 @@ func getAttribute(attributeName string, user *object.User) message.AttributeValu
return message.AttributeValue(user.Name)
case "uid":
return message.AttributeValue(user.Name)
case "displayname":
return message.AttributeValue(user.DisplayName)
case "email":
return message.AttributeValue(user.Email)
case "mail":
return message.AttributeValue(user.Email)
case "mobile":
return message.AttributeValue(user.Phone)
case "title":
return message.AttributeValue(user.Tag)
case "userPassword":
return message.AttributeValue(getUserPasswordWithType(user))
default:

View File

@@ -59,8 +59,8 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionOn = true
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"

View File

@@ -72,6 +72,7 @@ type Application struct {
SigninHtml string `xorm:"mediumtext" json:"signinHtml"`
ThemeData *ThemeData `xorm:"json" json:"themeData"`
FormCss string `xorm:"text" json:"formCss"`
FormCssMobile string `xorm:"text" json:"formCssMobile"`
FormOffset int `json:"formOffset"`
FormSideHtml string `xorm:"mediumtext" json:"formSideHtml"`
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
@@ -109,7 +110,7 @@ func GetApplications(owner string) []*Application {
func GetOrganizationApplications(owner string, organization string) []*Application {
applications := []*Application{}
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner, Organization: organization})
err := adapter.Engine.Desc("created_time").Find(&applications, &Application{Organization: organization})
if err != nil {
panic(err)
}
@@ -131,7 +132,7 @@ func GetPaginationApplications(owner string, offset, limit int, field, value, so
func GetPaginationOrganizationApplications(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Application {
applications := []*Application{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&applications, &Application{Owner: owner, Organization: organization})
err := session.Find(&applications, &Application{Organization: organization})
if err != nil {
panic(err)
}
@@ -325,6 +326,12 @@ func UpdateApplication(id string, application *Application) bool {
}
func AddApplication(application *Application) bool {
if application.Owner == "" {
application.Owner = "admin"
}
if application.Organization == "" {
application.Organization = "built-in"
}
if application.ClientId == "" {
application.ClientId = util.GenerateClientId()
}

View File

@@ -46,9 +46,9 @@ type CasbinAdapter struct {
Adapter *xormadapter.Adapter `xorm:"-" json:"-"`
}
func GetCasbinAdapterCount(owner, field, value string) int {
func GetCasbinAdapterCount(owner, organization, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&CasbinAdapter{})
count, err := session.Count(&CasbinAdapter{Organization: organization})
if err != nil {
panic(err)
}
@@ -56,9 +56,9 @@ func GetCasbinAdapterCount(owner, field, value string) int {
return int(count)
}
func GetCasbinAdapters(owner string) []*CasbinAdapter {
func GetCasbinAdapters(owner string, organization string) []*CasbinAdapter {
adapters := []*CasbinAdapter{}
err := adapter.Engine.Where("owner = ?", owner).Find(&adapters)
err := adapter.Engine.Where("owner = ? and organization = ?", owner, organization).Find(&adapters)
if err != nil {
panic(err)
}
@@ -66,10 +66,10 @@ func GetCasbinAdapters(owner string) []*CasbinAdapter {
return adapters
}
func GetPaginationCasbinAdapters(owner string, page, limit int, field, value, sort, order string) []*CasbinAdapter {
func GetPaginationCasbinAdapters(owner, organization string, page, limit int, field, value, sort, order string) []*CasbinAdapter {
session := GetSession(owner, page, limit, field, value, sort, order)
adapters := []*CasbinAdapter{}
err := session.Find(&adapters)
err := session.Find(&adapters, &CasbinAdapter{Organization: organization})
if err != nil {
panic(err)
}

View File

@@ -55,8 +55,8 @@ func GetMaskedCerts(certs []*Cert) []*Cert {
}
func GetCertCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Cert{})
session := GetSession("", -1, -1, field, value, "", "")
count, err := session.Where("owner = ? or owner = ? ", "admin", owner).Count(&Cert{})
if err != nil {
panic(err)
}
@@ -66,7 +66,7 @@ func GetCertCount(owner, field, value string) int {
func GetCerts(owner string) []*Cert {
certs := []*Cert{}
err := adapter.Engine.Desc("created_time").Find(&certs, &Cert{Owner: owner})
err := adapter.Engine.Where("owner = ? or owner = ? ", "admin", owner).Desc("created_time").Find(&certs, &Cert{})
if err != nil {
panic(err)
}
@@ -76,7 +76,38 @@ func GetCerts(owner string) []*Cert {
func GetPaginationCerts(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Cert {
certs := []*Cert{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Where("owner = ? or owner = ? ", "admin", owner).Find(&certs)
if err != nil {
panic(err)
}
return certs
}
func GetGlobalCertsCount(field, value string) int {
session := GetSession("", -1, -1, field, value, "", "")
count, err := session.Count(&Cert{})
if err != nil {
panic(err)
}
return int(count)
}
func GetGlobleCerts() []*Cert {
certs := []*Cert{}
err := adapter.Engine.Desc("created_time").Find(&certs)
if err != nil {
panic(err)
}
return certs
}
func GetPaginationGlobalCerts(offset, limit int, field, value, sortField, sortOrder string) []*Cert {
certs := []*Cert{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&certs)
if err != nil {
panic(err)
@@ -103,6 +134,24 @@ func getCert(owner string, name string) *Cert {
}
}
func getCertByName(name string) *Cert {
if name == "" {
return nil
}
cert := Cert{Name: name}
existed, err := adapter.Engine.Get(&cert)
if err != nil {
panic(err)
}
if existed {
return &cert
} else {
return nil
}
}
func GetCert(id string) *Cert {
owner, name := util.GetOwnerAndNameFromId(id)
return getCert(owner, name)
@@ -158,7 +207,7 @@ func (p *Cert) GetId() string {
func getCertByApplication(application *Application) *Cert {
if application.Cert != "" {
return getCert("admin", application.Cert)
return getCertByName(application.Cert)
} else {
return GetDefaultCert()
}

View File

@@ -175,7 +175,11 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
return i18n.Translate(lang, "check:Organization does not exist")
}
credManager := cred.GetCredManager(organization.PasswordType)
passwordType := user.PasswordType
if passwordType == "" {
passwordType = organization.PasswordType
}
credManager := cred.GetCredManager(passwordType)
if credManager != nil {
if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
@@ -282,6 +286,10 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
if userId != "" {
targetUser := GetUser(userId)
if targetUser == nil {
if strings.HasPrefix(requestUserId, "built-in/") {
return true, nil
}
return false, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), userId)
}
@@ -313,6 +321,10 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
}
func CheckAccessPermission(userId string, application *Application) (bool, error) {
if userId == "built-in/admin" {
return true, nil
}
permissions := GetPermissions(application.Organization)
allowed := true
var err error

View File

@@ -87,11 +87,13 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
continue
}
existed, failed := SyncLdapUsers(ldap.Owner, LdapUsersToLdapRespUsers(users), ldap.Id)
if len(*failed) != 0 {
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(*existed)-len(*failed), len(*failed)), *failed)
existed, failed, err := SyncLdapUsers(ldap.Owner, AutoAdjustLdapUser(users), ldap.Id)
if len(failed) != 0 {
logs.Warning(fmt.Sprintf("ldap autosync,%d new users,but %d user failed during :", len(users)-len(existed)-len(failed), len(failed)), failed)
logs.Warning(err.Error())
} else {
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(existed), len(existed)))
}
}
}

View File

@@ -19,7 +19,6 @@ import (
"fmt"
"strings"
"github.com/beego/beego"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
@@ -35,35 +34,26 @@ type LdapConn struct {
// Cn string
//}
type ldapUser struct {
UidNumber string
Uid string
Cn string
GidNumber string
type LdapUser struct {
UidNumber string `json:"uidNumber"`
Uid string `json:"uid"`
Cn string `json:"cn"`
GidNumber string `json:"gidNumber"`
// Gcn string
Uuid string
DisplayName string
Uuid string `json:"uuid"`
DisplayName string `json:"displayName"`
Mail string
Email string
Email string `json:"email"`
EmailAddress string
TelephoneNumber string
Mobile string
MobileTelephoneNumber string
RegisteredAddress string
PostalAddress string
}
type LdapRespUser struct {
UidNumber string `json:"uidNumber"`
Uid string `json:"uid"`
Cn string `json:"cn"`
GroupId string `json:"groupId"`
// GroupName string `json:"groupName"`
Uuid string `json:"uuid"`
DisplayName string `json:"displayName"`
Email string `json:"email"`
Phone string `json:"phone"`
Address string `json:"address"`
GroupId string `json:"groupId"`
Phone string `json:"phone"`
Address string `json:"address"`
}
func (ldap *Ldap) GetLdapConn() (c *LdapConn, err error) {
@@ -136,7 +126,7 @@ func isMicrosoftAD(Conn *goldap.Conn) (bool, error) {
return isMicrosoft, err
}
func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]LdapUser, error) {
SearchAttributes := []string{
"uidNumber", "cn", "sn", "gidNumber", "entryUUID", "displayName", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
@@ -159,9 +149,9 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
return nil, errors.New("no result")
}
var ldapUsers []ldapUser
var ldapUsers []LdapUser
for _, entry := range searchResult.Entries {
var user ldapUser
var user LdapUser
for _, attribute := range entry.Attributes {
switch attribute.Name {
case "uidNumber":
@@ -241,35 +231,30 @@ func (l *LdapConn) GetLdapUsers(ldapServer *Ldap) ([]ldapUser, error) {
// return groupMap, nil
// }
func LdapUsersToLdapRespUsers(users []ldapUser) []LdapRespUser {
res := make([]LdapRespUser, 0)
for _, user := range users {
res = append(res, LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
Uuid: user.Uuid,
DisplayName: user.DisplayName,
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
Phone: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
Address: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
})
func AutoAdjustLdapUser(users []LdapUser) []LdapUser {
res := make([]LdapUser, len(users))
for i, user := range users {
res[i] = LdapUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
Uuid: user.GetLdapUuid(),
DisplayName: user.DisplayName,
Email: util.ReturnAnyNotEmpty(user.Email, user.EmailAddress, user.Mail),
Mobile: util.ReturnAnyNotEmpty(user.Mobile, user.MobileTelephoneNumber, user.TelephoneNumber),
RegisteredAddress: util.ReturnAnyNotEmpty(user.PostalAddress, user.RegisteredAddress),
}
}
return res
}
func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]LdapRespUser, *[]LdapRespUser) {
var existUsers []LdapRespUser
var failedUsers []LdapRespUser
func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUsers []LdapUser, failedUsers []LdapUser, err error) {
var uuids []string
for _, user := range respUsers {
for _, user := range syncUsers {
uuids = append(uuids, user.Uuid)
}
existUuids := GetExistUuids(owner, uuids)
organization := getOrganization("admin", owner)
ldap := GetLdap(ldapId)
@@ -289,67 +274,59 @@ func SyncLdapUsers(owner string, respUsers []LdapRespUser, ldapId string) (*[]Ld
}
tag := strings.Join(ou, ".")
for _, respUser := range respUsers {
for _, syncUser := range syncUsers {
existUuids := GetExistUuids(owner, uuids)
found := false
if len(existUuids) > 0 {
for _, existUuid := range existUuids {
if respUser.Uuid == existUuid {
existUsers = append(existUsers, respUser)
if syncUser.Uuid == existUuid {
existUsers = append(existUsers, syncUser)
found = true
}
}
}
if !found {
score, _ := organization.GetInitScore()
newUser := &User{
Owner: owner,
Name: respUser.buildLdapUserName(),
Name: syncUser.buildLdapUserName(),
CreatedTime: util.GetCurrentTime(),
DisplayName: respUser.buildLdapDisplayName(),
DisplayName: syncUser.buildLdapDisplayName(),
Avatar: organization.DefaultAvatar,
Email: respUser.Email,
Phone: respUser.Phone,
Address: []string{respUser.Address},
Email: syncUser.Email,
Phone: syncUser.Phone,
Address: []string{syncUser.Address},
Affiliation: affiliation,
Tag: tag,
Score: beego.AppConfig.DefaultInt("initScore", 2000),
Ldap: respUser.Uuid,
Score: score,
Ldap: syncUser.Uuid,
}
affected := AddUser(newUser)
if !affected {
failedUsers = append(failedUsers, respUser)
failedUsers = append(failedUsers, syncUser)
continue
}
}
}
return &existUsers, &failedUsers
return existUsers, failedUsers, err
}
func GetExistUuids(owner string, uuids []string) []string {
var users []User
var existUuids []string
existUuidSet := make(map[string]struct{})
err := adapter.Engine.Where(fmt.Sprintf("ldap IN (%s) AND owner = ?", "'"+strings.Join(uuids, "','")+"'"), owner).Find(&users)
err := adapter.Engine.Table("user").Where("owner = ?", owner).Cols("ldap").
In("ldap", uuids).Select("DISTINCT ldap").Find(&existUuids)
if err != nil {
panic(err)
}
if len(users) > 0 {
for _, result := range users {
existUuidSet[result.Ldap] = struct{}{}
}
}
for uuid := range existUuidSet {
existUuids = append(existUuids, uuid)
}
return existUuids
}
func (ldapUser *LdapRespUser) buildLdapUserName() string {
func (ldapUser *LdapUser) buildLdapUserName() string {
user := User{}
uidWithNumber := fmt.Sprintf("%s_%s", ldapUser.Uid, ldapUser.UidNumber)
has, err := adapter.Engine.Where("name = ? or name = ?", ldapUser.Uid, uidWithNumber).Get(&user)
@@ -364,10 +341,14 @@ func (ldapUser *LdapRespUser) buildLdapUserName() string {
return fmt.Sprintf("%s_%s", uidWithNumber, randstr.Hex(6))
}
return ldapUser.Uid
if ldapUser.Uid != "" {
return ldapUser.Uid
}
return ldapUser.Cn
}
func (ldapUser *LdapRespUser) buildLdapDisplayName() string {
func (ldapUser *LdapUser) buildLdapDisplayName() string {
if ldapUser.DisplayName != "" {
return ldapUser.DisplayName
}
@@ -375,6 +356,17 @@ func (ldapUser *LdapRespUser) buildLdapDisplayName() string {
return ldapUser.Cn
}
func (ldapUser *LdapUser) GetLdapUuid() string {
if ldapUser.Uuid != "" {
return ldapUser.Uuid
}
if ldapUser.Uid != "" {
return ldapUser.Uid
}
return ldapUser.Cn
}
func (ldap *Ldap) buildFilterString(user *User) string {
if len(ldap.FilterFields) == 0 {
return fmt.Sprintf("(&%s(uid=%s))", ldap.Filter, user.Name)

View File

@@ -48,9 +48,9 @@ func GetMaskedMessages(messages []*Message) []*Message {
return messages
}
func GetMessageCount(owner, field, value string) int {
func GetMessageCount(owner, organization, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Message{})
count, err := session.Count(&Message{Organization: organization})
if err != nil {
panic(err)
}
@@ -78,10 +78,10 @@ func GetChatMessages(chat string) []*Message {
return messages
}
func GetPaginationMessages(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Message {
func GetPaginationMessages(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Message {
messages := []*Message{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&messages)
err := session.Find(&messages, &Message{Organization: organization})
if err != nil {
panic(err)
}

View File

@@ -51,6 +51,7 @@ const (
const (
MfaSessionUserId = "MfaSessionUserId"
NextMfa = "NextMfa"
RequiredMfa = "RequiredMfa"
)
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {

View File

@@ -26,6 +26,7 @@ func DoMigration() {
&Migrator_1_101_0_PR_1083{},
&Migrator_1_235_0_PR_1530{},
&Migrator_1_240_0_PR_1539{},
&Migrator_1_314_0_PR_1841{},
// more migrators add here in chronological order...
}

View File

@@ -0,0 +1,93 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"github.com/xorm-io/core"
"github.com/xorm-io/xorm"
"github.com/xorm-io/xorm/migrate"
)
type Migrator_1_314_0_PR_1841 struct{}
func (*Migrator_1_314_0_PR_1841) IsMigrationNeeded() bool {
users := []*User{}
err := adapter.Engine.Table("user").Find(&users)
if err != nil {
return false
}
for _, u := range users {
if u.PasswordType != "" {
return false
}
}
return true
}
func (*Migrator_1_314_0_PR_1841) DoMigration() *migrate.Migration {
migration := migrate.Migration{
ID: "20230515MigrateUser--Create a new field 'passwordType' for table `user`",
Migrate: func(engine *xorm.Engine) error {
tx := engine.NewSession()
defer tx.Close()
err := tx.Begin()
if err != nil {
return err
}
users := []*User{}
organizations := []*Organization{}
err = tx.Table("user").Find(&users)
if err != nil {
return err
}
err = tx.Table("organization").Find(&organizations)
if err != nil {
return err
}
passwordTypes := make(map[string]string)
for _, org := range organizations {
passwordTypes[org.Name] = org.PasswordType
}
columns := []string{
"password_type",
}
for _, u := range users {
u.PasswordType = passwordTypes[u.Owner]
_, err := tx.ID(core.PK{u.Owner, u.Name}).Cols(columns...).Update(u)
if err != nil {
return err
}
}
tx.Commit()
return nil
},
}
return &migration
}

View File

@@ -16,7 +16,9 @@ package object
import (
"fmt"
"strconv"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
@@ -38,6 +40,11 @@ type ThemeData struct {
IsEnabled bool `xorm:"bool" json:"isEnabled"`
}
type MfaItem struct {
Name string `json:"name"`
Rule string `json:"rule"`
}
type Organization struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@@ -59,6 +66,7 @@ type Organization struct {
EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
}
@@ -408,3 +416,20 @@ func organizationChangeTrigger(oldName string, newName string) error {
return session.Commit()
}
func (org *Organization) HasRequiredMfa() bool {
for _, item := range org.MfaItems {
if item.Rule == "Required" {
return true
}
}
return false
}
func (org *Organization) GetInitScore() (int, error) {
if org != nil {
return org.InitScore, nil
} else {
return strconv.Atoi(conf.GetConfigString("initScore"))
}
}

View File

@@ -15,8 +15,6 @@
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
)
@@ -65,30 +63,6 @@ func (p *Permission) GetId() string {
return util.GetId(p.Owner, p.Name)
}
func (p *PermissionRule) GetRequest(adapterName string, permissionId string) ([]interface{}, error) {
request := []interface{}{p.V0, p.V1, p.V2}
if p.V3 != "" {
request = append(request, p.V3)
}
if p.V4 != "" {
request = append(request, p.V4)
}
if adapterName == builtInAdapter {
if p.V5 != "" {
return nil, fmt.Errorf("too many parameters. The maximum parameter number cannot exceed %d", builtInAvailableField)
}
return request, nil
} else {
if p.V5 != "" {
request = append(request, p.V5)
}
return request, nil
}
}
func GetPermissionCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Permission{})
@@ -261,6 +235,16 @@ func GetPermissionsByRole(roleId string) []*Permission {
return permissions
}
func GetPermissionsByResource(resourceId string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
if err != nil {
panic(err)
}
return permissions
}
func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})
@@ -271,6 +255,16 @@ func GetPermissionsBySubmitter(owner string, submitter string) []*Permission {
return permissions
}
func GetPermissionsByModel(owner string, model string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Model: model})
if err != nil {
panic(err)
}
return permissions
}
func ContainsAsterisk(userId string, users []string) bool {
containsAsterisk := false
group, _ := util.GetOwnerAndNameFromId(userId)

View File

@@ -62,7 +62,11 @@ func getEnforcer(permission *Permission) *casbin.Enforcer {
panic(err)
}
enforcer.InitWithModelAndAdapter(m, nil)
err = enforcer.InitWithModelAndAdapter(m, nil)
if err != nil {
panic(err)
}
enforcer.SetAdapter(adapter)
policyFilter := xormadapter.Filter{
@@ -216,28 +220,23 @@ func removePolicies(permission *Permission) {
}
}
func Enforce(permissionRule *PermissionRule) bool {
permission := GetPermission(permissionRule.Id)
type CasbinRequest = []interface{}
func Enforce(permissionId string, request *CasbinRequest) bool {
permission := GetPermission(permissionId)
enforcer := getEnforcer(permission)
request, _ := permissionRule.GetRequest(builtInAdapter, permissionRule.Id)
allow, err := enforcer.Enforce(request...)
allow, err := enforcer.Enforce(*request...)
if err != nil {
panic(err)
}
return allow
}
func BatchEnforce(permissionRules []PermissionRule) []bool {
var requests [][]interface{}
for _, permissionRule := range permissionRules {
request, _ := permissionRule.GetRequest(builtInAdapter, permissionRule.Id)
requests = append(requests, request)
}
permission := GetPermission(permissionRules[0].Id)
func BatchEnforce(permissionId string, requests *[]CasbinRequest) []bool {
permission := GetPermission(permissionId)
enforcer := getEnforcer(permission)
allow, err := enforcer.BatchEnforce(requests)
allow, err := enforcer.BatchEnforce(*requests)
if err != nil {
panic(err)
}

View File

@@ -47,7 +47,8 @@ type Record struct {
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
Action string `xorm:"varchar(1000)" json:"action"`
ExtendedUser *User `xorm:"-" json:"extendedUser"`
Object string `xorm:"-" json:"object"`
ExtendedUser *User `xorm:"-" json:"extendedUser"`
IsTriggered bool `json:"isTriggered"`
}
@@ -60,6 +61,11 @@ func NewRecord(ctx *context.Context) *Record {
requestUri = requestUri[0:1000]
}
object := ""
if ctx.Input.RequestBody != nil && len(ctx.Input.RequestBody) != 0 {
object = string(ctx.Input.RequestBody)
}
record := Record{
Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(),
@@ -68,6 +74,7 @@ func NewRecord(ctx *context.Context) *Record {
Method: ctx.Request.Method,
RequestUri: requestUri,
Action: action,
Object: object,
IsTriggered: false,
}
return &record
@@ -159,7 +166,7 @@ func SendWebhooks(record *Record) error {
if matched {
if webhook.IsUserExtended {
user := getUser(record.Organization, record.User)
user := GetMaskedUser(getUser(record.Organization, record.User))
record.ExtendedUser = user
}

View File

@@ -55,9 +55,9 @@ type Syncer struct {
Adapter *Adapter `xorm:"-" json:"-"`
}
func GetSyncerCount(owner, field, value string) int {
func GetSyncerCount(owner, organization, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Syncer{})
count, err := session.Count(&Syncer{Organization: organization})
if err != nil {
panic(err)
}
@@ -75,10 +75,20 @@ func GetSyncers(owner string) []*Syncer {
return syncers
}
func GetPaginationSyncers(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer {
func GetOrganizationSyncers(owner, organization string) []*Syncer {
syncers := []*Syncer{}
err := adapter.Engine.Desc("created_time").Find(&syncers, &Syncer{Owner: owner, Organization: organization})
if err != nil {
panic(err)
}
return syncers
}
func GetPaginationSyncers(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Syncer {
syncers := []*Syncer{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&syncers)
err := session.Find(&syncers, &Syncer{Organization: organization})
if err != nil {
panic(err)
}

View File

@@ -91,9 +91,9 @@ type IntrospectionResponse struct {
Jti string `json:"jti,omitempty"`
}
func GetTokenCount(owner, field, value string) int {
func GetTokenCount(owner, organization, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Token{})
count, err := session.Count(&Token{Organization: organization})
if err != nil {
panic(err)
}
@@ -101,9 +101,9 @@ func GetTokenCount(owner, field, value string) int {
return int(count)
}
func GetTokens(owner string) []*Token {
func GetTokens(owner string, organization string) []*Token {
tokens := []*Token{}
err := adapter.Engine.Desc("created_time").Find(&tokens, &Token{Owner: owner})
err := adapter.Engine.Desc("created_time").Find(&tokens, &Token{Owner: owner, Organization: organization})
if err != nil {
panic(err)
}
@@ -111,10 +111,10 @@ func GetTokens(owner string) []*Token {
return tokens
}
func GetPaginationTokens(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Token {
func GetPaginationTokens(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Token {
tokens := []*Token{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&tokens)
err := session.Find(&tokens, &Token{Organization: organization})
if err != nil {
panic(err)
}

View File

@@ -224,6 +224,9 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours) * time.Hour)
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
if application.RefreshExpireInHours == 0 {
refreshExpireTime = expireTime
}
user = refineUser(user)

View File

@@ -41,6 +41,7 @@ type User struct {
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"`
@@ -250,6 +251,16 @@ func GetUsers(owner string) []*User {
return users
}
func GetUsersByTag(owner string, tag string) []*User {
users := []*User{}
err := adapter.Engine.Desc("created_time").Find(&users, &User{Owner: owner, Tag: tag})
if err != nil {
panic(err)
}
return users
}
func GetSortedUsers(owner string, sorter string, limit int) []*User {
users := []*User{}
err := adapter.Engine.Desc(sorter).Limit(limit, 0).Find(&users, &User{Owner: owner})
@@ -461,6 +472,13 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) bool {
"location", "address", "country_code", "region", "language", "affiliation", "title", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts",
"signin_wrong_times", "last_signin_wrong_time",
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
"spotify", "strava", "stripe", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
"yammer", "yandex", "zoom", "custom",
}
}
if isAdmin {

View File

@@ -35,5 +35,6 @@ func (user *User) UpdateUserPassword(organization *Organization) {
if credManager != nil {
hashedPassword := credManager.GetHashedPassword(user.Password, user.PasswordSalt, organization.PasswordSalt)
user.Password = hashedPassword
user.PasswordType = organization.PasswordType
}
}

View File

@@ -77,13 +77,17 @@ func GetUserByFields(organization string, field string) *User {
}
func SetUserField(user *User, field string, value string) bool {
bean := make(map[string]interface{})
if field == "password" {
organization := GetOrganizationByUser(user)
user.UpdateUserPassword(organization)
value = user.Password
bean[strings.ToLower(field)] = user.Password
bean["password_type"] = user.PasswordType
} else {
bean[strings.ToLower(field)] = value
}
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(map[string]interface{}{strings.ToLower(field): value})
affected, err := adapter.Engine.Table(user).ID(core.PK{user.Owner, user.Name}).Update(bean)
if err != nil {
panic(err)
}

View File

@@ -42,9 +42,9 @@ type Webhook struct {
IsEnabled bool `json:"isEnabled"`
}
func GetWebhookCount(owner, field, value string) int {
func GetWebhookCount(owner, organization, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Webhook{})
count, err := session.Count(&Webhook{Organization: organization})
if err != nil {
panic(err)
}
@@ -52,9 +52,9 @@ func GetWebhookCount(owner, field, value string) int {
return int(count)
}
func GetWebhooks(owner string) []*Webhook {
func GetWebhooks(owner string, organization string) []*Webhook {
webhooks := []*Webhook{}
err := adapter.Engine.Desc("created_time").Find(&webhooks, &Webhook{Owner: owner})
err := adapter.Engine.Desc("created_time").Find(&webhooks, &Webhook{Owner: owner, Organization: organization})
if err != nil {
panic(err)
}
@@ -62,10 +62,10 @@ func GetWebhooks(owner string) []*Webhook {
return webhooks
}
func GetPaginationWebhooks(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Webhook {
func GetPaginationWebhooks(owner, organization string, offset, limit int, field, value, sortField, sortOrder string) []*Webhook {
webhooks := []*Webhook{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&webhooks)
err := session.Find(&webhooks, &Webhook{Organization: organization})
if err != nil {
panic(err)
}

View File

@@ -184,6 +184,7 @@ func initAPI() {
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
beego.Router("/api/get-globle-certs", &controllers.ApiController{}, "GET:GetGlobleCerts")
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
@@ -246,6 +247,7 @@ func initAPI() {
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
beego.Router("/api/health", &controllers.ApiController{}, "GET:Health")
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
beego.Handler("/api/metrics", promhttp.Handler())

View File

@@ -29,7 +29,7 @@ func NewMinIOS3StorageProvider(clientId string, clientSecret string, region stri
Endpoint: endpoint,
S3Endpoint: endpoint,
ACL: awss3.BucketCannedACLPublicRead,
S3ForcePathStyle: true,
S3ForcePathStyle: false,
})
return sp

View File

@@ -1,16 +1,14 @@
describe('Test adapter', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
const selector = {
add: ".ant-table-title > div > .ant-btn"
};
it("test adapter", () => {
cy.visit("http://localhost:7001");
cy.visit("http://localhost:7001/adapters");
cy.url().should("eq", "http://localhost:7001/adapters");
cy.get(selector.add).click();
cy.get(selector.add,{timeout:10000}).click();
cy.url().should("include","http://localhost:7001/adapters/built-in/")
});
})

View File

@@ -1,6 +1,5 @@
describe('Test aplication', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test aplication", () => {

View File

@@ -1,6 +1,5 @@
describe('Test certs', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test certs", () => {

View File

@@ -1,6 +1,5 @@
describe('Test models', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test org", () => {

View File

@@ -1,6 +1,5 @@
describe('Test Orgnazition', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test org", () => {

View File

@@ -1,16 +1,14 @@
describe('Test payments', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
const selector = {
add: ".ant-table-title > div > .ant-btn"
};
it("test payments", () => {
cy.visit("http://localhost:7001");
cy.visit("http://localhost:7001/payments");
cy.url().should("eq", "http://localhost:7001/payments");
cy.get(selector.add).click();
cy.get(selector.add,{timeout:10000}).click();
cy.url().should("include","http://localhost:7001/payments/")
});
})

View File

@@ -1,6 +1,5 @@
describe('Test permissions', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test permissions", () => {

View File

@@ -1,16 +1,14 @@
describe('Test products', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
const selector = {
add: ".ant-table-title > div > .ant-btn > span"
};
it("test products", () => {
cy.visit("http://localhost:7001");
cy.visit("http://localhost:7001/products");
cy.url().should("eq", "http://localhost:7001/products");
cy.get(selector.add).click();
cy.get(selector.add,{timeout:10000}).click();
cy.url().should("include","http://localhost:7001/products/")
});
})

View File

@@ -1,6 +1,5 @@
describe('Test providers', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test providers", () => {

View File

@@ -1,6 +1,5 @@
describe('Test records', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test records", () => {

View File

@@ -1,6 +1,5 @@
describe('Test resource', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test resource", () => {

View File

@@ -1,6 +1,5 @@
describe('Test roles', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test role", () => {

View File

@@ -1,6 +1,5 @@
describe('Test sessions', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test sessions", () => {

View File

@@ -1,16 +1,14 @@
describe('Test syncers', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
const selector = {
add: ".ant-table-title > div > .ant-btn"
};
it("test syncers", () => {
cy.visit("http://localhost:7001");
cy.visit("http://localhost:7001/syncers");
cy.url().should("eq", "http://localhost:7001/syncers");
cy.get(selector.add).click();
cy.get(selector.add,{timeout:10000}).click();
cy.url().should("include","http://localhost:7001/syncers/")
});
})

View File

@@ -1,6 +1,5 @@
describe('Test sysinfo', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test sysinfo", () => {

View File

@@ -1,16 +1,14 @@
describe('Test tokens', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
const selector = {
add: ".ant-table-title > div > .ant-btn"
};
it("test records", () => {
cy.visit("http://localhost:7001");
cy.visit("http://localhost:7001/tokens");
cy.url().should("eq", "http://localhost:7001/tokens");
cy.get(selector.add).click();
cy.get(selector.add,{timeout:10000}).click();
cy.url().should("include","http://localhost:7001/tokens/")
});
})

View File

@@ -1,6 +1,5 @@
describe('Test User', () => {
beforeEach(()=>{
cy.visit("http://localhost:7001");
cy.login();
})
it("test user", () => {

View File

@@ -7,10 +7,10 @@ describe('Test webhooks', () => {
add: ".ant-table-title > div > .ant-btn"
};
it("test webhooks", () => {
cy.visit("http://localhost:7001");
cy.visit("http://localhost:7001/webhooks");
cy.url().should("eq", "http://localhost:7001/webhooks");
cy.get(selector.add).click();
cy.url().should("include","http://localhost:7001/webhooks/")
cy.get(selector.add,{timeout:10000}).click();
cy.url().should("include","http://localhost:7001/webhooks/");
});
})

View File

@@ -23,19 +23,15 @@
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
const selector = {
username: "#input",
password: "#normal_login_password",
loginButton: ".ant-btn",
};
Cypress.Commands.add('login', ()=>{
cy.request({
method: "POST",
url: "http://localhost:7001/api/login",
body: {
"application": "app-built-in",
"organization": "built-in",
"username": "admin",
"password": "123",
"autoSignin": true,
"type": "login",
},
}).then((Response) => {
expect(Response).property("body").property("status").to.equal("ok");
});
cy.visit("http://localhost:7001");
cy.get(selector.username).type("admin");
cy.get(selector.password).type("123");
cy.get(selector.loginButton).click();
cy.url().should("eq", "http://localhost:7001/");
})

View File

@@ -47,7 +47,7 @@ class AdapterEditPage extends React.Component {
}
getAdapter() {
AdapterBackend.getAdapter(this.state.owner, this.state.adapterName)
AdapterBackend.getAdapter("admin", this.state.adapterName)
.then((res) => {
if (res.status === "ok") {
this.setState({
@@ -60,7 +60,7 @@ class AdapterEditPage extends React.Component {
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
OrganizationBackend.getOrganizations(this.state.organizationName)
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
@@ -109,7 +109,7 @@ class AdapterEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.adapter.organization} onChange={(value => {
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.adapter.organization} onChange={(value => {
this.getModels(value);
this.updateAdapterField("organization", value);
this.updateAdapterField("owner", value);

View File

@@ -26,10 +26,10 @@ class AdapterListPage extends BaseListPage {
newAdapter() {
const randomName = Setting.getRandomName();
return {
owner: "built-in",
owner: "admin",
name: `adapter_${randomName}`,
createdTime: moment().format(),
organization: "built-in",
organization: this.props.account.owner,
type: "Database",
host: "localhost",
port: 3306,
@@ -247,7 +247,7 @@ class AdapterListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
AdapterBackend.getAdapters("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
AdapterBackend.getAdapters("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -410,7 +410,7 @@ class App extends Component {
));
}
if (Setting.isAdminUser(this.state.account)) {
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>,
"/models"
));
@@ -446,7 +446,7 @@ class App extends Component {
));
}
if (Setting.isAdminUser(this.state.account)) {
if (Setting.isLocalAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>,
"/tokens"
));
@@ -475,11 +475,13 @@ class App extends Component {
res.push(Setting.getItem(<Link to="/payments">{i18next.t("general:Payments")}</Link>,
"/payments"
));
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>,
"/sysinfo"
));
}
}
if (Setting.isAdminUser(this.state.account)) {
res.push(Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>,
"/sysinfo"
));
res.push(Setting.getItem(<a target="_blank" rel="noreferrer"
href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>,
"/swagger"
@@ -548,7 +550,7 @@ class App extends Component {
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:organizationName/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/chats" render={(props) => this.renderLoginIfNotLoggedIn(<ChatListPage account={this.state.account} {...props} />)} />
<Route exact path="/chats/:chatName" render={(props) => this.renderLoginIfNotLoggedIn(<ChatEditPage account={this.state.account} {...props} />)} />
<Route exact path="/chat" render={(props) => this.renderLoginIfNotLoggedIn(<ChatPage account={this.state.account} {...props} />)} />
@@ -649,7 +651,13 @@ class App extends Component {
textAlign: "center",
}
}>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
{
Conf.CustomFooter !== null ? Conf.CustomFooter : (
<React.Fragment>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
</React.Fragment>
)
}
</Footer>
</React.Fragment>
);

View File

@@ -112,7 +112,6 @@ class ApplicationEditPage extends React.Component {
UNSAFE_componentWillMount() {
this.getApplication();
this.getOrganizations();
this.getCerts();
this.getProviders();
this.getSamlMetadata();
}
@@ -126,6 +125,8 @@ class ApplicationEditPage extends React.Component {
this.setState({
application: application,
});
this.getCerts(application.organization);
});
}
@@ -144,8 +145,8 @@ class ApplicationEditPage extends React.Component {
});
}
getCerts() {
CertBackend.getCerts("admin")
getCerts(owner) {
CertBackend.getCerts(owner)
.then((res) => {
this.setState({
certs: (res.msg === undefined) ? res : [],
@@ -671,6 +672,27 @@ class ApplicationEditPage extends React.Component {
</Popover>
</Col>
</Row>
<Row>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Form CSS Mobile"), i18next.t("application:Form CSS Mobile - Tooltip"))} :
</Col>
<Col span={22}>
<Popover placement="right" content={
<div style={{width: "900px", height: "300px"}} >
<CodeMirror value={this.state.application.formCssMobile === "" ? template : this.state.application.formCssMobile}
options={{mode: "css", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {
this.updateApplicationField("formCssMobile", value);
}}
/>
</div>
} title={i18next.t("application:Form CSS Mobile - Edit")} trigger="click">
<Input value={this.state.application.formCssMobile} style={{marginBottom: "10px"}} onChange={e => {
this.updateApplicationField("formCssMobile", e.target.value);
}} />
</Popover>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Form position"), i18next.t("application:Form position - Tooltip"))} :
@@ -769,7 +791,7 @@ class ApplicationEditPage extends React.Component {
let signUpUrl = `/signup/${this.state.application.name}`;
let redirectUri;
if (this.state.application.redirectUris.length !== 0) {
if (this.state.application.redirectUris?.length > 0) {
redirectUri = this.state.application.redirectUris[0];
} else {
redirectUri = "\"ERROR: You must specify at least one Redirect URL in 'Redirect URLs'\"";

View File

@@ -65,6 +65,7 @@ class ApplicationListPage extends BaseListPage {
redirectUris: ["http://localhost:9000/callback"],
tokenFormat: "JWT",
expireInHours: 24 * 7,
refreshExpireInHours: 24 * 7,
formOffset: 2,
};
}
@@ -175,7 +176,7 @@ class ApplicationListPage extends BaseListPage {
// width: '600px',
render: (text, record, index) => {
const providers = text;
if (providers.length === 0) {
if (providers === null || providers.length === 0) {
return `(${i18next.t("general:empty")})`;
}
@@ -273,7 +274,7 @@ class ApplicationListPage extends BaseListPage {
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
(Setting.isAdminUser(this.props.account) ? ApplicationBackend.getApplications("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder) :
ApplicationBackend.getApplicationsByOrganization("admin", this.state.organizationName, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
ApplicationBackend.getApplicationsByOrganization("admin", this.props.account.organization.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -15,6 +15,7 @@
import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
import * as CertBackend from "./backend/CertBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import copy from "copy-to-clipboard";
@@ -29,17 +30,20 @@ class CertEditPage extends React.Component {
this.state = {
classes: props,
certName: props.match.params.certName,
owner: props.match.params.organizationName,
cert: null,
organizations: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
UNSAFE_componentWillMount() {
this.getCert();
this.getOrganizations();
}
getCert() {
CertBackend.getCert("admin", this.state.certName)
CertBackend.getCert(this.state.owner, this.state.certName)
.then((cert) => {
this.setState({
cert: cert,
@@ -47,6 +51,15 @@ class CertEditPage extends React.Component {
});
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
});
});
}
parseCertField(key, value) {
if (["port"].includes(key)) {
value = Setting.myParseInt(value);
@@ -75,6 +88,19 @@ class CertEditPage extends React.Component {
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.cert.owner} onChange={(value => {this.updateCertField("owner", value);})}>
{Setting.isAdminUser(this.props.account) ? <Option key={"admin"} value={"admin"}>{i18next.t("provider:admin (Shared)")}</Option> : null}
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -216,7 +242,7 @@ class CertEditPage extends React.Component {
submitCertEdit(willExist) {
const cert = Setting.deepCopy(this.state.cert);
CertBackend.updateCert(this.state.cert.owner, this.state.certName, cert)
CertBackend.updateCert(this.state.owner, this.state.certName, cert)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
@@ -227,7 +253,7 @@ class CertEditPage extends React.Component {
if (willExist) {
this.props.history.push("/certs");
} else {
this.props.history.push(`/certs/${this.state.cert.name}`);
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);

View File

@@ -23,10 +23,20 @@ import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./common/modal/PopconfirmModal";
class CertListPage extends BaseListPage {
constructor(props) {
super(props);
}
componentDidMount() {
this.setState({
owner: Setting.isAdminUser(this.props.account) ? "admin" : this.props.account.owner,
});
}
newCert() {
const randomName = Setting.getRandomName();
return {
owner: "admin", // this.props.account.certname,
owner: this.state.owner,
name: `cert_${randomName}`,
createdTime: moment().format(),
displayName: `New Cert - ${randomName}`,
@@ -45,7 +55,7 @@ class CertListPage extends BaseListPage {
CertBackend.addCert(newCert)
.then((res) => {
if (res.status === "ok") {
this.props.history.push({pathname: `/certs/${newCert.name}`, mode: "add"});
this.props.history.push({pathname: `/certs/${newCert.owner}/${newCert.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
@@ -86,12 +96,23 @@ class CertListPage extends BaseListPage {
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/certs/${text}`}>
<Link to={`/certs/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
},
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
@@ -168,8 +189,9 @@ class CertListPage extends BaseListPage {
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Button disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)} style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/certs/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal
disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)}
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteCert(index)}
>
@@ -214,7 +236,8 @@ class CertListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
CertBackend.getCerts("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
(Setting.isAdminUser(this.props.account) ? CertBackend.getGlobleCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: CertBackend.getCerts(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -99,7 +99,7 @@ class ChatEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.chat.organization} onChange={(value => {this.updateChatField("organization", value);})}
<Select virtual={false} disabled={!Setting.isAdminUser(this.props.account)} style={{width: "100%"}} value={this.state.chat.organization} onChange={(value => {this.updateChatField("organization", value);})}
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
} />
</Col>

View File

@@ -51,7 +51,7 @@ class ChatPage extends BaseListPage {
newMessage(text) {
const randomName = Setting.getRandomName();
return {
owner: "admin", // this.props.account.messagename,
owner: this.props.account.owner, // this.props.account.messagename,
name: `message_${randomName}`,
createdTime: moment().format(),
organization: this.props.account.owner,

View File

@@ -28,3 +28,5 @@ export const ThemeDefault = {
borderRadius: 6,
isCompact: false,
};
export const CustomFooter = null;

View File

@@ -94,7 +94,7 @@ class LdapSyncPage extends React.Component {
if (res.status === "ok") {
this.setState((prevState) => {
prevState.users = res.data.users;
prevState.existUuids = res.data2?.length > 0 ? res.data2 : [];
prevState.existUuids = res.data.existUuids?.length > 0 ? res.data.existUuids.filter(uuid => uuid !== "") : [];
return prevState;
});
} else {

View File

@@ -113,7 +113,7 @@ class MessageEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.message.organization} onChange={(value => {this.updateMessageField("organization", value);})}
<Select virtual={false} disabled={!Setting.isAdminUser(this.props.account)} style={{width: "100%"}} value={this.state.message.organization} onChange={(value => {this.updateMessageField("organization", value);})}
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
} />
</Col>

View File

@@ -209,7 +209,7 @@ class MessageListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
MessageBackend.getMessages("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
MessageBackend.getMessages("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -53,7 +53,7 @@ class ModelEditPage extends React.Component {
model: model,
});
this.getModels(model.owner);
this.getModels(model.organization);
});
}
@@ -107,7 +107,7 @@ class ModelEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.model.owner} onChange={(value => {this.updateModelField("owner", value);})}>
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.model.owner} onChange={(value => {this.updateModelField("owner", value);})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}

View File

@@ -41,7 +41,7 @@ class ModelListPage extends BaseListPage {
newModel() {
const randomName = Setting.getRandomName();
return {
owner: "built-in",
owner: this.props.account.owner,
name: `model_${randomName}`,
createdTime: moment().format(),
displayName: `New Model - ${randomName}`,
@@ -202,7 +202,7 @@ class ModelListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
ModelBackend.getModels("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
ModelBackend.getModels(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -24,6 +24,7 @@ import {LinkOutlined} from "@ant-design/icons";
import LdapTable from "./table/LdapTable";
import AccountTable from "./table/AccountTable";
import ThemeEditor from "./common/theme/ThemeEditor";
import MfaTable from "./table/MfaTable";
const {Option} = Select;
@@ -199,6 +200,22 @@ class OrganizationEditPage extends React.Component {
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Languages"), i18next.t("general:Languages - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}}
options={Setting.Countries.map((item) => {
return Setting.getOption(item.label, item.key);
})}
value={this.state.organization.languages ?? []}
onChange={(value => {
this.updateOrganizationField("languages", value);
})} >
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Default avatar"), i18next.t("general:Default avatar - Tooltip"))} :
@@ -258,22 +275,6 @@ 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:Languages"), i18next.t("general:Languages - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: "100%"}}
options={Setting.Countries.map((item) => {
return Setting.getOption(item.label, item.key);
})}
value={this.state.organization.languages ?? []}
onChange={(value => {
this.updateOrganizationField("languages", value);
})} >
</Select>
</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"))} :
@@ -316,6 +317,18 @@ 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:MFA items"), i18next.t("general:MFA items - Tooltip"))} :
</Col>
<Col span={22} >
<MfaTable
title={i18next.t("general:MFA items")}
table={this.state.organization.mfaItems ?? []}
onUpdateTable={(value) => {this.updateOrganizationField("mfaItems", value);}}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :

View File

@@ -40,7 +40,7 @@ class PaymentEditPage extends React.Component {
}
getPayment() {
PaymentBackend.getPayment("admin", this.state.paymentName)
PaymentBackend.getPayment(this.props.account.owner, this.state.paymentName)
.then((payment) => {
this.setState({
payment: payment,

View File

@@ -27,13 +27,13 @@ class PaymentListPage extends BaseListPage {
newPayment() {
const randomName = Setting.getRandomName();
return {
owner: "admin",
owner: this.props.account.owner,
name: `payment_${randomName}`,
createdTime: moment().format(),
displayName: `New Payment - ${randomName}`,
provider: "provider_pay_paypal",
type: "PayPal",
organization: "built-in",
organization: this.props.account.owner,
user: "admin",
productName: "computer-1",
productDisplayName: "A notebook computer",
@@ -265,7 +265,7 @@ class PaymentListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
PaymentBackend.getPayments("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
PaymentBackend.getPayments(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -33,7 +33,7 @@ class PaymentResultPage extends React.Component {
}
getPayment() {
PaymentBackend.getPayment("admin", this.state.paymentName)
PaymentBackend.getPayment(this.props.account.owner, this.state.paymentName)
.then((payment) => {
this.setState({
payment: payment,

View File

@@ -159,7 +159,7 @@ class PermissionEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.permission.owner} onChange={(owner => {
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.permission.owner} onChange={(owner => {
this.updatePermissionField("owner", owner);
this.getUsers(owner);
this.getRoles(owner);

View File

@@ -40,7 +40,7 @@ class ProductBuyPage extends React.Component {
return;
}
ProductBackend.getProduct("admin", this.state.productName)
ProductBackend.getProduct(this.props.account.owner, this.state.productName)
.then((product) => {
this.setState({
product: product,

View File

@@ -32,6 +32,7 @@ class ProductEditPage extends React.Component {
productName: props.match.params.productName,
product: null,
providers: [],
organizations: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
@@ -42,7 +43,7 @@ class ProductEditPage extends React.Component {
}
getProduct() {
ProductBackend.getProduct("admin", this.state.productName)
ProductBackend.getProduct(this.props.account.owner, this.state.productName)
.then((product) => {
this.setState({
product: product,
@@ -51,7 +52,7 @@ class ProductEditPage extends React.Component {
}
getPaymentProviders() {
ProviderBackend.getProviders("admin")
ProviderBackend.getProviders(this.props.account.owner)
.then((res) => {
this.setState({
providers: res.filter(provider => provider.category === "Payment"),
@@ -106,6 +107,18 @@ class ProductEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.product.owner} onChange={(value => {this.updateProductField("owner", value);})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :

View File

@@ -27,7 +27,7 @@ class ProductListPage extends BaseListPage {
newProduct() {
const randomName = Setting.getRandomName();
return {
owner: "admin",
owner: this.props.account.owner,
name: `product_${randomName}`,
createdTime: moment().format(),
displayName: `New Product - ${randomName}`,
@@ -94,6 +94,14 @@ class ProductListPage extends BaseListPage {
);
},
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("organization"),
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
@@ -282,7 +290,7 @@ class ProductListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
ProductBackend.getProducts("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
ProductBackend.getProducts(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -111,7 +111,10 @@ class ProviderListPage extends BaseListPage {
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("owner"),
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (text !== "admin") ? text : i18next.t("provider:admin (Shared)");
},
},
{
title: i18next.t("general:Created time"),
@@ -210,6 +213,7 @@ class ProviderListPage extends BaseListPage {
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteProvider(index)}
disabled={!Setting.isAdminUser(this.props.account) && (record.owner !== this.props.account.owner)}
>
</PopconfirmModal>
</div>
@@ -253,7 +257,7 @@ class ProviderListPage extends BaseListPage {
}
this.setState({loading: true});
(Setting.isAdminUser(this.props.account) ? ProviderBackend.getGlobalProviders(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: ProviderBackend.getProviders(this.state.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
: ProviderBackend.getProviders(this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -175,7 +175,7 @@ class RecordListPage extends BaseListPage {
];
if (Setting.isLocalAdminUser(this.props.account)) {
columns = columns.filter(column => column.key !== "name" && column.key !== "organization");
columns = columns.filter(column => column.key !== "name");
}
const paginationProps = {

View File

@@ -288,7 +288,7 @@ class ResourceListPage extends BaseListPage {
const field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
ResourceBackend.getResources(this.props.account.owner, this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
ResourceBackend.getResources("admin", this.props.account.name, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -111,7 +111,7 @@ class RoleEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.role.owner} onChange={(value => {this.updateRoleField("owner", value);})}
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.role.owner} onChange={(value => {this.updateRoleField("owner", value);})}
options={this.state.organizations.map((organization) => Setting.getOption(organization.name, organization.name))
} />
</Col>

View File

@@ -134,7 +134,7 @@ class SessionListPage extends BaseListPage {
value = params.contentType;
}
this.setState({loading: true});
SessionBackend.getSessions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
SessionBackend.getSessions(Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -325,19 +325,19 @@ export function isSignupItemPrompted(signupItem) {
}
export function getAllPromptedProviderItems(application) {
return application.providers.filter(providerItem => isProviderPrompted(providerItem));
return application.providers?.filter(providerItem => isProviderPrompted(providerItem));
}
export function getAllPromptedSignupItems(application) {
return application.signupItems.filter(signupItem => isSignupItemPrompted(signupItem));
return application.signupItems?.filter(signupItem => isSignupItemPrompted(signupItem));
}
export function getSignupItem(application, itemName) {
const signupItems = application.signupItems?.filter(signupItem => signupItem.name === itemName);
if (signupItems.length === 0) {
return null;
if (signupItems?.length > 0) {
return signupItems[0];
}
return signupItems[0];
return null;
}
export function isValidPersonName(personName) {
@@ -409,12 +409,12 @@ export function isAffiliationPrompted(application) {
export function hasPromptPage(application) {
const providerItems = getAllPromptedProviderItems(application);
if (providerItems.length !== 0) {
if (providerItems?.length > 0) {
return true;
}
const signupItems = getAllPromptedSignupItems(application);
if (signupItems.length !== 0) {
if (signupItems?.length > 0) {
return true;
}

View File

@@ -186,7 +186,7 @@ class SyncerEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.organization} onChange={(value => {this.updateSyncerField("organization", value);})}>
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account)} value={this.state.syncer.organization} onChange={(value => {this.updateSyncerField("organization", value);})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}

View File

@@ -29,7 +29,7 @@ class SyncerListPage extends BaseListPage {
owner: "admin",
name: `syncer_${randomName}`,
createdTime: moment().format(),
organization: "built-in",
organization: this.props.account.owner,
type: "Database",
host: "localhost",
port: 3306,
@@ -275,7 +275,7 @@ class SyncerListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
SyncerBackend.getSyncers("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
SyncerBackend.getSyncers("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

View File

@@ -33,14 +33,14 @@ class SystemInfo extends React.Component {
}
UNSAFE_componentWillMount() {
SystemBackend.getSystemInfo().then(res => {
SystemBackend.getSystemInfo("").then(res => {
this.setState({
systemInfo: res.data,
loading: false,
});
const id = setInterval(() => {
SystemBackend.getSystemInfo().then(res => {
SystemBackend.getSystemInfo("").then(res => {
this.setState({
systemInfo: res.data,
});

View File

@@ -94,7 +94,7 @@ class TokenEditPage extends React.Component {
{i18next.t("general:Organization")}:
</Col>
<Col span={22} >
<Input value={this.state.token.organization} onChange={e => {
<Input disabled={!Setting.isAdminUser(this.props.account)} value={this.state.token.organization} onChange={e => {
this.updateTokenField("organization", e.target.value);
}} />
</Col>

View File

@@ -30,7 +30,7 @@ class TokenListPage extends BaseListPage {
name: `token_${randomName}`,
createdTime: moment().format(),
application: "app-built-in",
organization: "built-in",
organization: this.props.account.owner,
user: "admin",
accessToken: "",
expiresIn: 7200,
@@ -240,7 +240,7 @@ class TokenListPage extends BaseListPage {
const field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
this.setState({loading: true});
TokenBackend.getTokens("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
TokenBackend.getTokens("admin", Setting.isAdminUser(this.props.account) ? "" : this.props.account.owner, params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({

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