diff --git a/package.json b/package.json index 9e328a0d7a08..40ff2e6d118e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "10.5.4", + "version": "10.5.5", "author": "CIPP Contributors", "homepage": "https://cipp.app/", "bugs": { @@ -116,4 +116,4 @@ "eslint-config-prettier": "^10.1.8", "prettier": "^3.8.1" } -} \ No newline at end of file +} diff --git a/public/version.json b/public/version.json index e9797ed43509..6c7341794d6a 100644 --- a/public/version.json +++ b/public/version.json @@ -1,3 +1,3 @@ { - "version": "10.5.4" -} \ No newline at end of file + "version": "10.5.5" +} diff --git a/src/components/CippComponents/AuthMethodCard.jsx b/src/components/CippComponents/AuthMethodCard.jsx index 97ade0f8d6c6..d684d8b4e694 100644 --- a/src/components/CippComponents/AuthMethodCard.jsx +++ b/src/components/CippComponents/AuthMethodCard.jsx @@ -16,8 +16,23 @@ export const AuthMethodCard = ({ data, isLoading }) => { return null; } - const phishableMethods = ["mobilePhone", "email", "microsoftAuthenticatorPush"]; - const phishResistantMethods = ["fido2", "windowsHelloForBusiness", "x509Certificate"]; + const phishableMethods = [ + "mobilePhone", + "alternateMobilePhone", + "officePhone", + "email", + "microsoftAuthenticatorPush", + "softwareOneTimePasscode", + "hardwareOneTimePasscode", + ]; + const passkeyMethods = [ + "fido2SecurityKey", + "passKeyDeviceBound", + "passKeyDeviceBoundAuthenticator", + "passKeyDeviceBoundWindowsHello", + "x509Certificate", + ]; + const phishResistantMethods = [...passkeyMethods, "windowsHelloForBusiness"]; let singleFactor = 0; let phishableCount = 0; @@ -48,7 +63,7 @@ export const AuthMethodCard = ({ data, isLoading }) => { if (hasPhishResistant) { phishResistantCount++; - if (methods.includes("fido2") || methods.includes("x509Certificate")) { + if (methods.some((m) => passkeyMethods.includes(m))) { passkeyCount++; } if (methods.includes("windowsHelloForBusiness")) { @@ -56,12 +71,18 @@ export const AuthMethodCard = ({ data, isLoading }) => { } } else if (hasPhishable) { phishableCount++; - if (methods.includes("mobilePhone") || methods.includes("email")) { + if ( + methods.includes("mobilePhone") || + methods.includes("alternateMobilePhone") || + methods.includes("officePhone") || + methods.includes("email") + ) { phoneCount++; } if ( methods.includes("microsoftAuthenticatorPush") || - methods.includes("softwareOneTimePasscode") + methods.includes("softwareOneTimePasscode") || + methods.includes("hardwareOneTimePasscode") ) { authenticatorCount++; } diff --git a/src/components/CippComponents/AuthMethodSankey.jsx b/src/components/CippComponents/AuthMethodSankey.jsx index f57c42573c52..f65ec13c1483 100644 --- a/src/components/CippComponents/AuthMethodSankey.jsx +++ b/src/components/CippComponents/AuthMethodSankey.jsx @@ -13,9 +13,23 @@ export const AuthMethodSankey = ({ data }) => { return null; } - // Categorize MFA methods as phishable or phish-resistant - const phishableMethods = ["mobilePhone", "email", "microsoftAuthenticatorPush"]; - const phishResistantMethods = ["fido2", "windowsHelloForBusiness", "x509Certificate"]; + const phishableMethods = [ + "mobilePhone", + "alternateMobilePhone", + "officePhone", + "email", + "microsoftAuthenticatorPush", + "softwareOneTimePasscode", + "hardwareOneTimePasscode", + ]; + const passkeyMethods = [ + "fido2SecurityKey", + "passKeyDeviceBound", + "passKeyDeviceBoundAuthenticator", + "passKeyDeviceBoundWindowsHello", + "x509Certificate", + ]; + const phishResistantMethods = [...passkeyMethods, "windowsHelloForBusiness"]; let singleFactor = 0; let phishableCount = 0; @@ -54,7 +68,7 @@ export const AuthMethodSankey = ({ data }) => { if (hasPhishResistant) { phishResistantCount++; // Count specific phish-resistant methods - if (methods.includes("fido2") || methods.includes("x509Certificate")) { + if (methods.some((m) => passkeyMethods.includes(m))) { passkeyCount++; } if (methods.includes("windowsHelloForBusiness")) { @@ -62,13 +76,18 @@ export const AuthMethodSankey = ({ data }) => { } } else if (hasPhishable) { phishableCount++; - // Count specific phishable methods - if (methods.includes("mobilePhone") || methods.includes("email")) { + if ( + methods.includes("mobilePhone") || + methods.includes("alternateMobilePhone") || + methods.includes("officePhone") || + methods.includes("email") + ) { phoneCount++; } if ( methods.includes("microsoftAuthenticatorPush") || - methods.includes("softwareOneTimePasscode") + methods.includes("softwareOneTimePasscode") || + methods.includes("hardwareOneTimePasscode") ) { authenticatorCount++; } diff --git a/src/components/CippComponents/CippAppPermissionBuilder.jsx b/src/components/CippComponents/CippAppPermissionBuilder.jsx index 07d21613fb84..013fe843096a 100644 --- a/src/components/CippComponents/CippAppPermissionBuilder.jsx +++ b/src/components/CippComponents/CippAppPermissionBuilder.jsx @@ -462,10 +462,11 @@ const CippAppPermissionBuilder = ({ if (appTable !== undefined && appTable?.length === 0) { setAppTable( spPermissions?.applicationPermissions - ?.sort((a, b) => a.value.localeCompare(b.value)) + ?.sort((a, b) => (a.value ?? "").localeCompare(b.value ?? "")) ?.map((perm) => ({ id: perm.id, value: perm.value, + required: perm.required ?? false, description: spInfo?.Results?.appRoles.find((role) => role.id === perm.id) ?.description, })), @@ -474,10 +475,11 @@ const CippAppPermissionBuilder = ({ if (delegatedTable !== undefined && delegatedTable.length === 0) { setDelegatedTable( spPermissions?.delegatedPermissions - ?.sort((a, b) => a.value.localeCompare(b.value)) + ?.sort((a, b) => (a.value ?? "").localeCompare(b.value ?? "")) ?.map((perm) => ({ id: perm.id, value: perm.value, + required: perm.required ?? false, description: spInfo?.Results?.publishedPermissionScopes.find((scope) => scope.id === perm.id) ?.userConsentDescription ?? "Manually added", @@ -625,6 +627,7 @@ const CippAppPermissionBuilder = ({ label: "Delete Permission", icon: , noConfirm: true, + condition: (row) => !row.required, customFunction: (row) => handleRemoveRow("applicationPermissions", row), }, ]} @@ -690,6 +693,7 @@ const CippAppPermissionBuilder = ({ label: "Delete Permission", icon: , noConfirm: true, + condition: (row) => !row.required, customFunction: (row) => handleRemoveRow("delegatedPermissions", row), }, ]} @@ -788,7 +792,7 @@ const CippAppPermissionBuilder = ({ - + { confirmReset(); @@ -929,79 +933,74 @@ const CippAppPermissionBuilder = ({ )} - {newPermissions?.MissingPermissions && - newPermissions?.Type === "Table" && - Object.keys(newPermissions?.MissingPermissions).length > 0 && ( - - - } - action={ - - { - var updatedPermissions = JSON.parse(JSON.stringify(newPermissions)); - Object.keys(newPermissions?.MissingPermissions).map((perm) => { - Object.keys(newPermissions?.MissingPermissions[perm]).map( - (type) => { - if (!updatedPermissions.Permissions[perm][type]) { - updatedPermissions.Permissions[perm][type] = []; - } - newPermissions?.MissingPermissions[perm][type].map((p) => { - updatedPermissions.Permissions[perm][type].push(p); - }); - }, - ); - }); - updatedPermissions.MissingPermissions = {}; - setNewPermissions(updatedPermissions); - }} - > - - - - - - } - > - New Permissions Available - {Object.keys(newPermissions?.MissingPermissions).map((perm) => { - // translate appid to display name - var sp = servicePrincipals?.Results?.find((sp) => sp.appId === perm); - return ( - - {sp?.displayName}:{" "} - {Object.keys(newPermissions?.MissingPermissions[perm]).map((type) => { - return ( - <> - {newPermissions?.MissingPermissions[perm][type].length > 0 && ( - - {type == "applicationPermissions" - ? "Application" - : "Delegated"}{" "} - -{" "} - {newPermissions?.MissingPermissions[perm][type] - .map((p) => { - return p.value; - }) - .join(", ")} - - )} - > - ); - })} - - ); - })} - + {newPermissions?.PartnerAppDiff && + Object.keys(newPermissions?.PartnerAppDiff).length > 0 && + (() => { + const diff = newPermissions.PartnerAppDiff; + const appIds = Object.keys(diff); + const hasMissing = appIds.some( + (perm) => + (diff[perm].missingApplicationPermissions?.length ?? 0) > 0 || + (diff[perm].missingDelegatedPermissions?.length ?? 0) > 0, + ); + const hasExtra = appIds.some( + (perm) => + (diff[perm].extraApplicationPermissions?.length ?? 0) > 0 || + (diff[perm].extraDelegatedPermissions?.length ?? 0) > 0, + ); + const renderList = (perm, appKey, delKey) => { + const sp = servicePrincipals?.Results?.find((sp) => sp.appId === perm); + const app = diff[perm][appKey] ?? []; + const del = diff[perm][delKey] ?? []; + if (app.length === 0 && del.length === 0) return null; + return ( + + {sp?.displayName ?? perm}:{" "} + {app.length > 0 && <>Application - {app.map((p) => p.value).join(", ")} >} + {del.length > 0 && <>Delegated - {del.map((p) => p.value).join(", ")}>} + + ); + }; + return ( + + {hasMissing && ( + + }> + + Permissions missing from the {appDisplayName} app registration (run + Repair Permissions to add, then a CPV refresh to apply to tenants) + + {appIds.map((perm) => + renderList( + perm, + "missingApplicationPermissions", + "missingDelegatedPermissions", + ), + )} + + + )} + {hasExtra && ( + + }> + + Extra permissions present on the {appDisplayName} app registration that + are not part of the CIPP defaults or your additional permissions + (review and remove manually if not required) + + {appIds.map((perm) => + renderList( + perm, + "extraApplicationPermissions", + "extraDelegatedPermissions", + ), + )} + + + )} - - )} + ); + })()} {selectedApp && diff --git a/src/components/CippComponents/CippAutocomplete.jsx b/src/components/CippComponents/CippAutocomplete.jsx index 04dd59da8e44..f55d8bc523b1 100644 --- a/src/components/CippComponents/CippAutocomplete.jsx +++ b/src/components/CippComponents/CippAutocomplete.jsx @@ -265,7 +265,7 @@ export const CippAutoComplete = React.forwardRef((props, ref) => { finalOptions = finalOptions.filter((o) => !removeOptions.includes(o.value)) } if (sortOptions) { - finalOptions.sort((a, b) => a.label?.localeCompare(b.label)) + finalOptions.sort((a, b) => String(a.label ?? "").localeCompare(String(b.label ?? ""))) } return finalOptions }, [api, usedOptions, options, removeOptions, sortOptions]) diff --git a/src/components/CippComponents/CippCADeployDrawer.jsx b/src/components/CippComponents/CippCADeployDrawer.jsx index 6b2a4a633ff4..dac9f8c2c7c8 100644 --- a/src/components/CippComponents/CippCADeployDrawer.jsx +++ b/src/components/CippComponents/CippCADeployDrawer.jsx @@ -1,92 +1,92 @@ -import { useEffect, useState, useCallback } from "react"; -import { Button, Stack } from "@mui/material"; -import { RocketLaunch } from "@mui/icons-material"; -import { useForm, useWatch } from "react-hook-form"; -import { CippOffCanvas } from "./CippOffCanvas"; -import { ApiGetCall, ApiPostCall } from "../../api/ApiCall"; -import CippFormComponent from "./CippFormComponent"; -import CippJsonView from "../CippFormPages/CippJSONView"; -import { CippApiResults } from "./CippApiResults"; -import { useSettings } from "../../hooks/use-settings"; -import { CippFormTenantSelector } from "./CippFormTenantSelector"; -import { CippFormCondition } from "./CippFormCondition"; +import { useEffect, useState, useCallback } from 'react' +import { Button, Stack } from '@mui/material' +import { RocketLaunch } from '@mui/icons-material' +import { useForm, useWatch } from 'react-hook-form' +import { CippOffCanvas } from './CippOffCanvas' +import { ApiGetCall, ApiPostCall } from '../../api/ApiCall' +import CippFormComponent from './CippFormComponent' +import CippJsonView from '../CippFormPages/CippJSONView' +import { CippApiResults } from './CippApiResults' +import { useSettings } from '../../hooks/use-settings' +import { CippFormTenantSelector } from './CippFormTenantSelector' +import { CippFormCondition } from './CippFormCondition' export const CippCADeployDrawer = ({ - buttonText = "Deploy CA Policy", + buttonText = 'Deploy CA Policy', requiredPermissions = [], PermissionButton = Button, templateId = null, // New prop for pre-supplying template ID open = null, // External control for drawer visibility onClose = null, // External close handler }) => { - const [internalDrawerVisible, setInternalDrawerVisible] = useState(false); - const formControl = useForm(); - const tenantFilter = useSettings()?.tenantFilter; - const CATemplates = ApiGetCall({ url: "/api/ListCATemplates", queryKey: "CATemplates" }); - const [JSONData, setJSONData] = useState(); - const watcher = useWatch({ control: formControl.control, name: "TemplateList" }); + const [internalDrawerVisible, setInternalDrawerVisible] = useState(false) + const formControl = useForm() + const tenantFilter = useSettings()?.tenantFilter + const CATemplates = ApiGetCall({ url: '/api/ListCATemplates', queryKey: 'CATemplates' }) + const [JSONData, setJSONData] = useState() + const watcher = useWatch({ control: formControl.control, name: 'TemplateList' }) const selectedReplaceMode = useWatch({ control: formControl.control, - name: "replacename", - }); + name: 'replacename', + }) // Use external open state if provided, otherwise use internal state - const drawerVisible = open !== null ? open : internalDrawerVisible; - const isExternallyControlled = open !== null && onClose !== null; + const drawerVisible = open !== null ? open : internalDrawerVisible + const isExternallyControlled = open !== null && onClose !== null const updateTemplate = useCallback( (templateGuid) => { if (CATemplates.isSuccess && templateGuid) { - const template = CATemplates.data.find((template) => template.GUID === templateGuid); + const template = CATemplates.data.find((template) => template.GUID === templateGuid) if (template) { - setJSONData(template); - formControl.setValue("rawjson", JSON.stringify(template, null)); + setJSONData(template) + formControl.setValue('rawjson', JSON.stringify(template, null)) } } }, [CATemplates.isSuccess, CATemplates.data, formControl.setValue] - ); + ) // Effect to set template when templateId prop is provided useEffect(() => { if (templateId && CATemplates.isSuccess) { // Find the template to get the display name - const template = CATemplates.data.find((template) => template.GUID === templateId); + const template = CATemplates.data.find((template) => template.GUID === templateId) if (template) { // Pre-select the template when drawer opens - formControl.setValue("TemplateList", { value: templateId, label: template.displayName }); - updateTemplate(templateId); + formControl.setValue('TemplateList', { value: templateId, label: template.displayName }) + updateTemplate(templateId) } } - }, [templateId, CATemplates.isSuccess, formControl, updateTemplate]); + }, [templateId, CATemplates.isSuccess, formControl, updateTemplate]) useEffect(() => { - updateTemplate(watcher?.value); - }, [updateTemplate, watcher?.value]); + updateTemplate(watcher?.value) + }, [updateTemplate, watcher?.value]) const deployPolicy = ApiPostCall({ urlFromData: true, - relatedQueryKeys: ["CATemplates", `Conditional Access Policies - ${tenantFilter}`], - }); + relatedQueryKeys: ['CATemplates', `Conditional Access Policies - ${tenantFilter}`], + }) const handleSubmit = () => { - const formData = formControl.getValues(); - console.log("Submitting CA form data:", formData); + const formData = formControl.getValues() + console.log('Submitting CA form data:', formData) deployPolicy.mutate({ - url: "/api/AddCAPolicy", - relatedQueryKeys: ["CATemplates", "Conditional Access Policies"], + url: '/api/AddCAPolicy', + relatedQueryKeys: ['CATemplates', 'Conditional Access Policies'], data: { ...formData }, - }); - }; + }) + } const handleCloseDrawer = () => { if (isExternallyControlled) { - onClose(); + onClose() } else { - setInternalDrawerVisible(false); + setInternalDrawerVisible(false) } - formControl.reset(); - }; + formControl.reset() + } return ( <> @@ -115,10 +115,10 @@ export const CippCADeployDrawer = ({ disabled={deployPolicy.isLoading} > {deployPolicy.isLoading - ? "Deploying..." + ? 'Deploying...' : deployPolicy.isSuccess - ? "Redeploy Policy" - : "Deploy Policy"} + ? 'Redeploy Policy' + : 'Deploy Policy'} Close @@ -141,9 +141,10 @@ export const CippCADeployDrawer = ({ @@ -184,10 +185,10 @@ export const CippCADeployDrawer = ({ label="Policy State" formControl={formControl} options={[ - { value: "donotchange", label: "Do not change state" }, - { value: "Enabled", label: "Set to enabled" }, - { value: "Disabled", label: "Set to disabled" }, - { value: "enabledForReportingButNotEnforced", label: "Set to report only" }, + { value: 'donotchange', label: 'Do not change state' }, + { value: 'Enabled', label: 'Set to enabled' }, + { value: 'Disabled', label: 'Set to disabled' }, + { value: 'enabledForReportingButNotEnforced', label: 'Set to report only' }, ]} /> @@ -217,14 +218,14 @@ export const CippCADeployDrawer = ({ label="Create groups if they do not exist" formControl={formControl} helperText={ - selectedReplaceMode !== "displayName" + selectedReplaceMode !== 'displayName' ? "Select 'Replace by display name' to create groups specified in the template." - : "Enable this option to create groups that do not exist in the tenant." + : 'Enable this option to create groups that do not exist in the tenant.' } /> > - ); -}; + ) +} diff --git a/src/components/CippComponents/CippFormComponent.jsx b/src/components/CippComponents/CippFormComponent.jsx index 628a9aa0dafc..d8b2f9d48443 100644 --- a/src/components/CippComponents/CippFormComponent.jsx +++ b/src/components/CippComponents/CippFormComponent.jsx @@ -25,6 +25,25 @@ import { CippDataTable } from "../CippTable/CippDataTable"; import React from "react"; import { CloudUpload } from "@mui/icons-material"; import { Stack } from "@mui/system"; +import countryList from "../../data/countryList"; +import languageList from "../../data/languageList"; + +// ISO 3166-1 alpha-2 country/region codes (uppercase), used by the CountryCodeMultiSelect type. +const countryCodeOptions = countryList + .map((c) => ({ label: `${c.Name} (${c.Code})`, value: c.Code })) + .sort((a, b) => a.label.localeCompare(b.label)); + +// ISO 639-1 alpha-2 language codes (lowercase), used by the LanguageCodeMultiSelect type. +// Derived from the locale tags in languageList.json, deduplicated to the two-letter primary subtag (e.g. "en-US" -> "en"). +const languageCodeOptions = Object.values( + languageList.reduce((acc, entry) => { + const code = entry.tag?.split("-")[0]?.toLowerCase(); + if (code && code.length === 2 && !acc[code]) { + acc[code] = { label: `${entry.language} (${code})`, value: code }; + } + return acc; + }, {}), +).sort((a, b) => a.label.localeCompare(b.label)); // The tiptap / prosemirror / mui-tiptap editor tree is large and only used by `richText` fields. // Load it on demand via next/dynamic so it is code-split into an async chunk instead of being @@ -87,6 +106,59 @@ export const CippFormComponent = (props) => { } }; + // Shared renderer for autoComplete-backed fields (autoComplete + the ISO-code multiselects). + const renderAutoCompleteField = (autoCompleteProps) => { + // Resolve options if it's a function + const resolvedOptions = + typeof autoCompleteProps.options === "function" + ? autoCompleteProps.options(row) + : autoCompleteProps.options; + + // Wrap validate function to pass row as third parameter + const resolvedValidators = validators + ? { + ...validators, + validate: + typeof validators.validate === "function" + ? (value, formValues) => validators.validate(value, formValues, row) + : validators.validate, + } + : validators; + + return ( + + ( + field.onChange(value)} + onBlur={field.onBlur} + /> + )} + /> + + {get(errors, convertedName, {})?.message && ( + + {get(errors, convertedName, {})?.message} + + )} + {helperText && ( + + {helperText} + + )} + + ); + }; + switch (type) { case "heading": return ( @@ -434,55 +506,26 @@ export const CippFormComponent = (props) => { > ); - case "autoComplete": { - // Resolve options if it's a function - const resolvedOptions = - typeof other.options === "function" ? other.options(row) : other.options; - - // Wrap validate function to pass row as third parameter - const resolvedValidators = validators - ? { - ...validators, - validate: - typeof validators.validate === "function" - ? (value, formValues) => validators.validate(value, formValues, row) - : validators.validate, - } - : validators; + case "autoComplete": + return renderAutoCompleteField(other); - return ( - - ( - field.onChange(value)} - onBlur={field.onBlur} - /> - )} - /> + // ISO 3166-1 alpha-2 region/country code multiselect (e.g. Spam Filter RegionBlockList). + case "CountryCodeMultiSelect": + return renderAutoCompleteField({ + ...other, + options: countryCodeOptions, + multiple: true, + creatable: false, + }); - {get(errors, convertedName, {})?.message && ( - - {get(errors, convertedName, {})?.message} - - )} - {helperText && ( - - {helperText} - - )} - - ); - } + // ISO 639-1 alpha-2 language code multiselect (e.g. Spam Filter LanguageBlockList). + case "LanguageCodeMultiSelect": + return renderAutoCompleteField({ + ...other, + options: languageCodeOptions, + multiple: true, + creatable: false, + }); case "richText": { return ( diff --git a/src/components/CippComponents/CippNotificationForm.jsx b/src/components/CippComponents/CippNotificationForm.jsx index f2f74b19d7f1..03ecce096ec9 100644 --- a/src/components/CippComponents/CippNotificationForm.jsx +++ b/src/components/CippComponents/CippNotificationForm.jsx @@ -42,7 +42,6 @@ export const CippNotificationForm = ({ { label: "Adding a group", value: "AddGroup" }, { label: "Adding a tenant", value: "NewTenant" }, { label: "Executing the offboard wizard", value: "ExecOffboardUser" }, - { label: "Custom Test Alerts", value: "CustomTests" }, ]; const severityTypes = [ diff --git a/src/components/CippComponents/CippPolicyImportDrawer.jsx b/src/components/CippComponents/CippPolicyImportDrawer.jsx index 7c1630ce2eb4..ca9f5cf4bf40 100644 --- a/src/components/CippComponents/CippPolicyImportDrawer.jsx +++ b/src/components/CippComponents/CippPolicyImportDrawer.jsx @@ -47,7 +47,7 @@ export const CippPolicyImportDrawer = ({ const tenantPolicies = ApiGetCall({ url: mode === 'ConditionalAccess' - ? `/api/ListCATemplates?TenantFilter=${tenantFilter?.value || ''}` + ? `/api/ListConditionalAccessPolicies?TenantFilter=${tenantFilter?.value || ''}` : mode === 'Standards' ? `/api/listStandardTemplates?TenantFilter=${tenantFilter?.value || ''}` : `/api/ListIntunePolicy?type=ESP&TenantFilter=${tenantFilter?.value || ''}`, @@ -110,12 +110,14 @@ export const CippPolicyImportDrawer = ({ // For Conditional Access, convert RawJSON to object and send the contents let policyData = policy - // If the policy has RawJSON, parse it and use that as the data - if (policy.RawJSON) { + // If the policy has rawjson, parse it and use that as the data. + // ListConditionalAccessPolicies returns the raw Graph policy as lowercase `rawjson`. + const rawJson = policy.rawjson ?? policy.RawJSON + if (rawJson) { try { - policyData = JSON.parse(policy.RawJSON) + policyData = JSON.parse(rawJson) } catch (e) { - console.error('Failed to parse RawJSON:', e) + console.error('Failed to parse rawjson:', e) policyData = policy } } @@ -187,8 +189,19 @@ export const CippPolicyImportDrawer = ({ }, }) } else { - // For tenant policies, use the policy object directly - setViewingPolicy(policy || {}) + // For tenant policies, show the raw policy JSON when available + // (ConditionalAccess returns the Graph policy as lowercase `rawjson`). + const rawJson = policy?.rawjson ?? policy?.RawJSON + if (mode === 'ConditionalAccess' && rawJson) { + try { + setViewingPolicy(JSON.parse(rawJson)) + } catch (e) { + console.error('Failed to parse rawjson for view:', e) + setViewingPolicy(policy || {}) + } + } else { + setViewingPolicy(policy || {}) + } } setViewDialogOpen(true) } catch (error) { diff --git a/src/components/CippComponents/CippTenantGroupOffCanvas.jsx b/src/components/CippComponents/CippTenantGroupOffCanvas.jsx index 05ed8e18f836..490818307141 100644 --- a/src/components/CippComponents/CippTenantGroupOffCanvas.jsx +++ b/src/components/CippComponents/CippTenantGroupOffCanvas.jsx @@ -46,6 +46,9 @@ export const CippTenantGroupOffCanvas = ({ data }) => { ne: "not equals", in: "in", notIn: "not in", + notin: "not in", + like: "contains", + notlike: "does not contain", contains: "contains", startsWith: "starts with", endsWith: "ends with", @@ -54,6 +57,54 @@ export const CippTenantGroupOffCanvas = ({ data }) => { // Handle both single rule object and array of rules const rules = Array.isArray(data.DynamicRules) ? data.DynamicRules : [data.DynamicRules]; + // Resolve a value that may be a string, a {label, value} option, or a raw object + const resolveOptionLabel = (item) => { + if (item === null || item === undefined) return ""; + if (typeof item === "object") return item.label ?? item.value ?? JSON.stringify(item); + return item; + }; + + const renderRuleValue = (rule) => { + // Custom Variable rules store a nested { variableName, value } object + if (rule.property === "customVariable" || rule.value?.variableName !== undefined) { + const variableName = resolveOptionLabel(rule.value?.variableName); + const expectedValue = resolveOptionLabel(rule.value?.value); + return ( + + ); + } + + if (Array.isArray(rule.value)) { + return ( + + {rule.value.map((item, valueIndex) => ( + + ))} + + ); + } + + return ( + + ); + }; + const renderRule = (rule, index) => ( { Value(s): - {Array.isArray(rule.value) ? ( - - {rule.value.map((item, valueIndex) => ( - - ))} - - ) : ( - - )} + {renderRuleValue(rule)} ); diff --git a/src/components/CippComponents/CippTenantGroupRuleBuilder.jsx b/src/components/CippComponents/CippTenantGroupRuleBuilder.jsx index e61aaf768be5..3133124dfe0b 100644 --- a/src/components/CippComponents/CippTenantGroupRuleBuilder.jsx +++ b/src/components/CippComponents/CippTenantGroupRuleBuilder.jsx @@ -40,9 +40,11 @@ const CippTenantGroupRuleBuilder = ({ formControl, name = "dynamicRules" }) => { // Flatten all pages and extract Results const allGroups = tenantGroupsQuery.data.pages.flatMap((page) => page?.Results || []); return allGroups - .filter((group) => group.GroupType === "static") .map((group) => ({ - label: group.Name || group.displayName, + label: + group.GroupType === "dynamic" + ? `${group.Name || group.displayName} (dynamic)` + : group.Name || group.displayName, value: group.Id || group.RowKey, type: group.GroupType, })) diff --git a/src/components/CippComponents/CippTimeAgo.jsx b/src/components/CippComponents/CippTimeAgo.jsx index a97d71e03acc..9573b4cfa936 100644 --- a/src/components/CippComponents/CippTimeAgo.jsx +++ b/src/components/CippComponents/CippTimeAgo.jsx @@ -1,11 +1,10 @@ import { Chip } from "@mui/material"; import ReactTimeAgo from "react-time-ago"; +import { parseCippDate } from "../../utils/parse-cipp-date"; export const CippTimeAgo = ({ data, type = "text", timeStyle = "round-minute" }) => { const isText = type === "text"; - const numberRegex = /^\d+$/; - const date = - typeof data === "number" || numberRegex.test(data) ? new Date(data * 1000) : new Date(data); + const date = parseCippDate(data); if (date.getTime() === 0) { return "Never"; diff --git a/src/components/CippIntegrations/CippIntegrationFieldMapping.jsx b/src/components/CippIntegrations/CippIntegrationFieldMapping.jsx index 53a88787de19..76162fef0daa 100644 --- a/src/components/CippIntegrations/CippIntegrationFieldMapping.jsx +++ b/src/components/CippIntegrations/CippIntegrationFieldMapping.jsx @@ -42,7 +42,7 @@ const CippIntegrationFieldMapping = () => { var missingMappings = []; fieldMapping?.data?.Mappings?.forEach((mapping) => { const exists = fieldMapping?.data?.IntegrationFields?.some( - (integrationField) => String(integrationField.value) === mapping.IntegrationId + (integrationField) => String(integrationField?.value) === mapping.IntegrationId ); if (exists) { newMappings[mapping.RowKey] = { diff --git a/src/components/CippTable/CippDataTable.js b/src/components/CippTable/CippDataTable.js index 284579c2b60d..1d902eb64f90 100644 --- a/src/components/CippTable/CippDataTable.js +++ b/src/components/CippTable/CippDataTable.js @@ -24,6 +24,7 @@ import { CippApiDialog } from '../CippComponents/CippApiDialog' import { getCippError } from '../../utils/get-cipp-error' import { Box } from '@mui/system' import { useSettings } from '../../hooks/use-settings' +import { parseCippDate } from '../../utils/parse-cipp-date' import { isEqual } from 'lodash' // Import lodash for deep comparison import { useLicenseBackfill } from '../../hooks/use-license-backfill' @@ -84,8 +85,8 @@ const SORTING_FNS = { dateTimeNullsLast: (a, b, id) => { const aRaw = getRowValueByColumnId(a, id) const bRaw = getRowValueByColumnId(b, id) - const aDate = aRaw ? new Date(aRaw) : null - const bDate = bRaw ? new Date(bRaw) : null + const aDate = aRaw ? parseCippDate(aRaw) : null + const bDate = bRaw ? parseCippDate(bRaw) : null const aTime = aDate && !Number.isNaN(aDate.getTime()) ? aDate.getTime() : null const bTime = bDate && !Number.isNaN(bDate.getTime()) ? bDate.getTime() : null diff --git a/src/data/M365Licenses.json b/src/data/M365Licenses.json index 3f725239335b..cb6847be1d57 100644 --- a/src/data/M365Licenses.json +++ b/src/data/M365Licenses.json @@ -46846,5 +46846,989 @@ "Service_Plan_Name": "AAD_WRKLDID_P1", "Service_Plan_Id": "84c289f0-efcb-486f-8581-07f44fc9efad", "Service_Plans_Included_Friendly_Names": "Azure Active Directory workload identities P1" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ENTRA_NETWORK_CONTROLS_FOR_ASSISTIVE_AGENTS", + "Service_Plan_Id": "27e196a4-8b80-4930-bd65-53fd28581878", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra Network Controls for Assistive Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ENTRA_ID_GOV_FOR_ASSISTIVE_AGENTS", + "Service_Plan_Id": "a9e85e05-1687-4958-8a4c-bdacda2943db", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID Governance for Assistive Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INSIDER_RISK_MANAGEMENT_FOR_AGENTS", + "Service_Plan_Id": "004ddfc0-c92f-4b0a-90c5-c60646299d71", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Insider Risk Management for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INFORMATION_PROTECTION_FOR_AGENTS", + "Service_Plan_Id": "48478b49-91a1-4ded-94f0-066db80035ca", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Information Protection for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "EDISCOVERY_FOR_AGENTS", + "Service_Plan_Id": "92cedcb2-3fb2-40b4-9df4-f9a5603d9631", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview eDiscovery for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "DEFENDER_FOR_AI", + "Service_Plan_Id": "a1c15058-5559-4c1a-ba05-8040847f91bb", + "Service_Plans_Included_Friendly_Names": "Microsoft Defender for AI" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "DATA_LOSS_PREVENTION_FOR_AGENTS", + "Service_Plan_Id": "46d3c309-0ba7-461f-9d0e-eaca165794c2", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Data Loss Prevention for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "DATA_LIFECYCLE_MANAGEMENT_FOR_AGENTS", + "Service_Plan_Id": "30d56d35-9be2-41c1-b4b8-7a8d6f073152", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Data Lifecycle Management for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "COMPLIANCE_MANAGER_FOR_AGENTS", + "Service_Plan_Id": "d1f65d05-a302-4861-bd35-da8933ba7655", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Compliance Manager for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "COMMUNICATION_COMPLIANCE_FOR_AGENTS", + "Service_Plan_Id": "135fe762-031a-4e79-bd3c-ac376addddec", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Communication Compliance for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "AUDIT_FOR_AGENTS", + "Service_Plan_Id": "6d9b0ae5-e6a0-4f04-991a-e5700fd84930", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Audit for Agents" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "AGENT_365", + "Service_Plan_Id": "d0ce5ebb-9db0-491f-b780-8973a1d815fe", + "Service_Plans_Included_Friendly_Names": "Agent 365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Defender_for_Iot_Enterprise", + "Service_Plan_Id": "99cd49a9-0e54-4e07-aea1-d8d9f5f704f5", + "Service_Plans_Included_Friendly_Names": "Defender for IoT - Enterprise IoT Security" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "DYN365_CDS_O365_P3", + "Service_Plan_Id": "28b0fa46-c39a-4188-89e2-58e979a6b014", + "Service_Plans_Included_Friendly_Names": "Common Data Service" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "RMS_S_PREMIUM2", + "Service_Plan_Id": "5689bec4-755d-4753-8b61-40975025187c", + "Service_Plans_Included_Friendly_Names": "AZURE INFORMATION PROTECTION PREMIUM P2" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "RMS_S_PREMIUM", + "Service_Plan_Id": "6c57d4b6-3b23-47a5-9bc9-69f17b4947b3", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra RIGHTS" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Verifiable_Credentials_Service_Request", + "Service_Plan_Id": "aae826b7-14cd-4691-8178-2b312f7072ea", + "Service_Plans_Included_Friendly_Names": "Verifiable Credentials Service Request" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "POWER_VIRTUAL_AGENTS_O365_P3", + "Service_Plan_Id": "ded3d325-1bdc-453e-8432-5bac26d7a014", + "Service_Plans_Included_Friendly_Names": "Power Virtual Agents for Office 365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_COPILOT_CONNECTORS", + "Service_Plan_Id": "89f1c4c8-0878-40f7-804d-869c9128ab5d", + "Service_Plans_Included_Friendly_Names": "Power Platform Connectors in Microsoft 365 Copilot" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "FLOW_O365_P3", + "Service_Plan_Id": "07699545-9485-468e-95b6-2fca3738be01", + "Service_Plans_Included_Friendly_Names": "Power Automate for Office 365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "WORKPLACE_ANALYTICS_INSIGHTS_BACKEND", + "Service_Plan_Id": "ff7b261f-d98b-415b-827c-42a3fdf015af", + "Service_Plans_Included_Friendly_Names": "Microsoft Viva Insights Backend" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "WORKPLACE_ANALYTICS_INSIGHTS_USER", + "Service_Plan_Id": "b622badb-1b45-48d5-920f-4b27a2c0996c", + "Service_Plans_Included_Friendly_Names": "Microsoft Viva Insights" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INTUNE_A", + "Service_Plan_Id": "c1ec4a95-1f05-45b3-a911-aa3fa01094f5", + "Service_Plans_Included_Friendly_Names": "Microsoft Intune" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Entra_Premium_Private_Access", + "Service_Plan_Id": "f057aab1-b184-49b2-85c0-881b02a405c5", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra Private Access" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Entra_Premium_Internet_Access", + "Service_Plan_Id": "8d23cb83-ab07-418f-8517-d7aca77307dc", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra Internet Access" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "AAD_PREMIUM_P2", + "Service_Plan_Id": "eec0eb4f-6444-4f95-aba0-50c24d67f998", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P2" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "AAD_PREMIUM", + "Service_Plan_Id": "41781fb2-bc02-4b7c-bd55-b576c07bb09d", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID P1" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ATA", + "Service_Plan_Id": "14ab5db5-e6c4-4b20-b4bc-13e36fd2227f", + "Service_Plans_Included_Friendly_Names": "MICROSOFT DEFENDER FOR IDENTITY" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ADALLOM_S_STANDALONE", + "Service_Plan_Id": "2e2ddb96-6af9-4b1d-a3f0-d6ecfd22edb2", + "Service_Plans_Included_Friendly_Names": "MICROSOFT CLOUD APP SECURITY" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_COPILOT_BUSINESS_CHAT", + "Service_Plan_Id": "3f30311c-6b1e-48a4-ab79-725b469da960", + "Service_Plans_Included_Friendly_Names": "Microsoft Copilot with Graph-grounded chat" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MFA_PREMIUM", + "Service_Plan_Id": "8a256a2b-b617-496d-b51b-e76466e88db0", + "Service_Plans_Included_Friendly_Names": "Microsoft Azure Multi-Factor Authentication" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_COPILOT_APPS", + "Service_Plan_Id": "a62f8878-de10-42f3-b68f-6149a25ceb97", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Copilot in Productivity Apps" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_COPILOT_TEAMS", + "Service_Plan_Id": "b95945de-b3bd-46db-8437-f2beb6ea2347", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Copilot in Microsoft Teams" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_COPILOT_SHAREPOINT", + "Service_Plan_Id": "0aedf20c-091d-420b-aadf-30c042609612", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Copilot for SharePoint" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_COPILOT_INTELLIGENT_SEARCH", + "Service_Plan_Id": "931e4a88-a67f-48b5-814f-16a5f1e6028d", + "Service_Plans_Included_Friendly_Names": "Intelligent Search" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "GRAPH_CONNECTORS_COPILOT", + "Service_Plan_Id": "82d30987-df9b-4486-b146-198b21d164c7", + "Service_Plans_Included_Friendly_Names": "Graph Connectors in Microsoft 365 Copilot" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Entra_Identity_Governance", + "Service_Plan_Id": "e866a266-3cff-43a3-acca-0c90a7e00c8b", + "Service_Plans_Included_Friendly_Names": "Entra Identity Governance" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "COPILOT_STUDIO_IN_COPILOT_FOR_M365", + "Service_Plan_Id": "fe6c28b3-d468-44ea-bbd0-a10a5167435c", + "Service_Plans_Included_Friendly_Names": "Copilot Studio in Copilot for M365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "WINDOWSUPDATEFORBUSINESS_DEPLOYMENTSERVICE", + "Service_Plan_Id": "7bf960f6-2cd9-443a-8046-5dbff9558365", + "Service_Plans_Included_Friendly_Names": "Windows Update for Business Deployment Service" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Windows_Autopatch", + "Service_Plan_Id": "9a6eeb79-0b4b-4bf0-9808-39d99a2cd5a3", + "Service_Plans_Included_Friendly_Names": "Windows Autopatch" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "WIN10_PRO_ENT_SUB", + "Service_Plan_Id": "21b439ba-a0ca-424f-a6cc-52f954a5b111", + "Service_Plans_Included_Friendly_Names": "Windows 10/11 Enterprise (Original)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "UNIVERSAL_PRINT_01", + "Service_Plan_Id": "795f6fe0-cc4d-4773-b050-5dde4dc704c9", + "Service_Plans_Included_Friendly_Names": "Universal Print" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MICROSOFTENDPOINTDLP", + "Service_Plan_Id": "64bfac92-2b17-4482-b5e5-a0304429de3e", + "Service_Plans_Included_Friendly_Names": "Microsoft Endpoint DLP" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "WINDEFATP", + "Service_Plan_Id": "871d91ec-ec1a-452b-a83f-bd76c7d770ef", + "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Endpoint" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "YAMMER_ENTERPRISE", + "Service_Plan_Id": "7547a3fe-08ee-4ccb-b430-5077c5041653", + "Service_Plans_Included_Friendly_Names": "YAMMER_ENTERPRISE" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "WHITEBOARD_PLAN3", + "Service_Plan_Id": "4a51bca5-1eff-43f5-878c-177680f191af", + "Service_Plans_Included_Friendly_Names": "Whiteboard (Plan 3)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "VIVA_LEARNING_SEEDED", + "Service_Plan_Id": "b76fb638-6ba6-402a-b9f9-83d28acb3d86", + "Service_Plans_Included_Friendly_Names": "Viva Learning Seeded" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "VIVAENGAGE_CORE", + "Service_Plan_Id": "a82fbf69-b4d7-49f4-83a6-915b2cf354f4", + "Service_Plans_Included_Friendly_Names": "Viva Engage Core" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "BPOS_S_TODO_3", + "Service_Plan_Id": "3fb82609-8c27-4f7b-bd51-30634711ee67", + "Service_Plans_Included_Friendly_Names": "To-Do (Plan 3)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "SWAY", + "Service_Plan_Id": "a23b959c-7ce8-4e57-9140-b90eb88a9e97", + "Service_Plans_Included_Friendly_Names": "Sway" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MCOSTANDARD", + "Service_Plan_Id": "0feaeb32-d00e-4d66-bd5a-43b5b83db82c", + "Service_Plans_Included_Friendly_Names": "Skype for Business Online (Plan 2)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "SHAREPOINTENTERPRISE", + "Service_Plan_Id": "5dbe027f-2339-4123-9542-606e4d348a72", + "Service_Plans_Included_Friendly_Names": "SharePoint (Plan 2)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "PURVIEW_DISCOVERY", + "Service_Plan_Id": "c948ea65-2053-4a5a-8a62-9eaaaf11b522", + "Service_Plans_Included_Friendly_Names": "Purview Discovery" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "PROJECT_O365_P3", + "Service_Plan_Id": "b21a6b06-1988-436e-a07b-51ec6d9f52ad", + "Service_Plans_Included_Friendly_Names": "Project for Office (Plan E5)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "PREMIUM_ENCRYPTION", + "Service_Plan_Id": "617b097b-4b93-4ede-83de-5f075bb5fb2f", + "Service_Plans_Included_Friendly_Names": "Premium Encryption in Office 365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "BI_AZURE_P2", + "Service_Plan_Id": "70d33638-9c74-4d01-bfd3-562de28bd4ba", + "Service_Plans_Included_Friendly_Names": "Power BI Pro" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "POWERAPPS_O365_P3", + "Service_Plan_Id": "9c0dab89-a30c-4117-86e7-97bda240acd2", + "Service_Plans_Included_Friendly_Names": "Power Apps for Office 365 (Plan 3)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "PEOPLE_SKILLS_FOUNDATION", + "Service_Plan_Id": "13b6da2c-0d84-450e-9f69-a33e221387ca", + "Service_Plans_Included_Friendly_Names": "People Skills - Foundation" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "SHAREPOINTWAC", + "Service_Plan_Id": "e95bec33-7c88-4a70-8e19-b10bd9d0c014", + "Service_Plans_Included_Friendly_Names": "Office for the Web" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "SAFEDOCS", + "Service_Plan_Id": "bf6f5520-59e3-4f82-974b-7dbbc4fd27c7", + "Service_Plans_Included_Friendly_Names": "Office 365 SafeDocs" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "PAM_ENTERPRISE", + "Service_Plan_Id": "b1188c4c-1b36-4018-b48b-ee07604f6feb", + "Service_Plans_Included_Friendly_Names": "Office 365 Privileged Access Management" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ADALLOM_S_O365", + "Service_Plan_Id": "8c098270-9dd4-4350-9b30-ba4703f3b36b", + "Service_Plans_Included_Friendly_Names": "Office 365 Cloud App Security" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "EQUIVIO_ANALYTICS", + "Service_Plan_Id": "4de31727-a228-4ec3-a5bf-8e45b5ca48cc", + "Service_Plans_Included_Friendly_Names": "Office 365 Advanced eDiscovery" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Nucleus", + "Service_Plan_Id": "db4d623d-b514-490b-b7ef-8885eee514de", + "Service_Plans_Included_Friendly_Names": "Nucleus" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INTUNE_O365", + "Service_Plan_Id": "882e1d05-acd1-4ccb-8708-6ee03664b117", + "Service_Plans_Included_Friendly_Names": "Mobile Device Management for Office 365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "TEAMS1", + "Service_Plan_Id": "57ff2da0-773e-42df-b2af-ffb7a2317929", + "Service_Plans_Included_Friendly_Names": "Microsoft Teams" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "STREAM_O365_E5", + "Service_Plan_Id": "6c6042f5-6f01-4d67-b8c1-eb99d36eed3e", + "Service_Plans_Included_Friendly_Names": "Microsoft Stream for Office 365 E5" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Deskless", + "Service_Plan_Id": "8c7d2df8-86f0-4902-b2ed-a0458298f3b3", + "Service_Plans_Included_Friendly_Names": "Microsoft StaffHub" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MICROSOFT_SEARCH", + "Service_Plan_Id": "94065c59-bc8e-4e8b-89e5-5138d471eaff", + "Service_Plans_Included_Friendly_Names": "Microsoft Search" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "RECORDS_MANAGEMENT", + "Service_Plan_Id": "65cc641f-cccd-4643-97e0-a17e3045e541", + "Service_Plans_Included_Friendly_Names": "Microsoft Records Management" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "PROJECTWORKMANAGEMENT", + "Service_Plan_Id": "b737dad2-2f6c-4c65-90e3-ca563267e8b9", + "Service_Plans_Included_Friendly_Names": "Microsoft Planner" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "EXCHANGE_ANALYTICS", + "Service_Plan_Id": "34c0d7a0-a70f-4668-9238-47f9fc208882", + "Service_Plans_Included_Friendly_Names": "Microsoft MyAnalytics (Full)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ML_CLASSIFICATION", + "Service_Plan_Id": "d2d51368-76c9-4317-ada2-a12c004c432f", + "Service_Plans_Included_Friendly_Names": "Microsoft ML-Based Classification" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MICROSOFT_LOOP", + "Service_Plan_Id": "c4b8c31a-fb44-4c65-9837-a21f55fcabda", + "Service_Plans_Included_Friendly_Names": "Microsoft Loop" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INSIDER_RISK_MANAGEMENT", + "Service_Plan_Id": "9d0c4ee5-e4a1-4625-ab39-d82b619b1a34", + "Service_Plans_Included_Friendly_Names": "Microsoft Insider Risk Management - Exchange" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INSIDER_RISK", + "Service_Plan_Id": "d587c7a3-bda9-4f99-8776-9bcf59c84f75", + "Service_Plans_Included_Friendly_Names": "Microsoft Insider Risk Management" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INFO_GOVERNANCE", + "Service_Plan_Id": "e26c2fcc-ab91-4a61-b35c-03cdc8dddf66", + "Service_Plans_Included_Friendly_Names": "Microsoft Information Governance" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "FORMS_PLAN_E5", + "Service_Plan_Id": "e212cbc7-0961-4c40-9825-01117710dcb1", + "Service_Plans_Included_Friendly_Names": "Microsoft Forms (Plan E5)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "EXCEL_PREMIUM", + "Service_Plan_Id": "531ee2f8-b1cb-453b-9c21-d2180d014ca5", + "Service_Plans_Included_Friendly_Names": "Microsoft Excel Advanced Analytics" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "THREAT_INTELLIGENCE", + "Service_Plan_Id": "8e0c0a52-6a6c-4d40-8370-dd62790dcd70", + "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Office 365 (Plan 2)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ATP_ENTERPRISE", + "Service_Plan_Id": "f20fedf3-f3c3-43c3-8267-2bfdd51c0939", + "Service_Plans_Included_Friendly_Names": "Microsoft Defender for Office 365 (Plan 1)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "CUSTOMER_KEY", + "Service_Plan_Id": "6db1f1db-2b46-403f-be40-e39395f08dbb", + "Service_Plans_Included_Friendly_Names": "Microsoft Customer Key" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "COMMUNICATIONS_DLP", + "Service_Plan_Id": "6dc145d6-95dd-4191-b9c3-185575ee6f6b", + "Service_Plans_Included_Friendly_Names": "Microsoft Communications DLP" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "CLIPCHAMP", + "Service_Plan_Id": "a1ace008-72f3-4ea0-8dac-33b3a23a2472", + "Service_Plans_Included_Friendly_Names": "Microsoft Clipchamp" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MICROSOFTBOOKINGS", + "Service_Plan_Id": "199a5c09-e0ca-4e37-8f7c-b05d533e1ea2", + "Service_Plans_Included_Friendly_Names": "Microsoft Bookings" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MCOEV", + "Service_Plan_Id": "4828c8ec-dc2e-4779-b502-87ac9ce28ab7", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Phone System" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_LIGHTHOUSE_CUSTOMER_PLAN1", + "Service_Plan_Id": "6f23d6a9-adbf-481c-8538-b4c095654487", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Lighthouse (Plan 1)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MTP", + "Service_Plan_Id": "bf28f719-7844-4079-9c78-c1307898e192", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Defender" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MICROSOFT_COMMUNICATION_COMPLIANCE", + "Service_Plan_Id": "a413a9ff-720c-4822-98ef-2f37c2a21f4c", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Communication Compliance" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_AUDIT_PLATFORM", + "Service_Plan_Id": "f6de4823-28fa-440b-b886-4783fa86ddba", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Audit Platform" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MCOMEETADV", + "Service_Plan_Id": "3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Audio Conferencing" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "OFFICESUBSCRIPTION", + "Service_Plan_Id": "43de0ff5-c92c-492b-9116-175376d08c38", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Apps for enterprise" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "M365_ADVANCED_AUDITING", + "Service_Plan_Id": "2f442157-a11c-46b9-ae5b-6e39ff4e5849", + "Service_Plans_Included_Friendly_Names": "Microsoft 365 Advanced Auditing" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INSIGHTS_BY_MYANALYTICS", + "Service_Plan_Id": "b088306e-925b-44ab-baa0-63291c629a91", + "Service_Plans_Included_Friendly_Names": "Insights by MyAnalytics Backend" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MYANALYTICS_P2", + "Service_Plan_Id": "33c4f319-9bdd-48d6-9c4d-410b750a4a5a", + "Service_Plans_Included_Friendly_Names": "Insights by MyAnalytics" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MIP_S_CLP1", + "Service_Plan_Id": "5136a095-5cf0-4aff-bec3-e84448b38ea5", + "Service_Plans_Included_Friendly_Names": "Information Protection for Office 365 - Standard" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MIP_S_CLP2", + "Service_Plan_Id": "efb0351d-3b08-4503-993d-383af8de41e3", + "Service_Plans_Included_Friendly_Names": "Information Protection for Office 365 - Premium" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "ContentExplorer_Standard", + "Service_Plan_Id": "2b815d45-56e4-4e3a-b65c-66cb9175b560", + "Service_Plans_Included_Friendly_Names": "Information Protection and Governance Analytics – Standard" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "Content_Explorer", + "Service_Plan_Id": "d9fa6af4-e046-4c89-9226-729a0786685d", + "Service_Plans_Included_Friendly_Names": "Information Protection and Governance Analytics - Premium" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "INFORMATION_BARRIERS", + "Service_Plan_Id": "c4801e8a-cb58-4c35-aca6-f2dcc106f287", + "Service_Plans_Included_Friendly_Names": "Information Barriers" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MESH_IMMERSIVE_FOR_TEAMS", + "Service_Plan_Id": "f0ff6ac6-297d-49cd-be34-6dfef97f0c28", + "Service_Plans_Included_Friendly_Names": "Immersive spaces for Teams" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "GRAPH_CONNECTORS_SEARCH_INDEX", + "Service_Plan_Id": "a6520331-d7d4-4276-95f5-15c0933bc757", + "Service_Plans_Included_Friendly_Names": "Graph Connectors Search with Index" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "EXCHANGE_S_ENTERPRISE", + "Service_Plan_Id": "efb87545-963c-4e0d-99df-69c6916d9eb0", + "Service_Plans_Included_Friendly_Names": "EXCHANGE ONLINE (PLAN 2)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "COMMON_DEFENDER_PLATFORM_FOR_OFFICE", + "Service_Plan_Id": "a312bdeb-1e21-40d0-84b1-0e73f128144f", + "Service_Plans_Included_Friendly_Names": "Defender Platform for Office 365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MIP_S_Exchange", + "Service_Plan_Id": "cd31b152-6326-4d1b-ae1b-997b625182e6", + "Service_Plans_Included_Friendly_Names": "Data Classification in Microsoft 365" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "CustomerLockboxA_Enterprise", + "Service_Plan_Id": "3ec18638-bd4c-4d3b-8905-479ed636b83e", + "Service_Plans_Included_Friendly_Names": "Customer Lockbox (A)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "LOCKBOX_ENTERPRISE", + "Service_Plan_Id": "9f431833-0334-42de-a7dc-70aa40db46db", + "Service_Plans_Included_Friendly_Names": "Customer Lockbox" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "CDS_O365_P3", + "Service_Plan_Id": "afa73018-811e-46e9-988f-f75d2b1b8430", + "Service_Plans_Included_Friendly_Names": "Common Data Service for Teams" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "RMS_S_ENTERPRISE", + "Service_Plan_Id": "bea4c11e-220a-4e6d-8eb8-8ea15d019f90", + "Service_Plans_Included_Friendly_Names": "Azure Rights Management" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MESH_AVATARS_ADDITIONAL_FOR_TEAMS", + "Service_Plan_Id": "3efbd4ed-8958-4824-8389-1321f8730af8", + "Service_Plans_Included_Friendly_Names": "Avatars for Teams (additional)" + }, + { + "Product_Display_Name": "Microsoft 365 E7", + "String_Id": "MICROSOFT_365_E7", + "GUID": "9a18296a-025f-4e37-9ffa-30bf8d1ce775", + "Service_Plan_Name": "MESH_AVATARS_FOR_TEAMS", + "Service_Plan_Id": "dcf9d2f4-772e-4434-b757-77a453cfbc02", + "Service_Plans_Included_Friendly_Names": "Avatars for Teams" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "AGENT_365", + "Service_Plan_Id": "d0ce5ebb-9db0-491f-b780-8973a1d815fe", + "Service_Plans_Included_Friendly_Names": "Agent 365" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "AUDIT_FOR_AGENTS", + "Service_Plan_Id": "6d9b0ae5-e6a0-4f04-991a-e5700fd84930", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Audit for Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "COMMUNICATION_COMPLIANCE_FOR_AGENTS", + "Service_Plan_Id": "135fe762-031a-4e79-bd3c-ac376addddec", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Communication Compliance for Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "COMPLIANCE_MANAGER_FOR_AGENTS", + "Service_Plan_Id": "d1f65d05-a302-4861-bd35-da8933ba7655", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Compliance Manager for Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "DATA_LIFECYCLE_MANAGEMENT_FOR_AGENTS", + "Service_Plan_Id": "30d56d35-9be2-41c1-b4b8-7a8d6f073152", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Data Lifecycle Management for Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "DATA_LOSS_PREVENTION_FOR_AGENTS", + "Service_Plan_Id": "46d3c309-0ba7-461f-9d0e-eaca165794c2", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Data Loss Prevention for Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "DEFENDER_FOR_AI", + "Service_Plan_Id": "a1c15058-5559-4c1a-ba05-8040847f91bb", + "Service_Plans_Included_Friendly_Names": "Microsoft Defender for AI" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "EDISCOVERY_FOR_AGENTS", + "Service_Plan_Id": "92cedcb2-3fb2-40b4-9df4-f9a5603d9631", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview eDiscovery for Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "ENTRA_ID_GOV_FOR_ASSISTIVE_AGENTS", + "Service_Plan_Id": "a9e85e05-1687-4958-8a4c-bdacda2943db", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra ID Governance for Assistive Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "ENTRA_NETWORK_CONTROLS_FOR_ASSISTIVE_AGENTS", + "Service_Plan_Id": "27e196a4-8b80-4930-bd65-53fd28581878", + "Service_Plans_Included_Friendly_Names": "Microsoft Entra Network Controls for Assistive Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "INFORMATION_PROTECTION_FOR_AGENTS", + "Service_Plan_Id": "48478b49-91a1-4ded-94f0-066db80035ca", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Information Protection for Agents" + }, + { + "Product_Display_Name": "Agent 365", + "String_Id": "AGENT_365", + "GUID": "796a6fb4-740b-4d36-bf56-9c12ca7fa069", + "Service_Plan_Name": "INSIDER_RISK_MANAGEMENT_FOR_AGENTS", + "Service_Plan_Id": "004ddfc0-c92f-4b0a-90c5-c60646299d71", + "Service_Plans_Included_Friendly_Names": "Microsoft Purview Insider Risk Management for Agents" } ] diff --git a/src/data/standards.json b/src/data/standards.json index 3d27a41de832..6552e8000429 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -378,8 +378,8 @@ "cat": "Global Standards", "tag": ["CIS M365 7.0.0 (1.3.6)", "CustomerLockBoxEnabled"], "appliesToTest": ["CIS_1_3_6"], - "helpText": "**Requires Entra ID P2.** Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data", - "docsDescription": "**Requires Entra ID P2.** Customer Lockbox ensures that Microsoft can't access your content to do service operations without your explicit approval. Customer Lockbox ensures only authorized requests allow access to your organizations data.", + "helpText": "**Requires CustomerLockbox (E5, E7, A5, Purview Addon for BP, EDU or FL)** Enables Customer Lockbox that offers an approval process for Microsoft support to access organization data", + "docsDescription": "**Requires CustomerLockbox (E5, E7, A5, Purview Addon for BP, EDU or FL)** Customer Lockbox ensures that Microsoft can't access your content to do service operations without your explicit approval. Customer Lockbox ensures only authorized requests allow access to your organizations data.", "executiveText": "Requires explicit organizational approval before Microsoft support staff can access company data for service operations. This provides an additional layer of data protection and ensures the organization maintains control over who can access sensitive business information, even during technical support scenarios.", "addedComponent": [], "label": "Enable Customer Lockbox", @@ -4157,12 +4157,10 @@ "defaultValue": false }, { - "type": "autoComplete", - "multiple": true, - "creatable": true, + "type": "LanguageCodeMultiSelect", "required": false, "name": "standards.SpamFilterPolicy.LanguageBlockList", - "label": "Languages to block (uppercase ISO 639-1 two-letter)", + "label": "Languages to block (ISO 639-1 two-letter)", "condition": { "field": "standards.SpamFilterPolicy.EnableLanguageBlockList", "compareType": "is", @@ -4176,12 +4174,10 @@ "defaultValue": false }, { - "type": "autoComplete", - "multiple": true, - "creatable": true, + "type": "CountryCodeMultiSelect", "required": false, "name": "standards.SpamFilterPolicy.RegionBlockList", - "label": "Regions to block (uppercase ISO 3166-1 two-letter)", + "label": "Regions to block (ISO 3166-1 two-letter)", "condition": { "field": "standards.SpamFilterPolicy.EnableRegionBlockList", "compareType": "is", @@ -5865,7 +5861,7 @@ { "type": "switch", "name": "standards.TeamsFederationConfiguration.AllowTeamsConsumer", - "label": "Allow users to communicate with other organizations" + "label": "Allow users to communicate with consumer Teams accounts" }, { "type": "autoComplete", @@ -7255,6 +7251,12 @@ "label": "Block Android if partner data unavailable", "defaultValue": false }, + { + "type": "switch", + "name": "standards.DefenderCompliancePolicy.grantMobileThreatDefensePartnerRole", + "label": "Grant MTD role to MDE on enrolled Android COBO/COPE devices", + "defaultValue": false + }, { "type": "switch", "name": "standards.DefenderCompliancePolicy.ConnectIos", @@ -7264,13 +7266,19 @@ { "type": "switch", "name": "standards.DefenderCompliancePolicy.ConnectIosCompliance", - "label": "Connect iOS 13.0+ (App-based MAM)", + "label": "Connect iOS/iPadOS devices for app protection policy evaluation (MAM)", "defaultValue": false }, { "type": "switch", "name": "standards.DefenderCompliancePolicy.appSync", - "label": "Enable App Sync for iOS", + "label": "Enable App Sync (sending application inventory) for iOS/iPadOS devices", + "defaultValue": false + }, + { + "type": "switch", + "name": "standards.DefenderCompliancePolicy.allowPartnerToCollectIosPersonalApplicationMetadata", + "label": "Send full application inventory data on personally-owned iOS/iPadOS devices", "defaultValue": false }, { @@ -7282,13 +7290,13 @@ { "type": "switch", "name": "standards.DefenderCompliancePolicy.allowPartnerToCollectIosCertificateMetadata", - "label": "Collect certificate metadata from iOS", + "label": "Enable Certificate Sync for iOS/iPadOS devices", "defaultValue": false }, { "type": "switch", "name": "standards.DefenderCompliancePolicy.allowPartnerToCollectIosPersonalCertificateMetadata", - "label": "Collect personal certificate metadata from iOS", + "label": "Send full certificate inventory data on personally-owned iOS/iPadOS devices", "defaultValue": false }, { diff --git a/src/layouts/index.js b/src/layouts/index.js index 0ee521c51313..2327f58afdd9 100644 --- a/src/layouts/index.js +++ b/src/layouts/index.js @@ -263,7 +263,7 @@ export const Layout = (props) => { }) const alertsAPI = ApiGetCall({ - url: `/api/GetCippAlerts?localversion=${version?.data?.version}`, + url: `/api/GetCippAlerts?localversion=${encodeURIComponent(version?.data?.version)}`, queryKey: 'alertsDashboard', waiting: false, refetchOnMount: false, diff --git a/src/pages/cipp/advanced/super-admin/sam-app-permissions.js b/src/pages/cipp/advanced/super-admin/sam-app-permissions.js index 3930c51811ff..19e26b4143cb 100644 --- a/src/pages/cipp/advanced/super-admin/sam-app-permissions.js +++ b/src/pages/cipp/advanced/super-admin/sam-app-permissions.js @@ -2,15 +2,21 @@ import { TabbedLayout } from "../../../../layouts/TabbedLayout"; import { Layout as DashboardLayout } from "../../../../layouts/index.js"; import tabOptions from "./tabOptions"; import { useForm } from "react-hook-form"; +import { useState } from "react"; import { ApiGetCall, ApiPostCall } from "../../../../api/ApiCall"; import CippAppPermissionBuilder from "../../../../components/CippComponents/CippAppPermissionBuilder"; import CippPageCard from "../../../../components/CippCards/CippPageCard"; -import { Alert, CardContent, Skeleton, Stack, Typography } from "@mui/material"; -import { WarningAmberOutlined } from "@mui/icons-material"; +import { CippApiResults } from "../../../../components/CippComponents/CippApiResults"; +import { ConfirmationDialog } from "../../../../components/confirmation-dialog"; +import { Alert, Button, CardContent, Skeleton, Stack, SvgIcon, Typography } from "@mui/material"; +import { SettingsBackupRestore, WarningAmberOutlined } from "@mui/icons-material"; const Page = () => { const pageTitle = "SAM App Permissions"; + const [resetKey, setResetKey] = useState(0); + const [resetDialogOpen, setResetDialogOpen] = useState(false); + const formControl = useForm({ mode: "onBlur", }); @@ -25,6 +31,15 @@ const Page = () => { urlFromData: true, }); + const resetPermissions = ApiPostCall({ + relatedQueryKeys: ["execSamAppPermissions"], + onResult: () => { + // Remount the builder so it re-seeds its internal state from the freshly defaulted data + setResetKey((prev) => prev + 1); + execSamAppPermissions.refetch(); + }, + }); + const handleUpdatePermissions = (data) => { updatePermissions.mutate({ url: "/api/ExecSAMAppPermissions?Action=Update", @@ -33,18 +48,46 @@ const Page = () => { }); }; + const handleResetToCippDefaults = () => { + resetPermissions.mutate({ + url: "/api/ExecSAMAppPermissions?Action=Reset", + data: {}, + }); + }; + return ( - + - This is an advanced setting that allows you to modify the permissions used for CPV consent on customer tenants. + + This is an advanced setting that allows you to modify the permissions used for CPV + consent on customer tenants. + }> - This functionality is in beta and should be treated as such. Removing permissions from - the CIPP-SAM app is not advised. + The default CIPP-SAM permissions are always applied and cannot be removed - you + can only layer additional permissions on top of them. Use "Reset to CIPP Defaults" + to remove all additional permissions and return to the built-in defaults. + + + + + } + onClick={() => setResetDialogOpen(true)} + disabled={resetPermissions.isPending} + > + Reset to CIPP Defaults + + + {execSamAppPermissions.isLoading && } {execSamAppPermissions.isSuccess && ( { )} + setResetDialogOpen(false)} + title="Reset to CIPP Defaults" + variant="warning" + message="This removes all additional permissions you have layered on top of the CIPP-SAM defaults and returns the saved permission set to the built-in CIPP manifest defaults. The default permissions themselves are unaffected. You will need to complete a Permissions repair from the Permissions page, then complete a CPV refresh to finalise the chnages. Continue?" + onConfirm={() => { + handleResetToCippDefaults(); + setResetDialogOpen(false); + }} + /> ); }; diff --git a/src/pages/email/administration/contacts-template/add.jsx b/src/pages/email/administration/contacts-template/add.jsx index b05da569e29e..f43bf99d7721 100644 --- a/src/pages/email/administration/contacts-template/add.jsx +++ b/src/pages/email/administration/contacts-template/add.jsx @@ -37,27 +37,27 @@ const AddContactTemplates = () => { resetForm={true} customDataformatter={(values) => { return { - DisplayName: values.displayName, + displayName: values.displayName, hidefromGAL: values.hidefromGAL, email: values.email, - FirstName: values.firstName, - LastName: values.lastName, - Title: values.jobTitle, - StreetAddress: values.streetAddress, - PostalCode: values.postalCode, - City: values.city, - State: values.state, - CountryOrRegion: values.country?.value || values.country, - Company: values.companyName, + firstName: values.firstName, + lastName: values.lastName, + jobTitle: values.jobTitle, + streetAddress: values.streetAddress, + postalCode: values.postalCode, + city: values.city, + state: values.state, + country: values.country?.value || values.country, + companyName: values.companyName, mobilePhone: values.mobilePhone, - phone: values.businessPhone, + businessPhone: values.businessPhone, website: values.website, mailTip: values.mailTip, }; }} > - ); diff --git a/src/pages/email/administration/contacts-template/edit.jsx b/src/pages/email/administration/contacts-template/edit.jsx index 987e9f45a3bb..33f40120a1c2 100644 --- a/src/pages/email/administration/contacts-template/edit.jsx +++ b/src/pages/email/administration/contacts-template/edit.jsx @@ -8,8 +8,6 @@ import { ApiGetCall } from "../../../../api/ApiCall"; import countryList from "../../../../data/countryList.json"; import { useRouter } from "next/router"; -const countryLookup = new Map(countryList.map((country) => [country.Name, country.Code])); - const EditContactTemplate = () => { const router = useRouter(); const { id } = router.query; @@ -57,32 +55,33 @@ const EditContactTemplate = () => { const contact = Array.isArray(contactTemplateInfo.data) ? contactTemplateInfo.data[0] : contactTemplateInfo.data; - const address = contact.addresses?.[0] || {}; - const phones = contact.phones || []; - // Use Map for O(1) phone lookup - const phoneMap = new Map(phones.map((p) => [p.type, p.number])); + // The template is stored as a flat object (see Invoke-AddContactTemplates), so read the + // fields directly rather than treating it as a Microsoft Graph contact. + const countryEntry = contact.country + ? countryList.find((c) => c.Code === contact.country || c.Name === contact.country) + : null; return { ContactTemplateID: id || "", displayName: contact.displayName || "", - firstName: contact.givenName || "", - lastName: contact.surname || "", + firstName: contact.firstName || "", + lastName: contact.lastName || "", email: contact.email || "", hidefromGAL: contact.hidefromGAL || false, - streetAddress: address.street || "", - postalCode: address.postalCode || "", - city: address.city || "", - state: address.state || "", - country: address.countryOrRegion ? countryLookup.get(address.countryOrRegion) || "" : "", + streetAddress: contact.streetAddress || "", + postalCode: contact.postalCode || "", + city: contact.city || "", + state: contact.state || "", + country: countryEntry ? { label: countryEntry.Name, value: countryEntry.Code } : "", companyName: contact.companyName || "", - mobilePhone: phoneMap.get("mobile") || "", - businessPhone: phoneMap.get("business") || "", + mobilePhone: contact.mobilePhone || "", + businessPhone: contact.businessPhone || "", jobTitle: contact.jobTitle || "", website: contact.website || "", mailTip: contact.mailTip || "", }; - }, [contactTemplateInfo.isSuccess, contactTemplateInfo.data]); + }, [contactTemplateInfo.isSuccess, contactTemplateInfo.data, id]); // Use callback to prevent unnecessary re-renders const resetForm = useCallback(() => { @@ -96,27 +95,30 @@ const EditContactTemplate = () => { }, [resetForm]); // Memoize custom data formatter - const customDataFormatter = useCallback((values) => { - return { - ContactTemplateID: id, - DisplayName: values.displayName, - hidefromGAL: values.hidefromGAL, - email: values.email, - FirstName: values.firstName, - LastName: values.lastName, - Title: values.jobTitle, - StreetAddress: values.streetAddress, - PostalCode: values.postalCode, - City: values.city, - State: values.state, - CountryOrRegion: values.country?.value || values.country, - Company: values.companyName, - mobilePhone: values.mobilePhone, - phone: values.businessPhone, - website: values.website, - mailTip: values.mailTip, - }; - }); + const customDataFormatter = useCallback( + (values) => { + return { + ContactTemplateID: id, + displayName: values.displayName, + hidefromGAL: values.hidefromGAL, + email: values.email, + firstName: values.firstName, + lastName: values.lastName, + jobTitle: values.jobTitle, + streetAddress: values.streetAddress, + postalCode: values.postalCode, + city: values.city, + state: values.state, + country: values.country?.value || values.country, + companyName: values.companyName, + mobilePhone: values.mobilePhone, + businessPhone: values.businessPhone, + website: values.website, + mailTip: values.mailTip, + }; + }, + [id] + ); const contactTemplate = Array.isArray(contactTemplateInfo.data) ? contactTemplateInfo.data[0] diff --git a/src/pages/email/administration/contacts-template/index.jsx b/src/pages/email/administration/contacts-template/index.jsx index d24604c9a9af..8d5fc6d69239 100644 --- a/src/pages/email/administration/contacts-template/index.jsx +++ b/src/pages/email/administration/contacts-template/index.jsx @@ -81,7 +81,14 @@ const Page = () => { target: "_self", }, ]; - const simpleColumns = ["name", "contactTemplateName", "GUID"]; + const simpleColumns = [ + "displayName", + "email", + "companyName", + "jobTitle", + "hidefromGAL", + "GUID", + ]; return ( { simpleColumns={simpleColumns} cardButton={ <> - + { isFetching={groupInfo.isFetching} disabled={groupInfo.isFetching} api={{ - url: "/api/ListUsers", + url: "/api/ListGraphRequest", + dataKey: "Results", + data: { + Endpoint: "users", + manualPagination: true, + $select: "id,userPrincipalName,displayName,mail", + $count: true, + $orderby: "displayName", + $top: 999, + }, labelField: (user) => `${user.displayName || "Unknown"} (${ user.userPrincipalName || user.mail || "No email" diff --git a/src/pages/endpoint/autopilot/list-devices/index.js b/src/pages/endpoint/autopilot/list-devices/index.js index c6694e3d1920..af8d7765b4c8 100644 --- a/src/pages/endpoint/autopilot/list-devices/index.js +++ b/src/pages/endpoint/autopilot/list-devices/index.js @@ -29,7 +29,16 @@ const Page = () => { multiple: false, creatable: false, api: { - url: '/api/listUsers', + url: '/api/ListGraphRequest', + dataKey: 'Results', + data: { + Endpoint: 'users', + manualPagination: true, + $select: 'id,userPrincipalName,displayName', + $count: true, + $orderby: 'displayName', + $top: 999, + }, labelField: (user) => `${user.displayName} (${user.userPrincipalName})`, valueField: 'userPrincipalName', addedField: { diff --git a/src/pages/identity/administration/users/index.js b/src/pages/identity/administration/users/index.js index a8942377f3ea..be8276449daf 100644 --- a/src/pages/identity/administration/users/index.js +++ b/src/pages/identity/administration/users/index.js @@ -1,67 +1,67 @@ -import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx"; -import { Layout as DashboardLayout } from "../../../../layouts/index.js"; -import { useSettings } from "../../../../hooks/use-settings.js"; -import { PermissionButton } from "../../../../utils/permissions"; -import { useCippUserActions } from "../../../../components/CippComponents/CippUserActions.jsx"; -import { CippInviteGuestDrawer } from "../../../../components/CippComponents/CippInviteGuestDrawer.jsx"; -import { CippBulkInviteGuestDrawer } from "../../../../components/CippComponents/CippBulkInviteGuestDrawer.jsx"; -import { CippBulkUserDrawer } from "../../../../components/CippComponents/CippBulkUserDrawer.jsx"; -import { CippAddUserDrawer } from "../../../../components/CippComponents/CippAddUserDrawer.jsx"; -import { CippApiLogsDrawer } from "../../../../components/CippComponents/CippApiLogsDrawer.jsx"; -import { Box } from "@mui/material"; +import { CippTablePage } from '../../../../components/CippComponents/CippTablePage.jsx' +import { Layout as DashboardLayout } from '../../../../layouts/index.js' +import { useSettings } from '../../../../hooks/use-settings.js' +import { PermissionButton } from '../../../../utils/permissions' +import { useCippUserActions } from '../../../../components/CippComponents/CippUserActions.jsx' +import { CippInviteGuestDrawer } from '../../../../components/CippComponents/CippInviteGuestDrawer.jsx' +import { CippBulkInviteGuestDrawer } from '../../../../components/CippComponents/CippBulkInviteGuestDrawer.jsx' +import { CippBulkUserDrawer } from '../../../../components/CippComponents/CippBulkUserDrawer.jsx' +import { CippAddUserDrawer } from '../../../../components/CippComponents/CippAddUserDrawer.jsx' +import { CippApiLogsDrawer } from '../../../../components/CippComponents/CippApiLogsDrawer.jsx' +import { Box } from '@mui/material' const Page = () => { - const userActions = useCippUserActions(); - const pageTitle = "Users"; - const tenant = useSettings().currentTenant; - const cardButtonPermissions = ["Identity.User.ReadWrite"]; + const userActions = useCippUserActions() + const pageTitle = 'Users' + const tenant = useSettings().currentTenant + const cardButtonPermissions = ['Identity.User.ReadWrite'] const filters = [ { - filterName: "Account Enabled", - value: [{ id: "accountEnabled", value: "Yes" }], - type: "column", + filterName: 'Account Enabled', + value: [{ id: 'accountEnabled', value: 'Yes' }], + type: 'column', }, { - filterName: "Account Disabled", - value: [{ id: "accountEnabled", value: "No" }], - type: "column", + filterName: 'Account Disabled', + value: [{ id: 'accountEnabled', value: 'No' }], + type: 'column', }, { - filterName: "Guest Accounts", - value: [{ id: "userType", value: "Guest" }], - type: "column", + filterName: 'Guest Accounts', + value: [{ id: 'userType', value: 'Guest' }], + type: 'column', }, - ]; + ] const offCanvas = { extendedInfoFields: [ - "createdDateTime", // Created Date (UTC) - "id", // Unique ID - "userPrincipalName", // UPN - "givenName", // Given Name - "surname", // Surname - "jobTitle", // Job Title - "assignedLicenses", // Licenses - "businessPhones", // Business Phone - "mobilePhone", // Mobile Phone - "mail", // Mail - "city", // City - "department", // Department - "onPremisesLastSyncDateTime", // OnPrem Last Sync - "onPremisesDistinguishedName", // OnPrem DN - "otherMails", // Alternate Email Addresses - "licenseAssignmentStates", // License Assignment States + 'createdDateTime', // Created Date (UTC) + 'id', // Unique ID + 'userPrincipalName', // UPN + 'givenName', // Given Name + 'surname', // Surname + 'jobTitle', // Job Title + 'assignedLicenses', // Licenses + 'businessPhones', // Business Phone + 'mobilePhone', // Mobile Phone + 'mail', // Mail + 'city', // City + 'department', // Department + 'onPremisesLastSyncDateTime', // OnPrem Last Sync + 'onPremisesDistinguishedName', // OnPrem DN + 'otherMails', // Alternate Email Addresses + 'licenseAssignmentStates', // License Assignment States ], actions: userActions, - }; + } return ( + { } apiData={{ - Endpoint: "users", + Endpoint: 'users', manualPagination: true, $select: - "id,accountEnabled,businessPhones,city,createdDateTime,companyName,country,department,displayName,faxNumber,givenName,isResourceAccount,jobTitle,mail,mailNickname,mobilePhone,officeLocation,otherMails,postalCode,preferredDataLocation,preferredLanguage,proxyAddresses,showInAddressList,state,streetAddress,surname,usageLocation,userPrincipalName,userType,assignedLicenses,licenseAssignmentStates,onPremisesSyncEnabled,OnPremisesImmutableId,onPremisesLastSyncDateTime,onPremisesDistinguishedName", + 'id,accountEnabled,businessPhones,city,createdDateTime,companyName,country,department,displayName,faxNumber,givenName,isResourceAccount,jobTitle,mail,mailNickname,mobilePhone,officeLocation,otherMails,postalCode,preferredDataLocation,preferredLanguage,proxyAddresses,showInAddressList,state,streetAddress,surname,usageLocation,userPrincipalName,userType,assignedLicenses,licenseAssignmentStates,onPremisesSyncEnabled,OnPremisesImmutableId,onPremisesLastSyncDateTime,onPremisesDistinguishedName', $count: true, - $orderby: "displayName", + $orderby: 'displayName', $top: 999, }} apiDataKey="Results" actions={userActions} offCanvas={offCanvas} simpleColumns={[ - "accountEnabled", - "userPrincipalName", - "displayName", - "mail", - "businessPhones", - "proxyAddresses", - "assignedLicenses", - "licenseAssignmentStates", + 'accountEnabled', + 'userPrincipalName', + 'displayName', + 'mail', + 'businessPhones', + 'proxyAddresses', + 'assignedLicenses', + 'licenseAssignmentStates', + 'userType', ]} filters={filters} /> - ); -}; + ) +} -Page.getLayout = (page) => {page}; +Page.getLayout = (page) => {page} -export default Page; +export default Page diff --git a/src/pages/security/compliance/labels/index.js b/src/pages/security/compliance/labels/index.js index 998865acdc87..9b485b274af9 100644 --- a/src/pages/security/compliance/labels/index.js +++ b/src/pages/security/compliance/labels/index.js @@ -22,6 +22,7 @@ const Page = () => { return { ...data } }, confirmText: 'Are you sure you want to create a template based on this sensitivity label?', + hideBulk: true, }, { label: 'Delete Label', diff --git a/src/pages/security/compliance/retention/index.js b/src/pages/security/compliance/retention/index.js index 940d88b4eb0a..3efd34798877 100644 --- a/src/pages/security/compliance/retention/index.js +++ b/src/pages/security/compliance/retention/index.js @@ -20,6 +20,7 @@ const Page = () => { url: '/api/AddRetentionCompliancePolicyTemplate', data: { Identity: 'Name' }, confirmText: 'Are you sure you want to create a template based on this retention policy?', + hideBulk: true, }, { label: 'Enable Policy', diff --git a/src/pages/security/defender/deployment/index.js b/src/pages/security/defender/deployment/index.js index b1012ad0f3a7..5567ba24d576 100644 --- a/src/pages/security/defender/deployment/index.js +++ b/src/pages/security/defender/deployment/index.js @@ -138,6 +138,12 @@ const DeployDefenderForm = () => { formControl={formControl} /> + @@ -249,7 +255,7 @@ const DeployDefenderForm = () => { > @@ -259,6 +265,20 @@ const DeployDefenderForm = () => { name="Compliance.AppSync" formControl={formControl} /> + + + { /> - + field="Compliance.allowPartnerToCollectIosCertificateMetadata" + compareType="is" + compareValue={true} + action="disable" + > + + diff --git a/src/pages/security/incidents/list-check-alerts/index.js b/src/pages/security/incidents/list-check-alerts/index.js index db56faf0454e..2586b6ad7167 100644 --- a/src/pages/security/incidents/list-check-alerts/index.js +++ b/src/pages/security/incidents/list-check-alerts/index.js @@ -14,7 +14,7 @@ const Page = () => { docs.check.tech {" "} - or install the plugin now: + or install the plugin now: { > Microsoft Edge {" "} - | + | { multiple: false, creatable: false, api: { - url: '/api/listUsers', + url: '/api/ListGraphRequest', + dataKey: 'Results', + data: { + Endpoint: 'users', + manualPagination: true, + $select: 'id,userPrincipalName,displayName', + $count: true, + $orderby: 'displayName', + $top: 999, + }, labelField: (onedriveAccessUser) => `${onedriveAccessUser.displayName} (${onedriveAccessUser.userPrincipalName})`, valueField: 'userPrincipalName', diff --git a/src/pages/teams-share/teams/business-voice/index.js b/src/pages/teams-share/teams/business-voice/index.js index 3c9354706147..89f2d298ac76 100644 --- a/src/pages/teams-share/teams/business-voice/index.js +++ b/src/pages/teams-share/teams/business-voice/index.js @@ -35,7 +35,16 @@ const Page = () => { multiple: false, creatable: false, api: { - url: "/api/listUsers", + url: "/api/ListGraphRequest", + dataKey: "Results", + data: { + Endpoint: "users", + manualPagination: true, + $select: "id,userPrincipalName,displayName", + $count: true, + $orderby: "displayName", + $top: 999, + }, labelField: (input) => `${input.displayName} (${input.userPrincipalName})`, valueField: "userPrincipalName", }, diff --git a/src/pages/tools/custom-tests/add.jsx b/src/pages/tools/custom-tests/add.jsx index 928795ee3fca..9762f797cb01 100644 --- a/src/pages/tools/custom-tests/add.jsx +++ b/src/pages/tools/custom-tests/add.jsx @@ -118,7 +118,7 @@ const Page = () => { ScriptContent: '', Enabled: false, AlertOnFailure: false, - AlertStatuses: [{ value: 'Failed', label: 'Failed' }], + AlertStatuses: { value: 'Failed', label: 'Failed' }, ReturnType: 'JSON', ResultMode: { value: 'Auto', label: 'Auto' }, MarkdownTemplate: '', @@ -148,12 +148,7 @@ const Page = () => { ScriptContent: script.ScriptContent || '', Enabled: script.Enabled || false, AlertOnFailure: script.AlertOnFailure || false, - AlertStatuses: script.AlertStatuses - ? (typeof script.AlertStatuses === 'string' - ? JSON.parse(script.AlertStatuses) - : script.AlertStatuses - ).map((s) => ({ value: s, label: s })) - : [{ value: 'Failed', label: 'Failed' }], + AlertStatuses: toSelectOption(script.AlertStatuses, 'Failed'), ReturnType: script.ReturnType || 'JSON', ResultMode: toSelectOption(script.ResultMode, 'Auto'), MarkdownTemplate: script.MarkdownTemplate || '', @@ -261,9 +256,7 @@ const Page = () => { ScriptContent: data.ScriptContent, Enabled: data.Enabled, AlertOnFailure: data.AlertOnFailure, - AlertStatuses: data.AlertOnFailure - ? (data.AlertStatuses?.map(s => s.value) || ['Failed']) - : [], + AlertStatuses: data.AlertStatuses?.value ?? data.AlertStatuses, ReturnType: data.ReturnType, ResultMode: data.ResultMode?.value ?? data.ResultMode, MarkdownTemplate: data.MarkdownTemplate, @@ -325,6 +318,14 @@ const Page = () => { { value: 'AlwaysInvestigate', label: 'Always Investigate' }, ] + const AlertStatuses = [ + { value: 'Failed', label: 'Failed' }, + { value: 'Passed', label: 'Passed' }, + { value: 'Info', label: 'Info' }, + { value: 'Investigate', label: 'Investigate' }, + { value: 'All', label: 'All' }, + ] + const scriptNameField = { name: 'ScriptName', label: 'Script Name', @@ -415,14 +416,8 @@ const Page = () => { const alertStatusesField = { name: 'AlertStatuses', label: 'Alert on Status', - type: 'autoComplete', - multiple: true, - options: [ - { label: 'Failed', value: 'Failed' }, - { label: 'Passed', value: 'Passed' }, - { label: 'Info', value: 'Info' }, - { label: 'Investigate', value: 'Investigate' }, - ], + type: 'select', + options: AlertStatuses, helperText: 'Choose which test result statuses trigger an alert.', } @@ -1297,6 +1292,7 @@ $md = $summaryTable + "\n\n---\n\n" + $policyTable formControl={formControl} compareType="is" compareValue={true} + clearOnHide={false} > { @@ -217,9 +218,9 @@ export const getCippFormatting = (data, cellName, type, canReceive, flatten = tr const matchDateTime = /([dD]ate[tT]ime|[Ee]xpiration|[Tt]imestamp|[sS]tart[Dd]ate)/ if (timeAgoArray.includes(cellName) || matchDateTime.test(cellName)) { return isText && canReceive === false ? ( - new Date(data).toLocaleString() // This runs if canReceive is false and isText is true + parseCippDate(data).toLocaleString() // This runs if canReceive is false and isText is true ) : isText && canReceive !== 'both' ? ( - new Date(data) // This runs if isText is true and canReceive is not "both" or false + parseCippDate(data) // This runs if isText is true and canReceive is not "both" or false ) : ( ) diff --git a/src/utils/parse-cipp-date.js b/src/utils/parse-cipp-date.js new file mode 100644 index 000000000000..f9ed14f41d4e --- /dev/null +++ b/src/utils/parse-cipp-date.js @@ -0,0 +1,16 @@ +// Parse a date value coming from the CIPP API into a JS Date. +// +// Some backend tables (e.g. ScheduledTasks / Extension Sync) store timestamps as a +// Unix epoch in *seconds*, often cast to a string (e.g. "1719225600"). Passing that +// straight to `new Date()` yields an Invalid Date, which silently breaks table +// sorting and date-range filtering. Numeric / all-digit values are therefore treated +// as epoch seconds and multiplied by 1000; everything else (ISO 8601 strings, etc.) +// is passed through to the native Date parser. +const allDigits = /^\d+$/ + +export const parseCippDate = (data) => { + if (typeof data === 'number' || (typeof data === 'string' && allDigits.test(data))) { + return new Date(Number(data) * 1000) + } + return new Date(data) +}