diff --git a/object/init.go b/object/init.go index ba03f664..68a8695a 100644 --- a/object/init.go +++ b/object/init.go @@ -84,6 +84,7 @@ func initBuiltInOrganization() bool { {Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"}, {Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, + {Name: "Managed accounts", Visible: true, ViewRule: "Self", ModifyRule: "Self"}, }, } AddOrganization(organization) diff --git a/object/organization.go b/object/organization.go index f6320aa4..58e7d7b8 100644 --- a/object/organization.go +++ b/object/organization.go @@ -46,7 +46,7 @@ type Organization struct { EnableSoftDeletion bool `json:"enableSoftDeletion"` IsProfilePublic bool `json:"isProfilePublic"` - AccountItems []*AccountItem `xorm:"varchar(2000)" json:"accountItems"` + AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"` } func GetOrganizationCount(owner, field, value string) int { diff --git a/object/user.go b/object/user.go index 6d6e48fe..153fb7db 100644 --- a/object/user.go +++ b/object/user.go @@ -114,6 +114,8 @@ type User struct { LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"` SigninWrongTimes int `json:"signinWrongTimes"` + + ManagedAccounts []ManagedAccount `xorm:"managedAccounts blob" json:"managedAccounts"` } type Userinfo struct { @@ -128,6 +130,13 @@ type Userinfo struct { Phone string `json:"phone,omitempty"` } +type ManagedAccount struct { + Application string `xorm:"varchar(100)" json:"application"` + Username string `xorm:"varchar(100)" json:"username"` + Password string `xorm:"varchar(100)" json:"password"` + SigninUrl string `xorm:"varchar(200)" json:"signinUrl"` +} + func GetGlobalUserCount(field, value string) int { session := GetSession("", -1, -1, field, value, "", "") count, err := session.Count(&User{}) @@ -334,6 +343,12 @@ func GetMaskedUser(user *User) *User { if user.Password != "" { user.Password = "***" } + + if user.ManagedAccounts != nil { + for _, manageAccount := range user.ManagedAccounts { + manageAccount.Password = "***" + } + } return user } @@ -378,7 +393,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo columns = []string{ "owner", "display_name", "avatar", "location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application", - "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", + "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "signin_wrong_times", "last_signin_wrong_time", } } diff --git a/web/src/AccountTable.js b/web/src/AccountTable.js index dd2dd7e5..debcd78d 100644 --- a/web/src/AccountTable.js +++ b/web/src/AccountTable.js @@ -95,6 +95,7 @@ class AccountTable extends React.Component { {name: "Is forbidden", displayName: i18next.t("user:Is forbidden")}, {name: "Is deleted", displayName: i18next.t("user:Is deleted")}, {name: "WebAuthn credentials", displayName: i18next.t("user:WebAuthn credentials")}, + {name: "Managed accounts", displayName: i18next.t("user:Managed accounts")}, ]; const getItemDisplayName = (text) => { diff --git a/web/src/ManagedAccountTable.js b/web/src/ManagedAccountTable.js new file mode 100644 index 00000000..f2a19a8b --- /dev/null +++ b/web/src/ManagedAccountTable.js @@ -0,0 +1,165 @@ +// 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 {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons"; +import {Button, Col, Input, Row, Select, Table, Tooltip} from "antd"; +import * as Setting from "./Setting"; +import i18next from "i18next"; + +const {Option} = Select; + +class ManagedAccountTable extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + }; + } + + updateTable(table) { + this.props.onUpdateTable(table); + } + + updateField(table, index, key, value) { + table[index][key] = value; + this.updateTable(table); + } + + addRow(table) { + const row = {application: "", username: "", password: "", signinUrl: ""}; + if (table === undefined || table === null) { + table = []; + } + table = Setting.addRow(table, row); + this.updateTable(table); + } + + deleteRow(table, i) { + table = Setting.deleteRow(table, i); + this.updateTable(table); + } + + upRow(table, i) { + table = Setting.swapRow(table, i - 1, i); + this.updateTable(table); + } + + downRow(table, i) { + table = Setting.swapRow(table, i, i + 1); + this.updateTable(table); + } + + renderTable(table) { + const columns = [ + { + title: i18next.t("general:Application"), + dataIndex: "application", + key: "application", + render: (text, record, index) => { + const items = this.props.applications; + const signinUrlMap = new Map(); + for (const application of items) { + signinUrlMap.set(application.name, application.signinUrl); + } + return ( + + ); + }, + }, + { + title: i18next.t("signup:Username"), + dataIndex: "username", + key: "username", + width: "420px", + render: (text, record, index) => { + return ( + { + this.updateField(table, index, "username", e.target.value); + }} /> + ); + }, + }, + { + title: i18next.t("general:Password"), + dataIndex: "password", + key: "password", + width: "420px", + render: (text, record, index) => { + return ( + { + this.updateField(table, index, "password", e.target.value); + }} /> + ); + }, + }, + { + title: i18next.t("general:Action"), + key: "action", + width: "100px", + render: (text, record, index) => { + return ( +