Compare commits

...

15 Commits

Author SHA1 Message Date
b525210835 feat: destroy session after delete user (#1441)
* fix: destroy session after delete user

* feat: visual session

* fix: go lint

* feat: add translation

* feat: auto flush after offline

* fix: delete one session

* fix: move 403 page to baseListPage
2023-01-06 15:04:13 +08:00
4ab2ca7a25 feat: fix checkPermissionForUpdateUser() logic (#1454)
* fix: fix `checkPermissionForUpdateUser()` logic

* fix: fix `checkPermissionForUpdateUser()` logic
2023-01-06 00:03:40 +08:00
dcf148fb7f fix: add GetMaskedRoles and GetMaskedPermissions when GetAccount (#1456) 2023-01-06 00:02:52 +08:00
c8846f1a2d feat: fix translate bug in UpdateUser() (#1451)
* fix: fix translate error

* fix translate bug in UpdateUser()

* Delete DiscordLoginButton.js
2023-01-04 22:54:50 +08:00
0559298d6c feat: extend user with roles and permissions in GetAccount (#1449) 2023-01-04 20:23:57 +08:00
ddb5e26fcd fix: mask user in get-account response (#1450) 2023-01-04 18:40:36 +08:00
Liu
1f39027b78 fix: convert line endings to LF on checkout for all envs (#1448)
* Convert line endings to LF on checkout for all envs

* fix: convert line endings to LF on checkout for all envs
2023-01-04 18:36:38 +08:00
eae3b0d367 feat: fix saml login failed by using oauth (#1443) 2023-01-03 19:42:12 +08:00
186f0ac97b feat: check permission when update user (#1438)
* feat: check permission when update user

* feat: check permission when update user

* fix: fix organization accountItem modifyRule

* fix: fix organization accountItem modifyRule
2023-01-02 09:27:25 +08:00
308f305c53 feat: add query and fragment response mode declare in OIDC (#1439) 2023-01-01 21:46:12 +08:00
d498bc60ce feat: edit user properties (#1435) 2022-12-31 15:27:53 +08:00
7bbe1e38c1 fix: fix translate error (#1432)
* fix:fix translate error

* Delete TelegramLoginButton.js

* Update data.json

* Update data.json

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2022-12-30 12:10:18 +08:00
f465fc6ce0 feat: support changing theme in antd 5 (#1430)
* feat: add global theme change function

* feat: add icons

* feat: in app theme changer

* feat: use antd built-in themes

* fix: multiple styling problem

* fix: theme init from localstorage

* feat: dark mode footer

* feat: casdoor logo color theme

* feat: select theme box icon adaptive to theme

* fix: menu bar style

* fix: language box style

* feat: translation

* feat: update translation of select theme box without reloading

* fix: mobile view

* fix: better structured select theme box

* feat: add compact icon

* fix: redundant theme fetch

* fix: redundant theme fetch

* fix: various styling problems
2022-12-29 22:30:37 +08:00
c952c2f2f4 feat: fix login with password bug when feature is disabled (#1428) 2022-12-27 14:46:57 +08:00
86ae97d1e5 feat: fix the bug that spin is always showing when response error (#1424) 2022-12-24 17:55:36 +08:00
62 changed files with 12968 additions and 11807 deletions

5
.gitattributes vendored
View File

@ -1,2 +1,5 @@
*.go linguist-detectable=true *.go linguist-detectable=true
*.js linguist-detectable=false *.js linguist-detectable=false
# Declare files that will always have LF line endings on checkout.
# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows.
*.sh text eol=lf

View File

@ -242,6 +242,7 @@ func (c *ApiController) Signup() {
// @router /logout [get,post] // @router /logout [get,post]
func (c *ApiController) Logout() { func (c *ApiController) Logout() {
user := c.GetSessionUsername() user := c.GetSessionUsername()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication() application := c.GetSessionApplication()
@ -271,12 +272,17 @@ func (c *ApiController) GetAccount() {
user = object.ExtendManagedAccountsWithUser(user) user = object.ExtendManagedAccountsWithUser(user)
} }
object.ExtendUserWithRolesAndPermissions(user)
user.Permissions = object.GetMaskedPermissions(user.Permissions)
user.Roles = object.GetMaskedRoles(user.Roles)
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user)) organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
resp := Response{ resp := Response{
Status: "ok", Status: "ok",
Sub: user.Id, Sub: user.Id,
Name: user.Name, Name: user.Name,
Data: user, Data: object.GetMaskedUser(user),
Data2: organization, Data2: organization,
} }
c.Data["json"] = resp c.Data["json"] = resp

View File

@ -103,12 +103,12 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = tokenToResponse(token) resp = tokenToResponse(token)
} }
} else if form.Type == ResponseTypeSaml { // saml flow } else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host) res, redirectUrl, method, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
} }
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl} resp = &Response{Status: "ok", Msg: "", Data: res, Data2: map[string]string{"redirectUrl": redirectUrl, "method": method}}
} else if form.Type == ResponseTypeCas { } else if form.Type == ResponseTypeCas {
// not oauth but CAS SSO protocol // not oauth but CAS SSO protocol
service := c.Input().Get("service") service := c.Input().Get("service")
@ -139,6 +139,10 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
}) })
} }
if resp.Status == "ok" {
object.SetSession(user.GetId(), c.Ctx.Input.CruSession.SessionID())
}
return resp return resp
} }
@ -265,6 +269,10 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application)) c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), form.Application))
return return
} }
if !application.EnablePassword {
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
return
}
if object.CheckToEnableCaptcha(application) { if object.CheckToEnableCaptcha(application) {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret) isHuman, err := captcha.VerifyCaptchaByCaptchaType(form.CaptchaType, form.CaptchaToken, form.ClientSecret)

68
controllers/session.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// DeleteSession
// @Title DeleteSession
// @Tag Session API
// @Description Delete session by userId
// @Param ID query string true "The ID(owner/name) of user."
// @Success 200 {array} string The Response object
// @router /delete-session [post]
func (c *ApiController) DeleteSession() {
var session object.Session
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteSession(util.GetId(session.Owner, session.Name)))
c.ServeJSON()
}
// GetSessions
// @Title GetSessions
// @Tag Session API
// @Description Get organization user sessions
// @Param owner query string true "The organization name"
// @Success 200 {array} string The Response object
// @router /get-sessions [get]
func (c *ApiController) GetSessions() {
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")
owner := c.Input().Get("owner")
if limit == "" || page == "" {
c.Data["json"] = object.GetSessions(owner)
c.ServeJSON()
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSessionCount(owner, field, value)))
sessions := object.GetPaginationSessions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(sessions, paginator.Nums())
}
}

View File

@ -125,6 +125,127 @@ func (c *ApiController) GetUser() {
c.ServeJSON() c.ServeJSON()
} }
func checkPermissionForUpdateUser(id string, newUser object.User, c *ApiController) (bool, string) {
oldUser := object.GetUser(id)
org := object.GetOrganizationByUser(oldUser)
var itemsChanged []*object.AccountItem
if oldUser.Owner != newUser.Owner {
item := object.GetAccountItemByName("Organization", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Name != newUser.Name {
item := object.GetAccountItemByName("Name", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Id != newUser.Id {
item := object.GetAccountItemByName("ID", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.DisplayName != newUser.DisplayName {
item := object.GetAccountItemByName("Display name", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Avatar != newUser.Avatar {
item := object.GetAccountItemByName("Avatar", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Type != newUser.Type {
item := object.GetAccountItemByName("User type", org)
itemsChanged = append(itemsChanged, item)
}
// The password is *** when not modified
if oldUser.Password != newUser.Password && newUser.Password != "***" {
item := object.GetAccountItemByName("Password", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Email != newUser.Email {
item := object.GetAccountItemByName("Email", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Phone != newUser.Phone {
item := object.GetAccountItemByName("Phone", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Region != newUser.Region {
item := object.GetAccountItemByName("Country/Region", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Location != newUser.Location {
item := object.GetAccountItemByName("Location", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Affiliation != newUser.Affiliation {
item := object.GetAccountItemByName("Affiliation", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Title != newUser.Title {
item := object.GetAccountItemByName("Title", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Homepage != newUser.Homepage {
item := object.GetAccountItemByName("Homepage", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Bio != newUser.Bio {
item := object.GetAccountItemByName("Bio", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Tag != newUser.Tag {
item := object.GetAccountItemByName("Tag", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.SignupApplication != newUser.SignupApplication {
item := object.GetAccountItemByName("Signup application", org)
itemsChanged = append(itemsChanged, item)
}
oldUserRolesJson, _ := json.Marshal(oldUser.Roles)
newUserRolesJson, _ := json.Marshal(newUser.Roles)
if string(oldUserRolesJson) != string(newUserRolesJson) {
item := object.GetAccountItemByName("Roles", org)
itemsChanged = append(itemsChanged, item)
}
oldUserPermissionJson, _ := json.Marshal(oldUser.Permissions)
newUserPermissionJson, _ := json.Marshal(newUser.Permissions)
if string(oldUserPermissionJson) != string(newUserPermissionJson) {
item := object.GetAccountItemByName("Permissions", org)
itemsChanged = append(itemsChanged, item)
}
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
item := object.GetAccountItemByName("Properties", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsAdmin != newUser.IsAdmin {
item := object.GetAccountItemByName("Is admin", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
item := object.GetAccountItemByName("Is global admin", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsForbidden != newUser.IsForbidden {
item := object.GetAccountItemByName("Is forbidden", org)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsDeleted != newUser.IsDeleted {
item := object.GetAccountItemByName("Is deleted", org)
itemsChanged = append(itemsChanged, item)
}
for i := range itemsChanged {
if pass, err := object.CheckAccountItemModifyRule(itemsChanged[i], c.getCurrentUser(), c.GetAcceptLanguage()); !pass {
return pass, err
}
}
return true, ""
}
// UpdateUser // UpdateUser
// @Title UpdateUser // @Title UpdateUser
// @Tag User API // @Tag User API
@ -159,6 +280,12 @@ func (c *ApiController) UpdateUser() {
} }
isGlobalAdmin := c.IsGlobalAdmin() isGlobalAdmin := c.IsGlobalAdmin()
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
c.ResponseError(err)
return
}
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin) affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
if affected { if affected {
object.UpdateUserToOriginalDatabase(&user) object.UpdateUserToOriginalDatabase(&user)

View File

@ -25,6 +25,7 @@
"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": "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", "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": "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",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -25,6 +25,7 @@
"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": "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", "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": "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",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -25,6 +25,7 @@
"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": "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", "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": "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",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -25,6 +25,7 @@
"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": "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", "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": "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",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -25,6 +25,7 @@
"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": "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", "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": "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",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -25,6 +25,7 @@
"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": "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", "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": "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",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -25,6 +25,7 @@
"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": "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", "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": "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",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist", "The application: %s does not exist": "The application: %s does not exist",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "The provider type: %s is not supported", "The provider type: %s is not supported": "The provider type: %s is not supported",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application", "The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator", "The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",

View File

@ -25,13 +25,14 @@
"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": "提供商账户: %s 与用户名: %s (%s) 不存在且 不允许注册新账户, 请联系IT支持", "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": "提供商账户: %s 与用户名: %s (%s) 不存在且 不允许注册新账户, 请联系IT支持",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定: %s (%s)", "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "提供商账户: %s 与用户名: %s (%s) 已经与其他账户绑定: %s (%s)",
"The application: %s does not exist": "应用 %s 不存在", "The application: %s does not exist": "应用 %s 不存在",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider type: %s is not supported": "不支持该类型的提供商: %s", "The provider type: %s is not supported": "不支持该类型的提供商: %s",
"The provider: %s is not enabled for the application": "提供商: %s 未被启用", "The provider: %s is not enabled for the application": "提供商: %s 未被启用",
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登陆,请联系管理员", "The user is forbidden to sign in, please contact the administrator": "该用户被禁止登陆,请联系管理员",
"The user: %s/%s doesn't exist": "用户不存在: %s/%s", "The user: %s/%s doesn't exist": "用户不存在: %s/%s",
"Turing test failed.": "真人验证失败", "Turing test failed.": "真人验证失败",
"Unauthorized operation": "未授权的操作", "Unauthorized operation": "未授权的操作",
"Unknown authentication type (not password or provider), form = %s": "未授权的操作" "Unknown authentication type (not password or provider), form = %s": "未知的认证类型(非密码或第三方提供商):%s"
}, },
"cas": { "cas": {
"Service %s and %s do not match": "服务 %s 与 %s 不匹配" "Service %s and %s do not match": "服务 %s 与 %s 不匹配"
@ -89,7 +90,7 @@
"You can't unlink yourself, you are not a member of any application": "您无法取消链接,您不是任何应用程序的成员" "You can't unlink yourself, you are not a member of any application": "您无法取消链接,您不是任何应用程序的成员"
}, },
"organization": { "organization": {
"Only admin can modify the %s.": "您无法取消链接,您不是任何应用程序的成员", "Only admin can modify the %s.": "仅允许管理员可以修改 %s",
"The %s is immutable.": "%s是不可变的", "The %s is immutable.": "%s是不可变的",
"Unknown modify rule %s.": "未知的修改规则" "Unknown modify rule %s.": "未知的修改规则"
}, },

View File

@ -222,6 +222,11 @@ func (a *Adapter) createTable() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(Session))
if err != nil {
panic(err)
}
} }
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session { func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {

View File

@ -76,7 +76,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend), JwksUri: fmt.Sprintf("%s/.well-known/jwks", originBackend),
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend), IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"}, ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
ResponseModesSupported: []string{"login", "code", "link"}, ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
GrantTypesSupported: []string{"password", "authorization_code"}, GrantTypesSupported: []string{"password", "authorization_code"},
SubjectTypesSupported: []string{"public"}, SubjectTypesSupported: []string{"public"},
IdTokenSigningAlgValuesSupported: []string{"RS256"}, IdTokenSigningAlgValuesSupported: []string{"RS256"},

View File

@ -269,3 +269,12 @@ func ContainsAsterisk(userId string, users []string) bool {
return containsAsterisk return containsAsterisk
} }
func GetMaskedPermissions(permissions []*Permission) []*Permission {
for _, permission := range permissions {
permission.Users = nil
permission.Submitter = ""
}
return permissions
}

View File

@ -192,3 +192,11 @@ func roleChangeTrigger(oldName string, newName string) error {
return session.Commit() return session.Commit()
} }
func GetMaskedRoles(roles []*Role) []*Role {
for _, role := range roles {
role.Users = nil
}
return roles
}

View File

@ -223,11 +223,14 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
// GetSamlResponse generates a SAML2.0 response // GetSamlResponse generates a SAML2.0 response
// parameter samlRequest is saml request in base64 format // parameter samlRequest is saml request in base64 format
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, error) { func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
// request type
method := "GET"
// base64 decode // base64 decode
defated, err := base64.StdEncoding.DecodeString(samlRequest) defated, err := base64.StdEncoding.DecodeString(samlRequest)
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
// decompress // decompress
var buffer bytes.Buffer var buffer bytes.Buffer
@ -236,12 +239,12 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
var authnRequest saml.AuthnRequest var authnRequest saml.AuthnRequest
err = xml.Unmarshal(buffer.Bytes(), &authnRequest) err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
// verify samlRequest // verify samlRequest
if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid { if isValid := application.IsRedirectUriValid(authnRequest.Issuer.Url); !isValid {
return "", "", fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url) return "", "", method, fmt.Errorf("err: Issuer URI: %s doesn't exist in the allowed Redirect URI list", authnRequest.Issuer.Url)
} }
// get certificate string // get certificate string
@ -253,6 +256,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
// redirect Url (Assertion Consumer Url) // redirect Url (Assertion Consumer Url)
if application.SamlReplyUrl != "" { if application.SamlReplyUrl != "" {
method = "POST"
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
} }
@ -275,7 +279,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
doc.SetRoot(samlResponse) doc.SetRoot(samlResponse)
xmlBytes, err := doc.WriteToBytes() xmlBytes, err := doc.WriteToBytes()
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
// compress // compress
@ -283,7 +287,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
flated := bytes.NewBuffer(nil) flated := bytes.NewBuffer(nil)
writer, err := flate.NewWriter(flated, flate.DefaultCompression) writer, err := flate.NewWriter(flated, flate.DefaultCompression)
if err != nil { if err != nil {
return "", "", fmt.Errorf("err: %s", err.Error()) return "", "", method, fmt.Errorf("err: %s", err.Error())
} }
writer.Write(xmlBytes) writer.Write(xmlBytes)
writer.Close() writer.Close()
@ -291,7 +295,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
} }
// base64 encode // base64 encode
res := base64.StdEncoding.EncodeToString(xmlBytes) res := base64.StdEncoding.EncodeToString(xmlBytes)
return res, authnRequest.AssertionConsumerServiceURL, nil return res, authnRequest.AssertionConsumerServiceURL, method, nil
} }
// NewSamlResponse11 return a saml1.1 response(not 2.0) // NewSamlResponse11 return a saml1.1 response(not 2.0)

132
object/session.go Normal file
View File

@ -0,0 +1,132 @@
// Copyright 2022 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 (
"time"
"github.com/beego/beego"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type Session struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
SessionId []string `json:"sessionId"`
}
func SetSession(id string, sessionId string) {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
session := &Session{Owner: owner, Name: name}
get, err := adapter.Engine.Get(session)
if err != nil {
panic(err)
}
session.SessionId = append(session.SessionId, sessionId)
if get {
_, err = adapter.Engine.ID(core.PK{owner, name}).Update(session)
} else {
session.CreatedTime = time.Now().Format(time.RFC3339)
_, err = adapter.Engine.Insert(session)
}
if err != nil {
panic(err)
}
}
func DeleteSession(id string) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
session := &Session{Owner: owner, Name: name}
_, err := adapter.Engine.ID(core.PK{owner, name}).Get(session)
if err != nil {
return false
}
DeleteBeegoSession(session.SessionId)
affected, err := adapter.Engine.ID(core.PK{owner, name}).Delete(session)
return affected != 0
}
func DeleteSessionId(id string, sessionId string) bool {
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
session := &Session{Owner: owner, Name: name}
_, err := adapter.Engine.ID(core.PK{owner, name}).Get(session)
if err != nil {
return false
}
DeleteBeegoSession([]string{sessionId})
session.SessionId = util.DeleteVal(session.SessionId, sessionId)
if len(session.SessionId) < 1 {
affected, _ := adapter.Engine.ID(core.PK{owner, name}).Delete(session)
return affected != 0
} else {
affected, _ := adapter.Engine.ID(core.PK{owner, name}).Update(session)
return affected != 0
}
}
func DeleteBeegoSession(sessionIds []string) {
for _, sessionId := range sessionIds {
err := beego.GlobalSessions.GetProvider().SessionDestroy(sessionId)
if err != nil {
return
}
}
}
func GetSessions(owner string) []*Session {
sessions := []*Session{}
var err error
if owner != "" {
err = adapter.Engine.Desc("created_time").Where("owner = ?", owner).Find(&sessions)
} else {
err = adapter.Engine.Desc("created_time").Find(&sessions)
}
if err != nil {
panic(err)
}
return sessions
}
func GetPaginationSessions(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Session {
sessions := []*Session{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&sessions)
if err != nil {
panic(err)
}
return sessions
}
func GetSessionCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Session{})
if err != nil {
panic(err)
}
return int(count)
}

View File

@ -529,6 +529,9 @@ func AddUsersInBatch(users []*User) bool {
} }
func DeleteUser(user *User) bool { func DeleteUser(user *User) bool {
// Forced offline the user first
DeleteSession(user.GetId())
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{}) affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Delete(&User{})
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -164,6 +164,9 @@ func initAPI() {
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter") beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord") beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
beego.Router("/api/delete-session", &controllers.ApiController{}, "POST:DeleteSession")
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks") beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook") beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook") beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook")

25
util/slice.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2022 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 util
func DeleteVal(values []string, val string) []string {
newValues := []string{}
for _, v := range values {
if v != val {
newValues = append(newValues, v)
}
}
return newValues
}

View File

@ -9,7 +9,7 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"antd": "5.0.5", "antd": "5.1.2",
"codemirror": "^5.61.1", "codemirror": "^5.61.1",
"copy-to-clipboard": "^3.3.1", "copy-to-clipboard": "^3.3.1",
"core-js": "^3.25.0", "core-js": "^3.25.0",

View File

@ -170,7 +170,7 @@ class AccountTable extends React.Component {
} }
let options; let options;
if (record.viewRule === "Admin") { if (record.viewRule === "Admin" || record.name === "Is admin" || record.name === "Is global admin") {
options = [ options = [
{id: "Admin", name: "Admin"}, {id: "Admin", name: "Admin"},
{id: "Immutable", name: "Immutable"}, {id: "Immutable", name: "Immutable"},

View File

@ -260,6 +260,13 @@ class AdapterListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -17,7 +17,7 @@ import "./App.less";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons"; import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd"; import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, theme} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom"; import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage"; import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage"; import OrganizationEditPage from "./OrganizationEditPage";
@ -69,6 +69,8 @@ import SystemInfo from "./SystemInfo";
import AdapterListPage from "./AdapterListPage"; import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage"; import AdapterEditPage from "./AdapterEditPage";
import {withTranslation} from "react-i18next"; import {withTranslation} from "react-i18next";
import SelectThemeBox from "./SelectThemeBox";
import SessionListPage from "./SessionListPage";
const {Header, Footer, Content} = Layout; const {Header, Footer, Content} = Layout;
@ -81,6 +83,8 @@ class App extends Component {
account: undefined, account: undefined,
uri: null, uri: null,
menuVisible: false, menuVisible: false,
themeAlgorithm: null,
logo: null,
}; };
Setting.initServerUrl(); Setting.initServerUrl();
@ -95,6 +99,16 @@ class App extends Component {
this.getAccount(); this.getAccount();
} }
componentDidMount() {
localStorage.getItem("theme") ?
this.setState({"themeAlgorithm": this.getTheme()}) : this.setState({"themeAlgorithm": theme.defaultAlgorithm});
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
addEventListener("themeChange", (e) => {
this.setState({"themeAlgorithm": this.getTheme()});
this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))});
});
}
componentDidUpdate() { componentDidUpdate() {
// eslint-disable-next-line no-restricted-globals // eslint-disable-next-line no-restricted-globals
const uri = location.pathname; const uri = location.pathname;
@ -184,6 +198,10 @@ class App extends Component {
return ""; return "";
} }
getTheme() {
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"];
}
setLanguage(account) { setLanguage(account) {
const language = account?.language; const language = account?.language;
if (language !== "" && language !== i18next.language) { if (language !== "" && language !== i18next.language) {
@ -404,6 +422,10 @@ class App extends Component {
"/tokens" "/tokens"
)); ));
res.push(Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>,
"/sessions"
));
res.push(Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, res.push(Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>,
"/webhooks" "/webhooks"
)); ));
@ -465,54 +487,63 @@ class App extends Component {
renderRouter() { renderRouter() {
return ( return (
<div> <ConfigProvider theme={{
<Switch> token: {
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} /> colorPrimary: "rgb(89,54,213)",
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} /> colorInfo: "rgb(89,54,213)",
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} /> },
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} /> algorithm: this.state.themeAlgorithm,
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} /> }}>
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} /> <div>
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} /> <Switch>
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} /> <Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} /> <Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} /> <Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} /> <Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} /> <Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} /> <Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} /> <Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} /> <Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} /> <Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} /> <Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} /> <Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} /> <Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} /> <Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/} <Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} /> <Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} /> <Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} /> <Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} /> <Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} /> <Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} /> <Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} /> {/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} /> <Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} /> <Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} /> <Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} /> <Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} /> <Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} /> <Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} /> <Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} /> <Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} /> <Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} /> <Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} /> <Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} /> <Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")} <Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} /> <Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
</Switch> <Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
</div> <Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>
</div>
</ConfigProvider>
); );
} }
@ -532,30 +563,27 @@ class App extends Component {
if (!Setting.isMobile()) { if (!Setting.isMobile()) {
return ( return (
<Layout id="parent-area"> <Layout id="parent-area">
<Header style={{marginBottom: "3px", paddingInline: 0}}> <Header style={{marginBottom: "3px", paddingInline: 0, backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
{ {
Setting.isMobile() ? null : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
<div className="logo" /> <div className="logo" style={{background: `url(${this.state.logo})`}} />
</Link> </Link>
) )
} }
<div> <Menu
<Menu items={this.renderMenu()}
// theme="dark" mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
items={this.renderMenu()} selectedKeys={[`${this.state.selectedMenuKey}`]}
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"} style={{position: "absolute", left: "145px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}
selectedKeys={[`${this.state.selectedMenuKey}`]} />
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}} {
> this.renderAccount()
</Menu> }
{ {this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
this.renderAccount() {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header> </Header>
<Content style={{backgroundColor: "#f5f5f5", alignItems: "stretch", display: "flex", flexDirection: "column"}}> <Content style={{alignItems: "stretch", display: "flex", flexDirection: "column"}}>
<Card className="content-warp-card"> <Card className="content-warp-card">
{ {
this.renderRouter() this.renderRouter()
@ -570,11 +598,11 @@ class App extends Component {
} else { } else {
return ( return (
<Layout> <Layout>
<Header style={{padding: "0", marginBottom: "3px"}}> <Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
{ {
Setting.isMobile() ? null : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
<div className="logo" /> <div className="logo" style={{background: `url(${this.state.logo})`}} />
</Link> </Link>
) )
} }
@ -592,12 +620,11 @@ class App extends Component {
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text"> <Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
{i18next.t("general:Menu")} {i18next.t("general:Menu")}
</Button> </Button>
<div style = {{float: "right"}}> {
{ this.renderAccount()
this.renderAccount() }
} {this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />} {this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
</div>
</Header> </Header>
<Content style={{display: "flex", flexDirection: "column"}} >{ <Content style={{display: "flex", flexDirection: "column"}} >{
this.renderRouter()} this.renderRouter()}
@ -617,12 +644,10 @@ class App extends Component {
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />} {!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
<Footer id="footer" style={ <Footer id="footer" style={
{ {
borderTop: "1px solid #e8e8e8",
backgroundColor: "white",
textAlign: "center", textAlign: "center",
} }
}> }>
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a> Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
</Footer> </Footer>
</React.Fragment> </React.Fragment>
); );
@ -690,6 +715,7 @@ class App extends Component {
colorPrimary: "rgb(89,54,213)", colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)",
}, },
algorithm: this.state.themeAlgorithm,
}}> }}>
{ {
this.renderPage() this.renderPage()
@ -711,6 +737,7 @@ class App extends Component {
colorPrimary: "rgb(89,54,213)", colorPrimary: "rgb(89,54,213)",
colorInfo: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)",
}, },
algorithm: this.state.themeAlgorithm,
}}> }}>
{ {
this.renderPage() this.renderPage()

View File

@ -13,14 +13,12 @@
} }
.App-header { .App-header {
background-color: #282c34;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
color: white;
} }
.App-link { .App-link {
@ -41,7 +39,6 @@ img {
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
min-height: 100vh; min-height: 100vh;
background-color: #f5f5f5;
} }
.panel-logo { .panel-logo {
@ -55,7 +52,7 @@ img {
background-repeat: no-repeat; background-repeat: no-repeat;
border-radius: 5px; border-radius: 5px;
width: 45px; width: 45px;
height: 65px; height: 100%;
float: right; float: right;
cursor: pointer; cursor: pointer;
@ -64,9 +61,32 @@ img {
} }
} }
.login-form .language-box {
height: 65px;
}
.theme-box {
background: url("@{StaticBaseUrl}/img/muti_language.svg");
background-size: 25px, 25px;
background-position: center !important;
background-repeat: no-repeat !important;
border-radius: 5px;
width: 45px;
height: 100%;
float: right;
cursor: pointer;
&:hover {
background-color: #f5f5f5 !important;
}
}
.rightDropDown { .rightDropDown {
border-radius: 7px;
&:hover { &:hover {
background-color: #f5f5f5; background-color: #f5f5f5;
color: black;
} }
} }
@ -128,3 +148,9 @@ img {
background-size: 100% 100%; background-size: 100% 100%;
background-attachment: fixed; background-attachment: fixed;
} }
.ant-menu-horizontal {
border-bottom: none !important;
margin-right: 30px;
right: 230px;
}

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Popover, Radio, Row, Select, Switch, Upload} from "antd"; import {Button, Card, Col, Input, Popover, Radio, Result, Row, Select, Switch, Upload} from "antd";
import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons"; import {CopyOutlined, LinkOutlined, UploadOutlined} from "@ant-design/icons";
import * as ApplicationBackend from "./backend/ApplicationBackend"; import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as CertBackend from "./backend/CertBackend"; import * as CertBackend from "./backend/CertBackend";
@ -103,6 +103,7 @@ class ApplicationEditPage extends React.Component {
uploading: false, uploading: false,
mode: props.location.mode !== undefined ? props.location.mode : "edit", mode: props.location.mode !== undefined ? props.location.mode : "edit",
samlMetadata: null, samlMetadata: null,
isAuthorized: true,
}; };
} }
@ -129,9 +130,15 @@ class ApplicationEditPage extends React.Component {
getOrganizations() { getOrganizations() {
OrganizationBackend.getOrganizations("admin") OrganizationBackend.getOrganizations("admin")
.then((res) => { .then((res) => {
this.setState({ if (res?.status === "error") {
organizations: (res.msg === undefined) ? res : [], this.setState({
}); isAuthorized: false,
});
} else {
this.setState({
organizations: (res.msg === undefined) ? res : [],
});
}
}); });
} }
@ -838,6 +845,17 @@ class ApplicationEditPage extends React.Component {
} }
render() { render() {
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
{ {

View File

@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Col, List, Popconfirm, Row, Table, Tooltip} from "antd"; import {Button, Col, List, Popconfirm, Result, Row, Table, Tooltip} from "antd";
import {EditOutlined} from "@ant-design/icons"; import {EditOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@ -36,6 +36,7 @@ class ApplicationListPage extends BaseListPage {
loading: false, loading: false,
searchText: "", searchText: "",
searchedColumn: "", searchedColumn: "",
isAuthorized: true,
}; };
} }
@ -258,6 +259,17 @@ class ApplicationListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
}; };
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={applications} rowKey="name" size="middle" bordered pagination={paginationProps}
@ -292,6 +304,13 @@ class ApplicationListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -13,9 +13,10 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Input, Space} from "antd"; import {Button, Input, Result, Space} from "antd";
import {SearchOutlined} from "@ant-design/icons"; import {SearchOutlined} from "@ant-design/icons";
import Highlighter from "react-highlight-words"; import Highlighter from "react-highlight-words";
import i18next from "i18next";
class BaseListPage extends React.Component { class BaseListPage extends React.Component {
constructor(props) { constructor(props) {
@ -127,6 +128,17 @@ class BaseListPage extends React.Component {
}; };
render() { render() {
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
{ {

View File

@ -227,6 +227,13 @@ class CertListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -200,6 +200,13 @@ class ModelListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -251,7 +251,7 @@ class OrganizationListPage extends BaseListPage {
<Result <Result
status="403" status="403"
title="403 Unauthorized" title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page.")} subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/> />
); );

View File

@ -278,6 +278,13 @@ class PaymentListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -358,6 +358,13 @@ class PermissionListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -295,6 +295,13 @@ class ProductListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Popconfirm, Table} from "antd"; import {Button, Popconfirm, Result, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as ProviderBackend from "./backend/ProviderBackend"; import * as ProviderBackend from "./backend/ProviderBackend";
@ -36,6 +36,7 @@ class ProviderListPage extends BaseListPage {
loading: false, loading: false,
searchText: "", searchText: "",
searchedColumn: "", searchedColumn: "",
isAuthorized: true,
}; };
} }
newProvider() { newProvider() {
@ -227,6 +228,17 @@ class ProviderListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
}; };
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={providers} rowKey="name" size="middle" bordered pagination={paginationProps}
@ -268,6 +280,13 @@ class ProviderListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -222,6 +222,13 @@ class RecordListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.data.includes("Please login first")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Popconfirm, Table, Upload} from "antd"; import {Button, Popconfirm, Result, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import copy from "copy-to-clipboard"; import copy from "copy-to-clipboard";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@ -37,6 +37,7 @@ class ResourceListPage extends BaseListPage {
searchedColumn: "", searchedColumn: "",
fileList: [], fileList: [],
uploading: false, uploading: false,
isAuthorized: true,
}; };
} }
@ -272,6 +273,17 @@ class ResourceListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
}; };
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={resources} rowKey="name" size="middle" bordered pagination={paginationProps}
@ -308,6 +320,13 @@ class ResourceListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.data.includes("Please login first")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -231,6 +231,13 @@ class RoleListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

81
web/src/SelectThemeBox.js Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2021 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.
import React from "react";
import * as Setting from "./Setting";
import {Dropdown} from "antd";
import "./App.less";
import i18next from "i18next";
function themeIcon(themeKey) {
return <img width={24} alt={themeKey} src={getLogoURL(themeKey)} />;
}
function getLogoURL(themeKey) {
if (themeKey) {
return Setting.Themes.find(t => t.key === themeKey)["selectThemeLogo"];
} else {
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["selectThemeLogo"];
}
}
class SelectThemeBox extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
themes: props.theme ?? ["Default", "Dark", "Compact"],
icon: null,
};
}
items = this.getThemes();
componentDidMount() {
i18next.on("languageChanged", () => {
this.items = this.getThemes();
});
localStorage.getItem("theme") ? this.setState({"icon": getLogoURL()}) : this.setState({"icon": getLogoURL("Default")});
addEventListener("themeChange", (e) => {
this.setState({"icon": getLogoURL()});
});
}
getThemes() {
return Setting.Themes.map((theme) => Setting.getItem(i18next.t(`general:${theme.label}`), theme.key, themeIcon(theme.key)));
}
getOrganizationThemes(themes) {
const select = [];
for (const theme of themes) {
this.items.map((item, index) => item.key === theme ? select.push(item) : null);
}
return select;
}
render() {
const themeItems = this.getOrganizationThemes(this.state.themes);
const onClick = (e) => {
Setting.setTheme(e.key);
};
return (
<Dropdown menu={{items: themeItems, onClick}} >
<div className="theme-box" style={{display: themeItems.length === 0 ? "none" : null, background: `url(${this.state.icon})`, ...this.props.style}} />
</Dropdown>
);
}
}
export default SelectThemeBox;

162
web/src/SessionListPage.js Normal file
View File

@ -0,0 +1,162 @@
// Copyright 2022 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.
import BaseListPage from "./BaseListPage";
import * as Setting from "./Setting";
import i18next from "i18next";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Table, Tag} from "antd";
import React from "react";
import * as SessionBackend from "./backend/SessionBackend";
class SessionListPage extends BaseListPage {
deleteSession(i) {
SessionBackend.deleteSession(this.state.data[i])
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
renderTable(sessions) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "150px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "organization",
width: "110px",
sorter: true,
...this.getColumnSearchProps("organization"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Created time"),
dataIndex: "createdTime",
key: "createdTime",
width: "180px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Session ID"),
dataIndex: "sessionId",
key: "sessionId",
width: "180px",
sorter: true,
render: (text, record, index) => {
return text.map((item, index) =>
<Tag key={index}>{item}</Tag>
);
},
},
{
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "70px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Popconfirm
title={`Sure to delete session: ${record.name} ?`}
onConfirm={() => this.deleteSession(index)}
>
<Button style={{marginBottom: "10px"}} type="primary" danger>{i18next.t("general:Delete")}</Button>
</Popconfirm>
</div>
);
},
},
];
const paginationProps = {
total: this.state.pagination.total,
showQuickJumper: true,
showSizeChanger: true,
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
};
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={sessions} rowKey="name" size="middle" bordered pagination={paginationProps}
loading={this.state.loading}
onChange={this.handleTableChange}
/>
</div>
);
}
fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
if (params.contentType !== undefined && params.contentType !== null) {
field = "contentType";
value = params.contentType;
}
this.setState({loading: true});
SessionBackend.getSessions("", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
if (res.status === "ok") {
this.setState({
loading: false,
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};
}
export default SessionListPage;

View File

@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Tag, Tooltip, message} from "antd"; import {Tag, Tooltip, message, theme} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons"; import {QuestionCircleTwoTone} from "@ant-design/icons";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
import "./i18n"; import "./i18n";
@ -43,6 +43,13 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
{label: "Русский", key: "ru", country: "RU", alt: "Русский"}, {label: "Русский", key: "ru", country: "RU", alt: "Русский"},
]; ];
const {defaultAlgorithm, darkAlgorithm, compactAlgorithm} = theme;
export const Themes = [{label: i18next.t("general:Dark"), key: "Dark", style: darkAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/dark.svg`},
{label: i18next.t("general:Compact"), key: "Compact", style: compactAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/compact.svg`},
{label: i18next.t("general:Default"), key: "Default", style: defaultAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/light.svg`},
];
export const OtherProviderInfo = { export const OtherProviderInfo = {
SMS: { SMS: {
"Aliyun SMS": { "Aliyun SMS": {
@ -562,6 +569,14 @@ export function getAvatarColor(s) {
return colorList[hash % 4]; return colorList[hash % 4];
} }
export function getLogo(theme) {
if (theme === "Dark") {
return `${StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`;
} else {
return `${StaticBaseUrl}/img/casdoor-logo_1185x256.png`;
}
}
export function getLanguageText(text) { export function getLanguageText(text) {
if (!text.includes("|")) { if (!text.includes("|")) {
return text; return text;
@ -587,6 +602,11 @@ export function setLanguage(language) {
i18next.changeLanguage(language); i18next.changeLanguage(language);
} }
export function setTheme(themeKey) {
localStorage.setItem("theme", themeKey);
dispatchEvent(new Event("themeChange"));
}
export function getAcceptLanguage() { export function getAcceptLanguage() {
if (i18next.language === null || i18next.language === "") { if (i18next.language === null || i18next.language === "") {
return "en;q=0.9,en;q=0.8"; return "en;q=0.9,en;q=0.8";

View File

@ -288,6 +288,13 @@ class SyncerListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -253,6 +253,13 @@ class TokenListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -28,11 +28,7 @@ import SamlWidget from "./common/SamlWidget";
import SelectRegionBox from "./SelectRegionBox"; import SelectRegionBox from "./SelectRegionBox";
import WebAuthnCredentialTable from "./WebauthnCredentialTable"; import WebAuthnCredentialTable from "./WebauthnCredentialTable";
import ManagedAccountTable from "./ManagedAccountTable"; import ManagedAccountTable from "./ManagedAccountTable";
import PropertyTable from "./propertyTable";
import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css";
require("codemirror/theme/material-darker.css");
require("codemirror/mode/javascript/javascript");
const {Option} = Select; const {Option} = Select;
@ -490,13 +486,10 @@ class UserEditPage extends React.Component {
return ( return (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("user:Properties")}: {Setting.getLabel(i18next.t("user:Properties"), i18next.t("user:Properties - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<CodeMirror <PropertyTable properties={this.state.user.properties} onUpdateTable={(value) => {this.updateUserField("properties", value);}} />
value={JSON.stringify(this.state.user.properties, null, 4)}
options={{mode: "javascript", theme: "material-darker"}}
/>
</Col> </Col>
</Row> </Row>
); );
@ -507,7 +500,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch disabled={this.state.user.owner === "built-in"} checked={this.state.user.isAdmin} onChange={checked => { <Switch disabled={disabled} checked={this.state.user.isAdmin} onChange={checked => {
this.updateUserField("isAdmin", checked); this.updateUserField("isAdmin", checked);
}} /> }} />
</Col> </Col>
@ -520,7 +513,7 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} : {Setting.getLabel(i18next.t("user:Is global admin"), i18next.t("user:Is global admin - Tooltip"))} :
</Col> </Col>
<Col span={(Setting.isMobile()) ? 22 : 2} > <Col span={(Setting.isMobile()) ? 22 : 2} >
<Switch disabled={this.state.user.owner === "built-in"} checked={this.state.user.isGlobalAdmin} onChange={checked => { <Switch disabled={disabled} checked={this.state.user.isGlobalAdmin} onChange={checked => {
this.updateUserField("isGlobalAdmin", checked); this.updateUserField("isGlobalAdmin", checked);
}} /> }} />
</Col> </Col>

View File

@ -14,7 +14,7 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table, Upload} from "antd"; import {Button, Popconfirm, Result, Switch, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
@ -38,6 +38,7 @@ class UserListPage extends BaseListPage {
loading: false, loading: false,
searchText: "", searchText: "",
searchedColumn: "", searchedColumn: "",
isAuthorized: true,
}; };
} }
@ -369,6 +370,17 @@ class UserListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total), showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
}; };
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page or logged in status invalid.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return ( return (
<div> <div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps} <Table scroll={{x: "max-content"}} columns={columns} dataSource={users} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
@ -411,6 +423,13 @@ class UserListPage extends BaseListPage {
if (users.length > 0) { if (users.length > 0) {
this.getOrganization(users[0].owner); this.getOrganization(users[0].owner);
} }
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
} else { } else {
@ -432,6 +451,13 @@ class UserListPage extends BaseListPage {
if (users.length > 0) { if (users.length > 0) {
this.getOrganization(users[0].owner); this.getOrganization(users[0].owner);
} }
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
} }

View File

@ -253,6 +253,13 @@ class WebhookListPage extends BaseListPage {
searchText: params.searchText, searchText: params.searchText,
searchedColumn: params.searchedColumn, searchedColumn: params.searchedColumn,
}); });
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
} }
}); });
}; };

View File

@ -20,6 +20,7 @@ import * as Util from "./Util";
import {authConfig} from "./Auth"; import {authConfig} from "./Auth";
import * as Setting from "../Setting"; import * as Setting from "../Setting";
import i18next from "i18next"; import i18next from "i18next";
import RedirectForm from "../common/RedirectForm";
class AuthCallback extends React.Component { class AuthCallback extends React.Component {
constructor(props) { constructor(props) {
@ -27,6 +28,9 @@ class AuthCallback extends React.Component {
this.state = { this.state = {
classes: props, classes: props,
msg: null, msg: null,
samlResponse: "",
relayState: "",
redirectUrl: "",
}; };
} }
@ -164,9 +168,17 @@ class AuthCallback extends React.Component {
const from = innerParams.get("from"); const from = innerParams.get("from");
Setting.goToLinkSoft(this, from); Setting.goToLinkSoft(this, from);
} else if (responseType === "saml") { } else if (responseType === "saml") {
const SAMLResponse = res.data; if (res.data2.method === "POST") {
const redirectUri = res.data2; this.setState({
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`); samlResponse: res.data,
redirectUrl: res.data2.redirectUrl,
relayState: oAuthParams.relayState,
});
} else {
const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
}
} }
} else { } else {
this.setState({ this.setState({
@ -177,6 +189,10 @@ class AuthCallback extends React.Component {
} }
render() { render() {
if (this.state.samlResponse !== "") {
return <RedirectForm samlResponse={this.state.samlResponse} redirectUrl={this.state.redirectUrl} relayState={this.state.relayState} />;
}
return ( return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}> <div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
{ {

View File

@ -40,6 +40,7 @@ class CasLogout extends React.Component {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", "Logged out successfully"); Setting.showMessage("success", "Logged out successfully");
this.props.onUpdateAccount(null); this.props.onUpdateAccount(null);
this.onUpdateApplication(null);
const redirectUri = res.data2; const redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") { if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri); Setting.goToLink(redirectUri);
@ -49,6 +50,7 @@ class CasLogout extends React.Component {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`); Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
} }
} else { } else {
this.onUpdateApplication(null);
Setting.showMessage("error", `Failed to log out: ${res.msg}`); Setting.showMessage("error", `Failed to log out: ${res.msg}`);
} }
}); });

View File

@ -102,6 +102,7 @@ class LoginPage extends React.Component {
}); });
} else { } else {
// Setting.showMessage("error", res.msg); // Setting.showMessage("error", res.msg);
this.onUpdateApplication(null);
this.setState({ this.setState({
application: res.data, application: res.data,
msg: res.msg, msg: res.msg,
@ -133,6 +134,7 @@ class LoginPage extends React.Component {
applicationName: res.data.name, applicationName: res.data.name,
}); });
} else { } else {
this.onUpdateApplication(null);
Setting.showMessage("error", res.msg); Setting.showMessage("error", res.msg);
} }
}); });
@ -318,15 +320,15 @@ class LoginPage extends React.Component {
const accessToken = res.data; const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`); Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "saml") { } else if (responseType === "saml") {
const SAMLResponse = res.data; if (res.data2.method === "POST") {
const redirectUri = res.data2;
if (this.state.application.assertionConsumerUrl !== "") {
this.setState({ this.setState({
samlResponse: res.data, samlResponse: res.data,
redirectUrl: res.data2, redirectUrl: res.data2.redirectUrl,
relayState: oAuthParams.relayState, relayState: oAuthParams.relayState,
}); });
} else { } else {
const SAMLResponse = res.data;
const redirectUri = res.data2.redirectUrl;
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`); Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
} }
} }
@ -614,13 +616,13 @@ class LoginPage extends React.Component {
this.sendSilentSigninData("signing-in"); this.sendSilentSigninData("signing-in");
const values = {}; const values = {};
values["application"] = this.state.application.name; values["application"] = application.name;
this.onFinish(values); this.onFinish(values);
} }
if (application.enableAutoSignin) { if (application.enableAutoSignin) {
const values = {}; const values = {};
values["application"] = this.state.application.name; values["application"] = application.name;
this.onFinish(values); this.onFinish(values);
} }
@ -635,7 +637,7 @@ class LoginPage extends React.Component {
<br /> <br />
<SelfLoginButton account={this.props.account} onClick={() => { <SelfLoginButton account={this.props.account} onClick={() => {
const values = {}; const values = {};
values["application"] = this.state.application.name; values["application"] = application.name;
this.onFinish(values); this.onFinish(values);
}} /> }} />
<br /> <br />

View File

@ -0,0 +1,36 @@
// Copyright 2022 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.
import * as Setting from "../Setting";
export function getSessions(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-sessions?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function deleteSession(session) {
return fetch(`${Setting.ServerUrl}/api/delete-session`, {
method: "POST",
credentials: "include",
body: JSON.stringify(session),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}

View File

@ -28,7 +28,7 @@ code {
.logo { .logo {
background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png"); background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png");
background-size: 130px, 27px; background-size: 130px, 27px !important;
width: 130px; width: 130px;
height: 27px; height: 27px;
margin: 17px 0 16px 15px; margin: 17px 0 16px 15px;
@ -45,7 +45,3 @@ code {
.ant-list-sm .ant-list-item { .ant-list-sm .ant-list-item {
padding: 2px !important; padding: 2px !important;
} }
.ant-layout-header {
background-color: white !important;
}

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client-IP", "Client IP": "Client-IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Erstellte Zeit", "Created time": "Erstellte Zeit",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Standard Avatar", "Default avatar": "Standard Avatar",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Speichern", "Save": "Speichern",
"Save & Exit": "Speichern & Beenden", "Save & Exit": "Speichern & Beenden",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Anmelde-URL", "Signin URL": "Anmelde-URL",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "Registrierungs-URL", "Signup URL": "Registrierungs-URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Die von Ihnen besuchte Seite existiert leider nicht.", "Sorry, the page you visited does not exist.": "Die von Ihnen besuchte Seite existiert leider nicht.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -723,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Ist globaler Admin", "Is global admin": "Ist globaler Admin",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Standort", "Location": "Standort",
"Location - Tooltip": "Standort - Tooltip", "Location - Tooltip": "Standort - Tooltip",
@ -738,6 +744,7 @@
"Password Set": "Passwort setzen", "Password Set": "Passwort setzen",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Eigenschaften", "Properties": "Eigenschaften",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Neu erneut eingeben", "Re-enter New": "Neu erneut eingeben",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Telefon zurücksetzen...", "Reset Phone...": "Telefon zurücksetzen...",
@ -753,6 +760,7 @@
"Unlink": "Link aufheben", "Unlink": "Link aufheben",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Foto hochladen", "Upload a photo": "Foto hochladen",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "Passwort eingeben" "input password": "Passwort eingeben"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Created time", "Created time": "Created time",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Save", "Save": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Signin URL", "Signin URL": "Signin URL",
"Signin URL - Tooltip": "Signin URL - Tooltip", "Signin URL - Tooltip": "Signin URL - Tooltip",
"Signup URL": "Signup URL", "Signup URL": "Signup URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.", "Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -723,6 +728,7 @@
"Is forbidden - Tooltip": "Is forbidden - Tooltip", "Is forbidden - Tooltip": "Is forbidden - Tooltip",
"Is global admin": "Is global admin", "Is global admin": "Is global admin",
"Is global admin - Tooltip": "Is global admin - Tooltip", "Is global admin - Tooltip": "Is global admin - Tooltip",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "Location - Tooltip", "Location - Tooltip": "Location - Tooltip",
@ -738,6 +744,7 @@
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Reset Phone...", "Reset Phone...": "Reset Phone...",
@ -753,6 +760,7 @@
"Unlink": "Unlink", "Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo", "Upload a photo": "Upload a photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "input password" "input password": "input password"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "IP du client", "Client IP": "IP du client",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Date de création", "Created time": "Date de création",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Avatar par défaut", "Default avatar": "Avatar par défaut",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Enregistrer", "Save": "Enregistrer",
"Save & Exit": "Enregistrer & Quitter", "Save & Exit": "Enregistrer & Quitter",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "URL de connexion", "Signin URL": "URL de connexion",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "URL d'inscription", "Signup URL": "URL d'inscription",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.", "Sorry, the page you visited does not exist.": "Désolé, la page que vous avez visitée n'existe pas.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Désolé, vous n'avez pas la permission d'accéder à cette page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -723,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Est un administrateur global", "Is global admin": "Est un administrateur global",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Lier", "Link": "Lier",
"Location": "Localisation", "Location": "Localisation",
"Location - Tooltip": "Localisation - Infobulle", "Location - Tooltip": "Localisation - Infobulle",
@ -738,6 +744,7 @@
"Password Set": "Mot de passe défini", "Password Set": "Mot de passe défini",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Propriétés", "Properties": "Propriétés",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Nouvelle entrée", "Re-enter New": "Nouvelle entrée",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Réinitialiser le téléphone...", "Reset Phone...": "Réinitialiser le téléphone...",
@ -753,6 +760,7 @@
"Unlink": "Délier", "Unlink": "Délier",
"Upload (.xlsx)": "Télécharger (.xlsx)", "Upload (.xlsx)": "Télécharger (.xlsx)",
"Upload a photo": "Télécharger une photo", "Upload a photo": "Télécharger une photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "saisir le mot de passe" "input password": "saisir le mot de passe"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "クライアント IP", "Client IP": "クライアント IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "作成日時", "Created time": "作成日時",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "デフォルトのアバター", "Default avatar": "デフォルトのアバター",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "保存", "Save": "保存",
"Save & Exit": "保存して終了", "Save & Exit": "保存して終了",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "サインインURL", "Signin URL": "サインインURL",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "サインアップURL", "Signup URL": "サインアップURL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "申し訳ありませんが、訪問したページは存在しません。", "Sorry, the page you visited does not exist.": "申し訳ありませんが、訪問したページは存在しません。",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "申し訳ありませんが、このページにアクセスする権限がありません。", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -723,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "グローバル管理者", "Is global admin": "グローバル管理者",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "リンク", "Link": "リンク",
"Location": "場所", "Location": "場所",
"Location - Tooltip": "場所 → ツールチップ", "Location - Tooltip": "場所 → ツールチップ",
@ -738,6 +744,7 @@
"Password Set": "パスワード設定", "Password Set": "パスワード設定",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "プロパティー", "Properties": "プロパティー",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "新しい入力", "Re-enter New": "新しい入力",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "電話番号をリセット...", "Reset Phone...": "電話番号をリセット...",
@ -753,6 +760,7 @@
"Unlink": "リンクを解除", "Unlink": "リンクを解除",
"Upload (.xlsx)": "アップロード (.xlsx)", "Upload (.xlsx)": "アップロード (.xlsx)",
"Upload a photo": "写真をアップロード", "Upload a photo": "写真をアップロード",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "パスワードを入力" "input password": "パスワードを入力"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Click to Upload", "Click to Upload": "Click to Upload",
"Client IP": "Client IP", "Client IP": "Client IP",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Created time", "Created time": "Created time",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Default avatar", "Default avatar": "Default avatar",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Save", "Save": "Save",
"Save & Exit": "Save & Exit", "Save & Exit": "Save & Exit",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "Signin URL", "Signin URL": "Signin URL",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "Signup URL", "Signup URL": "Signup URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.", "Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Sorry, you do not have permission to access this page.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -723,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Is global admin", "Is global admin": "Is global admin",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Link", "Link": "Link",
"Location": "Location", "Location": "Location",
"Location - Tooltip": "Location - Tooltip", "Location - Tooltip": "Location - Tooltip",
@ -738,6 +744,7 @@
"Password Set": "Password Set", "Password Set": "Password Set",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Properties", "Properties": "Properties",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Re-enter New", "Re-enter New": "Re-enter New",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Reset Phone...", "Reset Phone...": "Reset Phone...",
@ -753,6 +760,7 @@
"Unlink": "Unlink", "Unlink": "Unlink",
"Upload (.xlsx)": "Upload (.xlsx)", "Upload (.xlsx)": "Upload (.xlsx)",
"Upload a photo": "Upload a photo", "Upload a photo": "Upload a photo",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "input password" "input password": "input password"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "Нажмите здесь, чтобы загрузить", "Click to Upload": "Нажмите здесь, чтобы загрузить",
"Client IP": "IP клиента", "Client IP": "IP клиента",
"Close": "Close", "Close": "Close",
"Compact": "Compact",
"Created time": "Время создания", "Created time": "Время создания",
"Dark": "Dark",
"Default": "Default",
"Default application": "Default application", "Default application": "Default application",
"Default application - Tooltip": "Default application - Tooltip", "Default application - Tooltip": "Default application - Tooltip",
"Default avatar": "Аватар по умолчанию", "Default avatar": "Аватар по умолчанию",
@ -233,6 +236,8 @@
"Roles - Tooltip": "Roles - Tooltip", "Roles - Tooltip": "Roles - Tooltip",
"Save": "Сохранить", "Save": "Сохранить",
"Save & Exit": "Сохранить и выйти", "Save & Exit": "Сохранить и выйти",
"Session ID": "Session ID",
"Sessions": "Sessions",
"Signin URL": "URL входа", "Signin URL": "URL входа",
"Signin URL - Tooltip": "sign in url", "Signin URL - Tooltip": "sign in url",
"Signup URL": "URL регистрации", "Signup URL": "URL регистрации",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "Signup application - Tooltip", "Signup application - Tooltip": "Signup application - Tooltip",
"Sorry, the page you visited does not exist.": "Извините, посещенная вами страница не существует.", "Sorry, the page you visited does not exist.": "Извините, посещенная вами страница не существует.",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "Sorry, the user you visited does not exist or you are not authorized to access this user.",
"Sorry, you do not have permission to access this page.": "Извините, вы не имеете права доступа к этой странице.", "Sorry, you do not have permission to access this page or logged in status invalid.": "Sorry, you do not have permission to access this page or logged in status invalid.",
"State": "State", "State": "State",
"State - Tooltip": "State - Tooltip", "State - Tooltip": "State - Tooltip",
"Successfully added": "Successfully added", "Successfully added": "Successfully added",
@ -723,6 +728,7 @@
"Is forbidden - Tooltip": "Whether the account is disabled", "Is forbidden - Tooltip": "Whether the account is disabled",
"Is global admin": "Глобальный администратор", "Is global admin": "Глобальный администратор",
"Is global admin - Tooltip": "Is the application global administrator", "Is global admin - Tooltip": "Is the application global administrator",
"Keys": "Keys",
"Link": "Ссылка", "Link": "Ссылка",
"Location": "Местоположение", "Location": "Местоположение",
"Location - Tooltip": "Расположение - Подсказка", "Location - Tooltip": "Расположение - Подсказка",
@ -738,6 +744,7 @@
"Password Set": "Пароль установлен", "Password Set": "Пароль установлен",
"Please select avatar from resources": "Please select avatar from resources", "Please select avatar from resources": "Please select avatar from resources",
"Properties": "Свойства", "Properties": "Свойства",
"Properties - Tooltip": "Properties - Tooltip",
"Re-enter New": "Введите еще раз", "Re-enter New": "Введите еще раз",
"Reset Email...": "Reset Email...", "Reset Email...": "Reset Email...",
"Reset Phone...": "Сбросить телефон...", "Reset Phone...": "Сбросить телефон...",
@ -753,6 +760,7 @@
"Unlink": "Отвязать", "Unlink": "Отвязать",
"Upload (.xlsx)": "Загрузить (.xlsx)", "Upload (.xlsx)": "Загрузить (.xlsx)",
"Upload a photo": "Загрузить фото", "Upload a photo": "Загрузить фото",
"Values": "Values",
"WebAuthn credentials": "WebAuthn credentials", "WebAuthn credentials": "WebAuthn credentials",
"input password": "пароль для ввода" "input password": "пароль для ввода"
}, },

View File

@ -156,7 +156,10 @@
"Click to Upload": "点击上传", "Click to Upload": "点击上传",
"Client IP": "客户端IP", "Client IP": "客户端IP",
"Close": "关闭", "Close": "关闭",
"Compact": "紧凑",
"Created time": "创建时间", "Created time": "创建时间",
"Dark": "黑暗",
"Default": "默认",
"Default application": "默认应用", "Default application": "默认应用",
"Default application - Tooltip": "默认应用", "Default application - Tooltip": "默认应用",
"Default avatar": "默认头像", "Default avatar": "默认头像",
@ -233,6 +236,8 @@
"Roles - Tooltip": "角色", "Roles - Tooltip": "角色",
"Save": "保存", "Save": "保存",
"Save & Exit": "保存 & 退出", "Save & Exit": "保存 & 退出",
"Session ID": "会话 ID",
"Sessions": "会话",
"Signin URL": "登录URL", "Signin URL": "登录URL",
"Signin URL - Tooltip": "用户的登录地址", "Signin URL - Tooltip": "用户的登录地址",
"Signup URL": "注册URL", "Signup URL": "注册URL",
@ -241,7 +246,7 @@
"Signup application - Tooltip": "表示用户注册时通过哪个应用注册的", "Signup application - Tooltip": "表示用户注册时通过哪个应用注册的",
"Sorry, the page you visited does not exist.": "抱歉,您访问的页面不存在", "Sorry, the page you visited does not exist.": "抱歉,您访问的页面不存在",
"Sorry, the user you visited does not exist or you are not authorized to access this user.": "抱歉,您访问的用户不存在或您无权访问该用户", "Sorry, the user you visited does not exist or you are not authorized to access this user.": "抱歉,您访问的用户不存在或您无权访问该用户",
"Sorry, you do not have permission to access this page.": "抱歉,您无权访问该页面", "Sorry, you do not have permission to access this page or logged in status invalid.": "抱歉,您无权访问该页面或登录状态失效",
"State": "状态", "State": "状态",
"State - Tooltip": "状态", "State - Tooltip": "状态",
"Successfully added": "添加成功", "Successfully added": "添加成功",
@ -723,6 +728,7 @@
"Is forbidden - Tooltip": "账户是否已被禁用", "Is forbidden - Tooltip": "账户是否已被禁用",
"Is global admin": "是全局管理员", "Is global admin": "是全局管理员",
"Is global admin - Tooltip": "是应用程序管理员", "Is global admin - Tooltip": "是应用程序管理员",
"Keys": "键",
"Link": "绑定", "Link": "绑定",
"Location": "城市", "Location": "城市",
"Location - Tooltip": "居住地址所在的城市", "Location - Tooltip": "居住地址所在的城市",
@ -738,6 +744,7 @@
"Password Set": "密码已设置", "Password Set": "密码已设置",
"Please select avatar from resources": "从资源中选择...", "Please select avatar from resources": "从资源中选择...",
"Properties": "属性", "Properties": "属性",
"Properties - Tooltip": "属性",
"Re-enter New": "重复新密码", "Re-enter New": "重复新密码",
"Reset Email...": "重置邮箱...", "Reset Email...": "重置邮箱...",
"Reset Phone...": "重置手机号...", "Reset Phone...": "重置手机号...",
@ -753,6 +760,7 @@
"Unlink": "解绑", "Unlink": "解绑",
"Upload (.xlsx)": "上传(.xlsx", "Upload (.xlsx)": "上传(.xlsx",
"Upload a photo": "上传头像", "Upload a photo": "上传头像",
"Values": "值",
"WebAuthn credentials": "WebAuthn凭据", "WebAuthn credentials": "WebAuthn凭据",
"input password": "输入密码" "input password": "输入密码"
}, },

131
web/src/propertyTable.js Normal file
View File

@ -0,0 +1,131 @@
// Copyright 2022 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.
import React from "react";
import {Button, Input, Table} from "antd";
import i18next from "i18next";
import {DeleteOutlined} from "@ant-design/icons";
import * as Setting from "./Setting";
class PropertyTable extends React.Component {
constructor(props) {
super(props);
this.state = {
properties: [],
count: Object.entries(this.props.properties).length,
};
// transfer the Object to object[]
Object.entries(this.props.properties).map((item, index) => {
this.state.properties.push({key: index, name: item[0], value: item[1]});
});
}
page = 1;
updateTable(table) {
this.setState({properties: table});
const properties = {};
table.map((item) => {
properties[item.name] = item.value;
});
this.props.onUpdateTable(properties);
}
addRow(table) {
const row = {key: this.state.count, name: "", value: ""};
if (table === undefined) {
table = [];
}
table = Setting.addRow(table, row);
this.setState({count: this.state.count + 1});
this.updateTable(table);
}
deleteRow(table, index) {
table = Setting.deleteRow(table, this.getIndex(index));
this.updateTable(table);
}
getIndex(index) {
// Parameter is the row index in table, need to calculate the index in dataSource. 10 is the pageSize.
return index + (this.page - 1) * 10;
}
updateField(table, index, key, value) {
table[this.getIndex(index)][key] = value;
this.updateTable(table);
}
renderTable(table) {
const columns = [
{
title: i18next.t("user:Keys"),
dataIndex: "name",
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "name", e.target.value);
}} />
);
},
},
{
title: i18next.t("user:Values"),
dataIndex: "value",
width: "200px",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "value", e.target.value);
}} />
);
},
},
{
title: i18next.t("general:Action"),
dataIndex: "operation",
width: "20px",
render: (text, record, index) => {
return (
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
);
},
},
];
return (
<Table title={() => (
<div>
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
pagination={{onChange: page => {this.page = page;}}}
columns={columns} dataSource={table} rowKey="key" size="middle" bordered
/>
);
}
render() {
return (
<React.Fragment>
{
this.renderTable(this.state.properties)
}
</React.Fragment>
);
}
}
export default PropertyTable;

File diff suppressed because it is too large Load Diff