mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-23 06:23:22 +08:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
4ab2ca7a25 | |||
dcf148fb7f | |||
c8846f1a2d | |||
0559298d6c | |||
ddb5e26fcd | |||
1f39027b78 | |||
eae3b0d367 | |||
186f0ac97b | |||
308f305c53 | |||
d498bc60ce |
5
.gitattributes
vendored
5
.gitattributes
vendored
@ -1,2 +1,5 @@
|
||||
*.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
|
@ -271,12 +271,17 @@ func (c *ApiController) GetAccount() {
|
||||
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))
|
||||
resp := Response{
|
||||
Status: "ok",
|
||||
Sub: user.Id,
|
||||
Name: user.Name,
|
||||
Data: user,
|
||||
Data: object.GetMaskedUser(user),
|
||||
Data2: organization,
|
||||
}
|
||||
c.Data["json"] = resp
|
||||
|
@ -103,12 +103,12 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
|
||||
resp = tokenToResponse(token)
|
||||
}
|
||||
} 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 {
|
||||
c.ResponseError(err.Error(), nil)
|
||||
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 {
|
||||
// not oauth but CAS SSO protocol
|
||||
service := c.Input().Get("service")
|
||||
|
@ -125,6 +125,127 @@ func (c *ApiController) GetUser() {
|
||||
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
|
||||
// @Title UpdateUser
|
||||
// @Tag User API
|
||||
@ -159,6 +280,12 @@ func (c *ApiController) UpdateUser() {
|
||||
}
|
||||
|
||||
isGlobalAdmin := c.IsGlobalAdmin()
|
||||
|
||||
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
|
||||
c.ResponseError(err)
|
||||
return
|
||||
}
|
||||
|
||||
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
|
||||
if affected {
|
||||
object.UpdateUserToOriginalDatabase(&user)
|
||||
|
@ -90,7 +90,7 @@
|
||||
"You can't unlink yourself, you are not a member of any application": "您无法取消链接,您不是任何应用程序的成员"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "您无法取消链接,您不是任何应用程序的成员",
|
||||
"Only admin can modify the %s.": "仅允许管理员可以修改 %s",
|
||||
"The %s is immutable.": "%s是不可变的",
|
||||
"Unknown modify rule %s.": "未知的修改规则"
|
||||
},
|
||||
|
@ -76,7 +76,7 @@ func GetOidcDiscovery(host string) OidcDiscovery {
|
||||
JwksUri: fmt.Sprintf("%s/.well-known/jwks", 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"},
|
||||
ResponseModesSupported: []string{"login", "code", "link"},
|
||||
ResponseModesSupported: []string{"query", "fragment", "login", "code", "link"},
|
||||
GrantTypesSupported: []string{"password", "authorization_code"},
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IdTokenSigningAlgValuesSupported: []string{"RS256"},
|
||||
|
@ -269,3 +269,12 @@ func ContainsAsterisk(userId string, users []string) bool {
|
||||
|
||||
return containsAsterisk
|
||||
}
|
||||
|
||||
func GetMaskedPermissions(permissions []*Permission) []*Permission {
|
||||
for _, permission := range permissions {
|
||||
permission.Users = nil
|
||||
permission.Submitter = ""
|
||||
}
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
@ -192,3 +192,11 @@ func roleChangeTrigger(oldName string, newName string) error {
|
||||
|
||||
return session.Commit()
|
||||
}
|
||||
|
||||
func GetMaskedRoles(roles []*Role) []*Role {
|
||||
for _, role := range roles {
|
||||
role.Users = nil
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
@ -223,11 +223,14 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
|
||||
|
||||
// GetSamlResponse generates a SAML2.0 response
|
||||
// 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
|
||||
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
// decompress
|
||||
var buffer bytes.Buffer
|
||||
@ -236,12 +239,12 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
var authnRequest saml.AuthnRequest
|
||||
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
|
||||
// verify samlRequest
|
||||
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
|
||||
@ -253,6 +256,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
|
||||
// redirect Url (Assertion Consumer Url)
|
||||
if application.SamlReplyUrl != "" {
|
||||
method = "POST"
|
||||
authnRequest.AssertionConsumerServiceURL = application.SamlReplyUrl
|
||||
}
|
||||
|
||||
@ -275,7 +279,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
doc.SetRoot(samlResponse)
|
||||
xmlBytes, err := doc.WriteToBytes()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
|
||||
// compress
|
||||
@ -283,7 +287,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
flated := bytes.NewBuffer(nil)
|
||||
writer, err := flate.NewWriter(flated, flate.DefaultCompression)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("err: %s", err.Error())
|
||||
return "", "", method, fmt.Errorf("err: %s", err.Error())
|
||||
}
|
||||
writer.Write(xmlBytes)
|
||||
writer.Close()
|
||||
@ -291,7 +295,7 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
}
|
||||
// base64 encode
|
||||
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)
|
||||
|
@ -170,7 +170,7 @@ class AccountTable extends React.Component {
|
||||
}
|
||||
|
||||
let options;
|
||||
if (record.viewRule === "Admin") {
|
||||
if (record.viewRule === "Admin" || record.name === "Is admin" || record.name === "Is global admin") {
|
||||
options = [
|
||||
{id: "Admin", name: "Admin"},
|
||||
{id: "Immutable", name: "Immutable"},
|
||||
|
@ -28,11 +28,7 @@ import SamlWidget from "./common/SamlWidget";
|
||||
import SelectRegionBox from "./SelectRegionBox";
|
||||
import WebAuthnCredentialTable from "./WebauthnCredentialTable";
|
||||
import ManagedAccountTable from "./ManagedAccountTable";
|
||||
|
||||
import {Controlled as CodeMirror} from "react-codemirror2";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
require("codemirror/theme/material-darker.css");
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
import PropertyTable from "./propertyTable";
|
||||
|
||||
const {Option} = Select;
|
||||
|
||||
@ -490,13 +486,10 @@ class UserEditPage extends React.Component {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<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 span={22} >
|
||||
<CodeMirror
|
||||
value={JSON.stringify(this.state.user.properties, null, 4)}
|
||||
options={{mode: "javascript", theme: "material-darker"}}
|
||||
/>
|
||||
<PropertyTable properties={this.state.user.properties} onUpdateTable={(value) => {this.updateUserField("properties", value);}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
@ -507,7 +500,7 @@ class UserEditPage extends React.Component {
|
||||
{Setting.getLabel(i18next.t("user:Is admin"), i18next.t("user:Is admin - Tooltip"))} :
|
||||
</Col>
|
||||
<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);
|
||||
}} />
|
||||
</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"))} :
|
||||
</Col>
|
||||
<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);
|
||||
}} />
|
||||
</Col>
|
||||
|
@ -20,6 +20,7 @@ import * as Util from "./Util";
|
||||
import {authConfig} from "./Auth";
|
||||
import * as Setting from "../Setting";
|
||||
import i18next from "i18next";
|
||||
import RedirectForm from "../common/RedirectForm";
|
||||
|
||||
class AuthCallback extends React.Component {
|
||||
constructor(props) {
|
||||
@ -27,6 +28,9 @@ class AuthCallback extends React.Component {
|
||||
this.state = {
|
||||
classes: props,
|
||||
msg: null,
|
||||
samlResponse: "",
|
||||
relayState: "",
|
||||
redirectUrl: "",
|
||||
};
|
||||
}
|
||||
|
||||
@ -164,9 +168,17 @@ class AuthCallback extends React.Component {
|
||||
const from = innerParams.get("from");
|
||||
Setting.goToLinkSoft(this, from);
|
||||
} else if (responseType === "saml") {
|
||||
const SAMLResponse = res.data;
|
||||
const redirectUri = res.data2;
|
||||
Setting.goToLink(`${redirectUri}?SAMLResponse=${encodeURIComponent(SAMLResponse)}&RelayState=${oAuthParams.relayState}`);
|
||||
if (res.data2.method === "POST") {
|
||||
this.setState({
|
||||
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 {
|
||||
this.setState({
|
||||
@ -177,6 +189,10 @@ class AuthCallback extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.samlResponse !== "") {
|
||||
return <RedirectForm samlResponse={this.state.samlResponse} redirectUrl={this.state.redirectUrl} relayState={this.state.relayState} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
{
|
||||
|
@ -320,15 +320,15 @@ class LoginPage extends React.Component {
|
||||
const accessToken = res.data;
|
||||
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
|
||||
} else if (responseType === "saml") {
|
||||
const SAMLResponse = res.data;
|
||||
const redirectUri = res.data2;
|
||||
if (this.state.application.assertionConsumerUrl !== "") {
|
||||
if (res.data2.method === "POST") {
|
||||
this.setState({
|
||||
samlResponse: res.data,
|
||||
redirectUrl: res.data2,
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
@ -616,13 +616,13 @@ class LoginPage extends React.Component {
|
||||
this.sendSilentSigninData("signing-in");
|
||||
|
||||
const values = {};
|
||||
values["application"] = this.state.application.name;
|
||||
values["application"] = application.name;
|
||||
this.onFinish(values);
|
||||
}
|
||||
|
||||
if (application.enableAutoSignin) {
|
||||
const values = {};
|
||||
values["application"] = this.state.application.name;
|
||||
values["application"] = application.name;
|
||||
this.onFinish(values);
|
||||
}
|
||||
|
||||
@ -637,7 +637,7 @@ class LoginPage extends React.Component {
|
||||
<br />
|
||||
<SelfLoginButton account={this.props.account} onClick={() => {
|
||||
const values = {};
|
||||
values["application"] = this.state.application.name;
|
||||
values["application"] = application.name;
|
||||
this.onFinish(values);
|
||||
}} />
|
||||
<br />
|
||||
|
@ -726,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Ist globaler Admin",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Link",
|
||||
"Location": "Standort",
|
||||
"Location - Tooltip": "Standort - Tooltip",
|
||||
@ -741,6 +742,7 @@
|
||||
"Password Set": "Passwort setzen",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Eigenschaften",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Neu erneut eingeben",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Telefon zurücksetzen...",
|
||||
@ -756,6 +758,7 @@
|
||||
"Unlink": "Link aufheben",
|
||||
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||
"Upload a photo": "Foto hochladen",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "Passwort eingeben"
|
||||
},
|
||||
|
@ -726,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Is forbidden - Tooltip",
|
||||
"Is global admin": "Is global admin",
|
||||
"Is global admin - Tooltip": "Is global admin - Tooltip",
|
||||
"Keys": "Keys",
|
||||
"Link": "Link",
|
||||
"Location": "Location",
|
||||
"Location - Tooltip": "Location - Tooltip",
|
||||
@ -741,6 +742,7 @@
|
||||
"Password Set": "Password Set",
|
||||
"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 Phone...": "Reset Phone...",
|
||||
@ -756,6 +758,7 @@
|
||||
"Unlink": "Unlink",
|
||||
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||
"Upload a photo": "Upload a photo",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "input password"
|
||||
},
|
||||
|
@ -726,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Est un administrateur global",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Lier",
|
||||
"Location": "Localisation",
|
||||
"Location - Tooltip": "Localisation - Infobulle",
|
||||
@ -741,6 +742,7 @@
|
||||
"Password Set": "Mot de passe défini",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Propriétés",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Nouvelle entrée",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Réinitialiser le téléphone...",
|
||||
@ -756,6 +758,7 @@
|
||||
"Unlink": "Délier",
|
||||
"Upload (.xlsx)": "Télécharger (.xlsx)",
|
||||
"Upload a photo": "Télécharger une photo",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "saisir le mot de passe"
|
||||
},
|
||||
|
@ -726,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "グローバル管理者",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "リンク",
|
||||
"Location": "場所",
|
||||
"Location - Tooltip": "場所 → ツールチップ",
|
||||
@ -741,6 +742,7 @@
|
||||
"Password Set": "パスワード設定",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "プロパティー",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "新しい入力",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "電話番号をリセット...",
|
||||
@ -756,6 +758,7 @@
|
||||
"Unlink": "リンクを解除",
|
||||
"Upload (.xlsx)": "アップロード (.xlsx)",
|
||||
"Upload a photo": "写真をアップロード",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "パスワードを入力"
|
||||
},
|
||||
|
@ -726,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Is global admin",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Link",
|
||||
"Location": "Location",
|
||||
"Location - Tooltip": "Location - Tooltip",
|
||||
@ -741,6 +742,7 @@
|
||||
"Password Set": "Password Set",
|
||||
"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 Phone...": "Reset Phone...",
|
||||
@ -756,6 +758,7 @@
|
||||
"Unlink": "Unlink",
|
||||
"Upload (.xlsx)": "Upload (.xlsx)",
|
||||
"Upload a photo": "Upload a photo",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "input password"
|
||||
},
|
||||
|
@ -726,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "Whether the account is disabled",
|
||||
"Is global admin": "Глобальный администратор",
|
||||
"Is global admin - Tooltip": "Is the application global administrator",
|
||||
"Keys": "Keys",
|
||||
"Link": "Ссылка",
|
||||
"Location": "Местоположение",
|
||||
"Location - Tooltip": "Расположение - Подсказка",
|
||||
@ -741,6 +742,7 @@
|
||||
"Password Set": "Пароль установлен",
|
||||
"Please select avatar from resources": "Please select avatar from resources",
|
||||
"Properties": "Свойства",
|
||||
"Properties - Tooltip": "Properties - Tooltip",
|
||||
"Re-enter New": "Введите еще раз",
|
||||
"Reset Email...": "Reset Email...",
|
||||
"Reset Phone...": "Сбросить телефон...",
|
||||
@ -756,6 +758,7 @@
|
||||
"Unlink": "Отвязать",
|
||||
"Upload (.xlsx)": "Загрузить (.xlsx)",
|
||||
"Upload a photo": "Загрузить фото",
|
||||
"Values": "Values",
|
||||
"WebAuthn credentials": "WebAuthn credentials",
|
||||
"input password": "пароль для ввода"
|
||||
},
|
||||
|
@ -726,6 +726,7 @@
|
||||
"Is forbidden - Tooltip": "账户是否已被禁用",
|
||||
"Is global admin": "是全局管理员",
|
||||
"Is global admin - Tooltip": "是应用程序管理员",
|
||||
"Keys": "键",
|
||||
"Link": "绑定",
|
||||
"Location": "城市",
|
||||
"Location - Tooltip": "居住地址所在的城市",
|
||||
@ -741,6 +742,7 @@
|
||||
"Password Set": "密码已设置",
|
||||
"Please select avatar from resources": "从资源中选择...",
|
||||
"Properties": "属性",
|
||||
"Properties - Tooltip": "属性",
|
||||
"Re-enter New": "重复新密码",
|
||||
"Reset Email...": "重置邮箱...",
|
||||
"Reset Phone...": "重置手机号...",
|
||||
@ -756,6 +758,7 @@
|
||||
"Unlink": "解绑",
|
||||
"Upload (.xlsx)": "上传(.xlsx)",
|
||||
"Upload a photo": "上传头像",
|
||||
"Values": "值",
|
||||
"WebAuthn credentials": "WebAuthn凭据",
|
||||
"input password": "输入密码"
|
||||
},
|
||||
|
131
web/src/propertyTable.js
Normal file
131
web/src/propertyTable.js
Normal 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;
|
Reference in New Issue
Block a user