From 4d09517912e6008e2ef0a4ad3e0a6ab260cc1a2d Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Thu, 30 Apr 2026 19:13:10 +0200 Subject: [PATCH 1/9] feat(mem): use tooltip-enabled policy details in compare view --- .../endpoint/MEM/compare-policies/index.js | 43 ++++++------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/src/pages/endpoint/MEM/compare-policies/index.js b/src/pages/endpoint/MEM/compare-policies/index.js index da74c739462b..b634a6335839 100644 --- a/src/pages/endpoint/MEM/compare-policies/index.js +++ b/src/pages/endpoint/MEM/compare-policies/index.js @@ -4,7 +4,7 @@ import { ApiPostCall } from "../../../../api/ApiCall"; import { CippFormComponent } from "../../../../components/CippComponents/CippFormComponent"; import { CippFormCondition } from "../../../../components/CippComponents/CippFormCondition"; import { CippFormTenantSelector } from "../../../../components/CippComponents/CippFormTenantSelector"; -import { CippCodeBlock } from "../../../../components/CippComponents/CippCodeBlock"; +import CippJsonView from "../../../../components/CippFormPages/CippJSONView"; import { Box, Button, @@ -19,16 +19,12 @@ import { TableHead, TableRow, Paper, - Accordion, - AccordionSummary, - AccordionDetails, Alert, Stack, Chip, Skeleton, } from "@mui/material"; import { - ExpandMore as ExpandMoreIcon, CompareArrows as CompareArrowsIcon, CheckCircle as CheckCircleIcon, Error as ErrorIcon, @@ -359,15 +355,6 @@ const Page = () => { return errData?.Results || compareApi.error?.message || "An error occurred"; }, [compareApi.isError, compareApi.error]); - const sourceAJson = useMemo( - () => (results?.sourceAData ? JSON.stringify(results.sourceAData, null, 2) : ""), - [results?.sourceAData], - ); - const sourceBJson = useMemo( - () => (results?.sourceBData ? JSON.stringify(results.sourceBData, null, 2) : ""), - [results?.sourceBData], - ); - return ( @@ -457,23 +444,17 @@ const Page = () => { )} - - }> - Source A Raw JSON — {results.sourceALabel} - - - - - - - - }> - Source B Raw JSON — {results.sourceBLabel} - - - - - + + + )} From 17077aa800c59b90ce4ac6e6b16ecf502c9b0e15 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Thu, 30 Apr 2026 19:51:30 +0200 Subject: [PATCH 2/9] feat(compare): add null safety --- src/pages/endpoint/MEM/compare-policies/index.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pages/endpoint/MEM/compare-policies/index.js b/src/pages/endpoint/MEM/compare-policies/index.js index b634a6335839..80b660e666a1 100644 --- a/src/pages/endpoint/MEM/compare-policies/index.js +++ b/src/pages/endpoint/MEM/compare-policies/index.js @@ -355,6 +355,11 @@ const Page = () => { return errData?.Results || compareApi.error?.message || "An error occurred"; }, [compareApi.isError, compareApi.error]); + const comparisonRows = useMemo(() => { + if (!Array.isArray(results?.Results)) return []; + return results.Results.filter(Boolean); + }, [results?.Results]); + return ( @@ -405,14 +410,14 @@ const Page = () => { > {results.identical ? "Policies are identical - no differences found." - : `${results.Results?.length || 0} difference${results.Results?.length === 1 ? "" : "s"} found between policies.`} + : `${comparisonRows.length} difference${comparisonRows.length === 1 ? "" : "s"} found between policies.`} A: {results.sourceALabel} — B:{" "} {results.sourceBLabel} - {!results.identical && results.Results?.length > 0 && ( + {!results.identical && comparisonRows.length > 0 && ( @@ -424,7 +429,7 @@ const Page = () => { - {results.Results.map((row, index) => ( + {comparisonRows.map((row, index) => ( ({ From 7c057478c680134722ffbaf43087185c3a999f17 Mon Sep 17 00:00:00 2001 From: Joachim Date: Thu, 7 May 2026 13:28:05 +0200 Subject: [PATCH 3/9] Add Usage Location field to JIT Admin and template forms (#5910) --- .../administration/jit-admin-templates/add.jsx | 14 ++++++++++++++ .../administration/jit-admin-templates/edit.jsx | 14 ++++++++++++++ .../identity/administration/jit-admin/add.jsx | 17 +++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/pages/identity/administration/jit-admin-templates/add.jsx b/src/pages/identity/administration/jit-admin-templates/add.jsx index 986e7ea378d1..0a57a8b33882 100644 --- a/src/pages/identity/administration/jit-admin-templates/add.jsx +++ b/src/pages/identity/administration/jit-admin-templates/add.jsx @@ -9,6 +9,7 @@ import { CippFormDomainSelector } from "../../../../components/CippComponents/Ci import { CippFormUserSelector } from "../../../../components/CippComponents/CippFormUserSelector"; import { CippFormGroupSelector } from "../../../../components/CippComponents/CippFormGroupSelector"; import gdaproles from "../../../../data/GDAPRoles.json"; +import countryList from "../../../../data/countryList.json"; import { useSettings } from "../../../../hooks/use-settings"; import { useEffect } from "react"; @@ -352,6 +353,19 @@ const Page = () => { /> )} + + ({ + label: Name, + value: Code, + }))} + formControl={formControl} + /> + { /> )} + + ({ + label: Name, + value: Code, + }))} + formControl={formControl} + /> + { if (template.defaultExistingUser) { formControl.setValue("existingUser", template.defaultExistingUser, { shouldDirty: true }); } + if (template.defaultUsageLocation) { + formControl.setValue("usageLocation", template.defaultUsageLocation, { shouldDirty: true }); + } // Dates if (template.defaultDuration) { @@ -343,6 +347,19 @@ const Page = () => { validators={{ required: "Domain is required" }} /> + + ({ + label: Name, + value: Code, + }))} + formControl={formControl} + /> + From 397d0c9ff5e9ddf3c5df02b27aedf0b2845c2cb2 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 8 May 2026 00:19:28 +0200 Subject: [PATCH 4/9] add purview section --- .../CippDeployCompliancePolicyDrawer.jsx | 236 ++++++++++++++++++ .../CippComponents/CippPolicyImportDrawer.jsx | 41 ++- src/layouts/config.js | 46 ++++ .../compliance/dlp-templates/index.js | 107 ++++++++ src/pages/security/compliance/dlp/index.js | 111 ++++++++ .../compliance/labels-templates/index.js | 114 +++++++++ src/pages/security/compliance/labels/index.js | 90 +++++++ .../compliance/sit-templates/index.js | 107 ++++++++ src/pages/security/compliance/sit/index.js | 81 ++++++ 9 files changed, 923 insertions(+), 10 deletions(-) create mode 100644 src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx create mode 100644 src/pages/security/compliance/dlp-templates/index.js create mode 100644 src/pages/security/compliance/dlp/index.js create mode 100644 src/pages/security/compliance/labels-templates/index.js create mode 100644 src/pages/security/compliance/labels/index.js create mode 100644 src/pages/security/compliance/sit-templates/index.js create mode 100644 src/pages/security/compliance/sit/index.js diff --git a/src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx b/src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx new file mode 100644 index 000000000000..c881757e087b --- /dev/null +++ b/src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx @@ -0,0 +1,236 @@ +import React, { useEffect, useState } from "react"; +import { Button, Divider, Stack } from "@mui/material"; +import { Grid } from "@mui/system"; +import { useForm, useFormState, useWatch } from "react-hook-form"; +import { RocketLaunch } from "@mui/icons-material"; +import { CippOffCanvas } from "./CippOffCanvas"; +import CippFormComponent from "./CippFormComponent"; +import { CippFormTenantSelector } from "./CippFormTenantSelector"; +import { CippApiResults } from "./CippApiResults"; +import { ApiPostCall } from "../../api/ApiCall"; + +const MODE_CONFIG = { + DlpCompliancePolicy: { + title: "Deploy DLP Compliance Policy", + buttonLabel: "Deploy DLP Policy", + postUrl: "/api/AddDlpCompliancePolicy", + listTemplatesUrl: "/api/ListDlpCompliancePolicyTemplates", + templateQueryKey: "TemplateListDlpCompliancePolicy", + relatedQueryKeys: ["ListDlpCompliancePolicy", "ListDlpCompliancePolicyTemplates"], + placeholder: `{ + "Name": "Block Credit Card data", + "Comment": "Blocks documents containing credit card numbers", + "Mode": "Enable", + "ExchangeLocation": "All", + "SharePointLocation": "All", + "OneDriveLocation": "All", + "RuleParams": { + "Name": "Block Credit Card data Rule", + "ContentContainsSensitiveInformation": [{ "name": "Credit Card Number", "minCount": "1" }], + "BlockAccess": true + } +}`, + }, + SensitivityLabel: { + title: "Deploy Sensitivity Label", + buttonLabel: "Deploy Sensitivity Label", + postUrl: "/api/AddSensitivityLabel", + listTemplatesUrl: "/api/ListSensitivityLabelTemplates", + templateQueryKey: "TemplateListSensitivityLabel", + relatedQueryKeys: ["ListSensitivityLabel", "ListSensitivityLabelTemplates"], + placeholder: `{ + "Name": "Confidential", + "DisplayName": "Confidential", + "Tooltip": "Confidential data, do not share externally", + "Comment": "Internal-only confidential classification", + "ContentType": "File, Email", + "EncryptionEnabled": true, + "EncryptionProtectionType": "Template", + "ContentMarkingHeaderEnabled": true, + "ContentMarkingHeaderText": "Confidential - Internal Use Only", + "PolicyParams": { + "Name": "Confidential Label Policy", + "ExchangeLocation": "All", + "Settings": [ + ["mandatory", "false"], + ["disablemandatoryinoutlook", "true"] + ] + } +}`, + }, + SensitiveInfoType: { + title: "Deploy Sensitive Information Type", + buttonLabel: "Deploy SIT", + postUrl: "/api/AddSensitiveInfoType", + listTemplatesUrl: "/api/ListSensitiveInfoTypeTemplates", + templateQueryKey: "TemplateListSensitiveInfoType", + relatedQueryKeys: ["ListSensitiveInfoType", "ListSensitiveInfoTypeTemplates"], + placeholder: `{ + "Name": "Custom Employee ID", + "Description": "Internal Employee ID format EMP-NNNNN", + "Pattern": "EMP-\\\\d{5}", + "Confidence": "High", + "Recommended": true +} + +// Or with a base64-encoded XML rule pack: +// { +// "Name": "Custom Rule Pack", +// "FileDataBase64": "" +// }`, + }, +}; + +export const CippDeployCompliancePolicyDrawer = ({ + mode, + buttonText, + requiredPermissions = [], + PermissionButton = Button, +}) => { + const config = MODE_CONFIG[mode]; + + const [drawerVisible, setDrawerVisible] = useState(false); + + const formControl = useForm({ + mode: "onChange", + defaultValues: { + selectedTenants: [], + TemplateList: null, + PowerShellCommand: "", + }, + }); + + const { isValid } = useFormState({ control: formControl.control }); + + const templateListVal = useWatch({ control: formControl.control, name: "TemplateList" }); + + useEffect(() => { + if (templateListVal?.value) { + formControl.setValue("PowerShellCommand", JSON.stringify(templateListVal.value)); + } + }, [templateListVal, formControl]); + + const deployPolicy = ApiPostCall({ + urlFromData: true, + relatedQueryKeys: config?.relatedQueryKeys ?? [], + }); + + useEffect(() => { + if (deployPolicy.isSuccess) { + formControl.reset({ + selectedTenants: [], + TemplateList: null, + PowerShellCommand: "", + }); + } + }, [deployPolicy.isSuccess, formControl]); + + if (!config) { + return null; + } + + const handleSubmit = () => { + formControl.trigger(); + if (!isValid) return; + deployPolicy.mutate({ + url: config.postUrl, + data: formControl.getValues(), + }); + }; + + const handleCloseDrawer = () => { + setDrawerVisible(false); + formControl.reset({ + selectedTenants: [], + TemplateList: null, + PowerShellCommand: "", + }); + }; + + return ( + <> + setDrawerVisible(true)} + startIcon={} + > + {buttonText ?? config.buttonLabel} + + + + + + } + > + + + + + + + + + option, + url: config.listTemplatesUrl, + }} + placeholder="Select a template or enter PowerShell JSON manually" + /> + + + + + + + + + + + + + ); +}; diff --git a/src/components/CippComponents/CippPolicyImportDrawer.jsx b/src/components/CippComponents/CippPolicyImportDrawer.jsx index bbd1dbe73751..a4149928b19c 100644 --- a/src/components/CippComponents/CippPolicyImportDrawer.jsx +++ b/src/components/CippComponents/CippPolicyImportDrawer.jsx @@ -21,12 +21,40 @@ import { CippApiResults } from './CippApiResults' import { CippFormTenantSelector } from './CippFormTenantSelector' import { CippFolderNavigation } from './CippFolderNavigation' +// Modes that only support browsing community repos (no tenant fallback) +const REPO_ONLY_MODES = [ + 'ReportBuilder', + 'DlpCompliancePolicy', + 'SensitivityLabel', + 'SensitiveInfoType', +] + +const RELATED_QUERY_KEYS_BY_MODE = { + ConditionalAccess: ['ListCATemplates-table'], + Standards: ['listStandardTemplates'], + ReportBuilder: ['ListReportBuilderTemplates'], + DlpCompliancePolicy: ['ListDlpCompliancePolicyTemplates'], + SensitivityLabel: ['ListSensitivityLabelTemplates'], + SensitiveInfoType: ['ListSensitiveInfoTypeTemplates'], +} + +const MODE_LABELS = { + ReportBuilder: 'Report Template', + DlpCompliancePolicy: 'DLP Policy', + SensitivityLabel: 'Sensitivity Label', + SensitiveInfoType: 'Sensitive Info Type', +} + +const DEFAULT_QUERY_KEYS = ['ListIntuneTemplates-table', 'ListIntuneTemplates-autcomplete'] + export const CippPolicyImportDrawer = ({ buttonText = 'Browse Catalog', requiredPermissions = [], PermissionButton = Button, mode = 'Intune', }) => { + const isRepoOnlyMode = REPO_ONLY_MODES.includes(mode) + const [drawerVisible, setDrawerVisible] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [viewDialogOpen, setViewDialogOpen] = useState(false) @@ -73,14 +101,7 @@ export const CippPolicyImportDrawer = ({ const importPolicy = ApiPostCall({ urlFromData: true, - relatedQueryKeys: - mode === 'ConditionalAccess' - ? ['ListCATemplates-table'] - : mode === 'Standards' - ? ['listStandardTemplates'] - : mode === 'ReportBuilder' - ? ['ListReportBuilderTemplates'] - : ['ListIntuneTemplates-table', 'ListIntuneTemplates-autcomplete'], + relatedQueryKeys: RELATED_QUERY_KEYS_BY_MODE[mode] || DEFAULT_QUERY_KEYS, }) const viewPolicyQuery = ApiPostCall({ @@ -298,7 +319,7 @@ export const CippPolicyImportDrawer = ({ {buttonText} option.value) : []), - ...(mode !== 'ReportBuilder' + ...(!isRepoOnlyMode ? [{ label: 'Get template from existing tenant', value: 'tenant' }] : []), ]} diff --git a/src/layouts/config.js b/src/layouts/config.js index ec6a017bf71b..aac86af27397 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -301,6 +301,9 @@ export const nativeMenuItems = [ 'Security.Alert.*', 'Tenant.DeviceCompliance.*', 'Security.SafeLinksPolicy.*', + 'Security.DlpCompliancePolicy.*', + 'Security.SensitivityLabel.*', + 'Security.SensitiveInfoType.*', ], items: [ { @@ -383,6 +386,49 @@ export const nativeMenuItems = [ }, ], }, + { + title: 'Purview Compliance', + permissions: [ + 'Security.DlpCompliancePolicy.*', + 'Security.SensitivityLabel.*', + 'Security.SensitiveInfoType.*', + ], + items: [ + { + title: 'DLP Policies', + path: '/security/compliance/dlp', + permissions: ['Security.DlpCompliancePolicy.*'], + }, + { + title: 'DLP Policy Templates', + path: '/security/compliance/dlp-templates', + permissions: ['Security.DlpCompliancePolicy.*'], + scope: 'global', + }, + { + title: 'Sensitivity Labels', + path: '/security/compliance/labels', + permissions: ['Security.SensitivityLabel.*'], + }, + { + title: 'Sensitivity Label Templates', + path: '/security/compliance/labels-templates', + permissions: ['Security.SensitivityLabel.*'], + scope: 'global', + }, + { + title: 'Sensitive Information Types', + path: '/security/compliance/sit', + permissions: ['Security.SensitiveInfoType.*'], + }, + { + title: 'Sensitive Info Type Templates', + path: '/security/compliance/sit-templates', + permissions: ['Security.SensitiveInfoType.*'], + scope: 'global', + }, + ], + }, ], }, { diff --git a/src/pages/security/compliance/dlp-templates/index.js b/src/pages/security/compliance/dlp-templates/index.js new file mode 100644 index 000000000000..b2b597c51167 --- /dev/null +++ b/src/pages/security/compliance/dlp-templates/index.js @@ -0,0 +1,107 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { GitHub } from "@mui/icons-material"; +import { ApiGetCall } from "../../../../api/ApiCall"; +import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "DLP Compliance Policy Templates"; + const cardButtonPermissions = ["Security.DlpCompliancePolicy.ReadWrite"]; + + const integrations = ApiGetCall({ + url: "/api/ListExtensionsConfig", + queryKey: "Integrations", + refetchOnMount: false, + refetchOnReconnect: false, + }); + + const actions = [ + { + label: "Save to GitHub", + type: "POST", + url: "/api/ExecCommunityRepo", + icon: , + data: { + Action: "UploadTemplate", + GUID: "GUID", + }, + fields: [ + { + label: "Repository", + name: "FullName", + type: "select", + api: { + url: "/api/ListCommunityRepos", + data: { WriteAccess: true }, + queryKey: "CommunityRepos-Write", + dataKey: "Results", + valueField: "FullName", + labelField: "FullName", + }, + multiple: false, + creatable: false, + required: true, + validators: { required: { value: true, message: "This field is required" } }, + }, + { + label: "Commit Message", + placeholder: "Enter a commit message for adding this file to GitHub", + name: "Message", + type: "textField", + multiline: true, + required: true, + rows: 4, + }, + ], + confirmText: "Are you sure you want to save this template to the selected repository?", + condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled, + }, + { + label: "Delete Template", + type: "POST", + url: "/api/RemoveDlpCompliancePolicyTemplate", + data: { ID: "GUID" }, + confirmText: "Do you want to delete the template?", + icon: , + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: ["name", "comments", "Mode", "Workload", "Enabled", "GUID"], + actions: actions, + }; + + const simpleColumns = ["name", "comments", "Mode", "Workload", "Enabled", "GUID"]; + + return ( + + + + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; diff --git a/src/pages/security/compliance/dlp/index.js b/src/pages/security/compliance/dlp/index.js new file mode 100644 index 000000000000..cfafc189d642 --- /dev/null +++ b/src/pages/security/compliance/dlp/index.js @@ -0,0 +1,111 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { Book, Block, Check } from "@mui/icons-material"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "DLP Compliance Policies"; + const apiUrl = "/api/ListDlpCompliancePolicy"; + const cardButtonPermissions = ["Security.DlpCompliancePolicy.ReadWrite"]; + + const actions = [ + { + label: "Create template based on policy", + type: "POST", + icon: , + url: "/api/AddDlpCompliancePolicyTemplate", + dataFunction: (data) => { + return { ...data }; + }, + confirmText: "Are you sure you want to create a template based on this DLP policy?", + }, + { + label: "Enable Policy", + type: "POST", + icon: , + url: "/api/EditDlpCompliancePolicy", + data: { + State: "!enable", + Identity: "Name", + }, + confirmText: "Are you sure you want to enable this DLP policy?", + condition: (row) => row.Enabled === false, + }, + { + label: "Disable Policy", + type: "POST", + icon: , + url: "/api/EditDlpCompliancePolicy", + data: { + State: "!disable", + Identity: "Name", + }, + confirmText: "Are you sure you want to disable this DLP policy?", + condition: (row) => row.Enabled === true, + }, + { + label: "Delete Policy", + type: "POST", + icon: , + url: "/api/RemoveDlpCompliancePolicy", + data: { + Identity: "Name", + }, + confirmText: "Are you sure you want to delete this DLP policy?", + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: [ + "Name", + "Comment", + "Mode", + "Enabled", + "Workload", + "ExchangeLocation", + "SharePointLocation", + "OneDriveLocation", + "TeamsLocation", + "EndpointDlpLocation", + "RuleCount", + "CreatedBy", + "WhenCreatedUTC", + "WhenChangedUTC", + ], + actions: actions, + }; + + const simpleColumns = [ + "Name", + "Mode", + "Enabled", + "Workload", + "RuleCount", + "CreatedBy", + "WhenCreatedUTC", + "WhenChangedUTC", + ]; + + return ( + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; diff --git a/src/pages/security/compliance/labels-templates/index.js b/src/pages/security/compliance/labels-templates/index.js new file mode 100644 index 000000000000..3a4a5a13119d --- /dev/null +++ b/src/pages/security/compliance/labels-templates/index.js @@ -0,0 +1,114 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { GitHub } from "@mui/icons-material"; +import { ApiGetCall } from "../../../../api/ApiCall"; +import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "Sensitivity Label Templates"; + const cardButtonPermissions = ["Security.SensitivityLabel.ReadWrite"]; + + const integrations = ApiGetCall({ + url: "/api/ListExtensionsConfig", + queryKey: "Integrations", + refetchOnMount: false, + refetchOnReconnect: false, + }); + + const actions = [ + { + label: "Save to GitHub", + type: "POST", + url: "/api/ExecCommunityRepo", + icon: , + data: { + Action: "UploadTemplate", + GUID: "GUID", + }, + fields: [ + { + label: "Repository", + name: "FullName", + type: "select", + api: { + url: "/api/ListCommunityRepos", + data: { WriteAccess: true }, + queryKey: "CommunityRepos-Write", + dataKey: "Results", + valueField: "FullName", + labelField: "FullName", + }, + multiple: false, + creatable: false, + required: true, + validators: { required: { value: true, message: "This field is required" } }, + }, + { + label: "Commit Message", + placeholder: "Enter a commit message for adding this file to GitHub", + name: "Message", + type: "textField", + multiline: true, + required: true, + rows: 4, + }, + ], + confirmText: "Are you sure you want to save this template to the selected repository?", + condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled, + }, + { + label: "Delete Template", + type: "POST", + url: "/api/RemoveSensitivityLabelTemplate", + data: { ID: "GUID" }, + confirmText: "Do you want to delete the template?", + icon: , + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: [ + "name", + "DisplayName", + "comments", + "ContentType", + "EncryptionEnabled", + "GUID", + ], + actions: actions, + }; + + const simpleColumns = ["name", "DisplayName", "comments", "ContentType", "EncryptionEnabled", "GUID"]; + + return ( + + + + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; diff --git a/src/pages/security/compliance/labels/index.js b/src/pages/security/compliance/labels/index.js new file mode 100644 index 000000000000..53f1259877e9 --- /dev/null +++ b/src/pages/security/compliance/labels/index.js @@ -0,0 +1,90 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { Book } from "@mui/icons-material"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "Sensitivity Labels"; + const apiUrl = "/api/ListSensitivityLabel"; + const cardButtonPermissions = ["Security.SensitivityLabel.ReadWrite"]; + + const actions = [ + { + label: "Create template based on label", + type: "POST", + icon: , + url: "/api/AddSensitivityLabelTemplate", + dataFunction: (data) => { + return { ...data }; + }, + confirmText: "Are you sure you want to create a template based on this sensitivity label?", + }, + { + label: "Delete Label", + type: "POST", + icon: , + url: "/api/RemoveSensitivityLabel", + data: { + Identity: "Guid", + }, + confirmText: + "Are you sure you want to delete this sensitivity label? Labels currently published to users will be removed from policies.", + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: [ + "DisplayName", + "Name", + "Comment", + "Tooltip", + "ParentId", + "ContentType", + "EncryptionEnabled", + "EncryptionProtectionType", + "ContentMarkingHeaderEnabled", + "ContentMarkingFooterEnabled", + "ContentMarkingWatermarkEnabled", + "SiteAndGroupProtectionEnabled", + "Priority", + "Disabled", + "PublishedInPolicies", + ], + actions: actions, + }; + + const simpleColumns = [ + "DisplayName", + "Name", + "ContentType", + "EncryptionEnabled", + "ContentMarkingHeaderEnabled", + "ContentMarkingWatermarkEnabled", + "SiteAndGroupProtectionEnabled", + "Priority", + "Disabled", + ]; + + return ( + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; diff --git a/src/pages/security/compliance/sit-templates/index.js b/src/pages/security/compliance/sit-templates/index.js new file mode 100644 index 000000000000..8bf4c54cc9e1 --- /dev/null +++ b/src/pages/security/compliance/sit-templates/index.js @@ -0,0 +1,107 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { GitHub } from "@mui/icons-material"; +import { ApiGetCall } from "../../../../api/ApiCall"; +import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "Sensitive Information Type Templates"; + const cardButtonPermissions = ["Security.SensitiveInfoType.ReadWrite"]; + + const integrations = ApiGetCall({ + url: "/api/ListExtensionsConfig", + queryKey: "Integrations", + refetchOnMount: false, + refetchOnReconnect: false, + }); + + const actions = [ + { + label: "Save to GitHub", + type: "POST", + url: "/api/ExecCommunityRepo", + icon: , + data: { + Action: "UploadTemplate", + GUID: "GUID", + }, + fields: [ + { + label: "Repository", + name: "FullName", + type: "select", + api: { + url: "/api/ListCommunityRepos", + data: { WriteAccess: true }, + queryKey: "CommunityRepos-Write", + dataKey: "Results", + valueField: "FullName", + labelField: "FullName", + }, + multiple: false, + creatable: false, + required: true, + validators: { required: { value: true, message: "This field is required" } }, + }, + { + label: "Commit Message", + placeholder: "Enter a commit message for adding this file to GitHub", + name: "Message", + type: "textField", + multiline: true, + required: true, + rows: 4, + }, + ], + confirmText: "Are you sure you want to save this template to the selected repository?", + condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled, + }, + { + label: "Delete Template", + type: "POST", + url: "/api/RemoveSensitiveInfoTypeTemplate", + data: { ID: "GUID" }, + confirmText: "Do you want to delete the template?", + icon: , + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: ["name", "comments", "Pattern", "Confidence", "Locale", "GUID"], + actions: actions, + }; + + const simpleColumns = ["name", "comments", "Pattern", "Confidence", "Locale", "GUID"]; + + return ( + + + + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; diff --git a/src/pages/security/compliance/sit/index.js b/src/pages/security/compliance/sit/index.js new file mode 100644 index 000000000000..f88f324d0194 --- /dev/null +++ b/src/pages/security/compliance/sit/index.js @@ -0,0 +1,81 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { Book } from "@mui/icons-material"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "Sensitive Information Types"; + const apiUrl = "/api/ListSensitiveInfoType"; + const cardButtonPermissions = ["Security.SensitiveInfoType.ReadWrite"]; + + const actions = [ + { + label: "Create template based on SIT", + type: "POST", + icon: , + url: "/api/AddSensitiveInfoTypeTemplate", + dataFunction: (data) => { + return { ...data }; + }, + confirmText: + "Are you sure you want to create a template based on this Sensitive Information Type?", + }, + { + label: "Delete SIT", + type: "POST", + icon: , + url: "/api/RemoveSensitiveInfoType", + data: { + Identity: "Name", + }, + confirmText: + "Are you sure you want to delete this Sensitive Information Type? Built-in Microsoft types cannot be deleted.", + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: [ + "Name", + "Description", + "Publisher", + "Recommended", + "RulePackId", + "RulePackVersion", + "State", + "Type", + ], + actions: actions, + }; + + const simpleColumns = [ + "Name", + "Publisher", + "Description", + "Recommended", + "RulePackVersion", + "State", + ]; + + return ( + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; From 1b5174664ba9c91d9412e974e1d5a2e2a8532972 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Fri, 8 May 2026 00:46:37 +0200 Subject: [PATCH 5/9] feat: add AutoDiscover data retrieval to CippDomainCards - Implemented API call to fetch AutoDiscover data for the specified domain. - Added a new DomainResultCard to display AutoDiscover results, including validation passes, warns, and fails. Fixes #5972 --- src/components/CippCards/CippDomainCards.jsx | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/components/CippCards/CippDomainCards.jsx b/src/components/CippCards/CippDomainCards.jsx index 9268fcd08f96..fede72bd1347 100644 --- a/src/components/CippCards/CippDomainCards.jsx +++ b/src/components/CippCards/CippDomainCards.jsx @@ -470,6 +470,13 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false }) waiting: !!domain, }); + const { data: autoDiscoverData, isFetching: autoDiscoverLoading } = ApiGetCall({ + url: "/api/ListDomainHealth", + queryKey: `autodiscover-${domain}`, + data: { Domain: domain, Action: "ReadAutoDiscover" }, + waiting: !!domain, + }); + const { data: httpsData, isFetching: httpsLoading } = ApiGetCall({ url: "/api/ListDomainHealth", queryKey: `https-${domain}-${subdomains}`, @@ -684,6 +691,26 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false }) } /> + + +

+ AutoDiscover ({autoDiscoverData?.RecordType || "None"}): +

+ + + + } + /> +
{enableHttps && ( Date: Fri, 8 May 2026 12:33:46 +0200 Subject: [PATCH 6/9] pushing new compliance menus --- .../CippDeployCompliancePolicyDrawer.jsx | 23 ++++ .../CippComponents/CippPolicyImportDrawer.jsx | 41 ++----- src/layouts/config.js | 107 ++++++++++------- .../compliance/retention-templates/index.js | 107 +++++++++++++++++ .../security/compliance/retention/index.js | 112 ++++++++++++++++++ 5 files changed, 313 insertions(+), 77 deletions(-) create mode 100644 src/pages/security/compliance/retention-templates/index.js create mode 100644 src/pages/security/compliance/retention/index.js diff --git a/src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx b/src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx index c881757e087b..3cf35e3cb2f8 100644 --- a/src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx +++ b/src/components/CippComponents/CippDeployCompliancePolicyDrawer.jsx @@ -29,6 +29,29 @@ const MODE_CONFIG = { "ContentContainsSensitiveInformation": [{ "name": "Credit Card Number", "minCount": "1" }], "BlockAccess": true } +}`, + }, + RetentionCompliancePolicy: { + title: "Deploy Retention Compliance Policy", + buttonLabel: "Deploy Retention Policy", + postUrl: "/api/AddRetentionCompliancePolicy", + listTemplatesUrl: "/api/ListRetentionCompliancePolicyTemplates", + templateQueryKey: "TemplateListRetentionCompliancePolicy", + relatedQueryKeys: [ + "ListRetentionCompliancePolicy", + "ListRetentionCompliancePolicyTemplates", + ], + placeholder: `{ + "Name": "7-year Email Retention", + "Comment": "Retain Exchange mail for 7 years", + "ExchangeLocation": "All", + "Enabled": true, + "RuleParams": { + "Name": "7-year Email Retention Rule", + "RetentionDuration": 2555, + "RetentionComplianceAction": "Keep", + "ExpirationDateOption": "ModificationAgeInDays" + } }`, }, SensitivityLabel: { diff --git a/src/components/CippComponents/CippPolicyImportDrawer.jsx b/src/components/CippComponents/CippPolicyImportDrawer.jsx index a4149928b19c..bbd1dbe73751 100644 --- a/src/components/CippComponents/CippPolicyImportDrawer.jsx +++ b/src/components/CippComponents/CippPolicyImportDrawer.jsx @@ -21,40 +21,12 @@ import { CippApiResults } from './CippApiResults' import { CippFormTenantSelector } from './CippFormTenantSelector' import { CippFolderNavigation } from './CippFolderNavigation' -// Modes that only support browsing community repos (no tenant fallback) -const REPO_ONLY_MODES = [ - 'ReportBuilder', - 'DlpCompliancePolicy', - 'SensitivityLabel', - 'SensitiveInfoType', -] - -const RELATED_QUERY_KEYS_BY_MODE = { - ConditionalAccess: ['ListCATemplates-table'], - Standards: ['listStandardTemplates'], - ReportBuilder: ['ListReportBuilderTemplates'], - DlpCompliancePolicy: ['ListDlpCompliancePolicyTemplates'], - SensitivityLabel: ['ListSensitivityLabelTemplates'], - SensitiveInfoType: ['ListSensitiveInfoTypeTemplates'], -} - -const MODE_LABELS = { - ReportBuilder: 'Report Template', - DlpCompliancePolicy: 'DLP Policy', - SensitivityLabel: 'Sensitivity Label', - SensitiveInfoType: 'Sensitive Info Type', -} - -const DEFAULT_QUERY_KEYS = ['ListIntuneTemplates-table', 'ListIntuneTemplates-autcomplete'] - export const CippPolicyImportDrawer = ({ buttonText = 'Browse Catalog', requiredPermissions = [], PermissionButton = Button, mode = 'Intune', }) => { - const isRepoOnlyMode = REPO_ONLY_MODES.includes(mode) - const [drawerVisible, setDrawerVisible] = useState(false) const [searchQuery, setSearchQuery] = useState('') const [viewDialogOpen, setViewDialogOpen] = useState(false) @@ -101,7 +73,14 @@ export const CippPolicyImportDrawer = ({ const importPolicy = ApiPostCall({ urlFromData: true, - relatedQueryKeys: RELATED_QUERY_KEYS_BY_MODE[mode] || DEFAULT_QUERY_KEYS, + relatedQueryKeys: + mode === 'ConditionalAccess' + ? ['ListCATemplates-table'] + : mode === 'Standards' + ? ['listStandardTemplates'] + : mode === 'ReportBuilder' + ? ['ListReportBuilderTemplates'] + : ['ListIntuneTemplates-table', 'ListIntuneTemplates-autcomplete'], }) const viewPolicyQuery = ApiPostCall({ @@ -319,7 +298,7 @@ export const CippPolicyImportDrawer = ({ {buttonText} option.value) : []), - ...(!isRepoOnlyMode + ...(mode !== 'ReportBuilder' ? [{ label: 'Get template from existing tenant', value: 'tenant' }] : []), ]} diff --git a/src/layouts/config.js b/src/layouts/config.js index aac86af27397..c820f5664acc 100644 --- a/src/layouts/config.js +++ b/src/layouts/config.js @@ -301,9 +301,11 @@ export const nativeMenuItems = [ 'Security.Alert.*', 'Tenant.DeviceCompliance.*', 'Security.SafeLinksPolicy.*', - 'Security.DlpCompliancePolicy.*', - 'Security.SensitivityLabel.*', - 'Security.SensitiveInfoType.*', + // TEMP: Purview Compliance menu hidden for dev build + // 'Security.DlpCompliancePolicy.*', + // 'Security.RetentionCompliancePolicy.*', + // 'Security.SensitivityLabel.*', + // 'Security.SensitiveInfoType.*', ], items: [ { @@ -386,49 +388,62 @@ export const nativeMenuItems = [ }, ], }, - { - title: 'Purview Compliance', - permissions: [ - 'Security.DlpCompliancePolicy.*', - 'Security.SensitivityLabel.*', - 'Security.SensitiveInfoType.*', - ], - items: [ - { - title: 'DLP Policies', - path: '/security/compliance/dlp', - permissions: ['Security.DlpCompliancePolicy.*'], - }, - { - title: 'DLP Policy Templates', - path: '/security/compliance/dlp-templates', - permissions: ['Security.DlpCompliancePolicy.*'], - scope: 'global', - }, - { - title: 'Sensitivity Labels', - path: '/security/compliance/labels', - permissions: ['Security.SensitivityLabel.*'], - }, - { - title: 'Sensitivity Label Templates', - path: '/security/compliance/labels-templates', - permissions: ['Security.SensitivityLabel.*'], - scope: 'global', - }, - { - title: 'Sensitive Information Types', - path: '/security/compliance/sit', - permissions: ['Security.SensitiveInfoType.*'], - }, - { - title: 'Sensitive Info Type Templates', - path: '/security/compliance/sit-templates', - permissions: ['Security.SensitiveInfoType.*'], - scope: 'global', - }, - ], - }, + // TEMP: Purview Compliance menu hidden for dev build + // { + // title: 'Purview Compliance', + // permissions: [ + // 'Security.DlpCompliancePolicy.*', + // 'Security.RetentionCompliancePolicy.*', + // 'Security.SensitivityLabel.*', + // 'Security.SensitiveInfoType.*', + // ], + // items: [ + // { + // title: 'DLP Policies', + // path: '/security/compliance/dlp', + // permissions: ['Security.DlpCompliancePolicy.*'], + // }, + // { + // title: 'DLP Policy Templates', + // path: '/security/compliance/dlp-templates', + // permissions: ['Security.DlpCompliancePolicy.*'], + // scope: 'global', + // }, + // { + // title: 'Retention Policies', + // path: '/security/compliance/retention', + // permissions: ['Security.RetentionCompliancePolicy.*'], + // }, + // { + // title: 'Retention Policy Templates', + // path: '/security/compliance/retention-templates', + // permissions: ['Security.RetentionCompliancePolicy.*'], + // scope: 'global', + // }, + // { + // title: 'Sensitivity Labels', + // path: '/security/compliance/labels', + // permissions: ['Security.SensitivityLabel.*'], + // }, + // { + // title: 'Sensitivity Label Templates', + // path: '/security/compliance/labels-templates', + // permissions: ['Security.SensitivityLabel.*'], + // scope: 'global', + // }, + // { + // title: 'Sensitive Information Types', + // path: '/security/compliance/sit', + // permissions: ['Security.SensitiveInfoType.*'], + // }, + // { + // title: 'Sensitive Info Type Templates', + // path: '/security/compliance/sit-templates', + // permissions: ['Security.SensitiveInfoType.*'], + // scope: 'global', + // }, + // ], + // }, ], }, { diff --git a/src/pages/security/compliance/retention-templates/index.js b/src/pages/security/compliance/retention-templates/index.js new file mode 100644 index 000000000000..3c3faa0e833a --- /dev/null +++ b/src/pages/security/compliance/retention-templates/index.js @@ -0,0 +1,107 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { GitHub } from "@mui/icons-material"; +import { ApiGetCall } from "../../../../api/ApiCall"; +import { CippPolicyImportDrawer } from "../../../../components/CippComponents/CippPolicyImportDrawer.jsx"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "Retention Policy Templates"; + const cardButtonPermissions = ["Security.RetentionCompliancePolicy.ReadWrite"]; + + const integrations = ApiGetCall({ + url: "/api/ListExtensionsConfig", + queryKey: "Integrations", + refetchOnMount: false, + refetchOnReconnect: false, + }); + + const actions = [ + { + label: "Save to GitHub", + type: "POST", + url: "/api/ExecCommunityRepo", + icon: , + data: { + Action: "UploadTemplate", + GUID: "GUID", + }, + fields: [ + { + label: "Repository", + name: "FullName", + type: "select", + api: { + url: "/api/ListCommunityRepos", + data: { WriteAccess: true }, + queryKey: "CommunityRepos-Write", + dataKey: "Results", + valueField: "FullName", + labelField: "FullName", + }, + multiple: false, + creatable: false, + required: true, + validators: { required: { value: true, message: "This field is required" } }, + }, + { + label: "Commit Message", + placeholder: "Enter a commit message for adding this file to GitHub", + name: "Message", + type: "textField", + multiline: true, + required: true, + rows: 4, + }, + ], + confirmText: "Are you sure you want to save this template to the selected repository?", + condition: () => integrations.isSuccess && integrations?.data?.GitHub?.Enabled, + }, + { + label: "Delete Template", + type: "POST", + url: "/api/RemoveRetentionCompliancePolicyTemplate", + data: { ID: "GUID" }, + confirmText: "Do you want to delete the template?", + icon: , + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: ["name", "comments", "Enabled", "RestrictiveRetention", "GUID"], + actions: actions, + }; + + const simpleColumns = ["name", "comments", "Enabled", "RestrictiveRetention", "GUID"]; + + return ( + + + + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; diff --git a/src/pages/security/compliance/retention/index.js b/src/pages/security/compliance/retention/index.js new file mode 100644 index 000000000000..4bd75687fbce --- /dev/null +++ b/src/pages/security/compliance/retention/index.js @@ -0,0 +1,112 @@ +import { Layout as DashboardLayout } from "../../../../layouts/index.js"; +import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; +import { Book, Block, Check } from "@mui/icons-material"; +import { TrashIcon } from "@heroicons/react/24/outline"; +import { CippDeployCompliancePolicyDrawer } from "../../../../components/CippComponents/CippDeployCompliancePolicyDrawer.jsx"; +import { PermissionButton } from "../../../../utils/permissions.js"; + +const Page = () => { + const pageTitle = "Purview Retention Policies"; + const apiUrl = "/api/ListRetentionCompliancePolicy"; + const cardButtonPermissions = ["Security.RetentionCompliancePolicy.ReadWrite"]; + + const actions = [ + { + label: "Create template based on policy", + type: "POST", + icon: , + url: "/api/AddRetentionCompliancePolicyTemplate", + dataFunction: (data) => { + return { ...data }; + }, + confirmText: "Are you sure you want to create a template based on this retention policy?", + }, + { + label: "Enable Policy", + type: "POST", + icon: , + url: "/api/EditRetentionCompliancePolicy", + data: { + State: "!enable", + Identity: "Name", + }, + confirmText: "Are you sure you want to enable this retention policy?", + condition: (row) => row.Enabled === false, + }, + { + label: "Disable Policy", + type: "POST", + icon: , + url: "/api/EditRetentionCompliancePolicy", + data: { + State: "!disable", + Identity: "Name", + }, + confirmText: "Are you sure you want to disable this retention policy?", + condition: (row) => row.Enabled === true, + }, + { + label: "Delete Policy", + type: "POST", + icon: , + url: "/api/RemoveRetentionCompliancePolicy", + data: { + Identity: "Name", + }, + confirmText: "Are you sure you want to delete this retention policy?", + color: "danger", + }, + ]; + + const offCanvas = { + extendedInfoFields: [ + "Name", + "Comment", + "Enabled", + "Workload", + "RestrictiveRetention", + "ExchangeLocation", + "SharePointLocation", + "OneDriveLocation", + "ModernGroupLocation", + "TeamsChannelLocation", + "TeamsChatLocation", + "RuleCount", + "CreatedBy", + "WhenCreatedUTC", + "WhenChangedUTC", + ], + actions: actions, + }; + + const simpleColumns = [ + "Name", + "Enabled", + "Workload", + "RuleCount", + "RestrictiveRetention", + "CreatedBy", + "WhenCreatedUTC", + "WhenChangedUTC", + ]; + + return ( + + } + /> + ); +}; + +Page.getLayout = (page) => {page}; +export default Page; From 2a59c7970f9589c46861a1fbca1a811e8abda525 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Fri, 8 May 2026 12:37:07 +0200 Subject: [PATCH 7/9] feat: add manager and sponsor properties to user patching - Introduced 'manager' and 'sponsor' properties to PATCHABLE_PROPERTIES. - Implemented user selection for these properties using CippFormUserSelector. - Added validation for tenant domain restrictions when selecting users. - Updated confirmation step to handle new properties correctly. Fixes #5933 --- .../administration/users/patch-wizard.jsx | 70 +++++++++++++++++-- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/src/pages/identity/administration/users/patch-wizard.jsx b/src/pages/identity/administration/users/patch-wizard.jsx index 300aebfaafa0..1168f12f593e 100644 --- a/src/pages/identity/administration/users/patch-wizard.jsx +++ b/src/pages/identity/administration/users/patch-wizard.jsx @@ -15,11 +15,13 @@ import { Switch, FormControlLabel, Autocomplete, + Alert, } from '@mui/material' import { CippWizardStepButtons } from '../../../../components/CippWizard/CippWizardStepButtons' import { ApiPostCall, ApiGetCall } from '../../../../api/ApiCall' import { CippApiResults } from '../../../../components/CippComponents/CippApiResults' import { CippDataTable } from '../../../../components/CippTable/CippDataTable' +import { CippFormUserSelector } from '../../../../components/CippComponents/CippFormUserSelector' import { Delete } from '@mui/icons-material' // User properties that can be patched @@ -54,6 +56,11 @@ const PATCHABLE_PROPERTIES = [ label: 'Job Title', type: 'string', }, + { + property: 'manager', + label: 'Manager', + type: 'userSelector', + }, { property: 'officeLocation', label: 'Office Location', @@ -79,6 +86,11 @@ const PATCHABLE_PROPERTIES = [ label: 'Show in Address List', type: 'boolean', }, + { + property: 'sponsor', + label: 'Sponsor', + type: 'userSelector', + }, { property: 'state', label: 'State/Province', @@ -182,6 +194,21 @@ const PropertySelectionStep = (props) => { // Get unique tenant domains from users const tenantDomains = [...new Set(users?.map((user) => user.Tenant || user.tenantFilter).filter(Boolean))] || [] + const firstTenantDomain = tenantDomains[0] + const hasManagerSelected = selectedProperties.includes('manager') + const hasSponsorSelected = selectedProperties.includes('sponsor') + const hasRelationshipSelected = hasManagerSelected || hasSponsorSelected + + useEffect(() => { + if (!hasRelationshipSelected || !firstTenantDomain) { + return + } + + const currentTenantFilter = formControl.getValues('tenantFilter') + if (currentTenantFilter?.value !== firstTenantDomain) { + formControl.setValue('tenantFilter', { value: firstTenantDomain }) + } + }, [firstTenantDomain, formControl, hasRelationshipSelected]) // Fetch custom data mappings for all tenants const customDataMappings = ApiGetCall({ @@ -248,6 +275,21 @@ const PropertySelectionStep = (props) => { ) } + if (property?.type === 'userSelector') { + return ( + + ) + } + // Default to text input for string types with consistent styling return ( { Properties to update + {hasRelationshipSelected && tenantDomains.length > 1 && ( + + The user picker is scoped to {firstTenantDomain}. Cross-tenant manager or sponsor + assignment is not supported, so the selected user must exist in each target tenant. + + )} {selectedProperties.map(renderPropertyInput)} @@ -455,7 +503,14 @@ const ConfirmationStep = (props) => { } selectedProperties.forEach((propName) => { - if (propertyValues[propName] !== undefined && propertyValues[propName] !== '') { + const propertyValue = propertyValues[propName] + + if (propertyValue !== undefined && propertyValue !== '' && propertyValue !== null) { + if (propName === 'manager' || propName === 'sponsor') { + if (propertyValue?.value) userData[propName] = propertyValue.value + return + } + // Handle dot notation properties (e.g., "extension_abc123.customField") if (propName.includes('.')) { const parts = propName.split('.') @@ -470,10 +525,10 @@ const ConfirmationStep = (props) => { } // Set the final property value - current[parts[parts.length - 1]] = propertyValues[propName] + current[parts[parts.length - 1]] = propertyValue } else { // Handle regular properties - userData[propName] = propertyValues[propName] + userData[propName] = propertyValue } } }) @@ -522,8 +577,13 @@ const ConfirmationStep = (props) => { {selectedProperties.map((propName) => { const property = allProperties.find((p) => p.property === propName) const value = propertyValues[propName] - const displayValue = - property?.type === 'boolean' ? (value ? 'Yes' : 'No') : value || 'Not set' + let displayValue = value || 'Not set' + + if (propName === 'manager' || propName === 'sponsor') { + displayValue = value?.label || value?.value || 'Not set' + } else if (property?.type === 'boolean') { + displayValue = value ? 'Yes' : 'No' + } return ( From d25142333ac8e0787040f4b62d330d90c939cf44 Mon Sep 17 00:00:00 2001 From: Bobby <31723128+kris6673@users.noreply.github.com> Date: Fri, 8 May 2026 13:56:58 +0200 Subject: [PATCH 8/9] fix(jit-admin): submit TAP lifetime within policy bounds Fixes #5965 --- .../identity/administration/jit-admin/add.jsx | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/pages/identity/administration/jit-admin/add.jsx b/src/pages/identity/administration/jit-admin/add.jsx index a1c1fce87c82..d99a1b6518c1 100644 --- a/src/pages/identity/administration/jit-admin/add.jsx +++ b/src/pages/identity/administration/jit-admin/add.jsx @@ -31,6 +31,12 @@ const Page = () => { const watcher = useWatch({ control: formControl.control }); const useTAP = useWatch({ control: formControl.control, name: "UseTAP" }); + const startDate = useWatch({ control: formControl.control, name: "startDate" }); + const endDate = useWatch({ control: formControl.control, name: "endDate" }); + const tapLifetimeInMinutes = useWatch({ + control: formControl.control, + name: "tapLifetimeInMinutes", + }); const tapPolicy = ApiGetCall({ url: selectedTenant @@ -47,6 +53,22 @@ const Page = () => { const useRoles = useWatch({ control: formControl.control, name: "useRoles" }); const useGroups = useWatch({ control: formControl.control, name: "useGroups" }); + useEffect(() => { + if (!useTAP || !startDate || !endDate) { + formControl.setValue("tapLifetimeInMinutes", null); + return; + } + + const requestedMinutes = Math.max(1, Math.round((endDate - startDate) / 60)); + const tapPolicyConfig = tapPolicy.data?.Results?.[0]; + const policyMax = tapPolicyConfig?.maximumLifetimeInMinutes ?? 1440; + const policyMin = Math.min(tapPolicyConfig?.minimumLifetimeInMinutes ?? 1, policyMax); + formControl.setValue( + "tapLifetimeInMinutes", + Math.min(Math.max(requestedMinutes, policyMin), policyMax) + ); + }, [useTAP, startDate, endDate, tapPolicy.data, formControl]); + // Clear fields when switches are toggled off useEffect(() => { if (!useRoles) { @@ -501,6 +523,11 @@ const Page = () => { /> + { TAP is not enabled in this tenant. TAP generation will fail. )} + {useTAP && tapLifetimeInMinutes && ( + + TAP will be valid for {tapLifetimeInMinutes} minutes. + + )} Date: Fri, 8 May 2026 23:17:03 +0800 Subject: [PATCH 9/9] Disable all tenant support for message trace --- src/pages/email/tools/message-trace/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/email/tools/message-trace/index.js b/src/pages/email/tools/message-trace/index.js index 56ccf9bcd20a..d5876859b182 100644 --- a/src/pages/email/tools/message-trace/index.js +++ b/src/pages/email/tools/message-trace/index.js @@ -347,6 +347,5 @@ const Page = () => { ); }; -Page.getLayout = (page) => {page}; - +Page.getLayout = (page) => {page}; export default Page;