Skip to content

Commit aa74086

Browse files
authored
Merge pull request #47 from KelvinTegelaar/main
[pull] main from KelvinTegelaar:main
2 parents 0138b87 + 221faa7 commit aa74086

File tree

69 files changed

+7379
-3446
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+7379
-3446
lines changed

.github/dependabot.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# To get started with Dependabot version updates, you'll need to specify which
2+
# package ecosystems to update and where the package manifests are located.
3+
# Please see the documentation for all configuration options:
4+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5+
6+
version: 2
7+
updates:
8+
- package-ecosystem: "npm" # See documentation for possible values
9+
directory: "/" # Location of package manifests
10+
schedule:
11+
interval: "weekly"
12+
target-branch: "dev"

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cipp",
3-
"version": "8.8.2",
3+
"version": "10.0.0",
44
"author": "CIPP Contributors",
55
"homepage": "https://cipp.app/",
66
"bugs": {
@@ -106,7 +106,7 @@
106106
"redux-persist": "^6.0.0",
107107
"redux-thunk": "3.1.0",
108108
"rehype-raw": "^7.0.0",
109-
"remark-gfm": "^3.0.1",
109+
"remark-gfm": "^4.0.0",
110110
"simplebar": "6.3.2",
111111
"simplebar-react": "3.3.2",
112112
"stylis-plugin-rtl": "2.1.1",
361 KB
Loading
46.2 KB
Loading

public/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "8.8.2"
2+
"version": "10.0.0"
33
}

src/components/CippCards/CippBannerListCard.jsx

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useState, useCallback } from "react";
33
import {
44
Box,
55
Card,
6+
Checkbox,
67
Collapse,
78
Divider,
89
IconButton,
@@ -16,13 +17,34 @@ import { CippPropertyListCard } from "./CippPropertyListCard";
1617
import { CippDataTable } from "../CippTable/CippDataTable";
1718

1819
export const CippBannerListCard = (props) => {
19-
const { items = [], isCollapsible = false, isFetching = false, children, ...other } = props;
20+
const {
21+
items = [],
22+
isCollapsible = false,
23+
isFetching = false,
24+
children,
25+
onSelectionChange,
26+
selectedItems = [],
27+
...other
28+
} = props;
2029
const [expanded, setExpanded] = useState(null);
2130

2231
const handleExpand = useCallback((itemId) => {
2332
setExpanded((prevState) => (prevState === itemId ? null : itemId));
2433
}, []);
2534

35+
const handleCheckboxChange = useCallback(
36+
(itemId, checked) => {
37+
if (onSelectionChange) {
38+
if (checked) {
39+
onSelectionChange([...selectedItems, itemId]);
40+
} else {
41+
onSelectionChange(selectedItems.filter((id) => id !== itemId));
42+
}
43+
}
44+
},
45+
[onSelectionChange, selectedItems]
46+
);
47+
2648
const hasItems = items.length > 0;
2749

2850
if (isFetching) {
@@ -91,6 +113,16 @@ export const CippBannerListCard = (props) => {
91113
alignItems="center"
92114
sx={{ flex: 1, minWidth: 0 }}
93115
>
116+
{onSelectionChange && (
117+
<Checkbox
118+
checked={selectedItems.includes(item.id)}
119+
onChange={(e) => {
120+
e.stopPropagation();
121+
handleCheckboxChange(item.id, e.target.checked);
122+
}}
123+
onClick={(e) => e.stopPropagation()}
124+
/>
125+
)}
94126
<Box
95127
sx={{
96128
alignItems: "center",
@@ -224,4 +256,6 @@ CippBannerListCard.propTypes = {
224256
).isRequired,
225257
isCollapsible: PropTypes.bool,
226258
isFetching: PropTypes.bool,
259+
onSelectionChange: PropTypes.func,
260+
selectedItems: PropTypes.array,
227261
};
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { Card, CardHeader, CardContent, Box, Typography, Skeleton } from "@mui/material";
2+
import { Security as SecurityIcon } from "@mui/icons-material";
3+
import { ResponsiveContainer, RadialBarChart, RadialBar, PolarAngleAxis } from "recharts";
4+
import { CippTimeAgo } from "../CippComponents/CippTimeAgo";
5+
6+
export const AssessmentCard = ({ data, isLoading }) => {
7+
// Extract data with null safety
8+
const identityPassed = data?.TestResultSummary?.IdentityPassed || 0;
9+
const identityTotal = data?.TestResultSummary?.IdentityTotal || 1;
10+
const devicesPassed = data?.TestResultSummary?.DevicesPassed || 0;
11+
const devicesTotal = data?.TestResultSummary?.DevicesTotal || 0;
12+
13+
// Determine if we should show devices section
14+
const hasDeviceTests = devicesTotal > 0;
15+
16+
// Calculate percentages for the radial chart
17+
// If no device tests, set devices to 100% (complete)
18+
const devicesPercentage = hasDeviceTests ? (devicesPassed / devicesTotal) * 100 : 100;
19+
const identityPercentage = (identityPassed / identityTotal) * 100;
20+
21+
const chartData = [
22+
{
23+
value: devicesPercentage,
24+
fill: "#22c55e",
25+
},
26+
{
27+
value: identityPercentage,
28+
fill: "#3b82f6",
29+
},
30+
];
31+
32+
return (
33+
<Card sx={{ height: "100%" }}>
34+
<CardHeader
35+
title={
36+
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
37+
<SecurityIcon sx={{ fontSize: 20 }} />
38+
<Typography variant="subtitle1">Assessment</Typography>
39+
</Box>
40+
}
41+
sx={{ pb: 1.5 }}
42+
/>
43+
<CardContent>
44+
<Box sx={{ display: "flex", gap: 3 }}>
45+
<Box sx={{ flex: 1 }}>
46+
<Box sx={{ mb: 2 }}>
47+
<Typography variant="caption" color="text.secondary">
48+
Identity
49+
</Typography>
50+
<Typography variant="h5" fontWeight="bold">
51+
{isLoading ? (
52+
<Skeleton width={80} />
53+
) : (
54+
<>
55+
{identityPassed}/{identityTotal}
56+
<Typography
57+
component="span"
58+
variant="caption"
59+
color="text.secondary"
60+
sx={{ ml: 1 }}
61+
>
62+
tests
63+
</Typography>
64+
</>
65+
)}
66+
</Typography>
67+
</Box>
68+
{hasDeviceTests && (
69+
<Box sx={{ mb: 2 }}>
70+
<Typography variant="caption" color="text.secondary">
71+
Devices
72+
</Typography>
73+
<Typography variant="h5" fontWeight="bold">
74+
{isLoading ? (
75+
<Skeleton width={80} />
76+
) : (
77+
<>
78+
{devicesPassed}/{devicesTotal}
79+
<Typography
80+
component="span"
81+
variant="caption"
82+
color="text.secondary"
83+
sx={{ ml: 1 }}
84+
>
85+
tests
86+
</Typography>
87+
</>
88+
)}
89+
</Typography>
90+
</Box>
91+
)}
92+
<Box>
93+
<Typography variant="caption" color="text.secondary">
94+
Last Data Collection
95+
</Typography>
96+
<Typography variant="body2" fontSize="0.75rem">
97+
{isLoading ? (
98+
<Skeleton width={100} />
99+
) : data?.ExecutedAt ? (
100+
<CippTimeAgo data={data?.ExecutedAt} />
101+
) : (
102+
"Not Available"
103+
)}
104+
</Typography>
105+
</Box>
106+
</Box>
107+
<Box sx={{ width: "40%", maxWidth: 120, aspectRatio: 1 }}>
108+
{isLoading ? (
109+
<Skeleton variant="circular" width="100%" height="100%" />
110+
) : (
111+
<ResponsiveContainer width="100%" height="100%">
112+
<RadialBarChart
113+
innerRadius="20%"
114+
outerRadius="100%"
115+
data={chartData}
116+
startAngle={90}
117+
endAngle={450}
118+
>
119+
<PolarAngleAxis type="number" domain={[0, 100]} tick={false} />
120+
<RadialBar dataKey="value" background cornerRadius={5} />
121+
</RadialBarChart>
122+
</ResponsiveContainer>
123+
)}
124+
</Box>
125+
</Box>
126+
</CardContent>
127+
</Card>
128+
);
129+
};
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { Box, Card, CardHeader, CardContent, Typography, Skeleton } from "@mui/material";
2+
import { People as UsersIcon } from "@mui/icons-material";
3+
import { CippSankey } from "./CippSankey";
4+
5+
export const AuthMethodCard = ({ data, isLoading }) => {
6+
const processData = () => {
7+
if (!data || !Array.isArray(data) || data.length === 0) {
8+
return null;
9+
}
10+
11+
const enabledUsers = data.filter((user) => user.AccountEnabled === true);
12+
if (enabledUsers.length === 0) {
13+
return null;
14+
}
15+
16+
const phishableMethods = ["mobilePhone", "email", "microsoftAuthenticatorPush"];
17+
const phishResistantMethods = ["fido2", "windowsHelloForBusiness", "x509Certificate"];
18+
19+
let singleFactor = 0;
20+
let phishableCount = 0;
21+
let phishResistantCount = 0;
22+
let perUserMFA = 0;
23+
let phoneCount = 0;
24+
let authenticatorCount = 0;
25+
let passkeyCount = 0;
26+
let whfbCount = 0;
27+
28+
enabledUsers.forEach((user) => {
29+
const methods = user.MFAMethods || [];
30+
const perUser = user.PerUser === "enforced" || user.PerUser === "enabled";
31+
const hasRegistered = user.MFARegistration === true;
32+
33+
if (perUser && !hasRegistered && methods.length === 0) {
34+
perUserMFA++;
35+
return;
36+
}
37+
38+
if (!hasRegistered || methods.length === 0) {
39+
singleFactor++;
40+
return;
41+
}
42+
43+
const hasPhishResistant = methods.some((m) => phishResistantMethods.includes(m));
44+
const hasPhishable = methods.some((m) => phishableMethods.includes(m));
45+
46+
if (hasPhishResistant) {
47+
phishResistantCount++;
48+
if (methods.includes("fido2") || methods.includes("x509Certificate")) {
49+
passkeyCount++;
50+
}
51+
if (methods.includes("windowsHelloForBusiness")) {
52+
whfbCount++;
53+
}
54+
} else if (hasPhishable) {
55+
phishableCount++;
56+
if (methods.includes("mobilePhone") || methods.includes("email")) {
57+
phoneCount++;
58+
}
59+
if (
60+
methods.includes("microsoftAuthenticatorPush") ||
61+
methods.includes("softwareOneTimePasscode")
62+
) {
63+
authenticatorCount++;
64+
}
65+
} else {
66+
phishableCount++;
67+
authenticatorCount++;
68+
}
69+
});
70+
71+
const mfaPercentage = (
72+
((phishableCount + phishResistantCount + perUserMFA) / enabledUsers.length) *
73+
100
74+
).toFixed(1);
75+
const phishResistantPercentage = ((phishResistantCount / enabledUsers.length) * 100).toFixed(1);
76+
77+
const links = [
78+
{ source: "Users", target: "Single factor", value: singleFactor },
79+
{ source: "Users", target: "Multi factor", value: perUserMFA },
80+
{ source: "Users", target: "Phishable", value: phishableCount },
81+
{ source: "Users", target: "Phish resistant", value: phishResistantCount },
82+
];
83+
84+
if (phoneCount > 0) links.push({ source: "Phishable", target: "Phone", value: phoneCount });
85+
if (authenticatorCount > 0)
86+
links.push({ source: "Phishable", target: "Authenticator", value: authenticatorCount });
87+
88+
if (passkeyCount > 0)
89+
links.push({ source: "Phish resistant", target: "Passkey", value: passkeyCount });
90+
if (whfbCount > 0) links.push({ source: "Phish resistant", target: "WHfB", value: whfbCount });
91+
92+
const description = `${mfaPercentage}% of enabled users have MFA configured. ${phishResistantPercentage}% use phish-resistant authentication methods.`;
93+
94+
return {
95+
nodes: [
96+
{ id: "Users", nodeColor: "hsl(28, 100%, 53%)" },
97+
{ id: "Single factor", nodeColor: "hsl(0, 100%, 50%)" },
98+
{ id: "Multi factor", nodeColor: "hsl(200, 70%, 50%)" },
99+
{ id: "Phishable", nodeColor: "hsl(39, 100%, 50%)" },
100+
{ id: "Phone", nodeColor: "hsl(39, 100%, 45%)" },
101+
{ id: "Authenticator", nodeColor: "hsl(39, 100%, 55%)" },
102+
{ id: "Phish resistant", nodeColor: "hsl(99, 70%, 50%)" },
103+
{ id: "Passkey", nodeColor: "hsl(140, 70%, 50%)" },
104+
{ id: "WHfB", nodeColor: "hsl(160, 70%, 50%)" },
105+
],
106+
links,
107+
description,
108+
};
109+
};
110+
111+
const processedData = processData();
112+
113+
return (
114+
<Card sx={{ flex: 1 }}>
115+
<CardHeader
116+
title={
117+
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
118+
<UsersIcon sx={{ fontSize: 24 }} />
119+
<Typography variant="h6">All users auth methods</Typography>
120+
</Box>
121+
}
122+
sx={{ pb: 1 }}
123+
/>
124+
<CardContent sx={{ pb: 0 }}>
125+
<Box sx={{ height: 300 }}>
126+
{isLoading ? (
127+
<Skeleton variant="rectangular" width="100%" height={300} />
128+
) : processedData ? (
129+
<CippSankey data={{ nodes: processedData.nodes, links: processedData.links }} />
130+
) : (
131+
<Box
132+
sx={{
133+
display: "flex",
134+
alignItems: "center",
135+
justifyContent: "center",
136+
height: "100%",
137+
}}
138+
>
139+
<Typography variant="body2" color="text.secondary">
140+
No authentication method data available
141+
</Typography>
142+
</Box>
143+
)}
144+
</Box>
145+
</CardContent>
146+
{!isLoading && processedData?.description && (
147+
<CardContent sx={{ pt: 2 }}>
148+
<Typography variant="body2" color="text.secondary">
149+
{processedData.description}
150+
</Typography>
151+
</CardContent>
152+
)}
153+
</Card>
154+
);
155+
};

0 commit comments

Comments
 (0)