Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
40d3ee8
Fix formatting in alerts list component
michaelmsonne Jun 22, 2026
f01c13a
fix: disallow creatable for CA deploy drawer
kris6673 Jun 23, 2026
e36ec40
dynamic group nesting
Zacgoose Jun 24, 2026
5d77e82
Fixes phishing resistant MFA card
Zacgoose Jun 24, 2026
1a5ead7
Update M365Licenses.json
Zacgoose Jun 24, 2026
77a9289
Timestamp parsing fixes
Zacgoose Jun 24, 2026
ab94c4c
Update index.js
Zacgoose Jun 24, 2026
4b552b7
feat: add mega gigantic huuuuge change
kris6673 Jun 24, 2026
6cd2795
Update add.jsx
Zacgoose Jun 24, 2026
a2d216a
Update CippNotificationForm.jsx
Zacgoose Jun 24, 2026
3a6d96e
Update standards.json
Zacgoose Jun 25, 2026
a494cd7
feat(defender): add MTD role toggle, fix iOS sync
kris6673 Jun 25, 2026
3bb7e86
Merge pull request #6231 from kris6673/defender-deployment
Zacgoose Jun 25, 2026
ae7341d
Update standards.json
Zacgoose Jun 25, 2026
56756d4
ISO 639-1 and ISO 3166-1 autocomplete options
Zacgoose Jun 25, 2026
28178b0
Permission repair improvements
Zacgoose Jun 26, 2026
2b7aada
Update CippIntegrationFieldMapping.jsx
Zacgoose Jun 26, 2026
7011d92
Correct importing CA policy templates from live tenants
Zacgoose Jun 26, 2026
f17fb64
Correct contact template creation and editing
Zacgoose Jun 26, 2026
ce24422
fix: check for string before localeCompare
JohnDuprey Jun 26, 2026
0c58d53
Merge pull request #6219 from kris6673/disallow-creatable
JohnDuprey Jun 26, 2026
88d9032
Merge pull request #6224 from kris6673/mega-PR
JohnDuprey Jun 26, 2026
ae25e5f
Merge pull request #6213 from michaelmsonne/patch-2
Zacgoose Jun 26, 2026
289f726
hideBulk for compliance actions as they are not available at this stage
Zacgoose Jun 26, 2026
60949a4
fix: replace remaining ListUsers queries with ListGraphRequest
JohnDuprey Jun 26, 2026
bd63730
chore: update version to 10.5.5
JohnDuprey Jun 26, 2026
c38bdc6
Merge pull request #6243 from KelvinTegelaar/dev
JohnDuprey Jun 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cipp",
"version": "10.5.4",
"version": "10.5.5",
"author": "CIPP Contributors",
"homepage": "https://cipp.app/",
"bugs": {
Expand Down Expand Up @@ -116,4 +116,4 @@
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.1"
}
}
}
4 changes: 2 additions & 2 deletions public/version.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"version": "10.5.4"
}
"version": "10.5.5"
}
31 changes: 26 additions & 5 deletions src/components/CippComponents/AuthMethodCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,20 +63,26 @@ 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")) {
whfbCount++;
}
} 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++;
}
Expand Down
33 changes: 26 additions & 7 deletions src/components/CippComponents/AuthMethodSankey.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,21 +68,26 @@ 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")) {
whfbCount++;
}
} 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++;
}
Expand Down
149 changes: 74 additions & 75 deletions src/components/CippComponents/CippAppPermissionBuilder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})),
Expand All @@ -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",
Expand Down Expand Up @@ -625,6 +627,7 @@ const CippAppPermissionBuilder = ({
label: "Delete Permission",
icon: <Delete />,
noConfirm: true,
condition: (row) => !row.required,
customFunction: (row) => handleRemoveRow("applicationPermissions", row),
},
]}
Expand Down Expand Up @@ -690,6 +693,7 @@ const CippAppPermissionBuilder = ({
label: "Delete Permission",
icon: <Delete />,
noConfirm: true,
condition: (row) => !row.required,
customFunction: (row) => handleRemoveRow("delegatedPermissions", row),
},
]}
Expand Down Expand Up @@ -788,7 +792,7 @@ const CippAppPermissionBuilder = ({
</div>
</Tooltip>

<Tooltip title="Reset to Default">
<Tooltip title="Reset to Current Defaults (discard unsaved changes)">
<Button
onClick={() => {
confirmReset();
Expand Down Expand Up @@ -929,79 +933,74 @@ const CippAppPermissionBuilder = ({
</Grid>
)}

{newPermissions?.MissingPermissions &&
newPermissions?.Type === "Table" &&
Object.keys(newPermissions?.MissingPermissions).length > 0 && (
<Grid container sx={{ width: "100%", mt: 3 }}>
<Grid size={{ xl: 8, xs: 12 }}>
<Alert
color="warning"
icon={<WarningAmberOutlined />}
action={
<Tooltip title="Add Missing Permissions">
<IconButton
onClick={() => {
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);
}}
>
<SvgIcon fontSize="small">
<WrenchIcon />
</SvgIcon>
</IconButton>
</Tooltip>
}
>
<b>New Permissions Available</b>
{Object.keys(newPermissions?.MissingPermissions).map((perm) => {
// translate appid to display name
var sp = servicePrincipals?.Results?.find((sp) => sp.appId === perm);
return (
<Typography
variant="body2"
textColor="secondary"
key={`missing-${perm}`}
>
{sp?.displayName}:{" "}
{Object.keys(newPermissions?.MissingPermissions[perm]).map((type) => {
return (
<>
{newPermissions?.MissingPermissions[perm][type].length > 0 && (
<React.Fragment key={`missing-${perm}-${type}`}>
{type == "applicationPermissions"
? "Application"
: "Delegated"}{" "}
-{" "}
{newPermissions?.MissingPermissions[perm][type]
.map((p) => {
return p.value;
})
.join(", ")}
</React.Fragment>
)}
</>
);
})}
</Typography>
);
})}
</Alert>
{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 (
<Typography variant="body2" key={`${appKey}-${perm}`}>
{sp?.displayName ?? perm}:{" "}
{app.length > 0 && <>Application - {app.map((p) => p.value).join(", ")} </>}
{del.length > 0 && <>Delegated - {del.map((p) => p.value).join(", ")}</>}
</Typography>
);
};
return (
<Grid container sx={{ width: "100%", mt: 3 }} spacing={2}>
{hasMissing && (
<Grid size={{ xl: 8, xs: 12 }}>
<Alert color="warning" icon={<WarningAmberOutlined />}>
<b>
Permissions missing from the {appDisplayName} app registration (run
Repair Permissions to add, then a CPV refresh to apply to tenants)
</b>
{appIds.map((perm) =>
renderList(
perm,
"missingApplicationPermissions",
"missingDelegatedPermissions",
),
)}
</Alert>
</Grid>
)}
{hasExtra && (
<Grid size={{ xl: 8, xs: 12 }}>
<Alert color="info" icon={<WarningAmberOutlined />}>
<b>
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)
</b>
{appIds.map((perm) =>
renderList(
perm,
"extraApplicationPermissions",
"extraDelegatedPermissions",
),
)}
</Alert>
</Grid>
)}
</Grid>
</Grid>
)}
);
})()}

<Box sx={{ mt: 3 }}>
{selectedApp &&
Expand Down
2 changes: 1 addition & 1 deletion src/components/CippComponents/CippAutocomplete.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Loading