Skip to content

Commit 2368199

Browse files
authored
IDP management UI (#2487)
Adds UI to interact with IDP Configurations (CRUD)
1 parent bee98e1 commit 2368199

15 files changed

+1587
-0
lines changed

portal-ui/src/common/SecureComponent/permissions.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ export const IAM_PAGES = {
130130
ACCOUNT_ADD: "/access-keys/new-account",
131131
USER_SA_ACCOUNT_ADD: "/identity/users/new-user-sa/:userName",
132132

133+
/* IDP */
134+
IDP_LDAP_CONFIGURATIONS: "/idp/ldap/configurations",
135+
IDP_LDAP_CONFIGURATIONS_VIEW: "/idp/ldap/configurations/:idpName",
136+
IDP_LDAP_CONFIGURATIONS_ADD: "/idp/ldap/configurations/add-idp",
137+
138+
IDP_OPENID_CONFIGURATIONS: "/idp/openid/configurations",
139+
IDP_OPENID_CONFIGURATIONS_VIEW: "/idp/openid/configurations/:idpName",
140+
IDP_OPENID_CONFIGURATIONS_ADD: "/idp/openid/configurations/add-idp",
141+
133142
POLICIES: "/identity/policies",
134143
POLICY_ADD: "/identity/add-policy",
135144
POLICIES_VIEW: "/identity/policies/*",
@@ -430,6 +439,30 @@ export const IAM_PAGES_PERMISSIONS = {
430439
IAM_SCOPES.ADMIN_SERVER_INFO,
431440
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
432441
],
442+
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS]: [
443+
IAM_SCOPES.ADMIN_ALL_ACTIONS,
444+
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
445+
],
446+
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS_ADD]: [
447+
IAM_SCOPES.ADMIN_ALL_ACTIONS,
448+
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
449+
],
450+
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS_VIEW]: [
451+
IAM_SCOPES.ADMIN_ALL_ACTIONS,
452+
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
453+
],
454+
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS]: [
455+
IAM_SCOPES.ADMIN_ALL_ACTIONS,
456+
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
457+
],
458+
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS_ADD]: [
459+
IAM_SCOPES.ADMIN_ALL_ACTIONS,
460+
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
461+
],
462+
[IAM_PAGES.IDP_OPENID_CONFIGURATIONS_VIEW]: [
463+
IAM_SCOPES.ADMIN_ALL_ACTIONS,
464+
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
465+
],
433466
};
434467

435468
export const S3_ALL_RESOURCES = "arn:aws:s3:::*";

portal-ui/src/screens/Console/Console.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,24 @@ const AccountCreate = React.lazy(
122122

123123
const Users = React.lazy(() => import("./Users/Users"));
124124
const Groups = React.lazy(() => import("./Groups/Groups"));
125+
const IDPLDAPConfigurations = React.lazy(
126+
() => import("./IDP/IDPLDAPConfigurations")
127+
);
128+
const IDPOpenIDConfigurations = React.lazy(
129+
() => import("./IDP/IDPOpenIDConfigurations")
130+
);
131+
const AddIDPLDAPConfiguration = React.lazy(
132+
() => import("./IDP/AddIDPLDAPConfiguration")
133+
);
134+
const AddIDPOpenIDConfiguration = React.lazy(
135+
() => import("./IDP/AddIDPOpenIDConfiguration")
136+
);
137+
const IDPLDAPConfigurationDetails = React.lazy(
138+
() => import("./IDP/IDPLDAPConfigurationDetails")
139+
);
140+
const IDPOpenIDConfigurationDetails = React.lazy(
141+
() => import("./IDP/IDPOpenIDConfigurationDetails")
142+
);
125143

126144
const TenantDetails = React.lazy(
127145
() => import("./Tenants/TenantDetails/TenantDetails")
@@ -343,6 +361,30 @@ const Console = ({ classes }: IConsoleProps) => {
343361
component: Policies,
344362
path: IAM_PAGES.POLICIES,
345363
},
364+
{
365+
component: IDPLDAPConfigurations,
366+
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS,
367+
},
368+
{
369+
component: IDPOpenIDConfigurations,
370+
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS,
371+
},
372+
{
373+
component: AddIDPLDAPConfiguration,
374+
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS_ADD,
375+
},
376+
{
377+
component: AddIDPOpenIDConfiguration,
378+
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS_ADD,
379+
},
380+
{
381+
component: IDPLDAPConfigurationDetails,
382+
path: IAM_PAGES.IDP_LDAP_CONFIGURATIONS_VIEW,
383+
},
384+
{
385+
component: IDPOpenIDConfigurationDetails,
386+
path: IAM_PAGES.IDP_OPENID_CONFIGURATIONS_VIEW,
387+
},
346388
{
347389
component: Heal,
348390
path: IAM_PAGES.TOOLS_HEAL,
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2022 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import React, { useState } from "react";
18+
19+
import { Theme } from "@mui/material/styles";
20+
import createStyles from "@mui/styles/createStyles";
21+
import withStyles from "@mui/styles/withStyles";
22+
import { Box, Grid } from "@mui/material";
23+
import {
24+
formFieldStyles,
25+
modalBasic,
26+
} from "../Common/FormComponents/common/styleLibrary";
27+
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
28+
import { Button } from "mds";
29+
import { useNavigate } from "react-router-dom";
30+
import { ErrorResponseHandler } from "../../../common/types";
31+
import { useAppDispatch } from "../../../store";
32+
import {
33+
setErrorSnackMessage,
34+
setServerNeedsRestart,
35+
} from "../../../systemSlice";
36+
import useApi from "../Common/Hooks/useApi";
37+
import PageHeader from "../Common/PageHeader/PageHeader";
38+
import BackLink from "../../../common/BackLink";
39+
import PageLayout from "../Common/Layout/PageLayout";
40+
import SectionTitle from "../Common/SectionTitle";
41+
42+
type AddIDPConfigurationProps = {
43+
classes?: any;
44+
icon: React.ReactNode;
45+
helpBox: React.ReactNode;
46+
header: string;
47+
title: string;
48+
backLink: string;
49+
formFields: object;
50+
endpoint: string;
51+
};
52+
53+
const styles = (theme: Theme) =>
54+
createStyles({
55+
...formFieldStyles,
56+
...modalBasic,
57+
});
58+
59+
const AddIDPConfiguration = ({
60+
classes,
61+
icon,
62+
helpBox,
63+
header,
64+
backLink,
65+
title,
66+
formFields,
67+
endpoint,
68+
}: AddIDPConfigurationProps) => {
69+
const extraFormFields = {
70+
name: {
71+
required: true,
72+
hasError: (s: string, editMode: boolean) => {
73+
return !s && editMode ? "Config Name is required" : "";
74+
},
75+
label: "Name",
76+
tooltip: "Name for identity provider configuration",
77+
placeholder: "Name",
78+
type: "text",
79+
},
80+
...formFields,
81+
};
82+
83+
const navigate = useNavigate();
84+
const dispatch = useAppDispatch();
85+
86+
const [fields, setFields] = useState<any>({});
87+
88+
const onSuccess = (res: any) => {
89+
navigate(backLink);
90+
dispatch(setServerNeedsRestart(res.restart === true));
91+
};
92+
93+
const onError = (err: ErrorResponseHandler) =>
94+
dispatch(setErrorSnackMessage(err));
95+
96+
const [loading, invokeApi] = useApi(onSuccess, onError);
97+
98+
const validSave = () => {
99+
for (const [key, value] of Object.entries(extraFormFields)) {
100+
if (
101+
value.required &&
102+
!(
103+
fields[key] !== undefined &&
104+
fields[key] !== null &&
105+
fields[key] !== ""
106+
)
107+
) {
108+
return false;
109+
}
110+
}
111+
return true;
112+
};
113+
114+
const resetForm = () => {
115+
setFields({});
116+
};
117+
118+
const addRecord = (event: React.FormEvent) => {
119+
event.preventDefault();
120+
const name = fields["name"];
121+
let input = "";
122+
for (const key of Object.keys(formFields)) {
123+
if (fields[key]) {
124+
input += `${key}=${fields[key]} `;
125+
}
126+
}
127+
invokeApi("POST", endpoint, { name, input });
128+
};
129+
130+
return (
131+
<Grid item xs={12}>
132+
<PageHeader label={<BackLink to={backLink} label={header} />} />
133+
<PageLayout>
134+
<Box
135+
sx={{
136+
display: "grid",
137+
padding: "25px",
138+
gap: "25px",
139+
gridTemplateColumns: {
140+
md: "2fr 1.2fr",
141+
xs: "1fr",
142+
},
143+
border: "1px solid #eaeaea",
144+
}}
145+
>
146+
<Box>
147+
<SectionTitle icon={icon}>{title}</SectionTitle>
148+
<form
149+
noValidate
150+
autoComplete="off"
151+
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
152+
addRecord(e);
153+
}}
154+
>
155+
<Grid container item spacing="20" sx={{ marginTop: 1 }}>
156+
<Grid xs={12} item>
157+
{Object.entries(extraFormFields).map(([key, value]) => (
158+
<Grid
159+
item
160+
xs={12}
161+
className={classes.formFieldRow}
162+
key={key}
163+
>
164+
<InputBoxWrapper
165+
id={key}
166+
required={value.required}
167+
name={key}
168+
label={value.label}
169+
tooltip={value.tooltip}
170+
error={value.hasError(fields[key], true)}
171+
value={fields[key] ? fields[key] : ""}
172+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
173+
setFields({ ...fields, [key]: e.target.value })
174+
}
175+
placeholder={value.placeholder}
176+
type={value.type}
177+
/>
178+
</Grid>
179+
))}
180+
<Grid item xs={12} textAlign={"right"}>
181+
<Box
182+
sx={{
183+
display: "flex",
184+
alignItems: "center",
185+
justifyContent: "flex-end",
186+
marginTop: "20px",
187+
gap: "15px",
188+
}}
189+
>
190+
<Button
191+
id={"clear"}
192+
type="button"
193+
variant="regular"
194+
onClick={resetForm}
195+
label={"Clear"}
196+
/>
197+
198+
<Button
199+
id={"save-key"}
200+
type="submit"
201+
variant="callAction"
202+
color="primary"
203+
disabled={loading || !validSave()}
204+
label={"Save"}
205+
/>
206+
</Box>
207+
</Grid>
208+
</Grid>
209+
</Grid>
210+
</form>
211+
</Box>
212+
{helpBox}
213+
</Box>
214+
</PageLayout>
215+
</Grid>
216+
);
217+
};
218+
219+
export default withStyles(styles)(AddIDPConfiguration);

0 commit comments

Comments
 (0)