From 9004df845cd8ce1eeb823e395ee0741a9f648be1 Mon Sep 17 00:00:00 2001 From: Esco Date: Wed, 20 Mar 2024 14:09:47 +0100 Subject: [PATCH 01/27] SafeLinks Standard * Add SafeLinks Policy Standard * SafeLinks Policy Label fix * SafeLink Policy use boolean * Revert "SafeLink Policy use boolean" * boolean test * Added EnableOrganizationBranding to SafeLink option * Move SafeLinksPolicy to Defender Standard --- src/data/standards.json | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 95cc1a746259..3216c32f5289 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -736,6 +736,80 @@ "impact": "Medium Impact", "impactColour": "warning" }, + { + "name": "standards.SafeLinksPolicy", + "cat": "Defender Standards", + "tag": ["lowimpact", "CIS"], + "helpText": "Creates a SafeLinks Policy", + "addedComponent": [ + { + "type": "input", + "name": "standards.SafeLinksPolicy.Name", + "label": "SafeLink Policy Name" + }, + { + "type": "boolean", + "label": "EnableSafeLinksForEmail", + "name": "standards.SafeLinksPolicy.EnableSafeLinksForEmail", + "default": true + }, + { + "type": "boolean", + "label": "EnableSafeLinksForTeams", + "name": "standards.SafeLinksPolicy.EnableSafeLinksForTeams", + "default": true + }, + { + "type": "boolean", + "label": "EnableSafeLinksForOffice", + "name": "standards.SafeLinksPolicy.EnableSafeLinksForOffice", + "default": true + }, + { + "type": "boolean", + "label": "TrackClicks", + "name": "standards.SafeLinksPolicy.TrackClicks", + "default": true + }, + { + "type": "boolean", + "label": "ScanUrls", + "name": "standards.SafeLinksPolicy.ScanUrls", + "default": true + }, + { + "type": "boolean", + "label": "EnableForInternalSenders", + "name": "standards.SafeLinksPolicy.EnableForInternalSenders", + "default": true + }, + { + "type": "boolean", + "label": "DeliverMessageAfterScan", + "name": "standards.SafeLinksPolicy.DeliverMessageAfterScan", + "default": true + }, + { + "type": "boolean", + "label": "AllowClickThrough", + "name": "standards.SafeLinksPolicy.AllowClickThrough", + "default": false + }, + { + "type": "boolean", + "label": "DisableUrlRewrite", + "name": "standards.SafeLinksPolicy.DisableUrlRewrite" + }, + { + "type": "boolean", + "label": "EnableOrganizationBranding", + "name": "standards.SafeLinksPolicy.EnableOrganizationBranding" + } + ], + "label": "SafeLinks Policy", + "impact": "Low Impact", + "impactColour": "info" + }, { "name": "standards.intuneDeviceRetirementDays", "cat": "Intune Standards", From 861bc9a8755ff042f0062ca4dfddee816410674a Mon Sep 17 00:00:00 2001 From: Esco Date: Fri, 22 Mar 2024 17:33:12 +0100 Subject: [PATCH 02/27] Updated Name label --- src/data/standards.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/standards.json b/src/data/standards.json index 3216c32f5289..983edbff3c13 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -745,7 +745,7 @@ { "type": "input", "name": "standards.SafeLinksPolicy.Name", - "label": "SafeLink Policy Name" + "label": "Policy Name" }, { "type": "boolean", From 78899c02a71e90ee7fa268ce7e9eda5d7a89b9d6 Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 25 Mar 2024 12:36:45 +0100 Subject: [PATCH 03/27] Changed according to feedback --- src/data/standards.json | 52 ++--------------------------------------- 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/src/data/standards.json b/src/data/standards.json index 983edbff3c13..7ae40c0bb1a9 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -740,60 +740,12 @@ "name": "standards.SafeLinksPolicy", "cat": "Defender Standards", "tag": ["lowimpact", "CIS"], - "helpText": "Creates a SafeLinks Policy", + "helpText": "This creates a safelink policy that automatically scans, tracks, and and enables safe links for Email, Office, and Teams for both external and internal senders", "addedComponent": [ - { - "type": "input", - "name": "standards.SafeLinksPolicy.Name", - "label": "Policy Name" - }, - { - "type": "boolean", - "label": "EnableSafeLinksForEmail", - "name": "standards.SafeLinksPolicy.EnableSafeLinksForEmail", - "default": true - }, - { - "type": "boolean", - "label": "EnableSafeLinksForTeams", - "name": "standards.SafeLinksPolicy.EnableSafeLinksForTeams", - "default": true - }, - { - "type": "boolean", - "label": "EnableSafeLinksForOffice", - "name": "standards.SafeLinksPolicy.EnableSafeLinksForOffice", - "default": true - }, - { - "type": "boolean", - "label": "TrackClicks", - "name": "standards.SafeLinksPolicy.TrackClicks", - "default": true - }, - { - "type": "boolean", - "label": "ScanUrls", - "name": "standards.SafeLinksPolicy.ScanUrls", - "default": true - }, - { - "type": "boolean", - "label": "EnableForInternalSenders", - "name": "standards.SafeLinksPolicy.EnableForInternalSenders", - "default": true - }, - { - "type": "boolean", - "label": "DeliverMessageAfterScan", - "name": "standards.SafeLinksPolicy.DeliverMessageAfterScan", - "default": true - }, { "type": "boolean", "label": "AllowClickThrough", - "name": "standards.SafeLinksPolicy.AllowClickThrough", - "default": false + "name": "standards.SafeLinksPolicy.AllowClickThrough" }, { "type": "boolean", From 6b557bfc9bf8c1e291f0a8e1dcc0618ae48d5b8a Mon Sep 17 00:00:00 2001 From: Esco Date: Mon, 25 Mar 2024 13:04:46 +0100 Subject: [PATCH 04/27] Updated label name --- src/data/standards.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/standards.json b/src/data/standards.json index 7ae40c0bb1a9..6d26445f6515 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -758,7 +758,7 @@ "name": "standards.SafeLinksPolicy.EnableOrganizationBranding" } ], - "label": "SafeLinks Policy", + "label": "Default SafeLinks Policy", "impact": "Low Impact", "impactColour": "info" }, From fcfeed9301d248dd3082262505659ca4f72c4b55 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Mon, 25 Mar 2024 17:16:46 +0100 Subject: [PATCH 05/27] bulk user adds --- src/routes.js | 8 + .../identity/administration/AddUserBulk.jsx | 256 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 src/views/identity/administration/AddUserBulk.jsx diff --git a/src/routes.js b/src/routes.js index 4f119b52d8c8..e5e5e35ebb9a 100644 --- a/src/routes.js +++ b/src/routes.js @@ -8,6 +8,8 @@ const Users = React.lazy(() => import('src/views/identity/administration/Users') const DeletedItems = React.lazy(() => import('src/views/identity/administration/Deleted')) const ViewBEC = React.lazy(() => import('src/views/identity/administration/ViewBEC')) const AddUser = React.lazy(() => import('src/views/identity/administration/AddUser')) +const AddUserBulk = React.lazy(() => import('src/views/identity/administration/AddUserBulk')) + const InviteGuest = React.lazy(() => import('src/views/identity/administration/InviteGuest')) const EditUser = React.lazy(() => import('src/views/identity/administration/EditUser')) const ViewUser = React.lazy(() => import('src/views/identity/administration/ViewUser')) @@ -251,6 +253,12 @@ const routes = [ { path: '/cipp/500', name: 'Error', component: Page500 }, { path: '/identity', name: 'Identity' }, { path: '/identity/administration/users/add', name: 'Add User', component: AddUser }, + { + path: '/identity/administration/users/addbulk', + name: 'Add User Bulk', + component: AddUserBulk, + }, + { path: '/identity/administration/users/edit', name: 'Edit User', component: EditUser }, { path: '/identity/administration/users/view', name: 'View User', component: ViewUser }, { diff --git a/src/views/identity/administration/AddUserBulk.jsx b/src/views/identity/administration/AddUserBulk.jsx new file mode 100644 index 000000000000..9b1e6dd3a507 --- /dev/null +++ b/src/views/identity/administration/AddUserBulk.jsx @@ -0,0 +1,256 @@ +import React, { useState } from 'react' +import { CButton, CCallout, CCol, CRow, CSpinner } from '@coreui/react' +import { Field, FormSpy } from 'react-final-form' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faExclamationTriangle, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons' +import { CippCallout, CippWizard } from 'src/components/layout' +import PropTypes from 'prop-types' +import { RFFCFormInput } from 'src/components/forms' +import { CippTable } from 'src/components/tables' +import { CippCodeBlock, TenantSelector } from 'src/components/utilities' +import { CSVReader } from 'react-papaparse' +import { useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { useSelector } from 'react-redux' + +const Error = ({ name }) => ( + + touched && error ? ( + + + {error} + + ) : null + } + /> +) + +Error.propTypes = { + name: PropTypes.string.isRequired, +} + +const AddUserBulk = () => { + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const [BulkUser, setBulkUser] = useState([]) + const currentSettings = useSelector((state) => state.app) + const addedFields = currentSettings?.userSettingsDefaults?.defaultAttributes + ? //if we have default attributes, add the label object to the fields array + currentSettings.userSettingsDefaults.defaultAttributes.map((item) => item.label) + : [] + const fields = [ + 'givenName', + 'surName', + 'displayName', + 'mailNickName', + 'domain', + 'usageLocation', + 'JobTitle', + 'streetAddress', + 'PostalCode', + 'City', + 'State', + 'Department', + 'MobilePhone', + 'businessPhones', + ...addedFields, + ] + const columns = fields.map((field) => { + return { + name: field, + selector: (row) => row[field], + sortable: true, + } + }) + + const tableColumns = [ + ...columns, + { + name: 'Remove', + button: true, + cell: (row, index) => { + return ( + handleRemove(row)} size="sm" variant="ghost" color="danger"> + + + ) + }, + }, + ] + const valbutton = (value) => + BulkUser.length + ? undefined + : 'You must add at least one user. Did you forget to click add or upload the CSV?' + const handleOnDrop = (data) => { + const importdata = data.map((item) => { + //find any keys that have a null or blank string value, and remove them + Object.keys(item.data).forEach((key) => { + if (item.data[key] === null || item.data[key] === '') { + delete item.data[key] + } + }) + return item.data + }) + setBulkUser([...BulkUser, ...importdata]) + // console.log(importdata) + } + + const handleOnError = (err, file, inputElem, reason) => { + //set upload error + } + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + + const handleSubmit = async (values) => { + const shippedValues = { + TenantFilter: tenantDomain, + BulkUser, + ...values, + } + //alert(JSON.stringify(values, null, 2)) + genericPostRequest({ path: '/api/AddUserBulk', values: shippedValues }) + } + const addRowtoData = (values) => { + setBulkUser((prevState) => { + if (prevState) { + return [values, ...prevState] + } else { + return [values] + } + }) + } + const handleRemove = async (itemindex) => { + let RemovedItems = BulkUser.filter((item) => item !== itemindex) + setBulkUser((prevState) => { + return RemovedItems + }) + } + return ( + + +
+

Step 1

+
Choose a tenant
+
+
+ {(props) => } + +
+
+ +
+

Step 2

+
Enter user information
+
+
+ + + + Drop CSV file here or click to upload. + + +

+ + {fields.map((field, idx) => { + return ( + + + + ) + })} + + + {/* eslint-disable react/prop-types */} + {(props) => { + return ( + <> + addRowtoData(props.values)} + name="addButton" + className="mb-3" + > + + Add + + + ) + }} + + + + + + + + {BulkUser && ( + + )} + + + +
+
+ +
+

Step 4

+
Confirm and apply
+
+ {postResults.isFetching && ( + + Loading + + )} +

+ {postResults.isSuccess && ( + { + return

  • {item}
  • + })} + callout={true} + calloutCopyValue={postResults.data?.Results} + /> + )} +

    + {BulkUser && ( + + )} +
    +
    +
    +
    + ) +} + +export default AddUserBulk From df8e01138985ae50ae2d179bdeff11d61d330722 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 26 Mar 2024 12:30:46 +0100 Subject: [PATCH 06/27] create --- src/_nav.jsx | 7 +- src/routes.js | 16 ++- src/views/endpoint/intune/MEMAddPolicy.jsx | 1 + .../endpoint/intune/MEMListAppProtection.jsx | 131 ++++++++++++++++++ src/views/endpoint/intune/MEMListPolicies.jsx | 2 +- src/views/identity/administration/Users.jsx | 7 + 6 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 src/views/endpoint/intune/MEMListAppProtection.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index d701c055f36d..77c84d57f31a 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -481,9 +481,14 @@ const _nav = [ }, { component: CNavItem, - name: 'MEM Policies', + name: 'Configuration Policies', to: '/endpoint/MEM/list-policies', }, + { + component: CNavItem, + name: 'Protection Policies', + to: '/endpoint/MEM/list-appprotection-policies', + }, { component: CNavItem, name: 'Apply Policy', diff --git a/src/routes.js b/src/routes.js index e5e5e35ebb9a..d5b459b465b9 100644 --- a/src/routes.js +++ b/src/routes.js @@ -130,6 +130,10 @@ const AutopilotListStatusPages = React.lazy(() => import('src/views/endpoint/autopilot/AutopilotListStatusPages'), ) const IntuneListPolicies = React.lazy(() => import('src/views/endpoint/intune/MEMListPolicies')) +const IntuneListAppProtection = React.lazy(() => + import('src/views/endpoint/intune/MEMListAppProtection'), +) + const MEMEditPolicy = React.lazy(() => import('src/views/endpoint/intune/MEMEditPolicy')) const IntuneCAPolicies = React.lazy(() => import('src/views/endpoint/intune/MEMCAPolicies')) @@ -481,7 +485,17 @@ const routes = [ component: AutopilotListStatusPages, }, { path: '/endpoint/MEM', name: 'MEM' }, - { path: '/endpoint/MEM/list-policies', name: 'List MEM Policies', component: IntuneListPolicies }, + { + path: '/endpoint/MEM/list-policies', + name: 'List Intune Policies', + component: IntuneListPolicies, + }, + { + path: '/endpoint/MEM/list-appprotection-policies', + name: 'List App Protection Policies', + component: IntuneListAppProtection, + }, + { path: '/endpoint/MEM/edit-policy', name: 'Edit MEM Policy', component: MEMEditPolicy }, { path: '/endpoint/MEM/ca-policies', name: 'List Status Pages', component: IntuneCAPolicies }, { path: '/endpoint/MEM/add-policy', name: 'Add Intune Policy', component: IntuneAddPolicy }, diff --git a/src/views/endpoint/intune/MEMAddPolicy.jsx b/src/views/endpoint/intune/MEMAddPolicy.jsx index 6c7efa88c689..00987200dc65 100644 --- a/src/views/endpoint/intune/MEMAddPolicy.jsx +++ b/src/views/endpoint/intune/MEMAddPolicy.jsx @@ -170,6 +170,7 @@ const AddPolicy = () => { { label: 'Administrative Template', value: 'Admin' }, { label: 'Settings Catalog', value: 'Catalog' }, { label: 'Custom Configuration', value: 'Device' }, + { label: 'App Protection or Configuration Policy', value: 'AppProtection' }, ]} /> diff --git a/src/views/endpoint/intune/MEMListAppProtection.jsx b/src/views/endpoint/intune/MEMListAppProtection.jsx new file mode 100644 index 000000000000..9f63b4820fde --- /dev/null +++ b/src/views/endpoint/intune/MEMListAppProtection.jsx @@ -0,0 +1,131 @@ +import React, { useState } from 'react' +import { useSelector } from 'react-redux' +import { CButton } from '@coreui/react' +import { + faBook, + faEdit, + faEllipsisV, + faGlobeEurope, + faPager, + faTrashAlt, + faUser, +} from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { CippPageList } from 'src/components/layout' +import { Link } from 'react-router-dom' +import { CippActionsOffcanvas, CippCodeBlock } from 'src/components/utilities' +import { TitleButton } from 'src/components/buttons' +import { cellBooleanFormatter, cellDateFormatter } from 'src/components/tables' + +const Actions = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) + console.log(row) + const tenant = useSelector((state) => state.app.currentTenant) + return ( + <> + setOCVisible(true)}> + + + , + modalUrl: `/api/AddIntuneTemplate?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&URLName=managedAppPolicies`, + modalMessage: 'Are you sure you want to create a template based on this policy?', + }, + { + label: 'Delete Policy', + color: 'danger', + modal: true, + icon: , + modalUrl: `/api/RemovePolicy?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&URLName=${row.URLName}`, + modalMessage: 'Are you sure you want to delete this policy?', + }, + ]} + placement="end" + visible={ocVisible} + id={row.id} + hideFunction={() => setOCVisible(false)} + /> + + ) +} + +const columns = [ + { + selector: (row) => row['displayName'], + name: 'Name', + sortable: true, + exportSelector: 'displayName', + }, + { + selector: (row) => row['isAssigned'], + name: 'Is Assigned', + sortable: true, + exportSelector: 'isAssigned', + cell: cellBooleanFormatter(), + }, + { + selector: (row) => row['lastModifiedDateTime'], + name: 'Last Modified', + exportSelector: 'lastModifiedDateTime', + cell: cellDateFormatter({ format: 'relative' }), + }, + { + name: 'Actions', + cell: Actions, + maxWidth: '80px', + }, +] + +const AppProtectionList = () => { + const tenant = useSelector((state) => state.app.currentTenant) + + // eslint-disable-next-line react/prop-types + const ExpandedComponent = ({ data }) => ( + // eslint-disable-next-line react/prop-types + + ) + + return ( + + + + } + tenantSelector={true} + datatable={{ + path: '/api/ListGraphRequest', + params: { + TenantFilter: tenant?.defaultDomainName, + Endpoint: 'deviceAppManagement/managedAppPolicies', + $orderby: 'displayName', + }, + columns, + reportName: `${tenant?.defaultDomainName}-MEMPolicies-List`, + tableProps: { + expandableRows: true, + expandableRowsComponent: ExpandedComponent, + expandOnRowClicked: true, + }, + }} + /> + ) +} + +export default AppProtectionList diff --git a/src/views/endpoint/intune/MEMListPolicies.jsx b/src/views/endpoint/intune/MEMListPolicies.jsx index 980d68010132..3f95f45d3f5e 100644 --- a/src/views/endpoint/intune/MEMListPolicies.jsx +++ b/src/views/endpoint/intune/MEMListPolicies.jsx @@ -121,7 +121,7 @@ const IntuneList = () => { return ( { title="Invite Guest" /> +
    + +
    ) From d961d173e4fdcf23d50c401deecac122d52dece0 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 26 Mar 2024 14:54:27 +0100 Subject: [PATCH 07/27] Added Compliance Policies --- src/_nav.jsx | 5 + src/routes.js | 7 + src/views/endpoint/intune/MEMAddPolicy.jsx | 1 + .../endpoint/intune/MEMListCompliance.jsx | 155 ++++++++++++++++++ src/views/endpoint/intune/MEMListPolicies.jsx | 6 +- 5 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 src/views/endpoint/intune/MEMListCompliance.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index 77c84d57f31a..d3f46a503c75 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -484,6 +484,11 @@ const _nav = [ name: 'Configuration Policies', to: '/endpoint/MEM/list-policies', }, + { + component: CNavItem, + name: 'Compliance Policies', + to: '/endpoint/MEM/list-compliance-policies', + }, { component: CNavItem, name: 'Protection Policies', diff --git a/src/routes.js b/src/routes.js index d5b459b465b9..9997cd58f515 100644 --- a/src/routes.js +++ b/src/routes.js @@ -130,6 +130,8 @@ const AutopilotListStatusPages = React.lazy(() => import('src/views/endpoint/autopilot/AutopilotListStatusPages'), ) const IntuneListPolicies = React.lazy(() => import('src/views/endpoint/intune/MEMListPolicies')) +const IntuneListCompliance = React.lazy(() => import('src/views/endpoint/intune/MEMListCompliance')) + const IntuneListAppProtection = React.lazy(() => import('src/views/endpoint/intune/MEMListAppProtection'), ) @@ -490,6 +492,11 @@ const routes = [ name: 'List Intune Policies', component: IntuneListPolicies, }, + { + path: '/endpoint/MEM/list-compliance-policies', + name: 'List Intune Compliance Policies', + component: IntuneListCompliance, + }, { path: '/endpoint/MEM/list-appprotection-policies', name: 'List App Protection Policies', diff --git a/src/views/endpoint/intune/MEMAddPolicy.jsx b/src/views/endpoint/intune/MEMAddPolicy.jsx index 00987200dc65..9ab14f4840ca 100644 --- a/src/views/endpoint/intune/MEMAddPolicy.jsx +++ b/src/views/endpoint/intune/MEMAddPolicy.jsx @@ -171,6 +171,7 @@ const AddPolicy = () => { { label: 'Settings Catalog', value: 'Catalog' }, { label: 'Custom Configuration', value: 'Device' }, { label: 'App Protection or Configuration Policy', value: 'AppProtection' }, + { label: 'Compliance Policy', value: 'deviceCompliancePolicies' }, ]} /> diff --git a/src/views/endpoint/intune/MEMListCompliance.jsx b/src/views/endpoint/intune/MEMListCompliance.jsx new file mode 100644 index 000000000000..fed996b957b3 --- /dev/null +++ b/src/views/endpoint/intune/MEMListCompliance.jsx @@ -0,0 +1,155 @@ +import React, { useState } from 'react' +import { useSelector } from 'react-redux' +import { CButton } from '@coreui/react' +import { + faBook, + faEdit, + faEllipsisV, + faGlobeEurope, + faPager, + faTrashAlt, + faUser, +} from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { CippPageList } from 'src/components/layout' +import { Link } from 'react-router-dom' +import { CippActionsOffcanvas, CippCodeBlock } from 'src/components/utilities' +import { TitleButton } from 'src/components/buttons' +import { cellBooleanFormatter, cellDateFormatter } from 'src/components/tables' + +const Actions = (row, rowIndex, formatExtraData) => { + const [ocVisible, setOCVisible] = useState(false) + console.log(row) + const tenant = useSelector((state) => state.app.currentTenant) + return ( + <> + setOCVisible(true)}> + + + , + modalUrl: `/api/AddIntuneTemplate?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&URLName=deviceCompliancePolicies`, + modalMessage: 'Are you sure you want to create a template based on this policy?', + }, + { + icon: , + label: ' Assign to All Users', + color: 'info', + modal: true, + modalUrl: `/api/ExecAssignPolicy?AssignTo=allLicensedUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&type=deviceCompliancePolicies`, + modalMessage: `Are you sure you want to assign ${row.displayName} to all users?`, + }, + { + icon: , + label: ' Assign to All Devices', + color: 'info', + modal: true, + modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevices&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&type=deviceCompliancePolicies`, + modalMessage: `Are you sure you want to assign ${row.displayName} to all devices?`, + }, + { + icon: , + label: ' Assign Globally (All Users / All Devices)', + color: 'info', + modal: true, + modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevicesAndUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&type=deviceCompliancePolicies`, + modalMessage: `Are you sure you want to assign ${row.displayName} to all users and devices?`, + }, + { + label: 'Delete Policy', + color: 'danger', + modal: true, + icon: , + modalUrl: `/api/RemovePolicy?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&URLName=${row.URLName}`, + modalMessage: 'Are you sure you want to delete this policy?', + }, + ]} + placement="end" + visible={ocVisible} + id={row.id} + hideFunction={() => setOCVisible(false)} + /> + + ) +} + +const columns = [ + { + selector: (row) => row['displayName'], + name: 'Name', + sortable: true, + exportSelector: 'displayName', + }, + { + selector: (row) => row['description'], + name: 'Description', + sortable: true, + exportSelector: 'description', + }, + { + selector: (row) => row['lastModifiedDateTime'], + name: 'Last Modified', + exportSelector: 'lastModifiedDateTime', + cell: cellDateFormatter({ format: 'relative' }), + }, + { + name: 'Actions', + cell: Actions, + maxWidth: '80px', + }, +] + +const ComplianceList = () => { + const tenant = useSelector((state) => state.app.currentTenant) + + // eslint-disable-next-line react/prop-types + const ExpandedComponent = ({ data }) => ( + // eslint-disable-next-line react/prop-types + + ) + + return ( + + + + } + tenantSelector={true} + datatable={{ + path: '/api/ListGraphRequest', + params: { + TenantFilter: tenant?.defaultDomainName, + Endpoint: 'deviceManagement/deviceCompliancePolicies', + $orderby: 'displayName', + $count: true, + }, + columns, + reportName: `${tenant?.defaultDomainName}-MEMPolicies-List`, + tableProps: { + expandableRows: true, + expandableRowsComponent: ExpandedComponent, + expandOnRowClicked: true, + }, + }} + /> + ) +} + +export default ComplianceList diff --git a/src/views/endpoint/intune/MEMListPolicies.jsx b/src/views/endpoint/intune/MEMListPolicies.jsx index 3f95f45d3f5e..cd54f68eb9f6 100644 --- a/src/views/endpoint/intune/MEMListPolicies.jsx +++ b/src/views/endpoint/intune/MEMListPolicies.jsx @@ -47,7 +47,7 @@ const Actions = (row, rowIndex, formatExtraData) => { label: ' Assign to All Users', color: 'info', modal: true, - modalUrl: `/api/ExecAssignPolicy?AssignTo=allLicensedUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalUrl: `/api/ExecAssignPolicy?AssignTo=allLicensedUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&type=${row.URLName}`, modalMessage: `Are you sure you want to assign ${row.displayName} to all users?`, }, { @@ -55,7 +55,7 @@ const Actions = (row, rowIndex, formatExtraData) => { label: ' Assign to All Devices', color: 'info', modal: true, - modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevices&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevices&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&type=${row.URLName}`, modalMessage: `Are you sure you want to assign ${row.displayName} to all devices?`, }, { @@ -63,7 +63,7 @@ const Actions = (row, rowIndex, formatExtraData) => { label: ' Assign Globally (All Users / All Devices)', color: 'info', modal: true, - modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevicesAndUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalUrl: `/api/ExecAssignPolicy?AssignTo=AllDevicesAndUsers&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&type=${row.URLName}`, modalMessage: `Are you sure you want to assign ${row.displayName} to all users and devices?`, }, { From af12a3f906005bdad15c48ad644116a56f79a0a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristian=20Kj=C3=A6rg=C3=A5rd?= Date: Tue, 26 Mar 2024 20:50:00 +0100 Subject: [PATCH 08/27] Standard for trusting external MFA in Cross-tenant access setting --- src/data/standards.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 6d26445f6515..f3c97b88df85 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -232,6 +232,32 @@ "impact": "Low Impact", "impactColour": "info" }, + { + "name": "standards.ExternalMFATrusted", + "cat": "Entra (AAD) Standards", + "tag": ["lowimpact"], + "helpText": "Sets the state of the Cross-tenant access setting to trust external MFA. This allows guest users to use their home tenant MFA to access your tenant.", + "addedComponent": [ + { + "type": "Select", + "label": "Select value", + "name": "standards.ExternalMFATrusted.state", + "values": [ + { + "label": "Enabled", + "value": "true" + }, + { + "label": "Disabled", + "value": "false" + } + ] + } + ], + "label": "Sets the Cross-tenant access setting to trust external MFA", + "impact": "Low Impact", + "impactColour": "info" + }, { "name": "standards.DisableTenantCreation", "cat": "Entra (AAD) Standards", From 66b8be6d8ee92510225e85e1279ea375650b21b5 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Tue, 26 Mar 2024 21:14:50 +0100 Subject: [PATCH 09/27] dynamic lists --- src/views/identity/administration/AddGroup.jsx | 15 +++++++++++++++ .../identity/administration/AddGroupTemplate.jsx | 15 +++++++++++++++ .../administration/DeployGroupTemplate.jsx | 10 ++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/views/identity/administration/AddGroup.jsx b/src/views/identity/administration/AddGroup.jsx index 9faa8544bca7..3c2c308d1cef 100644 --- a/src/views/identity/administration/AddGroup.jsx +++ b/src/views/identity/administration/AddGroup.jsx @@ -139,6 +139,12 @@ const AddGroup = () => { + + { } /> + + + diff --git a/src/views/identity/administration/AddGroupTemplate.jsx b/src/views/identity/administration/AddGroupTemplate.jsx index 80d54b54c575..1a6bd6ecf6e6 100644 --- a/src/views/identity/administration/AddGroupTemplate.jsx +++ b/src/views/identity/administration/AddGroupTemplate.jsx @@ -70,6 +70,11 @@ const AddGroupTemplate = () => { value="security" />{' '} + { } /> + + + + diff --git a/src/views/identity/administration/DeployGroupTemplate.jsx b/src/views/identity/administration/DeployGroupTemplate.jsx index 64b730df4d43..e0524fa0d970 100644 --- a/src/views/identity/administration/DeployGroupTemplate.jsx +++ b/src/views/identity/administration/DeployGroupTemplate.jsx @@ -153,6 +153,8 @@ const ApplyGroupTemplate = () => { placeholder="Select a group type" values={[ { label: 'Dynamic Group', value: 'dynamic' }, + { label: 'Dynamic Distribution Group (Beta)', value: 'dynamicdistribution' }, + { label: 'Security Group', value: 'generic' }, { label: 'Distribution group', value: 'distribution' }, { label: 'Azure Role Group', value: 'azurerole' }, @@ -207,6 +209,14 @@ const ApplyGroupTemplate = () => { placeholder="Enter membership rule syntax" /> + + +
    From 712988ffe2d0c6030a63c2909515306522026aef Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Mar 2024 16:50:46 +0100 Subject: [PATCH 10/27] fixes bug with blank arrays --- src/components/tables/CellTable.jsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/tables/CellTable.jsx b/src/components/tables/CellTable.jsx index e03cd95059bd..549e4805dfa0 100644 --- a/src/components/tables/CellTable.jsx +++ b/src/components/tables/CellTable.jsx @@ -21,7 +21,11 @@ export default function cellTable( } if (!Array.isArray(columnProp) && typeof columnProp === 'object') { - columnProp = [columnProp] + columnProp = Object.keys(columnProp).map((key) => { + return { + [key]: columnProp[key], + } + }) } if (Array.isArray(columnProp) && typeof columnProp[0] !== 'object') { From d1dcd45528d4a9360322264a8209a9225f2a5f5c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Mar 2024 17:52:45 +0100 Subject: [PATCH 11/27] fixes #2210 --- src/components/tables/CippTable.jsx | 111 +++++++++++++--------------- 1 file changed, 51 insertions(+), 60 deletions(-) diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index 7b155136c17b..c033e8c39688 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -38,6 +38,7 @@ import { useSearchParams } from 'react-router-dom' import CopyToClipboard from 'react-copy-to-clipboard' import { setDefaultColumns } from 'src/store/features/app' import { end } from '@popperjs/core' +import { current } from '@reduxjs/toolkit' const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPreset }) => ( <> @@ -630,74 +631,64 @@ export default function CippTable({ return null }) - var exportData = filteredItems + // Define the flatten function + const flatten = (obj, prefix = '') => { + return Object.keys(obj).reduce((output, key) => { + const newKey = prefix ? `${prefix}.${key}` : key + const value = obj[key] === null ? '' : obj[key] - var filtered = - Array.isArray(exportData) && exportData.length > 0 - ? exportData.map((obj) => - // eslint-disable-next-line no-sequences - /* keys.reduce((acc, curr) => ((acc[curr] = obj[curr]), acc), {}),*/ - keys.reduce((acc, curr) => { - const key = curr.split('/') - if (key.length > 1) { - let property = obj - for (let x = 0; x < key.length; x++) { - if ( - Object.prototype.hasOwnProperty.call(property, key[x]) && - property[key[x]] !== null - ) { - property = property[key[x]] - } else { - property = 'n/a' - break - } - } - acc[curr] = property - } else { - if (typeof exportFormatter[curr] === 'function') { - acc[curr] = exportFormatter[curr]({ cell: obj[curr] }) - } else { - acc[curr] = obj[curr] - } - } - return acc - }, {}), - ) - : [] - - const flatten = (obj, prefix) => { - let output = {} - for (let k in obj) { - let val = obj[k] - if (val === null) { - val = '' - } - const newKey = prefix ? prefix + '.' + k : k - if (typeof val === 'object') { - if (Array.isArray(val)) { - const { ...arrToObj } = val - const newObj = flatten(arrToObj, newKey) - output = { ...output, ...newObj } - } else { - const newObj = flatten(val, newKey) - output = { ...output, ...newObj } - } + if (typeof value === 'object' && !Array.isArray(value)) { + Object.assign(output, flatten(value, newKey)) } else { - output = { ...output, [newKey]: val } + output[newKey] = value } - } - return output + return output + }, {}) } - filtered = filtered.map((item) => flatten(item)) - let dataFlat + // Define the applyFormatter function + const applyFormatter = (obj) => { + return Object.keys(obj).reduce((acc, key) => { + const formatter = exportFormatter[key] + // Since the keys after flattening will be dot-separated, we need to adjust this to support nested keys if necessary. + const keyParts = key.split('.') + const finalKeyPart = keyParts[keyParts.length - 1] + const formattedValue = + typeof formatter === 'function' ? formatter({ cell: obj[key] }) : obj[key] + acc[key] = formattedValue + return acc + }, {}) + } - if (Array.isArray(data)) { - dataFlat = data.map((item) => flatten(item)) - } else { - dataFlat = [] + // Process exportData function + const processExportData = (exportData, selectedColumns) => { + //filter out the columns that are not selected via selectedColumns + exportData = exportData.map((item) => { + return Object.keys(item) + .filter((key) => selectedColumns.find((o) => o.exportSelector === key)) + .reduce((obj, key) => { + obj[key] = item[key] + return obj + }, {}) + }) + return Array.isArray(exportData) && exportData.length > 0 + ? exportData.map((obj) => { + const flattenedObj = flatten(obj) + return applyFormatter(flattenedObj) + }) + : [] } + // Applying the processExportData function to both filteredItems and data + var filtered = processExportData(filteredItems, updatedColumns) + + // Adjusted dataFlat processing to include formatting + let dataFlat = Array.isArray(data) + ? data.map((item) => { + const flattenedItem = flatten(item) + return applyFormatter(flattenedItem) + }) + : [] if (!disablePDFExport) { if (dynamicColumns === true) { defaultActions.push([ From 31378543c12ffb44d33d307203ad8e5951ecef2d Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Mar 2024 18:32:44 +0100 Subject: [PATCH 12/27] added alerts --- src/views/tenant/administration/ListAlertsQueue.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index 1c155771a9bd..abce88eca036 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -39,6 +39,8 @@ const alertsList = [ inputName: 'SharePointQuotaQuota', }, { name: 'ExpiringLicenses', label: 'Alert on licenses expiring in 30 days' }, + { name: 'NewAppApproval', label: 'Alert on new apps in the application approval list' }, + { name: 'SecDefaultsUpsell', label: 'Alert on Security Defaults automatic enablement' }, { name: 'DefenderStatus', @@ -221,7 +223,6 @@ const ListClassicAlerts = () => { ) From 06a41ad15f32d7bdd5d98ec4a10b8f7d256955fe Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Mar 2024 18:57:49 +0100 Subject: [PATCH 13/27] changes --- src/views/identity/administration/EditUser.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/identity/administration/EditUser.jsx b/src/views/identity/administration/EditUser.jsx index f456f6f9c4fd..4f2aef34773e 100644 --- a/src/views/identity/administration/EditUser.jsx +++ b/src/views/identity/administration/EditUser.jsx @@ -107,7 +107,7 @@ const EditUser = () => { PostalCode: values.postalCode, usageLocation: values.usageLocation ? values.usageLocation.value : '', UserID: userId, - Username: values.mailNickname, + Username: values.username, streetAddress: values.streetAddress, tenantID: tenantDomain, mustchangepass: values.RequirePasswordChange, @@ -222,7 +222,7 @@ const EditUser = () => { From ee11d7097682debd7945f5dff4a87c72539da6bc Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 27 Mar 2024 13:59:56 -0400 Subject: [PATCH 14/27] fix radio/checkbox bug --- src/components/forms/RFFComponents.jsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index fef24a7cb418..b3d491caa5b7 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -14,7 +14,7 @@ import Select from 'react-select' import Creatable, { useCreatable } from 'react-select/creatable' import { Field } from 'react-final-form' import { FieldArray } from 'react-final-form-arrays' -import React, { useState, useMemo, useRef } from 'react' +import React, { useState, useMemo } from 'react' import PropTypes from 'prop-types' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { debounce } from 'lodash-es' @@ -37,6 +37,7 @@ const sharedPropTypes = { error: PropTypes.any, }), }), + onClick: PropTypes.func, } export const RFFCFormFeedback = ({ meta }) => { @@ -54,7 +55,14 @@ RFFCFormFeedback.propTypes = { }), } -export const RFFCFormCheck = ({ name, label, className = 'mb-3', validate, disabled = false }) => { +export const RFFCFormCheck = ({ + name, + label, + className = 'mb-3', + validate, + disabled = false, + onClick, +}) => { return ( {({ input, meta }) => ( @@ -67,6 +75,7 @@ export const RFFCFormCheck = ({ name, label, className = 'mb-3', validate, disab disabled={disabled} id={name} label={label} + onClick={onClick} /> @@ -92,6 +101,7 @@ export const RFFCFormSwitch = ({ validate, disabled = false, initialValue, + onClick, }) => { return ( @@ -113,6 +123,7 @@ export const RFFCFormSwitch = ({ disabled={disabled} id={name} label={label} + onClick={onClick} /> {input.value && } {sublabel} @@ -239,6 +250,7 @@ export const RFFCFormRadio = ({ className = 'mb-3', validate, disabled = false, + onClick, }) => { return ( @@ -252,6 +264,7 @@ export const RFFCFormRadio = ({ type="radio" name={name} label={label} + onClick={onClick} /> From 79946f852c746841c19bc19f5bc3fc87125ad9e3 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Mar 2024 21:32:46 +0100 Subject: [PATCH 15/27] fixes selector --- src/views/tenant/administration/Tenants.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/tenant/administration/Tenants.jsx b/src/views/tenant/administration/Tenants.jsx index 24b700a30f08..87ab0a9d12da 100644 --- a/src/views/tenant/administration/Tenants.jsx +++ b/src/views/tenant/administration/Tenants.jsx @@ -11,8 +11,9 @@ import { CippTenantOffcanvasRow } from 'src/components/utilities/CippTenantOffca const TenantsList = () => { const TenantListSelector = useSelector((state) => state.app.TenantListSelector) const tenant = useSelector((state) => state.app.currentTenant) + console.log('TenantListSelector', TenantListSelector) const [columnOmits, setOmitVisible] = useState(TenantListSelector) - + console.log('columnOmits', columnOmits) const generatePortalColumn = (portal) => ({ name: portal.label, omit: columnOmits, @@ -59,7 +60,7 @@ const TenantsList = () => { ] const titleButton = ( setOmitVisible(!columnOmits)} title={columnOmits ? 'Show Direct Links' : 'Hide Direct Links'} /> @@ -73,6 +74,7 @@ const TenantsList = () => { datatable={{ keyField: 'id', columns, + dynamicColumns: false, reportName: `${tenant.tenantId}-Tenants-List`, path: '/api/ListTenants', }} From e20249e1ac2982977db9fc6ee0ca6c342a84474c Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 27 Mar 2024 16:35:49 -0400 Subject: [PATCH 16/27] fix tenant access check --- src/views/cipp/app-settings/SettingsGeneral.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/cipp/app-settings/SettingsGeneral.jsx b/src/views/cipp/app-settings/SettingsGeneral.jsx index 887758f30abb..86297419f548 100644 --- a/src/views/cipp/app-settings/SettingsGeneral.jsx +++ b/src/views/cipp/app-settings/SettingsGeneral.jsx @@ -439,6 +439,7 @@ export function SettingsGeneral() { columns={checkAccessColumns} tableProps={tableProps} data={accessCheckResult.data.Results} + dynamicColumns={false} /> )} From 0a177407d205724a61104db774d10f0882c02999 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 27 Mar 2024 21:42:54 +0100 Subject: [PATCH 17/27] fixes ap issue disappearing offCanvas --- .../autopilot/AutopilotListDevices.jsx | 124 +++++++++--------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/src/views/endpoint/autopilot/AutopilotListDevices.jsx b/src/views/endpoint/autopilot/AutopilotListDevices.jsx index f7c58629f4b8..486d289bb881 100644 --- a/src/views/endpoint/autopilot/AutopilotListDevices.jsx +++ b/src/views/endpoint/autopilot/AutopilotListDevices.jsx @@ -14,71 +14,73 @@ import { useLazyGenericGetRequestQuery } from 'src/store/api/app' import { CellTip } from 'src/components/tables' import { TitleButton } from 'src/components/buttons' -const AutopilotListDevices = () => { +const OffCanvas = (row, index, column) => { const tenant = useSelector((state) => state.app.currentTenant) - const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const [ocVisible, setOCVisible] = useState(false) - const Actions = (row, index, column) => { - return ( - <> - setOCVisible(true)}> - - - + setOCVisible(true)}> + + + setOCVisible(false)} - /> - - ) - } + modalMessage: 'Select the user to assign', + }, + { + label: 'Delete Device', + color: 'danger', + modal: true, + modalUrl: `/api/RemoveAPDevice?ID=${row.id}&tenantFilter=${tenant.defaultDomainName}`, + modalMessage: 'Are you sure you want to delete this device?', + }, + ]} + placement="end" + visible={ocVisible} + id={row.id} + hideFunction={() => setOCVisible(false)} + /> + + ) +} +const AutopilotListDevices = () => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const tenant = useSelector((state) => state.app.currentTenant) const columns = [ { @@ -124,8 +126,8 @@ const AutopilotListDevices = () => { exportSelector: 'enrollmentState', }, { - name: (row) => row['Actions'], - cell: Actions, + name: 'Actions', + cell: OffCanvas, }, ] From d7cf61db1e40c84f73032c0aa92825429d3e1c40 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 27 Mar 2024 17:22:24 -0400 Subject: [PATCH 18/27] add standards from PR #2257 #2256 #2255 #2254 --- src/data/standards.json | 220 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index f3c97b88df85..cab53f3144d6 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -788,6 +788,226 @@ "impact": "Low Impact", "impactColour": "info" }, + { + "name": "standards.AntiPhishPolicy", + "cat": "Defender Standards", + "tag": ["lowimpact", "CIS"], + "helpText": "This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips.", + "addedComponent": [ + { + "type": "number", + "label": "Phishing email threshold. (Default 1)", + "name": "standards.AntiPhishPolicy.PhishThresholdLevel", + "default": 1 + }, + { + "type": "boolean", + "label": "Show first contact safety tip", + "name": "standards.AntiPhishPolicy.EnableFirstContactSafetyTips", + "default": true + }, + { + "type": "boolean", + "label": "Show user impersonation safety tip", + "name": "standards.AntiPhishPolicy.EnableSimilarUsersSafetyTips", + "default": true + }, + { + "type": "boolean", + "label": "Show domain impersonation safety tip", + "name": "standards.AntiPhishPolicy.EnableSimilarDomainsSafetyTips", + "default": true + }, + { + "type": "boolean", + "label": "Show user impersonation unusual characters safety tip", + "name": "standards.AntiPhishPolicy.EnableUnusualCharactersSafetyTips", + "default": true + }, + { + "type": "Select", + "label": "If Mailbox Intelligence detects an impersonated user", + "name": "standards.AntiPhishPolicy.MailboxIntelligenceProtectionAction", + "values": [ + { + "label": "Move to Junk Folder", + "value": "MoveToJmf" + }, + { + "label": "Delete the message before its delivered", + "value": "Delete" + }, + { + "label": "Quarantine the message", + "value": "Quarantine" + } + ] + }, + { + "type": "Select", + "label": "Apply quarantine policy", + "name": "standards.AntiPhishPolicy.MailboxIntelligenceQuarantineTag", + "values": [ + { + "label": "AdminOnlyAccessPolicy", + "value": "AdminOnlyAccessPolicy" + }, + { + "label": "DefaultFullAccessPolicy", + "value": "DefaultFullAccessPolicy" + }, + { + "label": "DefaultFullAccessWithNotificationPolicy", + "value": "DefaultFullAccessWithNotificationPolicy" + } + ] + } + ], + "label": "Default Anti-Phishing Policy", + "impact": "Low Impact", + "impactColour": "info" + }, + { + "name": "standards.SafeAttachmentPolicy", + "cat": "Defender Standards", + "tag": ["lowimpact", "CIS"], + "helpText": "This creates a Safe Attachment policy", + "addedComponent": [ + { + "type": "Select", + "label": "Action", + "name": "standards.SafeAttachmentPolicy.Action", + "values": [ + { + "label": "Allow", + "value": "Allow" + }, + { + "label": "Block", + "value": "Block" + }, + { + "label": "DynamicDelivery", + "value": "DynamicDelivery" + } + ] + }, + { + "type": "Select", + "label": "QuarantineTag", + "name": "standards.SafeAttachmentPolicy.QuarantineTag", + "values": [ + { + "label": "AdminOnlyAccessPolicy", + "value": "AdminOnlyAccessPolicy" + }, + { + "label": "DefaultFullAccessPolicy", + "value": "DefaultFullAccessPolicy" + }, + { + "label": "DefaultFullAccessWithNotificationPolicy", + "value": "DefaultFullAccessWithNotificationPolicy" + } + ] + }, + { + "type": "boolean", + "label": "Redirect", + "name": "standards.SafeAttachmentPolicy.Redirect" + }, + { + "type": "input", + "name": "standards.SafeAttachmentPolicy.RedirectAddress", + "label": "Redirect Address" + } + ], + "label": "Default Safe Attachment Policy", + "impact": "Low Impact", + "impactColour": "info" + }, + { + "name": "standards.AtpPolicyForO365", + "cat": "Defender Standards", + "tag": ["lowimpact", "CIS"], + "helpText": "This creates a Atp policy that enables Defender for Office 365 for Sharepoint, OneDrive and Microsoft Teams.", + "addedComponent": [ + { + "type": "boolean", + "label": "Allow people to click through Protected View even if Safe Documents identified the file as malicious", + "name": "standards.AtpPolicyForO365.AllowSafeDocsOpen", + "default": false + } + ], + "label": "Default Atp Policy For O365", + "impact": "Low Impact", + "impactColour": "info" + }, + { + "name": "standards.MalwareFilterPolicy", + "cat": "Defender Standards", + "tag": ["lowimpact", "CIS"], + "helpText": "This creates a Malware filter policy that enables the default File filter and Zero-hour auto purge for malware.", + "addedComponent": [ + { + "type": "Select", + "label": "FileTypeAction", + "name": "standards.MalwareFilterPolicy.FileTypeAction", + "values": [ + { + "label": "Reject", + "value": "Reject" + }, + { + "label": "Quarantine the message", + "value": "Quarantine" + } + ] + }, + { + "type": "Select", + "label": "QuarantineTag", + "name": "standards.MalwareFilterPolicy.QuarantineTag", + "values": [ + { + "label": "AdminOnlyAccessPolicy", + "value": "AdminOnlyAccessPolicy" + }, + { + "label": "DefaultFullAccessPolicy", + "value": "DefaultFullAccessPolicy" + }, + { + "label": "DefaultFullAccessWithNotificationPolicy", + "value": "DefaultFullAccessWithNotificationPolicy" + } + ] + }, + { + "type": "boolean", + "label": "Enable Internal Sender Admin Notifications", + "name": "standards.MalwareFilterPolicy.EnableInternalSenderAdminNotifications" + }, + { + "type": "input", + "name": "standards.MalwareFilterPolicy.InternalSenderAdminAddress", + "label": "Internal Sender Admin Address" + }, + { + "type": "boolean", + "label": "Enable Internal Sender Admin Notifications", + "name": "standards.MalwareFilterPolicy.EnableExternalSenderAdminNotifications" + }, + { + "type": "input", + "name": "standards.MalwareFilterPolicy.ExternalSenderAdminAddress", + "label": "External Sender Admin Address" + } + ], + "label": "Default Malware Filter Policy", + "impact": "Low Impact", + "impactColour": "info" + }, { "name": "standards.intuneDeviceRetirementDays", "cat": "Intune Standards", From 6e616b9cc701e5368104430fcc724442306bc6ad Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 28 Mar 2024 13:09:07 +0100 Subject: [PATCH 19/27] improved search --- src/components/tables/CippTable.jsx | 31 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index c033e8c39688..9498fc271c2e 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -278,28 +278,29 @@ export default function CippTable({ debounceSetGraphFilter(query) return data } else if (filterText.startsWith('Complex:')) { - const conditions = filterText.slice(9).split(';') + // Split conditions by ';' and 'or', and trim spaces + const conditions = filterText + .slice(9) + .split(/\s*or\s*|\s*;\s*/i) // Split by 'or' or ';', case insensitive, with optional spaces + .map((condition) => condition.trim()) - return conditions.reduce((filteredData, condition) => { - const match = condition.trim().match(/(\w+)\s*(eq|ne|like|notlike|gt|lt)\s*(.+)/) + return data.filter((item) => { + // Check if any condition is met for the item + return conditions.some((condition) => { + const match = condition.match(/(\w+)\s*(eq|ne|like|notlike|gt|lt)\s*(.+)/) - if (!match) { - return filteredData // Keep the current filtered data as is - } + if (!match) return false - let [property, operator, value] = match.slice(1) - value = escapeRegExp(value) // Escape special characters + let [property, operator, value] = match.slice(1) + value = escapeRegExp(value) // Escape special characters - return filteredData.filter((item) => { - // Find the actual key in the item that matches the property (case insensitive) const actualKey = Object.keys(item).find( (key) => key.toLowerCase() === property.toLowerCase(), ) if (!actualKey) { - //set the error message so the user understands the key is not found. console.error(`FilterError: Property "${property}" not found.`) - return false // Keep the item if the property is not found + return false } switch (operator) { @@ -316,10 +317,10 @@ export default function CippTable({ case 'lt': return parseFloat(item[actualKey]) < parseFloat(value) default: - return true + return false // Should not reach here normally } }) - }, data) + }) } else { return data.filter( (item) => JSON.stringify(item).toLowerCase().indexOf(filterText.toLowerCase()) !== -1, @@ -327,6 +328,8 @@ export default function CippTable({ } } + // Helper functions like `debounce` and `escapeRegExp` should be defined somewhere in your code + // For example, a simple escapeRegExp function could be: const filteredItems = Array.isArray(data) ? filterData(data, filterText) : [] const applyFilter = (e) => { From 9e6e01dc2de3a2a76f1e2d7629d5556b17b07981 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 28 Mar 2024 13:33:11 +0100 Subject: [PATCH 20/27] fixes for or --- src/components/tables/CippTable.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index 9498fc271c2e..3c246cbecb40 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -33,12 +33,11 @@ import { import { cellGenericFormatter } from './CellGenericFormat' import { ModalService } from '../utilities' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' -import { debounce, update } from 'lodash-es' +import { debounce } from 'lodash-es' import { useSearchParams } from 'react-router-dom' import CopyToClipboard from 'react-copy-to-clipboard' import { setDefaultColumns } from 'src/store/features/app' -import { end } from '@popperjs/core' -import { current } from '@reduxjs/toolkit' +import M365Licenses from 'src/data/M365Licenses' const FilterComponent = ({ filterText, onFilter, onClear, filterlist, onFilterPreset }) => ( <> From 329e0b07d76ec2d613d512c689b11e020d6387ab Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 28 Mar 2024 13:28:01 -0400 Subject: [PATCH 21/27] WizardTableField set dynamicColumns to false --- src/components/tables/WizardTableField.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/tables/WizardTableField.jsx b/src/components/tables/WizardTableField.jsx index bb6ba35a9da1..ac65a722029d 100644 --- a/src/components/tables/WizardTableField.jsx +++ b/src/components/tables/WizardTableField.jsx @@ -73,6 +73,7 @@ export default class WizardTableField extends React.Component { selectableRows: true, onSelectedRowsChange: this.handleSelect, }} + dynamicColumns={false} {...props} /> ) From 694ca32ce291ea34d749f92c60ef4694693c393b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 28 Mar 2024 17:06:48 -0400 Subject: [PATCH 22/27] remove lighthouse requirement for tenant check --- src/views/cipp/app-settings/SettingsGeneral.jsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/views/cipp/app-settings/SettingsGeneral.jsx b/src/views/cipp/app-settings/SettingsGeneral.jsx index 86297419f548..e2c887ecd806 100644 --- a/src/views/cipp/app-settings/SettingsGeneral.jsx +++ b/src/views/cipp/app-settings/SettingsGeneral.jsx @@ -85,13 +85,6 @@ export function SettingsGeneral() { omit: showExtendedInfo, exportSelector: 'GDAPRoles', }, - { - name: 'SAM User Roles', - selector: (row) => row?.SAMUserRoles, - cell: cellTableFormatter('SAMUserRoles', false, true), - omit: showExtendedInfo, - exportSelector: 'SAMUserRoles', - }, ] const checkGDAPColumns = [ From b0509ec8504ca257c0141a309766793cc8720ea5 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 28 Mar 2024 17:07:04 -0400 Subject: [PATCH 23/27] add version update function --- Tools/Update-Version.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Tools/Update-Version.ps1 diff --git a/Tools/Update-Version.ps1 b/Tools/Update-Version.ps1 new file mode 100644 index 000000000000..48ad6e6bf9ba --- /dev/null +++ b/Tools/Update-Version.ps1 @@ -0,0 +1,10 @@ +Param($Version) +Set-Location (Get-Item $PSScriptRoot).Parent.FullName +$Files = @('version_latest.txt', 'public/version_latest.txt') +foreach ($File in $Files) { + Set-Content $File -Value $Version +} + +$Package = Get-Content package.json | ConvertFrom-Json +$Package.version = $Version +$Package | ConvertTo-Json -Depth 10 | Set-Content package.json From 58a9cabc946c15f6589520f1c03fe7327ae7ae01 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 28 Mar 2024 23:13:07 -0400 Subject: [PATCH 24/27] Mail Test --- src/_nav.jsx | 5 + src/routes.js | 7 ++ src/views/email-exchange/tools/MailTest.jsx | 101 ++++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/views/email-exchange/tools/MailTest.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index d3f46a503c75..f55c93339bd1 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -622,6 +622,11 @@ const _nav = [ name: 'Mailbox Restores', to: '/email/tools/mailbox-restores', }, + { + component: CNavItem, + name: 'Mail Test', + to: '/email/tools/mail-test', + }, ], }, { diff --git a/src/routes.js b/src/routes.js index 9997cd58f515..794ebb4b4c4d 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1,4 +1,5 @@ import React from 'react' +import MailTest from 'src/views/email-exchange/tools/MailTest' const Home = React.lazy(() => import('src/views/home/Home')) const Logs = React.lazy(() => import('src/views/cipp/Logs')) @@ -247,6 +248,7 @@ const MailboxRestoreWizard = React.lazy(() => import('src/views/email-exchange/tools/MailboxRestoreWizard'), ) const MailboxRestores = React.lazy(() => import('src/views/email-exchange/tools/MailboxRestores')) +const Mailtest = React.lazy(() => import('src/views/email-exchange/tools/MailTest')) const routes = [ // { path: '/', exact: true, name: 'Home' }, @@ -611,6 +613,11 @@ const routes = [ name: 'Mailbox Restores', component: MailboxRestores, }, + { + path: '/email/tools/mail-test', + name: 'Mail Test', + component: MailTest, + }, { path: '/email/spamfilter/add-template', name: 'Add Spamfilter Template', diff --git a/src/views/email-exchange/tools/MailTest.jsx b/src/views/email-exchange/tools/MailTest.jsx new file mode 100644 index 000000000000..bc98185de466 --- /dev/null +++ b/src/views/email-exchange/tools/MailTest.jsx @@ -0,0 +1,101 @@ +import React from 'react' +import { CippCallout, CippPageList } from 'src/components/layout' +import { cellBooleanFormatter, cellDateFormatter } from 'src/components/tables' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import { useGenericGetRequestQuery } from 'src/store/api/app' + +const MailTest = () => { + const { data: config, isSuccess } = useGenericGetRequestQuery({ + path: '/api/ExecMailTest', + params: { Action: 'CheckConfig' }, + }) + + const columns = [ + { + name: 'Received', + selector: (row) => row['Received'], + sortable: true, + exportSelector: 'Received', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'From', + selector: (row) => row['From'], + sortable: true, + exportSelector: 'From', + cell: cellGenericFormatter(), + }, + { + name: 'Subject', + selector: (row) => row['Subject'], + sortable: true, + exportSelector: 'Subject', + cell: cellGenericFormatter(), + }, + { + name: 'SPF', + selector: (row) => row?.AuthResult.filter((x) => x?.Name === 'spf')[0].Status == 'pass', + cell: cellBooleanFormatter(), + }, + { + name: 'DKIM', + selector: (row) => row?.AuthResult.filter((x) => x?.Name === 'dkim')[0].Status == 'pass', + cell: cellBooleanFormatter(), + }, + { + name: 'DMARC', + selector: (row) => row?.AuthResult.filter((x) => x?.Name === 'dmarc')[0].Status == 'pass', + cell: cellBooleanFormatter(), + }, + { + name: 'Comp Auth', + selector: (row) => row?.AuthResult.filter((x) => x?.Name === 'compauth')[0].Status == 'pass', + cell: cellBooleanFormatter(), + }, + { + name: 'Auth Details', + selector: (row) => row['AuthResult'], + exportSelector: 'AuthResult', + cell: cellGenericFormatter(), + }, + { + name: 'Headers', + selector: (row) => row['Headers'], + exportSelector: 'Headers', + cell: cellGenericFormatter(), + }, + { + name: 'Open Message', + selector: (row) => row['Link'], + exportSelector: 'Link', + cell: cellGenericFormatter(), + }, + ] + return ( +
    + {isSuccess && ( + + {config?.HasMailRead && + 'Mail Test Email: ' + config?.MailAddresses.filter((x) => x?.IsPrimary)[0]?.Address} + {config?.HasMailRead == false && 'Permission Check: ' + config?.Message} + + )} + {isSuccess && config?.HasMailRead === true && ( + + )} +
    + ) +} + +export default MailTest From 2af5d5c5b63d5589c634d139b2465fc35575b793 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 29 Mar 2024 13:12:19 +0100 Subject: [PATCH 25/27] add multi select offboarding --- .../administration/OffboardingWizard.jsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/views/identity/administration/OffboardingWizard.jsx b/src/views/identity/administration/OffboardingWizard.jsx index 86ec8293eff6..af4358b1822e 100644 --- a/src/views/identity/administration/OffboardingWizard.jsx +++ b/src/views/identity/administration/OffboardingWizard.jsx @@ -74,7 +74,7 @@ const OffboardingWizard = () => { RemoveLicenses: values.RemoveLicenses, ResetPass: values.ResetPass, RevokeSessions: values.RevokeSessions, - user: values.User.value, + user: values.User, deleteuser: values.DeleteUser, removeRules: values.RemoveRules, removeMobile: values.RemoveMobile, @@ -119,6 +119,7 @@ const OffboardingWizard = () => {
    ({ value: user.userPrincipalName, @@ -278,10 +279,16 @@ const OffboardingWizard = () => {
    Selected Tenant:
    {tenantDomain} - -
    Selected User:
    - {props.values.User.value} -
    + + {props.values.User.map((user) => ( + +
    Selected User:
    + {user.value} +
    + ))}
    From fe5c2b36c2e40824d51b72529243bb5aeaf6d485 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 29 Mar 2024 13:18:47 +0100 Subject: [PATCH 26/27] add alert for going over maximum --- .../identity/administration/OffboardingWizard.jsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/views/identity/administration/OffboardingWizard.jsx b/src/views/identity/administration/OffboardingWizard.jsx index af4358b1822e..688be60b885f 100644 --- a/src/views/identity/administration/OffboardingWizard.jsx +++ b/src/views/identity/administration/OffboardingWizard.jsx @@ -129,6 +129,17 @@ const OffboardingWizard = () => { name="User" /> {usersError && Failed to load list of users} + + {/* eslint-disable react/prop-types */} + {(props) => ( + <> + {console.log(props.values)} + {props.values.User?.length >= 3 && ( + A maximum of three users is recommend. + )} + + )} +

    From eb56d1570caa41dc7ad6563203729d5aac2db3d6 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 29 Mar 2024 13:24:24 +0100 Subject: [PATCH 27/27] up version --- package.json | 2 +- public/version_latest.txt | 2 +- version_latest.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5103fa09b1bc..9e73b8d43a81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "5.3.2", + "version": "5.4.0", "description": "The CyberDrain Improved Partner Portal is a portal to help manage administration for Microsoft Partners.", "homepage": "https://cipp.app/", "bugs": { diff --git a/public/version_latest.txt b/public/version_latest.txt index 84197c89467d..8a30e8f94a39 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -5.3.2 +5.4.0 diff --git a/version_latest.txt b/version_latest.txt index 84197c89467d..8a30e8f94a39 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.3.2 +5.4.0