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 = ({ - + + + {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={ <> - +