mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-15 12:13:50 +08:00
feat: support forced binding MFA after login (#1845)
This commit is contained in:
@ -312,6 +312,11 @@ func (c *ApiController) Login() {
|
|||||||
|
|
||||||
resp = c.HandleLoggedIn(application, user, &authForm)
|
resp = c.HandleLoggedIn(application, user, &authForm)
|
||||||
|
|
||||||
|
organization := object.GetOrganizationByUser(user)
|
||||||
|
if user != nil && organization.HasRequiredMfa() && !user.IsMfaEnabled() {
|
||||||
|
resp.Msg = object.RequiredMfa
|
||||||
|
}
|
||||||
|
|
||||||
record := object.NewRecord(c.Ctx)
|
record := object.NewRecord(c.Ctx)
|
||||||
record.Organization = application.Organization
|
record.Organization = application.Organization
|
||||||
record.User = user.Name
|
record.User = user.Name
|
||||||
|
@ -51,6 +51,7 @@ const (
|
|||||||
const (
|
const (
|
||||||
MfaSessionUserId = "MfaSessionUserId"
|
MfaSessionUserId = "MfaSessionUserId"
|
||||||
NextMfa = "NextMfa"
|
NextMfa = "NextMfa"
|
||||||
|
RequiredMfa = "RequiredMfa"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
|
func GetMfaUtil(providerType string, config *MfaProps) MfaInterface {
|
||||||
|
@ -38,6 +38,11 @@ type ThemeData struct {
|
|||||||
IsEnabled bool `xorm:"bool" json:"isEnabled"`
|
IsEnabled bool `xorm:"bool" json:"isEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MfaItem struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Rule string `json:"rule"`
|
||||||
|
}
|
||||||
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
|
||||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||||
@ -59,6 +64,7 @@ type Organization struct {
|
|||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
|
|
||||||
|
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||||
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(3000)" json:"accountItems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,3 +414,12 @@ func organizationChangeTrigger(oldName string, newName string) error {
|
|||||||
|
|
||||||
return session.Commit()
|
return session.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (org *Organization) HasRequiredMfa() bool {
|
||||||
|
for _, item := range org.MfaItems {
|
||||||
|
if item.Rule == "Required" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -24,6 +24,7 @@ import {LinkOutlined} from "@ant-design/icons";
|
|||||||
import LdapTable from "./table/LdapTable";
|
import LdapTable from "./table/LdapTable";
|
||||||
import AccountTable from "./table/AccountTable";
|
import AccountTable from "./table/AccountTable";
|
||||||
import ThemeEditor from "./common/theme/ThemeEditor";
|
import ThemeEditor from "./common/theme/ThemeEditor";
|
||||||
|
import MfaTable from "./table/MfaTable";
|
||||||
|
|
||||||
const {Option} = Select;
|
const {Option} = Select;
|
||||||
|
|
||||||
@ -316,6 +317,18 @@ class OrganizationEditPage extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:MFA items"), i18next.t("general:MFA items - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={22} >
|
||||||
|
<MfaTable
|
||||||
|
title={i18next.t("general:MFA items")}
|
||||||
|
table={this.state.organization.mfaItems ?? []}
|
||||||
|
onUpdateTable={(value) => {this.updateOrganizationField("mfaItems", value);}}
|
||||||
|
/>
|
||||||
|
</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("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
|
{Setting.getLabel(i18next.t("theme:Theme"), i18next.t("theme:Theme - Tooltip"))} :
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
import {authConfig} from "./Auth";
|
import {authConfig} from "./Auth";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
|
|
||||||
export function getAccount(query) {
|
export function getAccount(query = "") {
|
||||||
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
|
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
|
@ -33,7 +33,7 @@ import LanguageSelect from "../common/select/LanguageSelect";
|
|||||||
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
import {CaptchaModal} from "../common/modal/CaptchaModal";
|
||||||
import {CaptchaRule} from "../common/modal/CaptchaModal";
|
import {CaptchaRule} from "../common/modal/CaptchaModal";
|
||||||
import RedirectForm from "../common/RedirectForm";
|
import RedirectForm from "../common/RedirectForm";
|
||||||
import {MfaAuthVerifyForm, NextMfa} from "./MfaAuthVerifyForm";
|
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./MfaAuthVerifyForm";
|
||||||
|
|
||||||
class LoginPage extends React.Component {
|
class LoginPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -224,23 +224,26 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
postCodeLoginAction(res) {
|
postCodeLoginAction(resp) {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
const ths = this;
|
const ths = this;
|
||||||
const oAuthParams = Util.getOAuthGetParameters();
|
const oAuthParams = Util.getOAuthGetParameters();
|
||||||
const code = res.data;
|
const code = resp.data;
|
||||||
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
|
||||||
const noRedirect = oAuthParams.noRedirect;
|
const noRedirect = oAuthParams.noRedirect;
|
||||||
if (Setting.hasPromptPage(application)) {
|
|
||||||
AuthBackend.getAccount("")
|
|
||||||
.then((res) => {
|
|
||||||
let account = null;
|
|
||||||
if (res.status === "ok") {
|
|
||||||
account = res.data;
|
|
||||||
account.organization = res.data2;
|
|
||||||
|
|
||||||
|
if (Setting.hasPromptPage(application) || resp.msg === RequiredMfa) {
|
||||||
|
AuthBackend.getAccount()
|
||||||
|
.then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
const account = res.data;
|
||||||
|
account.organization = res.data2;
|
||||||
this.onUpdateAccount(account);
|
this.onUpdateAccount(account);
|
||||||
|
|
||||||
|
if (resp.msg === RequiredMfa) {
|
||||||
|
Setting.goToLink(`/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}&promptType=mfa`);
|
||||||
|
}
|
||||||
|
|
||||||
if (Setting.isPromptAnswered(account, application)) {
|
if (Setting.isPromptAnswered(account, application)) {
|
||||||
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
|
||||||
} else {
|
} else {
|
||||||
@ -328,10 +331,20 @@ class LoginPage extends React.Component {
|
|||||||
const responseType = values["type"];
|
const responseType = values["type"];
|
||||||
|
|
||||||
if (responseType === "login") {
|
if (responseType === "login") {
|
||||||
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
if (res.msg === RequiredMfa) {
|
||||||
|
AuthBackend.getAccount().then((res) => {
|
||||||
const link = Setting.getFromLink();
|
if (res.status === "ok") {
|
||||||
Setting.goToLink(link);
|
const account = res.data;
|
||||||
|
account.organization = res.data2;
|
||||||
|
this.onUpdateAccount(account);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Setting.goToLink(`/prompt/${this.getApplicationObj().name}?promptType=mfa`);
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
|
||||||
|
const link = Setting.getFromLink();
|
||||||
|
Setting.goToLink(link);
|
||||||
|
}
|
||||||
} else if (responseType === "code") {
|
} else if (responseType === "code") {
|
||||||
this.postCodeLoginAction(res);
|
this.postCodeLoginAction(res);
|
||||||
} else if (responseType === "token" || responseType === "id_token") {
|
} else if (responseType === "token" || responseType === "id_token") {
|
||||||
@ -352,6 +365,7 @@ class LoginPage extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
callback(res);
|
callback(res);
|
||||||
} else if (res.status === NextMfa) {
|
} else if (res.status === NextMfa) {
|
||||||
|
@ -20,6 +20,7 @@ import {SmsMfaType} from "./MfaSetupPage";
|
|||||||
import {MfaSmsVerifyForm} from "./MfaVerifyForm";
|
import {MfaSmsVerifyForm} from "./MfaVerifyForm";
|
||||||
|
|
||||||
export const NextMfa = "NextMfa";
|
export const NextMfa = "NextMfa";
|
||||||
|
export const RequiredMfa = "RequiredMfa";
|
||||||
|
|
||||||
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
|
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
|
||||||
formValues.password = "";
|
formValues.password = "";
|
||||||
|
@ -97,9 +97,9 @@ export function MfaVerifyForm({mfaProps, application, user, onSuccess, onFail})
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mfaProps.type === SmsMfaType) {
|
if (mfaProps?.type === SmsMfaType) {
|
||||||
return <MfaSmsVerifyForm onFinish={onFinish} application={application} />;
|
return <MfaSmsVerifyForm onFinish={onFinish} application={application} />;
|
||||||
} else if (mfaProps.type === TotpMfaType) {
|
} else if (mfaProps?.type === TotpMfaType) {
|
||||||
return <MfaTotpVerifyForm onFinish={onFinish} mfaProps={mfaProps} />;
|
return <MfaTotpVerifyForm onFinish={onFinish} mfaProps={mfaProps} />;
|
||||||
} else {
|
} else {
|
||||||
return <div></div>;
|
return <div></div>;
|
||||||
@ -145,7 +145,11 @@ class MfaSetupPage extends React.Component {
|
|||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
account: props.account,
|
account: props.account,
|
||||||
current: 0,
|
applicationName: (props.applicationName ?? props.account?.signupApplication) ?? "",
|
||||||
|
isAuthenticated: props.isAuthenticated ?? false,
|
||||||
|
isPromptPage: props.isPromptPage,
|
||||||
|
redirectUri: props.redirectUri,
|
||||||
|
current: props.current ?? 0,
|
||||||
type: props.type ?? SmsMfaType,
|
type: props.type ?? SmsMfaType,
|
||||||
mfaProps: null,
|
mfaProps: null,
|
||||||
};
|
};
|
||||||
@ -155,8 +159,25 @@ class MfaSetupPage extends React.Component {
|
|||||||
this.getApplication();
|
this.getApplication();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
if (prevState.isAuthenticated === true && this.state.mfaProps === null) {
|
||||||
|
MfaBackend.MfaSetupInitiate({
|
||||||
|
type: this.state.type,
|
||||||
|
...this.getUser(),
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.status === "ok") {
|
||||||
|
this.setState({
|
||||||
|
mfaProps: res.data,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getApplication() {
|
getApplication() {
|
||||||
ApplicationBackend.getApplication("admin", this.state.account.signupApplication)
|
ApplicationBackend.getApplication("admin", this.state.applicationName)
|
||||||
.then((application) => {
|
.then((application) => {
|
||||||
if (application !== null) {
|
if (application !== null) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -181,18 +202,9 @@ class MfaSetupPage extends React.Component {
|
|||||||
return <CheckPasswordForm
|
return <CheckPasswordForm
|
||||||
user={this.getUser()}
|
user={this.getUser()}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
MfaBackend.MfaSetupInitiate({
|
this.setState({
|
||||||
type: this.state.type,
|
current: this.state.current + 1,
|
||||||
...this.getUser(),
|
isAuthenticated: true,
|
||||||
}).then((res) => {
|
|
||||||
if (res.status === "ok") {
|
|
||||||
this.setState({
|
|
||||||
current: this.state.current + 1,
|
|
||||||
mfaProps: res.data,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Setting.showMessage("error", i18next.t("mfa:Failed to initiate MFA"));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onFail={(res) => {
|
onFail={(res) => {
|
||||||
@ -200,8 +212,12 @@ class MfaSetupPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
case 1:
|
case 1:
|
||||||
|
if (!this.state.isAuthenticated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return <MfaVerifyForm
|
return <MfaVerifyForm
|
||||||
mfaProps={{...this.state.mfaProps}}
|
mfaProps={this.state.mfaProps}
|
||||||
application={this.state.application}
|
application={this.state.application}
|
||||||
user={this.getUser()}
|
user={this.getUser()}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
@ -214,10 +230,18 @@ class MfaSetupPage extends React.Component {
|
|||||||
}}
|
}}
|
||||||
/>;
|
/>;
|
||||||
case 2:
|
case 2:
|
||||||
|
if (!this.state.isAuthenticated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return <EnableMfaForm user={this.getUser()} mfaProps={{type: this.state.type, ...this.state.mfaProps}}
|
return <EnableMfaForm user={this.getUser()} mfaProps={{type: this.state.type, ...this.state.mfaProps}}
|
||||||
onSuccess={() => {
|
onSuccess={() => {
|
||||||
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
Setting.showMessage("success", i18next.t("general:Enabled successfully"));
|
||||||
Setting.goToLinkSoft(this, "/account");
|
if (this.state.isPromptPage && this.state.redirectUri) {
|
||||||
|
Setting.goToLink(this.state.redirectUri);
|
||||||
|
} else {
|
||||||
|
Setting.goToLink("/account");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onFail={(res) => {
|
onFail={(res) => {
|
||||||
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("general:Failed to enable")}: ${res.msg}`);
|
||||||
@ -265,7 +289,9 @@ class MfaSetupPage extends React.Component {
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
<Col span={24} style={{display: "flex", justifyContent: "center"}}>
|
||||||
<div style={{marginTop: "10px", textAlign: "center"}}>{this.renderStep()}</div>
|
<div style={{marginTop: "10px", textAlign: "center"}}>
|
||||||
|
{this.renderStep()}
|
||||||
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
@ -23,16 +23,19 @@ import AffiliationSelect from "../common/select/AffiliationSelect";
|
|||||||
import OAuthWidget from "../common/OAuthWidget";
|
import OAuthWidget from "../common/OAuthWidget";
|
||||||
import RegionSelect from "../common/select/RegionSelect";
|
import RegionSelect from "../common/select/RegionSelect";
|
||||||
import {withRouter} from "react-router-dom";
|
import {withRouter} from "react-router-dom";
|
||||||
|
import MfaSetupPage from "./MfaSetupPage";
|
||||||
|
|
||||||
class PromptPage extends React.Component {
|
class PromptPage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
const params = new URLSearchParams(this.props.location.search);
|
||||||
this.state = {
|
this.state = {
|
||||||
classes: props,
|
classes: props,
|
||||||
type: props.type,
|
type: props.type,
|
||||||
applicationName: props.applicationName ?? (props.match === undefined ? null : props.match.params.applicationName),
|
applicationName: props.applicationName ?? (props.match === undefined ? null : props.match.params.applicationName),
|
||||||
application: null,
|
application: null,
|
||||||
user: null,
|
user: null,
|
||||||
|
promptType: params.get("promptType"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,6 +228,26 @@ class PromptPage extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderPromptProvider(application) {
|
||||||
|
return <>
|
||||||
|
{this.renderContent(application)}
|
||||||
|
<div style={{marginTop: "50px"}}>
|
||||||
|
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
|
||||||
|
</div>;
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPromptMfa() {
|
||||||
|
return <MfaSetupPage
|
||||||
|
applicationName={this.getApplicationObj().name}
|
||||||
|
account={this.props.account}
|
||||||
|
current={1}
|
||||||
|
isAuthenticated={true}
|
||||||
|
isPromptPage={true}
|
||||||
|
redirectUri={this.getRedirectUrl()}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const application = this.getApplicationObj();
|
const application = this.getApplicationObj();
|
||||||
if (application === null) {
|
if (application === null) {
|
||||||
@ -259,12 +282,7 @@ class PromptPage extends React.Component {
|
|||||||
{
|
{
|
||||||
Setting.renderLogo(application)
|
Setting.renderLogo(application)
|
||||||
}
|
}
|
||||||
{
|
{this.state.promptType !== "mfa" ? this.renderPromptProvider(application) : this.renderPromptMfa(application)}
|
||||||
this.renderContent(application)
|
|
||||||
}
|
|
||||||
<div style={{marginTop: "50px"}}>
|
|
||||||
<Button disabled={!Setting.isPromptAnswered(this.state.user, application)} type="primary" size="large" onClick={() => {this.submitUserEdit(true);}}>{i18next.t("code:Submit and complete")}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
160
web/src/table/MfaTable.js
Normal file
160
web/src/table/MfaTable.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||||
|
import {Button, Col, Row, Select, Table, Tooltip} from "antd";
|
||||||
|
import * as Setting from "../Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
const {Option} = Select;
|
||||||
|
|
||||||
|
const MfaItems = [
|
||||||
|
{name: "Phone"},
|
||||||
|
{name: "Email"},
|
||||||
|
];
|
||||||
|
|
||||||
|
class MfaTable 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 = {name: Setting.getNewRowNameForTable(table, "Please select a MFA method"), rule: "Optional"};
|
||||||
|
if (table === undefined) {
|
||||||
|
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:Name"),
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: "100%"}}
|
||||||
|
value={text}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateField(table, index, "name", value);
|
||||||
|
}} >
|
||||||
|
{
|
||||||
|
Setting.getDeduplicatedArray(MfaItems, table, "name").map((item, index) => <Option key={index} value={item.name}>{item.name}</Option>)
|
||||||
|
}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("application:Rule"),
|
||||||
|
dataIndex: "rule",
|
||||||
|
key: "rule",
|
||||||
|
width: "100px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<Select virtual={false} style={{width: "100%"}}
|
||||||
|
value={text}
|
||||||
|
defaultValue="Optional"
|
||||||
|
options={[
|
||||||
|
{value: "Optional", label: i18next.t("organization:Optional")},
|
||||||
|
{value: "Required", label: i18next.t("organization:Required")}].map((item) =>
|
||||||
|
Setting.getOption(item.label, item.value))
|
||||||
|
}
|
||||||
|
onChange={value => {
|
||||||
|
this.updateField(table, index, "rule", value);
|
||||||
|
}} >
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: i18next.t("general:Action"),
|
||||||
|
key: "action",
|
||||||
|
width: "100px",
|
||||||
|
render: (text, record, index) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
|
||||||
|
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
|
||||||
|
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table scroll={{x: "max-content"}} rowKey="name" columns={columns} dataSource={table} size="middle" bordered pagination={false}
|
||||||
|
title={() => (
|
||||||
|
<div>
|
||||||
|
{this.props.title}
|
||||||
|
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col span={24}>
|
||||||
|
{
|
||||||
|
this.renderTable(this.props.table)
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MfaTable;
|
Reference in New Issue
Block a user