diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts index 0325608f1..79aa9d7c9 100644 --- a/portal-ui/src/common/SecureComponent/permissions.ts +++ b/portal-ui/src/common/SecureComponent/permissions.ts @@ -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, diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index caba83d74..8cc4d8c6b 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -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, diff --git a/portal-ui/src/screens/Console/EventDestinations/ConfTargetGeneric.tsx b/portal-ui/src/screens/Console/EventDestinations/ConfTargetGeneric.tsx index 202379054..3f5c1d7e6 100644 --- a/portal-ui/src/screens/Console/EventDestinations/ConfTargetGeneric.tsx +++ b/portal-ui/src/screens/Console/EventDestinations/ConfTargetGeneric.tsx @@ -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} diff --git a/portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx b/portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx index d75b3cc6a..91ea7080e 100644 --- a/portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx +++ b/portal-ui/src/screens/Console/IDP/AddIDPConfigurationHelpbox.tsx @@ -80,8 +80,8 @@ const AddIDPConfigurationHelpBox = ({
{helpText}
- {contents.map((content) => ( - + {contents.map((content, index) => ( + {content.icon && ( . - -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 ( - } - helpBox={ - - } - header={"LDAP Configurations"} - backLink={IAM_PAGES.IDP_LDAP_CONFIGURATIONS} - title={"Create LDAP Configuration"} - endpoint={"/api/v1/idp/ldap/"} - formFields={ldapFormFields} - /> - ); -}; - -export default AddIDPLDAPConfiguration; diff --git a/portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx b/portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx index f6bea053a..c7d517f46 100644 --- a/portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx +++ b/portal-ui/src/screens/Console/IDP/IDPConfigurationDetails.tsx @@ -168,6 +168,7 @@ const IDPConfigurationDetails = ({ setLoading(false); }); }; + if (loading) { loadRecord(); } diff --git a/portal-ui/src/screens/Console/IDP/IDPLDAPConfigurationDetails.tsx b/portal-ui/src/screens/Console/IDP/IDPLDAPConfigurationDetails.tsx deleted file mode 100644 index 2f8e2c688..000000000 --- a/portal-ui/src/screens/Console/IDP/IDPLDAPConfigurationDetails.tsx +++ /dev/null @@ -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 . - -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 ( - - } - formFields={ldapFormFields} - icon={} - /> - ); -}; - -export default IDPLDAPConfigurationDetails; diff --git a/portal-ui/src/screens/Console/IDP/LDAP/IDPLDAPConfigurationDetails.tsx b/portal-ui/src/screens/Console/IDP/LDAP/IDPLDAPConfigurationDetails.tsx new file mode 100644 index 000000000..6712d9fce --- /dev/null +++ b/portal-ui/src/screens/Console/IDP/LDAP/IDPLDAPConfigurationDetails.tsx @@ -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 . + +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(true); + const [isEnabled, setIsEnabled] = useState(false); + const [hasConfiguration, setHasConfiguration] = useState(false); + const [fields, setFields] = useState({}); + const [record, setRecord] = useState(null); + const [editMode, setEditMode] = useState(false); + const [resetOpen, setResetOpen] = useState(false); + const [curTab, setCurTab] = useState(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 ( + + + setFields({ ...fields, [key]: e.target.checked ? "on" : "off" }) + } + description="" + disabled={!editMode} + /> + + ); + default: + return ( + ) => + setFields({ ...fields, [key]: e.target.value }) + } + placeholder={value.placeholder} + disabled={!editMode} + type={value.type} + /> + ); + } + }; + + return ( + + {resetOpen && ( + + )} + + + { + setCurTab(newValue); + setEditMode(false); + }} + tabOptions={[ + { label: "Configuration" }, + { + label: "Entities", + disabled: !hasConfiguration, + }, + ]} + /> + + + + + + + ); +}; + +export default LDAPEntitiesQuery; diff --git a/portal-ui/src/screens/Console/IDP/IDPLDAPConfigurations.tsx b/portal-ui/src/screens/Console/IDP/LDAP/types.ts similarity index 56% rename from portal-ui/src/screens/Console/IDP/IDPLDAPConfigurations.tsx rename to portal-ui/src/screens/Console/IDP/LDAP/types.ts index 8860f5508..4bd0681d8 100644 --- a/portal-ui/src/screens/Console/IDP/IDPLDAPConfigurations.tsx +++ b/portal-ui/src/screens/Console/IDP/LDAP/types.ts @@ -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 . -import React from "react"; -import IDPConfigurations from "./IDPConfigurations"; +export interface LDAPEntitiesRequest { + users?: string[]; + groups?: string[]; + policies?: string[]; +} -const IDPLDAPConfigurations = () => { - return ; -}; +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[]; +} diff --git a/portal-ui/src/screens/Console/IDP/utils.tsx b/portal-ui/src/screens/Console/IDP/utils.tsx index 9c08db00b..6808dc092 100644 --- a/portal-ui/src/screens/Console/IDP/utils.tsx +++ b/portal-ui/src/screens/Console/IDP/utils.tsx @@ -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) => "", diff --git a/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx b/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx index cd8b09707..6c20ceb2b 100644 --- a/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx +++ b/portal-ui/src/screens/Console/Policies/PolicySelectors.tsx @@ -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([]); @@ -147,7 +145,9 @@ const PolicySelectors = ({ {records.length > 0 ? ( - Assign Policies + {!noTitle && ( + Assign Policies + )}