From 9e920181d20ada684b0c01c3a306ab55d3b6bbe6 Mon Sep 17 00:00:00 2001 From: Gucheng Wang Date: Sat, 6 Nov 2021 15:52:03 +0800 Subject: [PATCH] Add user soft deletion. --- controllers/account.go | 1 + controllers/auth.go | 3 ++- object/check.go | 2 +- object/init.go | 1 + object/organization.go | 15 ++++++++------- object/user.go | 5 +++-- original/sync.go | 1 + web/src/OrganizationEditPage.js | 12 +++++++++++- web/src/OrganizationListPage.js | 19 ++++++++++++++++--- web/src/UserEditPage.js | 10 ++++++++++ web/src/UserListPage.js | 22 ++++++++++++++++++---- 11 files changed, 72 insertions(+), 19 deletions(-) diff --git a/controllers/account.go b/controllers/account.go index 8eca8f49..60da8611 100644 --- a/controllers/account.go +++ b/controllers/account.go @@ -152,6 +152,7 @@ func (c *ApiController) Signup() { IsAdmin: false, IsGlobalAdmin: false, IsForbidden: false, + IsDeleted: false, SignupApplication: application.Name, Properties: map[string]string{}, } diff --git a/controllers/auth.go b/controllers/auth.go index 128c4a0a..f9b4b6eb 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -251,7 +251,7 @@ func (c *ApiController) Login() { user = object.GetUserByField(application.Organization, "name", userInfo.Username) } - if user != nil { + if user != nil && user.IsDeleted == false { // Sign in via OAuth (want to sign up but already have account) if user.IsForbidden { @@ -293,6 +293,7 @@ func (c *ApiController) Login() { IsAdmin: false, IsGlobalAdmin: false, IsForbidden: false, + IsDeleted: false, SignupApplication: application.Name, Properties: properties, } diff --git a/object/check.go b/object/check.go index 74227f30..85b83e93 100644 --- a/object/check.go +++ b/object/check.go @@ -102,7 +102,7 @@ func CheckPassword(user *User, password string) string { func CheckUserPassword(organization string, username string, password string) (*User, string) { user := GetUserByFields(organization, username) - if user == nil { + if user == nil || user.IsDeleted == true { return nil, "the user does not exist, please sign up first" } diff --git a/object/init.go b/object/init.go index 1031436e..6349c6fc 100644 --- a/object/init.go +++ b/object/init.go @@ -67,6 +67,7 @@ func initBuiltInUser() { IsAdmin: true, IsGlobalAdmin: true, IsForbidden: false, + IsDeleted: false, Properties: make(map[string]string), } AddUser(user) diff --git a/object/organization.go b/object/organization.go index c7516ea4..be3e7978 100644 --- a/object/organization.go +++ b/object/organization.go @@ -24,13 +24,14 @@ type Organization struct { Name string `xorm:"varchar(100) notnull pk" json:"name"` CreatedTime string `xorm:"varchar(100)" json:"createdTime"` - DisplayName string `xorm:"varchar(100)" json:"displayName"` - WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"` - Favicon string `xorm:"varchar(100)" json:"favicon"` - PasswordType string `xorm:"varchar(100)" json:"passwordType"` - PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` - PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"` - DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"` + DisplayName string `xorm:"varchar(100)" json:"displayName"` + WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"` + Favicon string `xorm:"varchar(100)" json:"favicon"` + PasswordType string `xorm:"varchar(100)" json:"passwordType"` + PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"` + PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"` + DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"` + EnableSoftDeletion bool `json:"enableSoftDeletion"` } func GetOrganizationCount(owner string) int { diff --git a/object/user.go b/object/user.go index 5c42bf0b..08d5963d 100644 --- a/object/user.go +++ b/object/user.go @@ -51,6 +51,7 @@ type User struct { IsAdmin bool `json:"isAdmin"` IsGlobalAdmin bool `json:"isGlobalAdmin"` IsForbidden bool `json:"isForbidden"` + IsDeleted bool `json:"isDeleted"` SignupApplication string `xorm:"varchar(100)" json:"signupApplication"` Hash string `xorm:"varchar(100)" json:"hash"` PreHash string `xorm:"varchar(100)" json:"preHash"` @@ -199,8 +200,8 @@ func UpdateUser(id string, user *User) bool { } affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols("owner", "display_name", "avatar", - "location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "is_admin", "is_global_admin", "is_forbidden", - "hash", "properties").Update(user) + "location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", + "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "properties").Update(user) if err != nil { panic(err) } diff --git a/original/sync.go b/original/sync.go index 73b8a8b6..e1ba9c9f 100644 --- a/original/sync.go +++ b/original/sync.go @@ -64,6 +64,7 @@ func createUserFromOriginalUser(originalUser *User, affiliationMap map[int]strin IsAdmin: false, IsGlobalAdmin: false, IsForbidden: originalUser.Deleted != 0, + IsDeleted: false, Properties: map[string]string{}, } return user diff --git a/web/src/OrganizationEditPage.js b/web/src/OrganizationEditPage.js index 301e07ce..4bd07f52 100644 --- a/web/src/OrganizationEditPage.js +++ b/web/src/OrganizationEditPage.js @@ -13,7 +13,7 @@ // limitations under the License. import React from "react"; -import {Button, Card, Col, Input, Row, Select} from 'antd'; +import {Button, Card, Col, Input, Row, Select, Switch} from 'antd'; import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as LdapBackend from "./backend/LdapBackend"; import * as Setting from "./Setting"; @@ -205,6 +205,16 @@ class OrganizationEditPage extends React.Component { + + + {Setting.getLabel(i18next.t("organization:Soft deletion"), i18next.t("organization:Soft deletion - Tooltip"))} : + + + { + this.updateOrganizationField('enableSoftDeletion', checked); + }} /> + + {Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} : diff --git a/web/src/OrganizationListPage.js b/web/src/OrganizationListPage.js index a1ee31ae..dd40b685 100644 --- a/web/src/OrganizationListPage.js +++ b/web/src/OrganizationListPage.js @@ -14,7 +14,7 @@ import React from "react"; import {Link} from "react-router-dom"; -import {Button, Popconfirm, Table} from 'antd'; +import {Button, Popconfirm, Switch, Table} from 'antd'; import moment from "moment"; import * as Setting from "./Setting"; import * as OrganizationBackend from "./backend/OrganizationBackend"; @@ -59,6 +59,7 @@ class OrganizationListPage extends React.Component { PasswordSalt: "", phonePrefix: "86", defaultAvatar: "https://casbin.org/img/casbin.svg", + enableSoftDeletion: false, } } @@ -158,7 +159,7 @@ class OrganizationListPage extends React.Component { title: i18next.t("general:Password type"), dataIndex: 'passwordType', key: 'passwordType', - width: '100px', + width: '150px', sorter: (a, b) => a.passwordType.localeCompare(b.passwordType), }, { @@ -172,7 +173,7 @@ class OrganizationListPage extends React.Component { title: i18next.t("organization:Default avatar"), dataIndex: 'defaultAvatar', key: 'defaultAvatar', - width: '50px', + width: '120px', render: (text, record, index) => { return ( @@ -181,6 +182,18 @@ class OrganizationListPage extends React.Component { ) } }, + { + title: i18next.t("organization:Soft deletion"), + dataIndex: 'enableSoftDeletion', + key: 'enableSoftDeletion', + width: '140px', + sorter: (a, b) => a.enableSoftDeletion - b.enableSoftDeletion, + render: (text, record, index) => { + return ( + + ) + } + }, { title: i18next.t("general:Action"), dataIndex: '', diff --git a/web/src/UserEditPage.js b/web/src/UserEditPage.js index 1ba20789..a98a0a9e 100644 --- a/web/src/UserEditPage.js +++ b/web/src/UserEditPage.js @@ -353,6 +353,16 @@ class UserEditPage extends React.Component { }} /> + + + {Setting.getLabel(i18next.t("user:Is deleted"), i18next.t("user:Is deleted - Tooltip"))} : + + + { + this.updateUserField('isDeleted', checked); + }} /> + + ) } diff --git a/web/src/UserListPage.js b/web/src/UserListPage.js index 4cb54950..b3a6fdce 100644 --- a/web/src/UserListPage.js +++ b/web/src/UserListPage.js @@ -67,6 +67,7 @@ class UserListPage extends React.Component { createdTime: moment().format(), type: "normal-user", password: "123", + passwordSalt: "", displayName: `New User - ${randomName}`, avatar: "https://casbin.org/img/casbin.svg", email: "user@example.com", @@ -78,6 +79,7 @@ class UserListPage extends React.Component { isAdmin: false, isGlobalAdmin: false, IsForbidden: false, + isDeleted: false, properties: {}, } } @@ -164,7 +166,7 @@ class UserListPage extends React.Component { title: i18next.t("general:Created time"), dataIndex: 'createdTime', key: 'createdTime', - width: '180px', + width: '160px', sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), render: (text, record, index) => { return Setting.getFormattedDate(text); @@ -243,7 +245,7 @@ class UserListPage extends React.Component { title: i18next.t("user:Is admin"), dataIndex: 'isAdmin', key: 'isAdmin', - width: '100px', + width: '110px', sorter: (a, b) => a.isAdmin - b.isAdmin, render: (text, record, index) => { return ( @@ -255,7 +257,7 @@ class UserListPage extends React.Component { title: i18next.t("user:Is global admin"), dataIndex: 'isGlobalAdmin', key: 'isGlobalAdmin', - width: '100px', + width: '110px', sorter: (a, b) => a.isGlobalAdmin - b.isGlobalAdmin, render: (text, record, index) => { return ( @@ -267,7 +269,7 @@ class UserListPage extends React.Component { title: i18next.t("user:Is forbidden"), dataIndex: 'isForbidden', key: 'isForbidden', - width: '100px', + width: '110px', sorter: (a, b) => a.isForbidden - b.isForbidden, render: (text, record, index) => { return ( @@ -275,6 +277,18 @@ class UserListPage extends React.Component { ) } }, + { + title: i18next.t("user:Is deleted"), + dataIndex: 'isDeleted', + key: 'isDeleted', + width: '110px', + sorter: (a, b) => a.isDeleted - b.isDeleted, + render: (text, record, index) => { + return ( + + ) + } + }, { title: i18next.t("general:Action"), dataIndex: '',