IDP management UI (#2487)
Adds UI to interact with IDP Configurations (CRUD)
This commit is contained in:
@@ -130,6 +130,15 @@ export const IAM_PAGES = {
|
||||
ACCOUNT_ADD: "/access-keys/new-account",
|
||||
USER_SA_ACCOUNT_ADD: "/identity/users/new-user-sa/:userName",
|
||||
|
||||
/* IDP */
|
||||
IDP_LDAP_CONFIGURATIONS: "/idp/ldap/configurations",
|
||||
IDP_LDAP_CONFIGURATIONS_VIEW: "/idp/ldap/configurations/:idpName",
|
||||
IDP_LDAP_CONFIGURATIONS_ADD: "/idp/ldap/configurations/add-idp",
|
||||
|
||||
IDP_OPENID_CONFIGURATIONS: "/idp/openid/configurations",
|
||||
IDP_OPENID_CONFIGURATIONS_VIEW: "/idp/openid/configurations/:idpName",
|
||||
IDP_OPENID_CONFIGURATIONS_ADD: "/idp/openid/configurations/add-idp",
|
||||
|
||||
POLICIES: "/identity/policies",
|
||||
POLICY_ADD: "/identity/add-policy",
|
||||
POLICIES_VIEW: "/identity/policies/*",
|
||||
@@ -430,6 +439,30 @@ export const IAM_PAGES_PERMISSIONS = {
|
||||
IAM_SCOPES.ADMIN_SERVER_INFO,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS_ADD]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS_ADD]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS_VIEW]: [
|
||||
IAM_SCOPES.ADMIN_ALL_ACTIONS,
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
],
|
||||
};
|
||||
|
||||
export const S3_ALL_RESOURCES = "arn:aws:s3:::*";
|
||||
|
||||
@@ -122,6 +122,24 @@ const AccountCreate = React.lazy(
|
||||
|
||||
const Users = React.lazy(() => import("./Users/Users"));
|
||||
const Groups = React.lazy(() => import("./Groups/Groups"));
|
||||
const IDPLDAPConfigurations = React.lazy(
|
||||
() => import("./IDP/IDPLDAPConfigurations")
|
||||
);
|
||||
const IDPOpenIDConfigurations = React.lazy(
|
||||
() => import("./IDP/IDPOpenIDConfigurations")
|
||||
);
|
||||
const AddIDPLDAPConfiguration = React.lazy(
|
||||
() => import("./IDP/AddIDPLDAPConfiguration")
|
||||
);
|
||||
const AddIDPOpenIDConfiguration = React.lazy(
|
||||
() => import("./IDP/AddIDPOpenIDConfiguration")
|
||||
);
|
||||
const IDPLDAPConfigurationDetails = React.lazy(
|
||||
() => import("./IDP/IDPLDAPConfigurationDetails")
|
||||
);
|
||||
const IDPOpenIDConfigurationDetails = React.lazy(
|
||||
() => import("./IDP/IDPOpenIDConfigurationDetails")
|
||||
);
|
||||
|
||||
const TenantDetails = React.lazy(
|
||||
() => import("./Tenants/TenantDetails/TenantDetails")
|
||||
@@ -343,6 +361,30 @@ const Console = ({ classes }: IConsoleProps) => {
|
||||
component: Policies,
|
||||
path: IAM_PAGES.POLICIES,
|
||||
},
|
||||
{
|
||||
component: IDPLDAPConfigurations,
|
||||
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS,
|
||||
},
|
||||
{
|
||||
component: IDPOpenIDConfigurations,
|
||||
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS,
|
||||
},
|
||||
{
|
||||
component: AddIDPLDAPConfiguration,
|
||||
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS_ADD,
|
||||
},
|
||||
{
|
||||
component: AddIDPOpenIDConfiguration,
|
||||
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS_ADD,
|
||||
},
|
||||
{
|
||||
component: IDPLDAPConfigurationDetails,
|
||||
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS_VIEW,
|
||||
},
|
||||
{
|
||||
component: IDPOpenIDConfigurationDetails,
|
||||
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS_VIEW,
|
||||
},
|
||||
{
|
||||
component: Heal,
|
||||
path: IAM_PAGES.TOOLS_HEAL,
|
||||
|
||||
219
portal-ui/src/screens/Console/IDP/AddIDPConfiguration.tsx
Normal file
219
portal-ui/src/screens/Console/IDP/AddIDPConfiguration.tsx
Normal file
@@ -0,0 +1,219 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import {
|
||||
formFieldStyles,
|
||||
modalBasic,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { Button } from "mds";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import BackLink from "../../../common/BackLink";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import SectionTitle from "../Common/SectionTitle";
|
||||
|
||||
type AddIDPConfigurationProps = {
|
||||
classes?: any;
|
||||
icon: React.ReactNode;
|
||||
helpBox: React.ReactNode;
|
||||
header: string;
|
||||
title: string;
|
||||
backLink: string;
|
||||
formFields: object;
|
||||
endpoint: string;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...formFieldStyles,
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const AddIDPConfiguration = ({
|
||||
classes,
|
||||
icon,
|
||||
helpBox,
|
||||
header,
|
||||
backLink,
|
||||
title,
|
||||
formFields,
|
||||
endpoint,
|
||||
}: AddIDPConfigurationProps) => {
|
||||
const extraFormFields = {
|
||||
name: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Config Name is required" : "";
|
||||
},
|
||||
label: "Name",
|
||||
tooltip: "Name for identity provider configuration",
|
||||
placeholder: "Name",
|
||||
type: "text",
|
||||
},
|
||||
...formFields,
|
||||
};
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [fields, setFields] = useState<any>({});
|
||||
|
||||
const onSuccess = (res: any) => {
|
||||
navigate(backLink);
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
|
||||
const onError = (err: ErrorResponseHandler) =>
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
|
||||
const [loading, invokeApi] = useApi(onSuccess, onError);
|
||||
|
||||
const validSave = () => {
|
||||
for (const [key, value] of Object.entries(extraFormFields)) {
|
||||
if (
|
||||
value.required &&
|
||||
!(
|
||||
fields[key] !== undefined &&
|
||||
fields[key] !== null &&
|
||||
fields[key] !== ""
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFields({});
|
||||
};
|
||||
|
||||
const addRecord = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
const name = fields["name"];
|
||||
let input = "";
|
||||
for (const key of Object.keys(formFields)) {
|
||||
if (fields[key]) {
|
||||
input += `${key}=${fields[key]} `;
|
||||
}
|
||||
}
|
||||
invokeApi("POST", endpoint, { name, input });
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<PageHeader label={<BackLink to={backLink} label={header} />} />
|
||||
<PageLayout>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
padding: "25px",
|
||||
gap: "25px",
|
||||
gridTemplateColumns: {
|
||||
md: "2fr 1.2fr",
|
||||
xs: "1fr",
|
||||
},
|
||||
border: "1px solid #eaeaea",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<SectionTitle icon={icon}>{title}</SectionTitle>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container item spacing="20" sx={{ marginTop: 1 }}>
|
||||
<Grid xs={12} item>
|
||||
{Object.entries(extraFormFields).map(([key, value]) => (
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={classes.formFieldRow}
|
||||
key={key}
|
||||
>
|
||||
<InputBoxWrapper
|
||||
id={key}
|
||||
required={value.required}
|
||||
name={key}
|
||||
label={value.label}
|
||||
tooltip={value.tooltip}
|
||||
error={value.hasError(fields[key], true)}
|
||||
value={fields[key] ? fields[key] : ""}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setFields({ ...fields, [key]: e.target.value })
|
||||
}
|
||||
placeholder={value.placeholder}
|
||||
type={value.type}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12} textAlign={"right"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "20px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"clear"}
|
||||
type="button"
|
||||
variant="regular"
|
||||
onClick={resetForm}
|
||||
label={"Clear"}
|
||||
/>
|
||||
|
||||
<Button
|
||||
id={"save-key"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
color="primary"
|
||||
disabled={loading || !validSave()}
|
||||
label={"Save"}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</Box>
|
||||
{helpBox}
|
||||
</Box>
|
||||
</PageLayout>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddIDPConfiguration);
|
||||
106
portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx
Normal file
106
portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { Fragment } from "react";
|
||||
|
||||
import { Box } from "@mui/material";
|
||||
import { HelpIconFilled } from "../../../icons";
|
||||
|
||||
interface IContent {
|
||||
icon: React.ReactNode;
|
||||
text: string;
|
||||
iconDescription: string;
|
||||
}
|
||||
|
||||
interface IAddIDPConfigurationHelpBoxProps {
|
||||
helpText: string;
|
||||
docLink: string;
|
||||
docText: string;
|
||||
contents: IContent[];
|
||||
}
|
||||
|
||||
const FeatureItem = ({
|
||||
icon,
|
||||
description,
|
||||
}: {
|
||||
icon: any;
|
||||
description: string;
|
||||
}) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
"& .min-icon": {
|
||||
marginRight: "10px",
|
||||
height: "23px",
|
||||
width: "23px",
|
||||
marginBottom: "10px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{icon}{" "}
|
||||
<div style={{ fontSize: "14px", fontStyle: "italic", color: "#5E5E5E" }}>
|
||||
{description}
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const AddIDPConfigurationHelpBox = ({
|
||||
helpText,
|
||||
docLink,
|
||||
docText,
|
||||
contents,
|
||||
}: IAddIDPConfigurationHelpBoxProps) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 600,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "16px",
|
||||
paddingBottom: "20px",
|
||||
|
||||
"& .min-icon": {
|
||||
height: "21px",
|
||||
width: "21px",
|
||||
marginRight: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HelpIconFilled />
|
||||
<div>{helpText}</div>
|
||||
</Box>
|
||||
<Box sx={{ fontSize: "14px", marginBottom: "15px" }}>
|
||||
{contents.map((content) => (
|
||||
<Fragment>
|
||||
{content.icon && (
|
||||
<Box sx={{ paddingBottom: "20px" }}>
|
||||
<FeatureItem
|
||||
icon={content.icon}
|
||||
description={content.iconDescription}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<Box sx={{ paddingBottom: "20px" }}>{content.text}</Box>
|
||||
</Fragment>
|
||||
))}
|
||||
<Box sx={{ paddingBottom: "20px" }}>
|
||||
<a href={docLink} target="_blank" rel="noreferrer">
|
||||
{docText}
|
||||
</a>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddIDPConfigurationHelpBox;
|
||||
@@ -0,0 +1,86 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import LoginIcon from "@mui/icons-material/Login";
|
||||
import {
|
||||
formFieldStyles,
|
||||
modalBasic,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
import AddIDPConfiguration from "./AddIDPConfiguration";
|
||||
import { ldapFormFields } from "./utils";
|
||||
import AddIDPConfigurationHelpBox from "./AddIDPConfigurationHelpbox";
|
||||
|
||||
type AddIDPLDAPConfigurationProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...formFieldStyles,
|
||||
formFieldRow: {
|
||||
...formFieldStyles.formFieldRow,
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
const AddIDPLDAPConfiguration = ({ classes }: AddIDPLDAPConfigurationProps) => {
|
||||
const helpBoxContents = [
|
||||
{
|
||||
text: "MinIO supports using an Active Directory or LDAP (AD/LDAP) service for external management of user identities. Configuring an external IDentity Provider (IDP) enables Single-Sign On (SSO) workflows, where applications authenticate against the external IDP before accessing MinIO.",
|
||||
icon: <LoginIcon />,
|
||||
iconDescription: "Create Configurations",
|
||||
},
|
||||
{
|
||||
text: "MinIO queries the configured Active Directory / LDAP server to verify the credentials specified by the application and optionally return a list of groups in which the user has membership. MinIO supports two modes (Lookup-Bind Mode and Username-Bind Mode) for performing these queries",
|
||||
icon: null,
|
||||
iconDescription: "",
|
||||
},
|
||||
{
|
||||
text: "MinIO recommends using Lookup-Bind mode as the preferred method for verifying AD/LDAP credentials. Username-Bind mode is a legacy method retained for backwards compatibility only.",
|
||||
icon: null,
|
||||
iconDescription: "",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<AddIDPConfiguration
|
||||
icon={<LoginIcon />}
|
||||
helpBox={
|
||||
<AddIDPConfigurationHelpBox
|
||||
helpText={"Learn more about LDAP Configurations"}
|
||||
contents={helpBoxContents}
|
||||
docLink={
|
||||
"https://min.io/docs/minio/linux/operations/external-iam.html?ref=con#minio-external-iam-ad-ldap"
|
||||
}
|
||||
docText={"Learn more about LDAP Configurations"}
|
||||
/>
|
||||
}
|
||||
header={"LDAP Configurations"}
|
||||
backLink={IAM_PAGES.IDP_LDAP_CONFIGURATIONS}
|
||||
title={"Create LDAP Configuration"}
|
||||
endpoint={"/api/v1/idp/ldap/"}
|
||||
formFields={ldapFormFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddIDPLDAPConfiguration);
|
||||
@@ -0,0 +1,71 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
import { LockIcon } from "../../../icons";
|
||||
import AddIDPConfiguration from "./AddIDPConfiguration";
|
||||
import { openIDFormFields } from "./utils";
|
||||
import AddIDPConfigurationHelpBox from "./AddIDPConfigurationHelpbox";
|
||||
|
||||
type AddIDPOpenIDConfigurationProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const AddIDPOpenIDConfiguration = ({
|
||||
classes,
|
||||
}: AddIDPOpenIDConfigurationProps) => {
|
||||
const helpBoxContents = [
|
||||
{
|
||||
text: "MinIO supports using an OpenID Connect (OIDC) compatible IDentity Provider (IDP) such as Okta, KeyCloak, Dex, Google, or Facebook for external management of user identities.",
|
||||
icon: <LockIcon />,
|
||||
iconDescription: "Create Configurations",
|
||||
},
|
||||
{
|
||||
text: "Configuring an external IDP enables Single-Sign On workflows, where applications authenticate against the external IDP before accessing MinIO.",
|
||||
icon: null,
|
||||
iconDescription: "",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<AddIDPConfiguration
|
||||
icon={<LockIcon />}
|
||||
helpBox={
|
||||
<AddIDPConfigurationHelpBox
|
||||
helpText={"Learn more about OpenID Connect Configurations"}
|
||||
contents={helpBoxContents}
|
||||
docLink={
|
||||
"https://min.io/docs/minio/linux/operations/external-iam.html?ref=con#minio-external-iam-oidc"
|
||||
}
|
||||
docText={"Learn more about OpenID Connect Configurations"}
|
||||
/>
|
||||
}
|
||||
header={"OpenID Configurations"}
|
||||
backLink={IAM_PAGES.IDP_OPENID_CONFIGURATIONS}
|
||||
title={"Create OpenID Configuration"}
|
||||
endpoint={"/api/v1/idp/openid/"}
|
||||
formFields={openIDFormFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(AddIDPOpenIDConfiguration);
|
||||
@@ -0,0 +1,86 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { DialogContentText } from "@mui/material";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
import { ConfirmDeleteIcon } from "../../../icons";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
|
||||
interface IDeleteIDPConfigurationModalProps {
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
idp: string;
|
||||
idpType: string;
|
||||
}
|
||||
|
||||
const DeleteIDPConfigurationModal = ({
|
||||
closeDeleteModalAndRefresh,
|
||||
deleteOpen,
|
||||
idp,
|
||||
idpType,
|
||||
}: IDeleteIDPConfigurationModalProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onDelSuccess = (res: any) => {
|
||||
closeDeleteModalAndRefresh(true);
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
const onDelError = (err: ErrorResponseHandler) =>
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
const onClose = () => closeDeleteModalAndRefresh(false);
|
||||
|
||||
const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
|
||||
|
||||
if (!idp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const onConfirmDelete = () => {
|
||||
invokeDeleteApi("DELETE", `/api/v1/idp/${idpType}/${idp}`);
|
||||
};
|
||||
|
||||
const displayName = idp === "_" ? "Default" : idp;
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
title={`Delete ${displayName}`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
titleIcon={<ConfirmDeleteIcon />}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmButtonProps={{
|
||||
disabled: deleteLoading,
|
||||
}}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete IDP <b>{displayName}</b>{" "}
|
||||
configuration? <br />
|
||||
</DialogContentText>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteIDPConfigurationModal;
|
||||
364
portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx
Normal file
364
portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx
Normal file
@@ -0,0 +1,364 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import {
|
||||
buttonsStyles,
|
||||
containerForHeader,
|
||||
formFieldStyles,
|
||||
hrClass,
|
||||
modalBasic,
|
||||
pageContentStyles,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { RefreshIcon, TrashIcon } from "../../../icons";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import { Button } from "mds";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import {
|
||||
setErrorSnackMessage,
|
||||
setServerNeedsRestart,
|
||||
} from "../../../systemSlice";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import api from "../../../common/api";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import BackLink from "../../../common/BackLink";
|
||||
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
|
||||
import DeleteIDPConfigurationModal from "./DeleteIDPConfigurationModal";
|
||||
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
|
||||
type IDPConfigurationDetailsProps = {
|
||||
classes?: any;
|
||||
formFields: object;
|
||||
endpoint: string;
|
||||
backLink: string;
|
||||
header: string;
|
||||
idpType: string;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...formFieldStyles,
|
||||
formFieldRow: {
|
||||
...formFieldStyles.formFieldRow,
|
||||
},
|
||||
...modalBasic,
|
||||
pageContainer: {
|
||||
height: "100%",
|
||||
},
|
||||
screenTitle: {
|
||||
border: 0,
|
||||
paddingTop: 0,
|
||||
},
|
||||
...pageContentStyles,
|
||||
...searchField,
|
||||
capitalize: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
...hrClass,
|
||||
...buttonsStyles,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const IDPConfigurationDetails = ({
|
||||
classes,
|
||||
formFields,
|
||||
endpoint,
|
||||
backLink,
|
||||
header,
|
||||
idpType,
|
||||
icon,
|
||||
}: IDPConfigurationDetailsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const params = useParams();
|
||||
|
||||
const configurationName = params.idpName;
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [isEnabled, setIsEnabled] = useState<boolean>(false);
|
||||
const [fields, setFields] = useState<any>({});
|
||||
const [originalFields, setOriginalFields] = useState<any>({});
|
||||
const [record, setRecord] = useState<any>({});
|
||||
const [editMode, setEditMode] = useState<boolean>(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
|
||||
const onSuccess = (res: any) => {
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
|
||||
const onError = (err: ErrorResponseHandler) =>
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
|
||||
const [loadingSave, invokeApi] = useApi(onSuccess, onError);
|
||||
|
||||
const onEnabledSuccess = (res: any) => {
|
||||
setIsEnabled(!isEnabled);
|
||||
dispatch(setServerNeedsRestart(res.restart === true));
|
||||
};
|
||||
|
||||
const onEnabledError = (err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
};
|
||||
|
||||
const [loadingEnabledSave, invokeEnabledApi] = useApi(
|
||||
onEnabledSuccess,
|
||||
onEnabledError
|
||||
);
|
||||
|
||||
const toggleEditMode = () => {
|
||||
if (editMode) {
|
||||
parseFields(record);
|
||||
}
|
||||
setEditMode(!editMode);
|
||||
};
|
||||
|
||||
const parseFields = (record: any) => {
|
||||
let fields: any = {};
|
||||
if (record.info) {
|
||||
record.info.forEach((item: any) => {
|
||||
if (item.key === "enable") {
|
||||
setIsEnabled(item.value === "on");
|
||||
}
|
||||
fields[item.key] = item.value;
|
||||
});
|
||||
}
|
||||
setFields(fields);
|
||||
};
|
||||
|
||||
const parseOriginalFields = (record: any) => {
|
||||
let fields: any = {};
|
||||
if (record.info) {
|
||||
record.info.forEach((item: any) => {
|
||||
fields[item.key] = item.value;
|
||||
});
|
||||
}
|
||||
setOriginalFields(fields);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadRecord = () => {
|
||||
api
|
||||
.invoke("GET", `${endpoint}${configurationName}`)
|
||||
.then((result: any) => {
|
||||
if (result) {
|
||||
setRecord(result);
|
||||
parseFields(result);
|
||||
parseOriginalFields(result);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
if (loading) {
|
||||
loadRecord();
|
||||
}
|
||||
}, [dispatch, loading, configurationName, endpoint]);
|
||||
|
||||
const validSave = () => {
|
||||
for (const [key, value] of Object.entries(formFields)) {
|
||||
if (
|
||||
value.required &&
|
||||
!(
|
||||
fields[key] !== undefined &&
|
||||
fields[key] !== null &&
|
||||
fields[key] !== ""
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setFields({});
|
||||
};
|
||||
|
||||
const saveRecord = (event: React.FormEvent) => {
|
||||
event.preventDefault();
|
||||
let input = "";
|
||||
for (const key of Object.keys(formFields)) {
|
||||
if (fields[key] || fields[key] !== originalFields[key]) {
|
||||
input += `${key}=${fields[key]} `;
|
||||
}
|
||||
}
|
||||
invokeApi("PUT", `${endpoint}${configurationName}`, { input });
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = async (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
navigate(backLink);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleConfiguration = (value: boolean) => {
|
||||
const input = `enable=${value ? "on" : "off"}`;
|
||||
invokeEnabledApi("PUT", `${endpoint}${configurationName}`, { input });
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
{deleteOpen && configurationName && (
|
||||
<DeleteIDPConfigurationModal
|
||||
deleteOpen={deleteOpen}
|
||||
idp={configurationName}
|
||||
idpType={idpType}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<PageHeader
|
||||
label={<BackLink to={backLink} label={header} />}
|
||||
actions={
|
||||
<FormSwitchWrapper
|
||||
label={""}
|
||||
indicatorLabels={["Enabled", "Disabled"]}
|
||||
checked={isEnabled}
|
||||
value={"is-configuration-enabled"}
|
||||
id={"is-configuration-enabled"}
|
||||
name={"is-configuration-enabled"}
|
||||
onChange={(e) => toggleConfiguration(e.target.checked)}
|
||||
description=""
|
||||
disabled={loadingEnabledSave}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PageLayout className={classes.pageContainer}>
|
||||
<Grid item xs={12}>
|
||||
<ScreenTitle
|
||||
classes={{
|
||||
screenTitle: classes.screenTitle,
|
||||
}}
|
||||
icon={icon}
|
||||
title={configurationName === "_" ? "Default" : configurationName}
|
||||
actions={
|
||||
<Fragment>
|
||||
{configurationName !== "_" && (
|
||||
<Button
|
||||
id={"delete-idp-config"}
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
label={"Delete Configuration"}
|
||||
icon={<TrashIcon />}
|
||||
variant={"secondary"}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
id={"refresh-idp-config"}
|
||||
onClick={() => setLoading(true)}
|
||||
label={"Refresh"}
|
||||
icon={<RefreshIcon />}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
saveRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container item spacing="20" sx={{ marginTop: 1 }}>
|
||||
<Grid xs={12} item className={classes.fieldBox}>
|
||||
{Object.entries(formFields).map(([key, value]) => (
|
||||
<Grid item xs={12} className={classes.formFieldRow} key={key}>
|
||||
<InputBoxWrapper
|
||||
id={key}
|
||||
required={value.required}
|
||||
name={key}
|
||||
label={value.label}
|
||||
tooltip={value.tooltip}
|
||||
error={value.hasError(fields[key], editMode)}
|
||||
value={fields[key] ? fields[key] : ""}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setFields({ ...fields, [key]: e.target.value })
|
||||
}
|
||||
placeholder={value.placeholder}
|
||||
disabled={!editMode}
|
||||
type={value.type}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12} textAlign={"right"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "20px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"edit"}
|
||||
type="button"
|
||||
variant={editMode ? "regular" : "callAction"}
|
||||
onClick={toggleEditMode}
|
||||
label={editMode ? "Cancel" : "Edit"}
|
||||
/>
|
||||
{editMode && (
|
||||
<Button
|
||||
id={"clear"}
|
||||
type="button"
|
||||
variant="regular"
|
||||
onClick={resetForm}
|
||||
label={"Clear"}
|
||||
/>
|
||||
)}
|
||||
|
||||
{editMode && (
|
||||
<Button
|
||||
id={"save-key"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
color="primary"
|
||||
disabled={loading || loadingSave || !validSave()}
|
||||
label={"Save"}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</PageLayout>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPConfigurationDetails);
|
||||
225
portal-ui/src/screens/Console/IDP/IDPConfigurations.tsx
Normal file
225
portal-ui/src/screens/Console/IDP/IDPConfigurations.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import {
|
||||
hasPermission,
|
||||
SecureComponent,
|
||||
} from "../../../common/SecureComponent";
|
||||
import api from "../../../common/api";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Grid } from "@mui/material";
|
||||
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
||||
import { Button } from "mds";
|
||||
import { AddIcon, RefreshIcon } from "../../../icons";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import DeleteIDPConfigurationModal from "./DeleteIDPConfigurationModal";
|
||||
|
||||
type IDPConfigurationsProps = {
|
||||
classes?: any;
|
||||
idpType: string;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const IDPConfigurations = ({ classes, idpType }: IDPConfigurationsProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedIDP, setSelectedIDP] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [records, setRecords] = useState<[]>([]);
|
||||
|
||||
const deleteIDP = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
]);
|
||||
|
||||
const viewIDP = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
]);
|
||||
|
||||
const displayIDPs = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRecords();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
if (displayIDPs) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/idp/${idpType}`)
|
||||
.then((res) => {
|
||||
setLoading(false);
|
||||
setRecords(
|
||||
res.results.map((r: any) => {
|
||||
r.name = r.name === "_" ? "Default" : r.name;
|
||||
r.enabled = r.enabled === true ? "Enabled" : "Disabled";
|
||||
return r;
|
||||
})
|
||||
);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [loading, setLoading, setRecords, dispatch, displayIDPs, idpType]);
|
||||
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
const confirmDeleteIDP = (idp: string) => {
|
||||
setDeleteOpen(true);
|
||||
idp = idp === "Default" ? "_" : idp;
|
||||
setSelectedIDP(idp);
|
||||
};
|
||||
|
||||
const viewAction = (idp: any) => {
|
||||
let name = idp.name === "Default" ? "_" : idp.name;
|
||||
navigate(`/idp/${idpType}/configurations/${name}`);
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = async (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
fetchRecords();
|
||||
}
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "view",
|
||||
onClick: viewAction,
|
||||
disableButtonFunction: () => !viewIDP,
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
onClick: confirmDeleteIDP,
|
||||
sendOnlyId: true,
|
||||
disableButtonFunction: (idp: string) => !deleteIDP || idp === "Default",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteIDPConfigurationModal
|
||||
deleteOpen={deleteOpen}
|
||||
idp={selectedIDP}
|
||||
idpType={idpType}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<PageHeader label={`${idpType.toUpperCase()} Configurations`} />
|
||||
<PageLayout className={classes.pageContainer}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"flex-end"}
|
||||
sx={{
|
||||
"& button": {
|
||||
marginLeft: "8px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CONFIG_UPDATE]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={"Refresh"}>
|
||||
<Button
|
||||
id={"refresh-keys"}
|
||||
variant="regular"
|
||||
icon={<RefreshIcon />}
|
||||
onClick={() => setLoading(true)}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CONFIG_UPDATE]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={`Create ${idpType} configuration`}>
|
||||
<Button
|
||||
id={"create-idp"}
|
||||
label={"Create Configuration"}
|
||||
variant={"callAction"}
|
||||
icon={<AddIcon />}
|
||||
onClick={() =>
|
||||
navigate(`/idp/${idpType}/configurations/add-idp`)
|
||||
}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.ADMIN_CONFIG_UPDATE]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Type", elementKey: "type" },
|
||||
{ label: "Enabled", elementKey: "enabled" },
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={records}
|
||||
entityName="Keys"
|
||||
idField="name"
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPConfigurations);
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { ldapFormFields } from "./utils";
|
||||
import LoginIcon from "@mui/icons-material/Login";
|
||||
import IDPConfigurationDetails from "./IDPConfigurationDetails";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
|
||||
type IDPLDAPConfigurationDetailsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPLDAPConfigurationDetails = ({
|
||||
classes,
|
||||
}: IDPLDAPConfigurationDetailsProps) => {
|
||||
return (
|
||||
<IDPConfigurationDetails
|
||||
backLink={IAM_PAGES.IDP_LDAP_CONFIGURATIONS}
|
||||
header={"LDAP Configurations"}
|
||||
endpoint={"/api/v1/idp/ldap/"}
|
||||
idpType={"ldap"}
|
||||
formFields={ldapFormFields}
|
||||
icon={<LoginIcon width={40} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPLDAPConfigurationDetails);
|
||||
34
portal-ui/src/screens/Console/IDP/IDPLDAPConfigurations.tsx
Normal file
34
portal-ui/src/screens/Console/IDP/IDPLDAPConfigurations.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import IDPConfigurations from "./IDPConfigurations";
|
||||
|
||||
type IDPLDAPConfigurationsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPLDAPConfigurations = ({ classes }: IDPLDAPConfigurationsProps) => {
|
||||
return <IDPConfigurations idpType={"ldap"} />;
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPLDAPConfigurations);
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
|
||||
import { LockIcon } from "../../../icons";
|
||||
import { openIDFormFields } from "./utils";
|
||||
import IDPConfigurationDetails from "./IDPConfigurationDetails";
|
||||
|
||||
type IDPOpenIDConfigurationDetailsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPOpenIDConfigurationDetails = ({
|
||||
classes,
|
||||
}: IDPOpenIDConfigurationDetailsProps) => {
|
||||
return (
|
||||
<IDPConfigurationDetails
|
||||
backLink={IAM_PAGES.IDP_OPENID_CONFIGURATIONS}
|
||||
header={"OpenID Configurations"}
|
||||
endpoint={"/api/v1/idp/openid/"}
|
||||
idpType={"openid"}
|
||||
formFields={openIDFormFields}
|
||||
icon={<LockIcon width={40} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPOpenIDConfigurationDetails);
|
||||
@@ -0,0 +1,34 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import IDPConfigurations from "./IDPConfigurations";
|
||||
|
||||
type IDPOpenIDConfigurationsProps = {
|
||||
classes?: any;
|
||||
};
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const IDPOpenIDConfigurations = ({ classes }: IDPOpenIDConfigurationsProps) => {
|
||||
return <IDPConfigurations idpType={"openid"} />;
|
||||
};
|
||||
|
||||
export default withStyles(styles)(IDPOpenIDConfigurations);
|
||||
176
portal-ui/src/screens/Console/IDP/utils.tsx
Normal file
176
portal-ui/src/screens/Console/IDP/utils.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
export const openIDFormFields = {
|
||||
config_url: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Config URL is required" : "";
|
||||
},
|
||||
label: "Config URL",
|
||||
tooltip: "Config URL for identity provider configuration",
|
||||
placeholder:
|
||||
"https://identity-provider-url/.well-known/openid-configuration",
|
||||
type: "text",
|
||||
},
|
||||
client_id: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Client ID is required" : "";
|
||||
},
|
||||
label: "Client ID",
|
||||
tooltip: "Identity provider Client ID",
|
||||
placeholder: "Enter Client ID",
|
||||
type: "text",
|
||||
},
|
||||
client_secret: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Client Secret is required" : "";
|
||||
},
|
||||
label: "Client Secret",
|
||||
tooltip: "Identity provider Client Secret",
|
||||
placeholder: "Enter Client Secret",
|
||||
type: "password",
|
||||
},
|
||||
display_name: {
|
||||
required: false,
|
||||
label: "Display Name",
|
||||
tooltip: "Display Name",
|
||||
placeholder: "Enter Display Name",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
claim_name: {
|
||||
required: false,
|
||||
label: "Claim Name",
|
||||
tooltip: "Claim from which MinIO will read the policy or role to use",
|
||||
placeholder: "Enter Claim Name",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
claim_prefix: {
|
||||
required: false,
|
||||
label: "Claim Prefix",
|
||||
tooltip: "Claim Prefix",
|
||||
placeholder: "Enter Claim Prefix",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
scopes: {
|
||||
required: false,
|
||||
label: "Scopes",
|
||||
tooltip: "Scopes",
|
||||
placeholder: "openid,profile,email",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
redirect_uri: {
|
||||
required: false,
|
||||
label: "Redirect URI",
|
||||
tooltip: "Redirect URI",
|
||||
placeholder: "https://console-endpoint-url/oauth_callback",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
role_policy: {
|
||||
required: false,
|
||||
label: "Role Policy",
|
||||
tooltip: "Role Policy",
|
||||
placeholder: "readonly",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
};
|
||||
|
||||
export const ldapFormFields = {
|
||||
server_addr: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Server Address is required" : "";
|
||||
},
|
||||
label: "Server Address",
|
||||
tooltip: 'AD/LDAP server address e.g. "myldapserver.com:636"',
|
||||
placeholder: "myldapserver.com:636",
|
||||
type: "text",
|
||||
},
|
||||
lookup_bind_dn: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Lookup Bind DN is required" : "";
|
||||
},
|
||||
label: "Lookup Bind DN",
|
||||
tooltip:
|
||||
"DN for LDAP read-only service account used to perform DN and group lookups",
|
||||
placeholder: "cn=admin,dc=min,dc=io",
|
||||
type: "text",
|
||||
},
|
||||
lookup_bind_password: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "Lookup Bind Password is required" : "";
|
||||
},
|
||||
label: "Lookup Bind Password",
|
||||
tooltip:
|
||||
"Password for LDAP read-only service account used to perform DN and group lookups",
|
||||
placeholder: "admin",
|
||||
type: "password",
|
||||
},
|
||||
user_dn_search_base_dn: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "User DN Search Base DN is required" : "";
|
||||
},
|
||||
label: "User DN Search Base",
|
||||
tooltip: "Base LDAP DN to search for user DN",
|
||||
placeholder: "DC=example,DC=net",
|
||||
type: "text",
|
||||
},
|
||||
user_dn_search_filter: {
|
||||
required: true,
|
||||
hasError: (s: string, editMode: boolean) => {
|
||||
return !s && editMode ? "User DN Search Filter is required" : "";
|
||||
},
|
||||
label: "User DN Search Filter",
|
||||
tooltip: "Search filter to lookup user DN",
|
||||
placeholder: "(sAMAcountName=%s)",
|
||||
type: "text",
|
||||
},
|
||||
display_name: {
|
||||
required: false,
|
||||
label: "Display Name",
|
||||
tooltip: "Display Name",
|
||||
placeholder: "Enter Display Name",
|
||||
type: "text",
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
},
|
||||
group_search_base_dn: {
|
||||
required: false,
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
label: "Group Search Base DN",
|
||||
tooltip: "Group Search Base DN",
|
||||
placeholder: "ou=swengg,dc=min,dc=io",
|
||||
type: "text",
|
||||
},
|
||||
group_search_filter: {
|
||||
required: false,
|
||||
hasError: (s: string, editMode: boolean) => "",
|
||||
label: "Group Search Filter",
|
||||
tooltip: "Group Search Filter",
|
||||
placeholder: "(&(objectclass=groupofnames)(member=%d))",
|
||||
type: "text",
|
||||
},
|
||||
};
|
||||
@@ -59,6 +59,7 @@ import {
|
||||
import SettingsIcon from "../../icons/SettingsIcon";
|
||||
import React from "react";
|
||||
import LicenseBadge from "./Menu/LicenseBadge";
|
||||
import { LockOpen, Login } from "@mui/icons-material";
|
||||
|
||||
export const validRoutes = (
|
||||
features: string[] | null | undefined,
|
||||
@@ -141,6 +142,20 @@ export const validRoutes = (
|
||||
to: IAM_PAGES.POLICIES,
|
||||
icon: AccessMenuIcon,
|
||||
},
|
||||
{
|
||||
name: "OpenID",
|
||||
component: NavLink,
|
||||
id: "openID",
|
||||
to: IAM_PAGES.IDP_OPENID_CONFIGURATIONS,
|
||||
icon: LockOpen,
|
||||
},
|
||||
{
|
||||
name: "LDAP",
|
||||
component: NavLink,
|
||||
id: "ldap",
|
||||
to: IAM_PAGES.IDP_LDAP_CONFIGURATIONS,
|
||||
icon: Login,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user