LDAP Page improvements (#2708)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-03-16 11:48:59 -06:00
committed by GitHub
parent c1d39a910f
commit dd356b6ea9
13 changed files with 855 additions and 145 deletions

View File

@@ -157,9 +157,7 @@ export const IAM_PAGES = {
USER_SA_ACCOUNT_ADD: "/identity/users/new-user-sa/:userName",
/* IDP */
IDP_LDAP_CONFIGURATIONS: "/identity/idp/ldap/configurations",
IDP_LDAP_CONFIGURATIONS_VIEW: "/identity/idp/ldap/configurations/:idpName",
IDP_LDAP_CONFIGURATIONS_ADD: "/identity/idp/ldap/configurations/add-idp",
IDP_LDAP_CONFIGURATIONS: "/identity/ldap/configuration",
IDP_OPENID_CONFIGURATIONS: "/identity/idp/openid/configurations",
IDP_OPENID_CONFIGURATIONS_VIEW:
@@ -451,14 +449,6 @@ export const IAM_PAGES_PERMISSIONS = {
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,

View File

@@ -113,20 +113,14 @@ 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")
() => import("./IDP/LDAP/IDPLDAPConfigurationDetails")
);
const IDPOpenIDConfigurationDetails = React.lazy(
() => import("./IDP/IDPOpenIDConfigurationDetails")
@@ -344,25 +338,17 @@ const Console = ({ classes }: IConsoleProps) => {
path: IAM_PAGES.POLICIES,
},
{
component: IDPLDAPConfigurations,
component: IDPLDAPConfigurationDetails,
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,

View File

@@ -163,8 +163,16 @@ const ConfTargetGeneric = ({
elements={holderItem ? holderItem.value : ""}
label={field.label}
name={field.name}
onChange={(value: string) => {
setValueElement(field.name, value, item);
onChange={(value: string | string[]) => {
let valCh = "";
if (Array.isArray(value)) {
valCh = value.join(",");
} else {
valCh = value;
}
setValueElement(field.name, valCh, item);
}}
tooltip={field.tooltip}
commonPlaceholder={field.placeholder}

View File

@@ -80,8 +80,8 @@ const AddIDPConfigurationHelpBox = ({
<div>{helpText}</div>
</Box>
<Box sx={{ fontSize: "14px", marginBottom: "15px" }}>
{contents.map((content) => (
<Fragment>
{contents.map((content, index) => (
<Fragment key={`feature-item-${index}`}>
{content.icon && (
<Box sx={{ paddingBottom: "20px" }}>
<FeatureItem

View File

@@ -1,47 +0,0 @@
// 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 LoginIcon from "@mui/icons-material/Login";
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
import AddIDPConfiguration from "./AddIDPConfiguration";
import { ldapFormFields, ldapHelpBoxContents } from "./utils";
import AddIDPConfigurationHelpBox from "./AddIDPConfigurationHelpbox";
const AddIDPLDAPConfiguration = () => {
return (
<AddIDPConfiguration
icon={<LoginIcon />}
helpBox={
<AddIDPConfigurationHelpBox
helpText={"Learn more about LDAP Configurations"}
contents={ldapHelpBoxContents}
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 AddIDPLDAPConfiguration;

View File

@@ -168,6 +168,7 @@ const IDPConfigurationDetails = ({
setLoading(false);
});
};
if (loading) {
loadRecord();
}

View File

@@ -1,47 +0,0 @@
// 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 { ldapFormFields, ldapHelpBoxContents } from "./utils";
import LoginIcon from "@mui/icons-material/Login";
import IDPConfigurationDetails from "./IDPConfigurationDetails";
import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
import AddIDPConfigurationHelpBox from "./AddIDPConfigurationHelpbox";
const IDPLDAPConfigurationDetails = () => {
return (
<IDPConfigurationDetails
backLink={IAM_PAGES.IDP_LDAP_CONFIGURATIONS}
header={"LDAP Configurations"}
endpoint={"/api/v1/idp/ldap/"}
idpType={"ldap"}
helpBox={
<AddIDPConfigurationHelpBox
helpText={"Learn more about LDAP Configurations"}
contents={ldapHelpBoxContents}
docLink={
"https://min.io/docs/minio/linux/operations/external-iam.html?ref=con#minio-external-iam-ad-ldap"
}
docText={"Learn more about LDAP Configurations"}
/>
}
formFields={ldapFormFields}
icon={<LoginIcon width={40} />}
/>
);
};
export default IDPLDAPConfigurationDetails;

View File

@@ -0,0 +1,436 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 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 {
Box,
Button,
EditIcon,
FormLayout,
Grid,
InputBox,
Loader,
PageLayout,
RefreshIcon,
} from "mds";
import { ErrorResponseHandler } from "../../../../common/types";
import { useAppDispatch } from "../../../../store";
import {
setErrorSnackMessage,
setServerNeedsRestart,
setSnackBarMessage,
} from "../../../../systemSlice";
import api from "../../../../common/api";
import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
import FormSwitchWrapper from "../../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
import LabelValuePair from "../../Common/UsageBarWrapper/LabelValuePair";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import { ldapFormFields, ldapHelpBoxContents } from "../utils";
import AddIDPConfigurationHelpBox from "../AddIDPConfigurationHelpbox";
import LDAPEntitiesQuery from "./LDAPEntitiesQuery";
import ResetConfigurationModal from "../../EventDestinations/CustomForms/ResetConfigurationModal";
import { IConfigurationSys, IElementValue } from "../../Configurations/types";
import { TabPanel } from "../../../shared/tabs";
import TabSelector from "../../Common/TabSelector/TabSelector";
const enabledConfigLDAP = [
"server_addr",
"lookup_bind_dn",
"lookup_bind_password",
"user_dn_search_base_dn",
"user_dn_search_filter",
];
const IDPLDAPConfigurationDetails = () => {
const dispatch = useAppDispatch();
const formFields = ldapFormFields;
const [loading, setLoading] = useState<boolean>(true);
const [isEnabled, setIsEnabled] = useState<boolean>(false);
const [hasConfiguration, setHasConfiguration] = useState<boolean>(false);
const [fields, setFields] = useState<any>({});
const [record, setRecord] = useState<IElementValue[] | null>(null);
const [editMode, setEditMode] = useState<boolean>(false);
const [resetOpen, setResetOpen] = useState<boolean>(false);
const [curTab, setCurTab] = useState<number>(0);
const toggleEditMode = () => {
if (editMode && record) {
parseFields(record);
}
setEditMode(!editMode);
};
const parseFields = (record: IElementValue[]) => {
let fields: any = {};
if (record && record.length > 0) {
const enabled = record.find((item: any) => item.key === "enable");
let totalCoincidences = 0;
record.forEach((item: any) => {
fields[item.key] = item.value;
if (
enabledConfigLDAP.includes(item.key) &&
item.value &&
item.value !== "" &&
item.value !== "off"
) {
totalCoincidences++;
}
});
const hasConfig = totalCoincidences === enabledConfigLDAP.length;
if ((!enabled || enabled.value === "on") && hasConfig) {
setIsEnabled(true);
} else {
setIsEnabled(false);
}
setHasConfiguration(hasConfig);
}
setFields(fields);
};
useEffect(() => {
const loadRecord = () => {
api
.invoke("GET", `/api/v1/configs/identity_ldap`)
.then((res: IConfigurationSys[]) => {
if (res.length > 0) {
setRecord(res[0].key_values);
parseFields(res[0].key_values);
}
setLoading(false);
})
.catch((err: ErrorResponseHandler) => {
setLoading(false);
dispatch(setErrorSnackMessage(err));
});
};
if (loading) {
loadRecord();
}
}, [dispatch, loading]);
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 saveRecord = () => {
const keyVals = Object.keys(formFields).map((key) => {
return {
key,
value: fields[key],
};
});
api
.invoke("PUT", `/api/v1/configs/identity_ldap`, {
key_values: keyVals,
})
.then((res) => {
setEditMode(false);
setRecord(keyVals);
parseFields(keyVals);
dispatch(setServerNeedsRestart(res.restart));
if (!res.restart) {
dispatch(setSnackBarMessage("Configuration saved successfully"));
}
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
});
};
const closeDeleteModalAndRefresh = async (refresh: boolean) => {
setResetOpen(false);
if (refresh) {
dispatch(setServerNeedsRestart(refresh));
setRecord(null);
setFields({});
setIsEnabled(false);
setHasConfiguration(false);
setEditMode(false);
}
};
/*
TODO: Review enable / disable functionality for LDAP and enable this module
const toggleConfiguration = (value: boolean) => {
const input: any[] = [];
const payload = {
key_values: [
...input,
{
key: "enable",
value: value ? "on" : "off",
},
],
};
api
.invoke("PUT", `/api/v1/configs/identity_ldap`, payload)
.then((res) => {
setIsEnabled(!isEnabled);
dispatch(setServerNeedsRestart(res.restart));
if (!res.restart) {
dispatch(setSnackBarMessage("Configuration saved successfully"));
}
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
});
};*/
const renderFormField = (key: string, value: any) => {
switch (value.type) {
case "toggle":
return (
<Box className={"inputItem"}>
<FormSwitchWrapper
key={key}
indicatorLabels={["Enabled", "Disabled"]}
checked={fields[key] === "on"}
value={"is-field-enabled"}
id={"is-field-enabled"}
name={"is-field-enabled"}
label={value.label}
tooltip={value.tooltip}
onChange={(e) =>
setFields({ ...fields, [key]: e.target.checked ? "on" : "off" })
}
description=""
disabled={!editMode}
/>
</Box>
);
default:
return (
<InputBox
key={key}
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}
/>
);
}
};
return (
<Grid item xs={12}>
{resetOpen && (
<ResetConfigurationModal
configurationName={"identity_ldap"}
closeResetModalAndRefresh={closeDeleteModalAndRefresh}
resetOpen={resetOpen}
/>
)}
<PageHeaderWrapper label={"LDAP"} />
<PageLayout variant={"constrained"}>
<TabSelector
selectedTab={curTab}
onChange={(newValue: number) => {
setCurTab(newValue);
setEditMode(false);
}}
tabOptions={[
{ label: "Configuration" },
{
label: "Entities",
disabled: !hasConfiguration,
},
]}
/>
<TabPanel index={0} value={curTab}>
<ScreenTitle
title={editMode ? "Edit Configuration" : ""}
actions={
!editMode ? (
<Fragment>
<Button
id={"edit"}
type="button"
variant={"callAction"}
icon={<EditIcon />}
onClick={toggleEditMode}
label={"Edit Configuration"}
disabled={loading}
/>
{/*<Button
id={"is-configuration-enabled"}
onClick={() => toggleConfiguration(!isEnabled)}
label={isEnabled ? "Disable LDAP" : "Enable LDAP"}
disabled={loadingEnabledSave}
variant={isEnabled ? "secondary" : "regular"}
/>*/}
<Button
id={"refresh-idp-config"}
onClick={() => setLoading(true)}
label={"Refresh"}
icon={<RefreshIcon />}
/>
</Fragment>
) : null
}
/>
<br />
{loading ? (
<Box
sx={{ display: "flex", justifyContent: "center", marginTop: 10 }}
>
<Loader />
</Box>
) : (
<Fragment>
{editMode ? (
<Fragment>
<FormLayout
helpBox={
<AddIDPConfigurationHelpBox
helpText={"Learn more about LDAP Configurations"}
contents={ldapHelpBoxContents}
docLink={
"https://min.io/docs/minio/linux/operations/external-iam.html?ref=con#minio-external-iam-ad-ldap"
}
docText={"Learn more about LDAP Configurations"}
/>
}
>
{Object.entries(formFields).map(([key, value]) =>
renderFormField(key, value)
)}
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
marginTop: "20px",
gap: "15px",
}}
>
{editMode && (
<Button
id={"clear"}
type="button"
variant="secondary"
onClick={() => setResetOpen(true)}
label={"Reset Configuration"}
/>
)}
{editMode && (
<Button
id={"cancel"}
type="button"
variant="regular"
onClick={toggleEditMode}
label={"Cancel"}
/>
)}
{editMode && (
<Button
id={"save-key"}
type="submit"
variant="callAction"
color="primary"
disabled={loading || !validSave()}
label={"Save"}
onClick={saveRecord}
/>
)}
</Box>
</FormLayout>
</Fragment>
) : (
<Fragment>
<Box
sx={{
display: "grid",
gridTemplateColumns: "1fr",
gridAutoFlow: "dense",
gap: 3,
padding: "15px",
border: "1px solid #eaeaea",
[`@media (min-width: 576px)`]: {
gridTemplateColumns: "2fr 1fr",
gridAutoFlow: "row",
},
}}
>
<LabelValuePair
label={"LDAP Enabled"}
value={isEnabled ? "Yes" : "No"}
/>
{hasConfiguration && (
<Fragment>
{Object.entries(formFields).map(([key, value]) => (
<LabelValuePair
key={key}
label={value.label}
value={fields[key] ? fields[key] : ""}
/>
))}
</Fragment>
)}
</Box>
</Fragment>
)}
</Fragment>
)}
</TabPanel>
<TabPanel index={1} value={curTab}>
{hasConfiguration && (
<Box>
<LDAPEntitiesQuery />
</Box>
)}
</TabPanel>
</PageLayout>
</Grid>
);
};
export default IDPLDAPConfigurationDetails;

View File

@@ -0,0 +1,360 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 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, useState } from "react";
import api from "../../../../common/api";
import { ErrorResponseHandler } from "../../../../common/types";
import { setErrorSnackMessage } from "../../../../systemSlice";
import { AppState, useAppDispatch } from "../../../../store";
import {
AddIcon,
Box,
Button,
Grid,
InputBox,
Loader,
RemoveIcon,
SectionTitle,
UptimeIcon,
} from "mds";
import PolicySelectors from "../../Policies/PolicySelectors";
import { useSelector } from "react-redux";
import { LDAPEntitiesResponse } from "./types";
import { DateTime } from "luxon";
const LDAPEntitiesQuery = () => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState<boolean>(false);
const [users, setUsers] = useState<string[]>([""]);
const [groups, setGroups] = useState<string[]>([""]);
const [results, setResults] = useState<LDAPEntitiesResponse | null>(null);
const selectedPolicies = useSelector(
(state: AppState) => state.createUser.selectedPolicies
);
const searchEntities = () => {
setLoading(true);
let data: any = {};
let cleanPolicies = selectedPolicies.filter((pol) => pol !== "");
let cleanUsers = users.filter((usr) => usr !== "");
let cleanGroups = groups.filter((grp) => grp !== "");
if (cleanPolicies.length > 0) {
data["policies"] = cleanPolicies;
}
if (cleanUsers.length > 0) {
data["users"] = cleanUsers;
}
if (cleanGroups.length > 0) {
data["groups"] = cleanGroups;
}
api
.invoke("POST", "/api/v1/ldap-entities", data)
.then((result: LDAPEntitiesResponse) => {
setResults(result);
setLoading(false);
})
.catch((err: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(err));
setLoading(false);
});
};
const alterUsersList = (addItem: boolean, index: number) => {
if (addItem) {
const alterUsers = [...users, ""];
setUsers(alterUsers);
return;
}
const filteredUsers = users.filter((_, indx) => indx !== index);
setUsers(filteredUsers);
};
const alterGroupsList = (addItem: boolean, index: number) => {
if (addItem) {
const alterGroups = [...groups, ""];
setGroups(alterGroups);
return;
}
const filteredGroups = groups.filter((_, indx) => indx !== index);
setGroups(filteredGroups);
};
return (
<Box sx={{ marginTop: 15, paddingTop: 0 }} withBorders>
<Grid container sx={{ marginTop: 5 }}>
<Grid item sm={12} md={6} lg={5} sx={{ padding: 10, paddingTop: 0 }}>
<SectionTitle separator>Query Filters</SectionTitle>
<Box sx={{ padding: "0 10px" }}>
<h4>Users</h4>
<Box
sx={{
overflowY: "auto",
minHeight: 220,
maxHeight: 250,
"& > div > div": {
width: "100%",
},
}}
>
{users.map((userDat, index) => {
return (
<InputBox
id={`search-user-${index}`}
key={`search-user-${index}`}
value={userDat}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const usersElements = [...users];
usersElements[index] = e.target.value;
setUsers(usersElements);
}}
overlayIcon={
users.length === index + 1 ? <AddIcon /> : <RemoveIcon />
}
overlayAction={() => {
alterUsersList(users.length === index + 1, index);
}}
/>
);
})}
</Box>
<h4>Groups</h4>
<Box
sx={{
overflowY: "auto",
minHeight: 220,
maxHeight: 250,
"& > div > div": {
width: "100%",
},
}}
>
{groups.map((groupDat, index) => {
return (
<InputBox
id={`search-group-${index}`}
key={`search-group-${index}`}
value={groupDat}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const groupsElements = [...groups];
groupsElements[index] = e.target.value;
setGroups(groupsElements);
}}
overlayIcon={
groups.length === index + 1 ? <AddIcon /> : <RemoveIcon />
}
overlayAction={() => {
alterGroupsList(groups.length === index + 1, index);
}}
/>
);
})}
</Box>
<h4>Policies</h4>
<Box
sx={{
minHeight: 220,
maxHeight: "calc(100vh - 740px)",
}}
>
<PolicySelectors selectedPolicy={selectedPolicies} noTitle />
</Box>
</Box>
</Grid>
<Grid item sm={12} md={6} lg={7} sx={{ padding: 10, paddingTop: 0 }}>
{loading ? (
<Box sx={{ textAlign: "center" }}>
<Loader />
</Box>
) : (
<Fragment>
<SectionTitle
separator
sx={{ marginBottom: 15 }}
actions={
<Box
sx={{
display: "flex",
flexDirection: "row",
alignItems: "center",
fontSize: 14,
}}
>
{results?.timestamp ? (
<Fragment>
<UptimeIcon
style={{ width: 18, height: 18, marginRight: 5 }}
/>
{DateTime.fromISO(results.timestamp).toFormat(
"D HH:mm:ss"
)}
</Fragment>
) : (
""
)}
</Box>
}
>
Results
</SectionTitle>
{results ? (
<Box>
{!results.groups && !results.users && !results.policies && (
<Box sx={{ textAlign: "center" }}>
<h4>No Results Available</h4>
</Box>
)}
{!!results.groups && (
<Box className={"resultElement"}>
<SectionTitle separator sx={{ fontSize: 12 }}>
Group Mappings
</SectionTitle>
<Box sx={{ padding: "0 15px" }}>
{results.groups.map((groupData, index) => {
return (
<Fragment key={`policy-res-${index}`}>
<h4>{groupData.group}</h4>
{groupData.policies && (
<Fragment>
Policies:
<ul>
{groupData.policies.map(
(policy, index2) => (
<li key={`policy-group-${index2}`}>
{policy}
</li>
)
)}
</ul>
</Fragment>
)}
</Fragment>
);
})}
</Box>
</Box>
)}
{!!results.users && (
<Box className={"resultElement"}>
<SectionTitle separator sx={{ fontSize: 12 }}>
User Mappings
</SectionTitle>
<Box sx={{ padding: "0 15px" }}>
{results.users.map((groupData, index) => {
return (
<Fragment key={`users-res-${index}`}>
<h4>{groupData.user}</h4>
{groupData.policies && (
<Fragment>
Policies:
<ul>
{groupData.policies.map(
(policy, index2) => (
<li key={`policy-users-${index2}`}>
{policy}
</li>
)
)}
</ul>
</Fragment>
)}
</Fragment>
);
})}
</Box>
</Box>
)}
{!!results.policies && (
<Box className={"resultElement"}>
<SectionTitle separator sx={{ fontSize: 12 }}>
Policy Mappings
</SectionTitle>
<Box sx={{ padding: "0 15px" }}>
{results.policies.map((groupData, index) => {
return (
<Fragment key={`policy-map-${index}`}>
<h4>{groupData.policy}</h4>
{groupData.groups && (
<Fragment>
Groups:
<ul>
{groupData.groups.map((group, index2) => (
<li key={`policy-map-group-${index}`}>
{group}
</li>
))}
</ul>
</Fragment>
)}
{groupData.users && (
<Fragment>
Users:
<ul>
{groupData.users.map((user, index3) => (
<li key={`policy-map-user-${index}`}>
{user}
</li>
))}
</ul>
</Fragment>
)}
</Fragment>
);
})}
</Box>
</Box>
)}
</Box>
) : (
<Box sx={{ textAlign: "center" }}>No query results yet</Box>
)}
</Fragment>
)}
</Grid>
</Grid>
<Grid container>
<Grid item xs={12} sx={{ display: "flex", justifyContent: "flex-end" }}>
<Button
id={"search-entity"}
type={"button"}
variant={"callAction"}
onClick={searchEntities}
>
Search
</Button>
</Grid>
</Grid>
</Box>
);
};
export default LDAPEntitiesQuery;

View File

@@ -1,5 +1,5 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
// Copyright (c) 2023 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
@@ -14,11 +14,31 @@
// 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 IDPConfigurations from "./IDPConfigurations";
export interface LDAPEntitiesRequest {
users?: string[];
groups?: string[];
policies?: string[];
}
const IDPLDAPConfigurations = () => {
return <IDPConfigurations idpType={"ldap"} />;
};
export interface LDAPEntitiesResponse {
timestamp: string;
users?: LDAPUsersResponse[];
groups?: LDAPGroupsResponse[];
policies?: LDAPPoliciesResponse[];
}
export default IDPLDAPConfigurations;
export interface LDAPUsersResponse {
user: string;
policies: string[];
}
export interface LDAPGroupsResponse {
group: string;
policies: string[];
}
export interface LDAPPoliciesResponse {
policy: string;
users: string[];
groups: string[];
}

View File

@@ -147,6 +147,16 @@ export const openIDFormFields = {
};
export const ldapFormFields = {
server_insecure: {
required: true,
hasError: (s: string, editMode: boolean) => {
return !s && editMode ? "Server Address is required" : "";
},
label: "Server Insecure",
tooltip: "disable SSL certificate verification ",
placeholder: "myldapserver.com:636",
type: "toggle",
},
server_addr: {
required: true,
hasError: (s: string, editMode: boolean) => {
@@ -199,14 +209,6 @@ export const ldapFormFields = {
placeholder: "(sAMAcountName=%s)",
type: "text",
},
display_name: {
required: false,
label: "Display Name",
tooltip: "",
placeholder: "Enter Display Name",
type: "text",
hasError: (s: string, editMode: boolean) => "",
},
group_search_base_dn: {
required: false,
hasError: (s: string, editMode: boolean) => "",

View File

@@ -42,6 +42,7 @@ import { useSelector } from "react-redux";
interface ISelectPolicyProps {
classes: any;
selectedPolicy?: string[];
noTitle?: boolean;
}
const styles = (theme: Theme) =>
@@ -75,10 +76,7 @@ const styles = (theme: Theme) =>
...selectorsCommon,
});
const PolicySelectors = ({
classes,
selectedPolicy = [],
}: ISelectPolicyProps) => {
const PolicySelectors = ({ classes, noTitle = false }: ISelectPolicyProps) => {
const dispatch = useAppDispatch();
// Local State
const [records, setRecords] = useState<any[]>([]);
@@ -147,7 +145,9 @@ const PolicySelectors = ({
{records.length > 0 ? (
<React.Fragment>
<Grid item xs={12} className={classes.filterBox}>
<span className={classes.fieldLabel}>Assign Policies</span>
{!noTitle && (
<span className={classes.fieldLabel}>Assign Policies</span>
)}
<div className={classes.searchBox}>
<SearchBox
placeholder="Start typing to search for a Policy"

View File

@@ -244,6 +244,7 @@ func getEntitiesResult(ctx context.Context, client MinioAdmin, users, groups, po
}
var result models.LdapEntities
var usersEntity []*models.LdapUserPolicyEntity
var groupsEntity []*models.LdapGroupPolicyEntity
var policiesEntity []*models.LdapPolicyEntity