Compare commits

...

8 Commits

Author SHA1 Message Date
UsherFall
aab6a799fe fix: use client secret field for providers (#2355)
* feat: fix key exposure problem

* fix display bug
2023-09-24 18:35:58 +08:00
haiwu
b94d06fb07 feat: add some Radius protocol code (#2351)
* feat: add radius server

* feat: parse org from packet

* feat: add comment

* Update main.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-09-24 16:50:31 +08:00
Yang Luo
f9cc6ed064 Add groups to role 2023-09-24 10:17:18 +08:00
Yang Luo
4cc9137637 Improve permission, adapter page UI 2023-09-24 09:56:06 +08:00
Yang Luo
d145ab780c feat: fix wrong elements in getPermissionsByUser() related functions 2023-09-24 09:13:54 +08:00
Yang Luo
687830697e Refactor getPermissionsAndRolesByUser() related code 2023-09-24 08:08:32 +08:00
Yang Luo
111d1a5786 Use UserInfo's ID in OAuth login 2023-09-23 00:13:13 +08:00
Yang Luo
775dd9eb57 Improve email provider error handling and fix bug 2023-09-21 23:11:58 +08:00
25 changed files with 378 additions and 91 deletions

View File

@@ -20,6 +20,7 @@ staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false
batchSize = 100
ldapServerPort = 389
radiusServerPort = 1812
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataFile = "./init_data.json"

View File

@@ -615,11 +615,16 @@ func (c *ApiController) Login() {
return
}
userId := userInfo.Id
if userId == "" {
userId = util.GenerateId()
}
user = &object.User{
Owner: application.Organization,
Name: userInfo.Username,
CreatedTime: util.GetCurrentTime(),
Id: util.GenerateId(),
Id: userId,
Type: "normal-user",
DisplayName: userInfo.DisplayName,
Avatar: userInfo.AvatarUrl,

View File

@@ -123,7 +123,9 @@ func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
bodyBuffer := bytes.NewBuffer(postBody)
req, err := http.NewRequest("POST", a.Endpoint+sendEmailEndpoint+"?api-version="+apiVersion, bodyBuffer)
endpoint := strings.TrimSuffix(a.Endpoint, "/")
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
req, err := http.NewRequest("POST", url, bodyBuffer)
if err != nil {
return fmt.Errorf("error creating AzureACS API request: %s", err)
}
@@ -149,7 +151,7 @@ func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
defer resp.Body.Close()
// Response error Handling
if resp.StatusCode == http.StatusBadRequest {
if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
commError := ErrorResponse{}
err = json.NewDecoder(resp.Body).Decode(&commError)

View File

@@ -18,9 +18,9 @@ type EmailProvider interface {
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
}
func GetEmailProvider(typ string, clientId string, clientSecret string, appId string, host string, port int, disableSsl bool) EmailProvider {
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool) EmailProvider {
if typ == "Azure ACS" {
return NewAzureACSEmailProvider(appId, host)
return NewAzureACSEmailProvider(clientSecret, host)
} else {
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
}

1
go.mod
View File

@@ -66,6 +66,7 @@ require (
google.golang.org/api v0.138.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 // indirect
maunium.net/go/mautrix v0.16.0
modernc.org/sqlite v1.18.2
)

3
go.sum
View File

@@ -1912,6 +1912,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -2768,6 +2769,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 h1:2NDro2Jzkrqfngy/sA5GVnChs7fx8EzcQKFi/lI2cfg=
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68/go.mod h1:pFWM9De99EY9TPVyHIyA56QmoRViVck/x41WFkUlc9A=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=

View File

@@ -25,6 +25,7 @@ import (
"github.com/casdoor/casdoor/ldap"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/radius"
"github.com/casdoor/casdoor/routers"
"github.com/casdoor/casdoor/util"
)
@@ -81,6 +82,7 @@ func main() {
logs.SetLogFuncCall(false)
go ldap.StartLdapServer()
go radius.StartRadiusServer()
go object.ClearThroughputPerSecond()
beego.Run(fmt.Sprintf(":%v", port))

View File

@@ -21,7 +21,7 @@ import (
"maunium.net/go/mautrix/id"
)
func NewMatrixProvider(userId string, roomId string, accessToken string, homeServer string) (*notify.Notify, error) {
func NewMatrixProvider(userId string, accessToken string, roomId string, homeServer string) (*notify.Notify, error) {
matrixSrv, err := matrix.New(id.UserID(userId), id.RoomID(roomId), homeServer, accessToken)
if err != nil {
return nil, err

View File

@@ -18,27 +18,27 @@ import "github.com/casdoor/notify"
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
if typ == "Telegram" {
return NewTelegramProvider(appId, receiver)
return NewTelegramProvider(clientSecret, receiver)
} else if typ == "Custom HTTP" {
return NewCustomHttpProvider(receiver, method, title)
} else if typ == "DingTalk" {
return NewDingTalkProvider(appId, receiver)
return NewDingTalkProvider(clientId, clientSecret)
} else if typ == "Lark" {
return NewLarkProvider(receiver)
return NewLarkProvider(clientSecret)
} else if typ == "Microsoft Teams" {
return NewMicrosoftTeamsProvider(receiver)
return NewMicrosoftTeamsProvider(clientSecret)
} else if typ == "Bark" {
return NewBarkProvider(receiver)
return NewBarkProvider(clientSecret)
} else if typ == "Pushover" {
return NewPushoverProvider(appId, receiver)
return NewPushoverProvider(clientSecret, receiver)
} else if typ == "Pushbullet" {
return NewPushbulletProvider(appId, receiver)
return NewPushbulletProvider(clientSecret, receiver)
} else if typ == "Slack" {
return NewSlackProvider(appId, receiver)
return NewSlackProvider(clientSecret, receiver)
} else if typ == "Webpush" {
return NewWebpushProvider(clientId, clientSecret, receiver)
} else if typ == "Discord" {
return NewDiscordProvider(appId, receiver)
return NewDiscordProvider(clientSecret, receiver)
} else if typ == "Google Chat" {
return NewGoogleChatProvider(metaData)
} else if typ == "Line" {

View File

@@ -36,7 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
}
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.AppId, provider.Host, provider.Port, provider.DisableSsl)
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl)
fromAddress := provider.ClientId2
if fromAddress == "" {

View File

@@ -226,7 +226,7 @@ func GetGroupUserCount(groupId string, field, value string) (int64, error) {
} else {
return ormer.Engine.Table("user").
Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
}
}
@@ -247,7 +247,7 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
}
if field != "" && value != "" {
session = session.And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%")
session = session.And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%")
}
if sortField == "" || sortOrder == "" {

View File

@@ -258,9 +258,59 @@ func DeletePermission(permission *Permission) (bool, error) {
return affected != 0, nil
}
func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) {
func getPermissionsByUser(userId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
res := []*Permission{}
for _, permission := range permissions {
if util.InSlice(permission.Users, userId) {
res = append(res, permission)
}
}
return res, nil
}
func GetPermissionsByRole(roleId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("roles like ?", "%"+roleId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
res := []*Permission{}
for _, permission := range permissions {
if util.InSlice(permission.Roles, roleId) {
res = append(res, permission)
}
}
return res, nil
}
func GetPermissionsByResource(resourceId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
res := []*Permission{}
for _, permission := range permissions {
if util.InSlice(permission.Resources, resourceId) {
res = append(res, permission)
}
}
return res, nil
}
func getPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) {
permissions, err := getPermissionsByUser(userId)
if err != nil {
return nil, nil, err
}
@@ -277,14 +327,13 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
permFromRoles := []*Permission{}
roles, err := GetRolesByUser(userId)
roles, err := getRolesByUser(userId)
if err != nil {
return nil, nil, err
}
for _, role := range roles {
perms := []*Permission{}
err := ormer.Engine.Where("roles like ?", "%"+role.GetId()+"\"%").Find(&perms)
perms, err := GetPermissionsByRole(role.GetId())
if err != nil {
return nil, nil, err
}
@@ -302,26 +351,6 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
return permissions, roles, nil
}
func GetPermissionsByRole(roleId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("roles like ?", "%"+roleId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
return permissions, nil
}
func GetPermissionsByResource(resourceId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
return permissions, nil
}
func GetPermissionsBySubmitter(owner string, submitter string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})

View File

@@ -258,7 +258,7 @@ func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionI
}
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
permissions, _, err := GetPermissionsAndRolesByUser(userId)
permissions, _, err := getPermissionsAndRolesByUser(userId)
if err != nil {
panic(err)
}
@@ -293,7 +293,7 @@ func GetAllActions(userId string) []string {
}
func GetAllRoles(userId string) []string {
roles, err := GetRolesByUser(userId)
roles, err := getRolesByUser(userId)
if err != nil {
panic(err)
}

View File

@@ -32,6 +32,7 @@ type Role struct {
Description string `xorm:"varchar(100)" json:"description"`
Users []string `xorm:"mediumtext" json:"users"`
Groups []string `xorm:"mediumtext" json:"groups"`
Roles []string `xorm:"mediumtext" json:"roles"`
Domains []string `xorm:"mediumtext" json:"domains"`
IsEnabled bool `json:"isEnabled"`
@@ -252,15 +253,30 @@ func (role *Role) GetId() string {
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
}
func GetRolesByUser(userId string) ([]*Role, error) {
func getRolesByUserInternal(userId string) ([]*Role, error) {
roles := []*Role{}
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&roles)
if err != nil {
return roles, err
}
allRolesIds := make([]string, 0, len(roles))
res := []*Role{}
for _, role := range roles {
if util.InSlice(role.Users, userId) {
res = append(res, role)
}
}
return res, nil
}
func getRolesByUser(userId string) ([]*Role, error) {
roles, err := getRolesByUserInternal(userId)
if err != nil {
return roles, err
}
allRolesIds := []string{}
for _, role := range roles {
allRolesIds = append(allRolesIds, role.GetId())
}
@@ -336,16 +352,6 @@ func GetMaskedRoles(roles []*Role) []*Role {
return roles
}
func GetRolesByNamePrefix(owner string, prefix string) ([]*Role, error) {
roles := []*Role{}
err := ormer.Engine.Where("owner=? and name like ?", owner, prefix+"%").Find(&roles)
if err != nil {
return roles, err
}
return roles, nil
}
// GetAncestorRoles returns a list of roles that contain the given roleIds
func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
var (

View File

@@ -796,7 +796,7 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
return
}
user.Permissions, user.Roles, err = GetPermissionsAndRolesByUser(user.GetId())
user.Permissions, user.Roles, err = getPermissionsAndRolesByUser(user.GetId())
if err != nil {
return err
}

67
radius/server.go Normal file
View File

@@ -0,0 +1,67 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package radius
import (
"log"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
)
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3
func StartRadiusServer() {
server := radius.PacketServer{
Addr: "0.0.0.0:" + conf.GetConfigString("radiusServerPort"),
Handler: radius.HandlerFunc(handlerRadius),
SecretSource: radius.StaticSecretSource([]byte(`secret`)),
}
log.Printf("Starting Radius server on %s", server.Addr)
if err := server.ListenAndServe(); err != nil {
log.Fatalf("StartRadiusServer() failed, err = %v", err)
}
}
func handlerRadius(w radius.ResponseWriter, r *radius.Request) {
switch r.Code {
case radius.CodeAccessRequest:
handleAccessRequest(w, r)
default:
log.Printf("radius message, code = %d", r.Code)
}
}
func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
username := rfc2865.UserName_GetString(r.Packet)
password := rfc2865.UserPassword_GetString(r.Packet)
organization := parseOrganization(r.Packet)
code := radius.CodeAccessAccept
log.Printf("username=%v, password=%v, code=%v, org=%v", username, password, code, organization)
if organization == "" {
code = radius.CodeAccessReject
w.Write(r.Response(code))
return
}
_, msg := object.CheckUserPassword(organization, username, password, "en")
if msg != "" {
code = radius.CodeAccessReject
w.Write(r.Response(code))
return
}
w.Write(r.Response(code))
}

62
radius/server_test.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !skipCi
// +build !skipCi
package radius
import (
"context"
"testing"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
)
func TestAccessRequestRejected(t *testing.T) {
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
rfc2865.UserName_SetString(packet, "admin")
rfc2865.UserPassword_SetString(packet, "12345")
vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in"))
if err != nil {
t.Fatal(err)
}
packet.Add(rfc2865.VendorSpecific_Type, vsa)
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
if err != nil {
t.Fatal(err)
}
if response.Code != radius.CodeAccessReject {
t.Fatalf("Expected %v, got %v", radius.CodeAccessReject, response.Code)
}
}
func TestAccessRequestAccepted(t *testing.T) {
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
rfc2865.UserName_SetString(packet, "admin")
rfc2865.UserPassword_SetString(packet, "123")
vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in"))
if err != nil {
t.Fatal(err)
}
packet.Add(rfc2865.VendorSpecific_Type, vsa)
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
if err != nil {
t.Fatal(err)
}
if response.Code != radius.CodeAccessAccept {
t.Fatalf("Expected %v, got %v", radius.CodeAccessAccept, response.Code)
}
}

40
radius/util.go Normal file
View File

@@ -0,0 +1,40 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package radius
import (
"layeh.com/radius"
"layeh.com/radius/rfc2865"
)
const (
OrganizationVendorID = uint32(100)
)
func parseOrganization(p *radius.Packet) string {
for _, avp := range p.Attributes {
if avp.Type == rfc2865.VendorSpecific_Type {
attr := avp.Attribute
vendorId, value, err := radius.VendorSpecific(attr)
if err != nil {
return ""
}
if vendorId == OrganizationVendorID {
return string(value)
}
}
}
return ""
}

View File

@@ -227,7 +227,7 @@ class AdapterEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:DB Test"), i18next.t("provider:DB Test - Tooltip"))} :
{Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
</Col>
<Col span={2} >
<Button type={"primary"} onClick={() => {

View File

@@ -129,6 +129,13 @@ class AdapterListPage extends BaseListPage {
{text: "Database", value: "Database"},
],
},
{
title: i18next.t("syncer:Database type"),
dataIndex: "databaseType",
key: "databaseType",
width: "120px",
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
},
{
title: i18next.t("provider:Host"),
dataIndex: "host",
@@ -144,6 +151,12 @@ class AdapterListPage extends BaseListPage {
width: "100px",
sorter: true,
...this.getColumnSearchProps("port"),
render: (text, record, index) => {
if (text === 0) {
return "";
}
return text;
},
},
{
title: i18next.t("general:User"),
@@ -161,13 +174,6 @@ class AdapterListPage extends BaseListPage {
sorter: true,
...this.getColumnSearchProps("password"),
},
{
title: i18next.t("syncer:Database type"),
dataIndex: "databaseType",
key: "databaseType",
width: "120px",
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
},
{
title: i18next.t("syncer:Database"),
dataIndex: "database",

View File

@@ -298,6 +298,13 @@ class PermissionListPage extends BaseListPage {
filterMultiple: false,
width: "120px",
sorter: true,
render: (text, record, index) => {
return (
<Link to={`/users/${record.owner}/${encodeURIComponent(text)}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("permission:Approver"),
@@ -306,6 +313,13 @@ class PermissionListPage extends BaseListPage {
filterMultiple: false,
width: "120px",
sorter: true,
render: (text, record, index) => {
return (
<Link to={`/users/${record.owner}/${encodeURIComponent(text)}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("permission:Approve time"),

View File

@@ -172,6 +172,12 @@ class ProviderEditPage extends React.Component {
} else {
return Setting.getLabel(i18next.t("provider:Site key"), i18next.t("provider:Site key - Tooltip"));
}
case "Notification":
if (provider.type === "DingTalk") {
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
default:
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
@@ -180,7 +186,11 @@ class ProviderEditPage extends React.Component {
getClientSecretLabel(provider) {
switch (provider.category) {
case "Email":
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
if (provider.type === "Azure ACS") {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
} else {
return Setting.getLabel(i18next.t("general:Password"), i18next.t("general:Password - Tooltip"));
}
case "SMS":
if (provider.type === "Volc Engine SMS" || provider.type === "Amazon SNS" || provider.type === "Baidu Cloud SMS") {
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:Secret access key - Tooltip"));
@@ -202,8 +212,10 @@ class ProviderEditPage extends React.Component {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
}
case "Notification":
if (provider.type === "Line") {
if (provider.type === "Line" || provider.type === "Telegram" || provider.type === "Bark" || provider.type === "DingTalk" || provider.type === "Discord" || provider.type === "Slack" || provider.type === "Pushover" || provider.type === "Pushbullet") {
return Setting.getLabel(i18next.t("provider:Secret key"), i18next.t("provider:Secret key - Tooltip"));
} else if (provider.type === "Lark" || provider.type === "Microsoft Teams") {
return Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Endpoint - Tooltip"));
} else {
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
@@ -297,7 +309,7 @@ class ProviderEditPage extends React.Component {
tooltip = i18next.t("provider:Project Id - Tooltip");
}
} else if (provider.category === "Email") {
if (provider.type === "SUBMAIL" || provider.type === "Azure ACS") {
if (provider.type === "SUBMAIL") {
text = i18next.t("provider:App ID");
tooltip = i18next.t("provider:App ID - Tooltip");
}
@@ -305,7 +317,7 @@ class ProviderEditPage extends React.Component {
if (provider.type === "Viber") {
text = i18next.t("provider:Domain");
tooltip = i18next.t("provider:Domain - Tooltip");
} else if (provider.type === "Telegram" || provider.type === "DingTalk" || provider.type === "Pushover" || provider.type === "Pushbullet" || provider.type === "Slack" || provider.type === "Discord" || provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
} else if (provider.type === "Line" || provider.type === "Matrix" || provider.type === "Rocket Chat") {
text = i18next.t("provider:App Key");
tooltip = i18next.t("provider:App Key - Tooltip");
}
@@ -336,12 +348,9 @@ class ProviderEditPage extends React.Component {
if (provider.type === "Telegram" || provider.type === "Pushover" || provider.type === "Pushbullet" || provider.type === "Slack" || provider.type === "Discord" || provider.type === "Line" || provider.type === "Twitter" || provider.type === "Reddit" || provider.type === "Rocket Chat" || provider.type === "Viber") {
text = i18next.t("provider:Chat ID");
tooltip = i18next.t("provider:Chat ID - Tooltip");
} else if (provider.type === "Custom HTTP" || provider.type === "Lark" || provider.type === "Microsoft Teams" || provider.type === "Webpush" || provider.type === "Matrix") {
} else if (provider.type === "Custom HTTP" || provider.type === "Webpush" || provider.type === "Matrix") {
text = i18next.t("provider:Endpoint");
tooltip = i18next.t("provider:Endpoint - Tooltip");
} else if (provider.type === "DingTalk" || provider.type === "Bark") {
text = i18next.t("provider:Secret Key");
tooltip = i18next.t("provider:Secret Key - Tooltip");
}
if (text === "" && tooltip === "") {
@@ -626,24 +635,24 @@ class ProviderEditPage extends React.Component {
}
{
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") ||
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
(this.state.provider.category === "Web3") ||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System" ||
(this.state.provider.category === "Notification" && this.state.provider.type !== "Webpush" && this.state.provider.type !== "Line" && this.state.provider.type !== "Matrix" && this.state.provider.type !== "Twitter" && this.state.provider.type !== "Reddit" && this.state.provider.type !== "Rocket Chat" && this.state.provider.type !== "Viber")) ? null : (
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP")) ? null : (
<React.Fragment>
{
this.state.provider.type === "Line" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientIdLabel(this.state.provider)} :
</Col>
<Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => {
this.updateProviderField("clientId", e.target.value);
}} />
</Col>
</Row>
)
(this.state.provider.category === "Email" && this.state.provider.type === "Azure ACS") ||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Line" || this.state.provider.type === "Telegram" || this.state.provider.type === "Bark" || this.state.provider.type === "Discord" || this.state.provider.type === "Slack" || this.state.provider.type === "Pushbullet" || this.state.provider.type === "Pushover" || this.state.provider.type === "Lark" || this.state.provider.type === "Microsoft Teams")) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{this.getClientIdLabel(this.state.provider)} :
</Col>
<Col span={22} >
<Input value={this.state.provider.clientId} onChange={e => {
this.updateProviderField("clientId", e.target.value);
}} />
</Col>
</Row>
)
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>

View File

@@ -14,11 +14,12 @@
import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as RoleBackend from "./backend/RoleBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend";
import * as GroupBackend from "./backend/GroupBackend";
import * as RoleBackend from "./backend/RoleBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
import * as UserBackend from "./backend/UserBackend";
class RoleEditPage extends React.Component {
constructor(props) {
@@ -30,6 +31,7 @@ class RoleEditPage extends React.Component {
role: null,
organizations: [],
users: [],
groups: [],
roles: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
@@ -57,6 +59,7 @@ class RoleEditPage extends React.Component {
});
this.getUsers(this.state.organizationName);
this.getGroups(this.state.organizationName);
this.getRoles(this.state.organizationName);
});
}
@@ -84,6 +87,20 @@ class RoleEditPage extends React.Component {
});
}
getGroups(organizationName) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
groups: res.data,
});
});
}
getRoles(organizationName) {
RoleBackend.getRoles(organizationName)
.then((res) => {
@@ -176,6 +193,17 @@ class RoleEditPage extends React.Component {
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub groups"), i18next.t("role:Sub groups - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.role.groups}
onChange={(value => {this.updateRoleField("groups", value);})}
options={this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`))}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :

View File

@@ -33,6 +33,7 @@ class RoleListPage extends BaseListPage {
createdTime: moment().format(),
displayName: `New Role - ${randomName}`,
users: [],
groups: [],
roles: [],
domains: [],
isEnabled: true,
@@ -171,6 +172,17 @@ class RoleListPage extends BaseListPage {
return Setting.getTags(text, "users");
},
},
{
title: i18next.t("role:Sub groups"),
dataIndex: "groups",
key: "groups",
// width: '100px',
sorter: true,
...this.getColumnSearchProps("groups"),
render: (text, record, index) => {
return Setting.getTags(text, "groups");
},
},
{
title: i18next.t("role:Sub roles"),
dataIndex: "roles",

View File

@@ -749,7 +749,7 @@ export function isMobile() {
}
export function getFormattedDate(date) {
if (date === undefined) {
if (!date) {
return null;
}