feat: add isProfilePublic setting for accessing user info (#656)

* feat: add isProfilePublic setting for accessing user info

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: requested changes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
This commit is contained in:
Yixiang Zhao
2022-04-16 15:10:03 +08:00
committed by GitHub
parent 70a1428972
commit 530330bd66
7 changed files with 95 additions and 39 deletions

View File

@@ -87,6 +87,17 @@ func (c *ApiController) GetUser() {
id := c.Input().Get("id") id := c.Input().Get("id")
owner := c.Input().Get("owner") owner := c.Input().Get("owner")
email := c.Input().Get("email") email := c.Input().Get("email")
userOwner, _ := util.GetOwnerAndNameFromId(id)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
if !hasPermission {
c.ResponseError(err.Error())
return
}
}
var user *object.User var user *object.User
if email == "" { if email == "" {
@@ -233,39 +244,15 @@ func (c *ApiController) SetPassword() {
newPassword := c.Ctx.Request.Form.Get("newPassword") newPassword := c.Ctx.Request.Form.Get("newPassword")
requestUserId := c.GetSessionUsername() requestUserId := c.GetSessionUsername()
if requestUserId == "" {
c.ResponseError("Please login first")
return
}
userId := fmt.Sprintf("%s/%s", userOwner, userName) userId := fmt.Sprintf("%s/%s", userOwner, userName)
targetUser := object.GetUser(userId)
if targetUser == nil { hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId)) if !hasPermission {
c.ResponseError(err.Error())
return return
} }
hasPermission := false targetUser := object.GetUser(userId)
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := object.GetUser(requestUserId)
if requestUser == nil {
c.ResponseError("Session outdated. Please login again.")
return
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner && requestUser.IsAdmin {
hasPermission = true
}
}
if !hasPermission {
c.ResponseError("You don't have the permission to do this.")
return
}
if oldPassword != "" { if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword) msg := object.CheckPassword(targetUser, oldPassword)

View File

@@ -17,6 +17,7 @@ package object
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
"github.com/casdoor/casdoor/cred" "github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@@ -195,3 +196,37 @@ func CheckUserPassword(organization string, username string, password string) (*
func filterField(field string) bool { func filterField(field string) bool {
return reFieldWhiteList.MatchString(field) return reFieldWhiteList.MatchString(field)
} }
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf("please login first")
}
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
}
hasPermission := false
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := GetUser(requestUserId)
if requestUser == nil {
return false, fmt.Errorf("session outdated, please login again")
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner {
if strict {
hasPermission = requestUser.IsAdmin
} else {
hasPermission = true
}
}
}
return hasPermission, fmt.Errorf("you don't have the permission to do this")
}

View File

@@ -35,6 +35,7 @@ type Organization struct {
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"` MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"` EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
} }
func GetOrganizationCount(owner, field, value string) int { func GetOrganizationCount(owner, field, value string) int {

View File

@@ -240,6 +240,16 @@ class OrganizationEditPage extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Is profile public"), i18next.t("organization:Is profile public - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.organization.isProfilePublic} onChange={checked => {
this.updateOrganizationField('isProfilePublic', checked);
}} />
</Col>
</Row>
<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}>
{Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} : {Setting.getLabel(i18next.t("general:LDAPs"), i18next.t("general:LDAPs - Tooltip"))} :

View File

@@ -39,6 +39,7 @@ class OrganizationListPage extends BaseListPage {
tags: [], tags: [],
masterPassword: "", masterPassword: "",
enableSoftDeletion: false, enableSoftDeletion: false,
isProfilePublic: true,
} }
} }

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, Row, Select, Switch} from 'antd'; import {Button, Card, Col, Input, Result, Row, Select, Spin, Switch} from 'antd';
import * as UserBackend from "./backend/UserBackend"; import * as UserBackend from "./backend/UserBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@@ -47,6 +47,7 @@ class UserEditPage extends React.Component {
organizations: [], organizations: [],
applications: [], applications: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit", mode: props.location.mode !== undefined ? props.location.mode : "edit",
loading: true,
}; };
} }
@@ -59,9 +60,14 @@ class UserEditPage extends React.Component {
getUser() { getUser() {
UserBackend.getUser(this.state.organizationName, this.state.userName) UserBackend.getUser(this.state.organizationName, this.state.userName)
.then((user) => { .then((data) => {
if (data.status === null || data.status !== "error") {
this.setState({
user: data,
});
}
this.setState({ this.setState({
user: user, loading: false,
}); });
}); });
} }
@@ -423,6 +429,8 @@ class UserEditPage extends React.Component {
) )
} }
</Card> </Card>
) )
} }
@@ -469,13 +477,24 @@ class UserEditPage extends React.Component {
return ( return (
<div> <div>
{ {
this.state.user !== null ? this.renderUser() : null this.state.loading ? <Spin loading={this.state.loading} size="large" /> : (
this.state.user !== null ? this.renderUser() :
<Result
status="404"
title="404 NOT FOUND"
subTitle={i18next.t("general:Sorry, the user you visited does not exist or you are not authorized to access this user.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
)
}
{
this.state.user === null ? null :
<div style={{marginTop: '20px', marginLeft: '40px'}}>
<Button size="large" onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} }
<div style={{marginTop: '20px', marginLeft: '40px'}}>
<Button size="large" onClick={() => this.submitUserEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: '20px'}} type="primary" size="large" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: '20px'}} size="large" onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div> </div>
); );
} }

View File

@@ -171,6 +171,7 @@
"Signup application": "注册应用", "Signup application": "注册应用",
"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.": "抱歉,您访问的用户不存在或您无权访问该用户",
"State": "状态", "State": "状态",
"State - Tooltip": "状态", "State - Tooltip": "状态",
"Swagger": "API文档", "Swagger": "API文档",
@@ -252,7 +253,9 @@
"Tags": "标签集合", "Tags": "标签集合",
"Tags - Tooltip": "可供用户选择的标签的集合", "Tags - Tooltip": "可供用户选择的标签的集合",
"Website URL": "网页地址", "Website URL": "网页地址",
"Website URL - Tooltip": "网页地址" "Website URL - Tooltip": "网页地址",
"Is profile public": "公开用户信息",
"Is profile public - Tooltip": "关闭后,只有全局管理员或同组织才能访问用户信息"
}, },
"payment": { "payment": {
"Currency": "币种", "Currency": "币种",