From 8c7296f3d0c452cf4698cbd754983df37752449d Mon Sep 17 00:00:00 2001 From: Esco Date: Sat, 27 Jul 2024 16:54:20 +0200 Subject: [PATCH 01/37] Added Teams Global Meeting Policy standard --- src/data/standards.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 63f9c47d07a2..f49e96a91139 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -2302,5 +2302,41 @@ "impactColour": "danger", "powershellEquivalent": "Update-MgAdminSharepointSetting", "recommendedBy": [] + }, + { + "name": "standards.TeamsGlobalMeetingPolicy", + "cat": "Teams Standards", + "tag": ["lowimpact"], + "helpText": "Defines the CIS recommended global meeting policy for Teams. This includes AllowAnonymousUsersToJoinMeeting, AllowAnonymousUsersToStartMeeting, AutoAdmittedUsers, AllowPSTNUsersToBypassLobby, MeetingChatEnabledType, DesignatedPresenterRoleMode, AllowExternalParticipantGiveRequestControl", + "addedComponent": [ + { + "type": "Select", + "name": "standards.TeamsGlobalMeetingPolicy.DesignatedPresenterRoleMode", + "label": "Default value of the `Who can present?`", + "values": [ + { + "label": "EveryoneUserOverride", + "value": "EveryoneUserOverride" + }, + { + "label": "EveryoneInCompanyUserOverride", + "value": "EveryoneInCompanyUserOverride" + }, + { + "label": "EveryoneInSameAndFederatedCompanyUserOverride", + "value": "EveryoneInSameAndFederatedCompanyUserOverride" + }, + { + "label": "OrganizerOnlyUserOverride", + "value": "OrganizerOnlyUserOverride" + } + ] + } + ], + "label": "Define Global Meeting Policy for Teams", + "impact": "Low Impact", + "impactColour": "info", + "powershellEquivalent": "Set-CsTeamsMeetingPolicy -AllowAnonymousUsersToJoinMeeting $false -AllowAnonymousUsersToStartMeeting $false -AutoAdmittedUsers EveryoneInCompanyExcludingGuests -AllowPSTNUsersToBypassLobby $false -MeetingChatEnabledType EnabledExceptAnonymous -DesignatedPresenterRoleMode $DesignatedPresenterRoleMode -AllowExternalParticipantGiveRequestControl $false", + "recommendedBy": ["CIS 3.0"] } ] From 9cb5b30a882d18fd30455a811e068bdb88909952 Mon Sep 17 00:00:00 2001 From: Esco Date: Sat, 27 Jul 2024 10:12:34 +0200 Subject: [PATCH 02/37] Added Teams External File Sharing Standard --- src/data/standards.json | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index f49e96a91139..51e5cf143234 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -2338,5 +2338,43 @@ "impactColour": "info", "powershellEquivalent": "Set-CsTeamsMeetingPolicy -AllowAnonymousUsersToJoinMeeting $false -AllowAnonymousUsersToStartMeeting $false -AutoAdmittedUsers EveryoneInCompanyExcludingGuests -AllowPSTNUsersToBypassLobby $false -MeetingChatEnabledType EnabledExceptAnonymous -DesignatedPresenterRoleMode $DesignatedPresenterRoleMode -AllowExternalParticipantGiveRequestControl $false", "recommendedBy": ["CIS 3.0"] + }, + { + "name": "standards.TeamsExternalFileSharing", + "cat": "Teams Standards", + "tag": ["lowimpact"], + "helpText": "Ensure external file sharing in Teams is enabled for only approved cloud storage services.", + "addedComponent": [ + { + "type": "boolean", + "name": "standards.TeamsExternalFileSharing.AllowGoogleDrive", + "label": "Allow Google Drive" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalFileSharing.AllowShareFile", + "label": "Allow ShareFile" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalFileSharing.AllowBox", + "label": "Allow Box" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalFileSharing.AllowDropBox", + "label": "Allow Dropbox" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalFileSharing.AllowEgnyte", + "label": "Allow Egnyte" + } + ], + "label": "Define approved cloud storage services for external file sharing in Teams", + "impact": "Low Impact", + "impactColour": "info", + "powershellEquivalent": "Set-CsTeamsClientConfiguration -AllowGoogleDrive $false -AllowShareFile $false -AllowBox $false -AllowDropBox $false -AllowEgnyte $false", + "recommendedBy": ["CIS 3.0"] } ] From 3a8c74e4dac88826569334823958b4eecc246c05 Mon Sep 17 00:00:00 2001 From: cipptesting Date: Mon, 29 Jul 2024 14:09:02 -0400 Subject: [PATCH 03/37] Updated Spam Filter Standard --- src/data/standards.json | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 51e5cf143234..1536d4a20a2c 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -1703,6 +1703,12 @@ "tag": ["mediumimpact"], "helpText": "This standard creates a Spam filter policy similar to the default strict policy.", "addedComponent": [ + { + "type": "number", + "label": "Bulk email threshold (Default 7)", + "name": "standards.SpamFilterPolicy.BulkThreshold", + "default": 7 + }, { "type": "Select", "label": "Spam Action", @@ -1737,6 +1743,21 @@ } ] }, + { + "type": "Select", + "label": "High Confidence Spam Action", + "name": "standards.SpamFilterPolicy.HighConfidenceSpamAction", + "values": [ + { + "label": "Quarantine the message", + "value": "Quarantine" + }, + { + "label": "Move message to Junk Email folder", + "value": "MoveToJmf" + } + ] + }, { "type": "Select", "label": "High Confidence Spam Quarantine Tag", @@ -1756,6 +1777,21 @@ } ] }, + { + "type": "Select", + "label": "Bulk Spam Action", + "name": "standards.SpamFilterPolicy.BulkSpamAction", + "values": [ + { + "label": "Move message to Junk Email folder", + "value": "MoveToJmf" + }, + { + "label": "Quarantine the message", + "value": "Quarantine" + } + ] + }, { "type": "Select", "label": "Bulk Quarantine Tag", @@ -1775,6 +1811,21 @@ } ] }, + { + "type": "Select", + "label": "Phish Spam Action", + "name": "standards.SpamFilterPolicy.PhishSpamAction", + "values": [ + { + "label": "Quarantine the message", + "value": "Quarantine" + }, + { + "label": "Move message to Junk Email folder", + "value": "MoveToJmf" + } + ] + }, { "type": "Select", "label": "Phish Quarantine Tag", From e2cbb688cd480fcef6f306a784cc7dd73ac6570d Mon Sep 17 00:00:00 2001 From: cipptesting Date: Mon, 29 Jul 2024 15:37:09 -0400 Subject: [PATCH 04/37] Updated standards.json based on feedback --- src/data/standards.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/data/standards.json b/src/data/standards.json index 1536d4a20a2c..310890368c4d 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -1714,13 +1714,13 @@ "label": "Spam Action", "name": "standards.SpamFilterPolicy.SpamAction", "values": [ - { - "label": "Move message to Junk Email folder", - "value": "MoveToJmf" - }, { "label": "Quarantine the message", "value": "Quarantine" + }, + { + "label": "Move message to Junk Email folder", + "value": "MoveToJmf" } ] }, @@ -1782,13 +1782,13 @@ "label": "Bulk Spam Action", "name": "standards.SpamFilterPolicy.BulkSpamAction", "values": [ - { - "label": "Move message to Junk Email folder", - "value": "MoveToJmf" - }, { "label": "Quarantine the message", "value": "Quarantine" + }, + { + "label": "Move message to Junk Email folder", + "value": "MoveToJmf" } ] }, From 2bb4af823c38069b47055766433377cd35905402 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Mon, 29 Jul 2024 17:38:13 -0400 Subject: [PATCH 05/37] Add controlStateUpdates table to Secure Score page --- .../tenant/administration/SecureScore.jsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/views/tenant/administration/SecureScore.jsx b/src/views/tenant/administration/SecureScore.jsx index 76c77d584296..be036997339c 100644 --- a/src/views/tenant/administration/SecureScore.jsx +++ b/src/views/tenant/administration/SecureScore.jsx @@ -15,7 +15,7 @@ import { CRow, } from '@coreui/react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCheck, faTimes, faExclamation } from '@fortawesome/free-solid-svg-icons' +import { faCheck, faTimes } from '@fortawesome/free-solid-svg-icons' import { CippTable } from 'src/components/tables' import { CippPage } from 'src/components/layout/CippPage' import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' @@ -27,6 +27,7 @@ import { ModalService } from 'src/components/utilities' import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat' import { CippCallout } from 'src/components/layout' import CippPrettyCard from 'src/components/contentcards/CippPrettyCard' +import { TableModalButton } from 'src/components/buttons' const SecureScore = () => { const textRef = useRef() @@ -192,6 +193,11 @@ const SecureScore = () => { cell: cellGenericFormatter(), exportSelector: 'actionUrl', }, + { + name: 'Updates', + selector: (row) => row?.controlStateUpdates, + cell: cellGenericFormatter(), + }, ] return ( @@ -278,7 +284,7 @@ const SecureScore = () => { - {viewMode && translateData.controlScores.length > 1 && isSuccess && isSuccessTranslation && ( + {viewMode && translateData.controlScores?.length > 1 && isSuccess && isSuccessTranslation && ( Best Practice Report @@ -286,7 +292,7 @@ const SecureScore = () => { { openResolution(info)} className="me-3"> Change Status + + From 68d6e5cec6b03ae9c51fd019347d8c1520223e49 Mon Sep 17 00:00:00 2001 From: Esco Date: Tue, 30 Jul 2024 10:11:56 +0200 Subject: [PATCH 06/37] Added Teams Email Integration standard --- src/data/standards.json | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 51e5cf143234..f4e4e8f28b5f 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -2339,6 +2339,25 @@ "powershellEquivalent": "Set-CsTeamsMeetingPolicy -AllowAnonymousUsersToJoinMeeting $false -AllowAnonymousUsersToStartMeeting $false -AutoAdmittedUsers EveryoneInCompanyExcludingGuests -AllowPSTNUsersToBypassLobby $false -MeetingChatEnabledType EnabledExceptAnonymous -DesignatedPresenterRoleMode $DesignatedPresenterRoleMode -AllowExternalParticipantGiveRequestControl $false", "recommendedBy": ["CIS 3.0"] }, + { + "name": "standards.TeamsEmailIntegration", + "cat": "Teams Standards", + "tag": ["lowimpact"], + "helpText": "Should users be allowed to send emails directly to a channel email addresses?", + "docsDescription": "Teams channel email addresses are an optional feature that allows users to email the Teams channel directly.", + "addedComponent": [ + { + "type": "boolean", + "name": "standards.TeamsEmailIntegration.AllowEmailIntoChannel", + "label": "Allow channel emails" + } + ], + "label": "Disallow emails to be sent to channel email addresses", + "impact": "Low Impact", + "impactColour": "info", + "powershellEquivalent": "Set-CsTeamsClientConfiguration -AllowEmailIntoChannel $false", + "recommendedBy": ["CIS 3.0"] + }, { "name": "standards.TeamsExternalFileSharing", "cat": "Teams Standards", From 562e40f39302f1a625d0fae7fbb8017c76444a11 Mon Sep 17 00:00:00 2001 From: Esco Date: Sat, 27 Jul 2024 23:47:50 +0200 Subject: [PATCH 07/37] Added Teams External Access Policy Standard --- src/data/standards.json | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 51e5cf143234..4850180e2bee 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -2376,5 +2376,34 @@ "impactColour": "info", "powershellEquivalent": "Set-CsTeamsClientConfiguration -AllowGoogleDrive $false -AllowShareFile $false -AllowBox $false -AllowDropBox $false -AllowEgnyte $false", "recommendedBy": ["CIS 3.0"] + }, + { + "name": "standards.TeamsExternalAccessPolicy", + "cat": "Teams Standards", + "tag": ["mediumimpact"], + "helpText": "Sets the properties of the Global external access policy.", + "docsDescription": "Sets the properties of the Global external access policy. External access policies determine whether or not your users can: 1) communicate with users who have Session Initiation Protocol (SIP) accounts with a federated organization; 2) communicate with users who are using custom applications built with Azure Communication Services; 3) access Skype for Business Server over the Internet, without having to log on to your internal network; 4) communicate with users who have SIP accounts with a public instant messaging (IM) provider such as Skype; and, 5) communicate with people who are using Teams with an account that's not managed by an organization.", + "addedComponent": [ + { + "type": "boolean", + "name": "standards.TeamsExternalAccessPolicy.EnableFederationAccess", + "label": "Allow communication from trusted organizations" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalAccessPolicy.EnablePublicCloudAccess", + "label": "Allow user to communicate with Skype users" + }, + { + "type": "boolean", + "name": "standards.TeamsExternalAccessPolicy.EnableTeamsConsumerAccess", + "label": "Allow communication with unmanaged Teams accounts" + } + ], + "label": "External Access Settings for Microsoft Teams", + "impact": "Medium Impact", + "impactColour": "warning", + "powershellEquivalent": "Set-CsExternalAccessPolicy", + "recommendedBy": [] } ] From 4ff96472ac6f94f127c68337506e00e1c1fa931e Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 30 Jul 2024 15:31:04 -0400 Subject: [PATCH 08/37] Add form validation for GDAP invite wizard Remove the ability to have duplicate roleDefinitionIds selected --- .../administration/GDAPInviteWizard.jsx | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx index 5f303a3c827f..e46c6b7180e7 100644 --- a/src/views/tenant/administration/GDAPInviteWizard.jsx +++ b/src/views/tenant/administration/GDAPInviteWizard.jsx @@ -28,7 +28,7 @@ const Error = ({ name }) => ( render={({ meta: { touched, error } }) => touched && error ? ( - + {error} ) : null @@ -40,7 +40,31 @@ Error.propTypes = { name: PropTypes.string.isRequired, } -const requiredArray = (value) => (value && value.length !== 0 ? undefined : 'Required') +const requiredArray = (value) => { + if (value && value.length !== 0) { + /// group each item in value by roleDefinitionId and select Role name where count is greater than 1 + const duplicateRoles = value + .map((item) => item.roleDefinitionId) + .filter((item, index, self) => index !== self.indexOf(item)) + console.log(duplicateRoles) + + if (duplicateRoles.length > 0) { + var duplicates = value.filter((item) => duplicateRoles.includes(item.roleDefinitionId)) + /// get unique list of duplicate roles + duplicates = duplicates + .filter( + (role, index, self) => + index === self.findIndex((t) => t.roleDefinitionId === role.roleDefinitionId), + ) + .map((role) => role.RoleName) + return `Duplicate GDAP Roles selected, remove one of the mapped groups for the listed roles to continue: ${duplicates}` + } else { + return undefined + } + } else { + return 'You must select at least one GDAP Role' + } +} const GDAPInviteWizard = () => { const defaultRolesArray = [ From da771ee9d0414d95c18b6e70313e7e3a246e53be Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 30 Jul 2024 16:22:26 -0400 Subject: [PATCH 09/37] Add GDAP invite page --- src/_nav.jsx | 7 ++- src/importsMap.jsx | 3 +- src/routes.json | 8 ++- .../tenant/administration/ListGDAPInvites.jsx | 63 +++++++++++++++++++ 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/views/tenant/administration/ListGDAPInvites.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index c0faa91279c0..c83f7f179148 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -326,7 +326,12 @@ const _nav = [ { component: CNavItem, name: 'Invite Wizard', - to: '/tenant/administration/gdap-invite', + to: '/tenant/administration/gdap-invite-wizard', + }, + { + component: CNavItem, + name: 'Invite List', + to: '/tenant/administration/gdap-invites', }, { component: CNavItem, diff --git a/src/importsMap.jsx b/src/importsMap.jsx index eaa260ad4dbd..21174e32a0f2 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -147,7 +147,8 @@ import React from 'react' "/cipp/setup": React.lazy(() => import('./views/cipp/Setup')), "/tenant/administration/securescore": React.lazy(() => import('./views/tenant/administration/SecureScore')), "/tenant/administration/gdap": React.lazy(() => import('./views/tenant/administration/GDAPWizard')), - "/tenant/administration/gdap-invite": React.lazy(() => import('./views/tenant/administration/GDAPInviteWizard')), + "/tenant/administration/gdap-invite-wizard": React.lazy(() => import('./views/tenant/administration/GDAPInviteWizard')), + "/tenant/administration/gdap-invites": React.lazy(() => import('./views/tenant/administration/ListGDAPInvites')), "/tenant/administration/gdap-role-wizard": React.lazy(() => import('./views/tenant/administration/GDAPRoleWizard')), "/tenant/administration/gdap-roles": React.lazy(() => import('./views/tenant/administration/ListGDAPRoles')), "/tenant/administration/gdap-relationships": React.lazy(() => import('././views/tenant/administration/ListGDAPRelationships')), diff --git a/src/routes.json b/src/routes.json index f5b6053359a7..c0a996610e41 100644 --- a/src/routes.json +++ b/src/routes.json @@ -999,11 +999,17 @@ "allowedRoles": ["admin"] }, { - "path": "/tenant/administration/gdap-invite", + "path": "/tenant/administration/gdap-invite-wizard", "name": "GDAP Invite Wizard", "component": "views/tenant/administration/GDAPInviteWizard", "allowedRoles": ["admin"] }, + { + "path": "/tenant/administration/gdap-invites", + "name": "GDAP Invites", + "component": "views/tenant/administration/ListGDAPInvites", + "allowedRoles": ["admin"] + }, { "path": "/tenant/administration/gdap-role-wizard", "name": "GDAP Role Wizard", diff --git a/src/views/tenant/administration/ListGDAPInvites.jsx b/src/views/tenant/administration/ListGDAPInvites.jsx new file mode 100644 index 000000000000..24a44e6b253f --- /dev/null +++ b/src/views/tenant/administration/ListGDAPInvites.jsx @@ -0,0 +1,63 @@ +import React from 'react' +import { CippPageList } from 'src/components/layout' +import { TitleButton } from 'src/components/buttons' +import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import { cellDateFormatter } from 'src/components/tables' + +const ListGDAPInvites = () => { + const columns = [ + { + name: 'Created', + selector: (row) => row['Timestamp'], + sortable: true, + exportSelector: 'Timestamp', + cell: cellDateFormatter({ format: 'short' }), + }, + { + name: 'Relationship ID', + selector: (row) => row['RowKey'], + sortable: true, + exportSelector: 'RowKey', + cell: cellGenericFormatter(), + }, + { + name: 'Invite URL', + selector: (row) => row['InviteUrl'], + exportSelector: 'InviteUrl', + cell: cellGenericFormatter(), + }, + { + name: 'Onboarding URL', + selector: (row) => row['OnboardingUrl'], + exportSelector: 'OnboardingUrl', + cell: cellGenericFormatter(), + }, + { + name: 'Role Mapping', + selector: (row) => row['RoleMappings'], + exportSelector: 'RoleMappings', + cell: cellGenericFormatter(), + }, + ] + return ( +
+ +
+ ) +} + +export default ListGDAPInvites From 4b18fc4b1d089f6ea9c5bfbe38f66fb5eb9225f1 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Tue, 30 Jul 2024 16:37:15 -0400 Subject: [PATCH 10/37] wording, remove console log --- src/views/tenant/administration/GDAPInviteWizard.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx index e46c6b7180e7..a3c38dbf7dc6 100644 --- a/src/views/tenant/administration/GDAPInviteWizard.jsx +++ b/src/views/tenant/administration/GDAPInviteWizard.jsx @@ -46,18 +46,20 @@ const requiredArray = (value) => { const duplicateRoles = value .map((item) => item.roleDefinitionId) .filter((item, index, self) => index !== self.indexOf(item)) - console.log(duplicateRoles) if (duplicateRoles.length > 0) { var duplicates = value.filter((item) => duplicateRoles.includes(item.roleDefinitionId)) /// get unique list of duplicate roles + duplicates = duplicates .filter( (role, index, self) => index === self.findIndex((t) => t.roleDefinitionId === role.roleDefinitionId), ) .map((role) => role.RoleName) - return `Duplicate GDAP Roles selected, remove one of the mapped groups for the listed roles to continue: ${duplicates}` + return `Duplicate GDAP Roles selected, ensure there is only one group mapping for the listed roles to continue: ${duplicates.join( + ', ', + )}` } else { return undefined } From a6aa56c511c426b8bd58e2e72466e2f46ce1b91d Mon Sep 17 00:00:00 2001 From: Esco Date: Sun, 28 Jul 2024 00:52:21 +0200 Subject: [PATCH 11/37] Added Teams Federation Configuration Standard --- src/data/standards.json | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/data/standards.json b/src/data/standards.json index 9667715a5607..ef14f236cd1a 100644 --- a/src/data/standards.json +++ b/src/data/standards.json @@ -2475,5 +2475,57 @@ "impactColour": "warning", "powershellEquivalent": "Set-CsExternalAccessPolicy", "recommendedBy": [] + }, + { + "name": "standards.TeamsFederationConfiguration", + "cat": "Teams Standards", + "tag": ["mediumimpact"], + "helpText": "Sets the properties of the Global federation configuration.", + "docsDescription": "Sets the properties of the Global federation configuration. Federation configuration settings determine whether or not your users can communicate with users who have SIP accounts with a federated organization.", + "addedComponent": [ + { + "type": "boolean", + "name": "standards.TeamsFederationConfiguration.AllowTeamsConsumer", + "label": "Allow users to communicate with other organizations" + }, + { + "type": "boolean", + "name": "standards.TeamsFederationConfiguration.AllowPublicUsers", + "label": "Allow users to communicate with Skype Users" + }, + { + "type": "Select", + "name": "standards.TeamsFederationConfiguration.DomainControl", + "label": "Communication Mode", + "values": [ + { + "label": "Allow all external domains", + "value": "AllowAllExternal" + }, + { + "label": "Block all external domains", + "value": "BlockAllExternal" + }, + { + "label": "Allow specific external domains", + "value": "AllowSpecificExternal" + }, + { + "label": "Block specific external domains", + "value": "BlockSpecificExternal" + } + ] + }, + { + "type": "input", + "name": "standards.TeamsFederationConfiguration.DomainList", + "label": "Domains, Comma separated" + } + ], + "label": "Federation Configuration for Microsoft Teams", + "impact": "Medium Impact", + "impactColour": "warning", + "powershellEquivalent": "Set-CsTenantFederationConfiguration", + "recommendedBy": [] } ] From b168c995bafc24a891d6d29b5f634f49efed8974 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 31 Jul 2024 16:18:06 -0400 Subject: [PATCH 12/37] Message Viewer --- package-lock.json | 188 ++++++++++- package.json | 6 +- src/_nav.jsx | 5 + src/components/utilities/CippDropzone.jsx | 78 +++++ src/importsMap.jsx | 1 + src/routes.json | 6 + .../email-exchange/tools/MessageViewer.jsx | 310 ++++++++++++++++++ 7 files changed, 582 insertions(+), 12 deletions(-) create mode 100644 src/components/utilities/CippDropzone.jsx create mode 100644 src/views/email-exchange/tools/MessageViewer.jsx diff --git a/package-lock.json b/package-lock.json index bc2ed9783377..5148294a75a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cipp", - "version": "5.8.5", + "version": "6.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cipp", - "version": "5.8.5", + "version": "6.1.1", "license": "AGPL-3.0", "dependencies": { "@coreui/chartjs": "^3.0.0", @@ -32,6 +32,8 @@ "chart.js": "^3.5.1", "classnames": "^2.3.1", "core-js": "^3.18.3", + "dompurify": "^3.1.6", + "eml-parse-js": "^1.1.14", "enzyme": "^3.11.0", "final-form": "^4.20.4", "final-form-arrays": "^3.1.0", @@ -51,11 +53,13 @@ "react-data-table-component": "^7.4.5", "react-datepicker": "^4.10.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-final-form": "^6.5.9", "react-final-form-arrays": "^3.1.4", "react-final-form-listeners": "^1.0.3", "react-helmet-async": "^1.3.0", "react-hotkeys-hook": "^3.4.4", + "react-html-parser": "^2.0.2", "react-loading-skeleton": "^3.1.0", "react-masonry-component": "^6.3.0", "react-media-hook": "^0.4.9", @@ -70,7 +74,7 @@ "redux-persist": "^6.0.0", "simplebar-react": "^2.3.6", "source-map-loader": "^3.0.0", - "styled-components": "^5.3.3" + "styled-components": "^5.3.11" }, "devDependencies": { "@types/react": "^18.2.39", @@ -1714,6 +1718,11 @@ "win32" ] }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2438,6 +2447,14 @@ "node": ">= 4.5.0" } }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "engines": { + "node": ">=4" + } + }, "node_modules/auto-changelog": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/auto-changelog/-/auto-changelog-2.3.0.tgz", @@ -3331,10 +3348,9 @@ } }, "node_modules/dompurify": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.7.tgz", - "integrity": "sha512-kxxKlPEDa6Nc5WJi+qRgPbOAbgTpSULL+vI3NUXsZMlkJxTqYI9wg5ZTay2sFrdZRWHPWNi+EdAhcJf81WtoMQ==", - "optional": true + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", + "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==" }, "node_modules/domutils": { "version": "3.1.0", @@ -3362,6 +3378,15 @@ "batch-processor": "1.0.0" } }, + "node_modules/eml-parse-js": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/eml-parse-js/-/eml-parse-js-1.1.14.tgz", + "integrity": "sha512-6wUmZQ4k67CHGaQdNTukUMtCQ77e/676pRRsn/ga6CdaIwitzbQwqA/YTq/Wk+l1gghFJTPhbRyQphrAptK/GA==", + "dependencies": { + "@sinonjs/text-encoding": "^0.7.2", + "js-base64": "^3.7.2" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -4259,6 +4284,17 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5031,8 +5067,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -5537,6 +5572,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-base64": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz", + "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5658,6 +5698,12 @@ "jspdf": "^2.5.1" } }, + "node_modules/jspdf/node_modules/dompurify": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz", + "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==", + "optional": true + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -6931,6 +6977,22 @@ "react": "^18.2.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", @@ -7008,6 +7070,85 @@ "react-dom": ">=16.8.1" } }, + "node_modules/react-html-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-html-parser/-/react-html-parser-2.0.2.tgz", + "integrity": "sha512-XeerLwCVjTs3njZcgCOeDUqLgNIt/t+6Jgi5/qPsO/krUWl76kWKXMeVs2LhY2gwM6X378DkhLjur0zUQdpz0g==", + "dependencies": { + "htmlparser2": "^3.9.0" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16.0.0-0" + } + }, + "node_modules/react-html-parser/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/react-html-parser/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/react-html-parser/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/react-html-parser/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/react-html-parser/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/react-html-parser/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/react-html-parser/node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "node_modules/react-html-parser/node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -7393,6 +7534,19 @@ "node": ">=8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7699,8 +7853,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "peer": true + ] }, "node_modules/safe-regex-test": { "version": "1.0.3", @@ -8079,6 +8232,14 @@ "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -8595,6 +8756,11 @@ "json5": "lib/cli.js" } }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index e3b9958fe19e..169a8fbc8a05 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,8 @@ "chart.js": "^3.5.1", "classnames": "^2.3.1", "core-js": "^3.18.3", + "dompurify": "^3.1.6", + "eml-parse-js": "^1.1.14", "enzyme": "^3.11.0", "final-form": "^4.20.4", "final-form-arrays": "^3.1.0", @@ -69,11 +71,13 @@ "react-data-table-component": "^7.4.5", "react-datepicker": "^4.10.0", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-final-form": "^6.5.9", "react-final-form-arrays": "^3.1.4", "react-final-form-listeners": "^1.0.3", "react-helmet-async": "^1.3.0", "react-hotkeys-hook": "^3.4.4", + "react-html-parser": "^2.0.2", "react-loading-skeleton": "^3.1.0", "react-masonry-component": "^6.3.0", "react-media-hook": "^0.4.9", @@ -88,7 +92,7 @@ "redux-persist": "^6.0.0", "simplebar-react": "^2.3.6", "source-map-loader": "^3.0.0", - "styled-components": "^5.3.3" + "styled-components": "^5.3.11" }, "devDependencies": { "@types/react": "^18.2.39", diff --git a/src/_nav.jsx b/src/_nav.jsx index c83f7f179148..59d2980e2ae1 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -687,6 +687,11 @@ const _nav = [ name: 'Mail Test', to: '/email/tools/mail-test', }, + { + component: CNavItem, + name: 'Message Viewer', + to: '/email/tools/message-viewer', + }, ], }, { diff --git a/src/components/utilities/CippDropzone.jsx b/src/components/utilities/CippDropzone.jsx new file mode 100644 index 000000000000..e42ab066ae6f --- /dev/null +++ b/src/components/utilities/CippDropzone.jsx @@ -0,0 +1,78 @@ +import React, { useCallback, useMemo, useState } from 'react' +import PropTypes from 'prop-types' +import { CippContentCard } from 'src/components/layout' +import { useDropzone } from 'react-dropzone' +import styled from 'styled-components' +import { useMediaPredicate } from 'react-media-hook' +import { useSelector } from 'react-redux' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +const getColor = (props) => { + if (props.isDragAccept) { + return '#00e676' + } + if (props.isDragReject) { + return '#ff1744' + } + if (props.isFocused) { + return '#2196f3' + } + return '#eeeeee' +} + +const BackgroundColor = () => { + const currentTheme = useSelector((state) => state.app.currentTheme) + const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'impact' : 'cyberdrain' + const isDark = + currentTheme === 'impact' || (currentTheme === 'default' && preferredTheme === 'impact') + + if (isDark) { + return '#333' + } else { + return '#fafafa' + } +} + +const Container = styled.div` + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + border-width: 2px; + border-radius: 2px; + border-color: ${(props) => getColor(props)}; + border-style: dashed; + background-color: ${() => BackgroundColor()}; + color: #bdbdbd; + outline: none; + transition: border 0.24s ease-in-out; +` + +const CippDropzone = ({ title, onDrop, dropMessage, accept, maxFiles = 1, ...props }) => { + const { getRootProps, getInputProps, isFocused, isDragAccept, isDragReject } = useDropzone({ + onDrop, + accept: accept, + maxFiles: maxFiles, + }) + return ( + +
+ + + {dropMessage} + +
+
+ ) +} + +CippDropzone.propTypes = { + title: PropTypes.string, + onDrop: PropTypes.func.isRequired, + dropMessage: PropTypes.string, + accept: PropTypes.object, + maxFiles: PropTypes.number, +} + +export default CippDropzone diff --git a/src/importsMap.jsx b/src/importsMap.jsx index 21174e32a0f2..f7c2a6e83234 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -115,6 +115,7 @@ import React from 'react' "/email/tools/mailbox-restore-wizard": React.lazy(() => import('./views/email-exchange/tools/MailboxRestoreWizard')), "/email/tools/mailbox-restores": React.lazy(() => import('./views/email-exchange/tools/MailboxRestores')), "/email/tools/mail-test": React.lazy(() => import('./views/email-exchange/tools/MailTest')), + "/email/tools/message-viewer": React.lazy(() => import('./views/email-exchange/tools/MessageViewer')), "/email/spamfilter/add-template": React.lazy(() => import('./views/email-exchange/spamfilter/AddSpamfilterTemplate')), "/email/administration/edit-mailbox-permissions": React.lazy(() => import('./views/email-exchange/administration/EditMailboxPermissions')), "/email/administration/add-shared-mailbox": React.lazy(() => import('./views/email-exchange/administration/AddSharedMailbox')), diff --git a/src/routes.json b/src/routes.json index c0a996610e41..be956782af6d 100644 --- a/src/routes.json +++ b/src/routes.json @@ -776,6 +776,12 @@ "component": "views/email-exchange/tools/MailTest", "allowedRoles": ["admin", "editor", "readonly"] }, + { + "path": "/email/tools/message-viewer", + "name": "Message Viewer", + "component": "views/email-exchange/tools/MessageViewer", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/email/spamfilter/add-template", "name": "Add Spamfilter Template", diff --git a/src/views/email-exchange/tools/MessageViewer.jsx b/src/views/email-exchange/tools/MessageViewer.jsx new file mode 100644 index 000000000000..314762758830 --- /dev/null +++ b/src/views/email-exchange/tools/MessageViewer.jsx @@ -0,0 +1,310 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import PropTypes from 'prop-types' +import { CippPage, CippMasonry, CippMasonryItem, CippContentCard } from 'src/components/layout' +import { parseEml, readEml, GBKUTF8, decode } from 'eml-parse-js' +import { useMediaPredicate } from 'react-media-hook' +import { useSelector } from 'react-redux' +import { CellDate } from 'src/components/tables' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { + CButton, + CCard, + CCardBody, + CCol, + CDropdown, + CDropdownMenu, + CDropdownToggle, + CLink, + CRow, +} from '@coreui/react' +import ReactTimeAgo from 'react-time-ago' +import { CippCodeBlock, ModalService } from 'src/components/utilities' +import DOMPurify from 'dompurify' +import ReactHtmlParser from 'react-html-parser' +import CippDropzone from 'src/components/utilities/CippDropzone' + +const MessageViewer = ({ emlFile }) => { + const [emlContent, setEmlContent] = useState(null) + const [emailSource, setEmailSource] = useState(emlFile) + const [emlError, setEmlError] = useState(false) + const [messageHtml, setMessageHtml] = useState('') + + const getAttachmentIcon = (contentType) => { + if (contentType.includes('image')) { + return 'image' + } else if (contentType.includes('audio')) { + return 'volume-up' + } else if (contentType.includes('video')) { + return 'video' + } else if (contentType.includes('text')) { + return 'file-lines' + } else if (contentType.includes('pdf')) { + return 'file-pdf' + } else if ( + contentType.includes('zip') || + contentType.includes('compressed') || + contentType.includes('tar') || + contentType.includes('gzip') + ) { + return 'file-zipper' + } else if (contentType.includes('msword')) { + return 'file-word' + } else if (contentType.includes('spreadsheet')) { + return 'file-excel' + } else if (contentType.includes('presentation')) { + return 'file-powerpoint' + } else if (contentType.includes('json') || contentType.includes('xml')) { + return 'file-code' + } else if (contentType.includes('rfc822')) { + return 'envelope' + } else { + return 'file' + } + } + + const downloadAttachment = (attachment, newTab = false) => { + if (attachment?.data) { + var contentType = attachment?.contentType?.split(';')[0] ?? 'text/plain' + var fileBytes = attachment.data + var fileName = attachment.name + downloadFileBytes(fileName, fileBytes, contentType, newTab) + } else { + downloadFile(attachment.name, attachment.data64) + } + } + + const downloadFile = (fileName, base64Content, newTab) => { + const link = document.createElement('a') + link.href = `data:application/octet-stream;base64,${base64Content}` + link.download = fileName + link.click() + } + + const downloadFileBytes = (fileName, fileBytes, contentType, newTab = false) => { + const blob = new Blob([fileBytes], { type: contentType ?? 'application/octet-stream' }) + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + if (newTab) { + if (contentType.includes('rfc822')) { + var content = fileBytes + const nestedMessage = + ModalService.open({ + body: nestedMessage, + title: fileName, + size: 'lg', + }) + } else { + const newWindow = window.open() + newWindow.location.href = url + } + } else { + link.href = url + link.download = fileName + link.click() + URL.revokeObjectURL(url) + } + } + + function isValidDate(d) { + return d instanceof Date && !isNaN(d) + } + + const showEmailModal = (emailSource) => { + ModalService.open({ + data: emailSource, + componentType: 'codeblock', + title: 'Email Source', + size: 'lg', + }) + } + + const EmailButtons = (emailSource) => { + return ( + showEmailModal(emailSource)}> + + View Source + + ) + } + + useEffect(() => { + readEml(emailSource, (err, ReadEmlJson) => { + if (err) { + setEmlError(true) + setEmlContent(null) + setMessageHtml(null) + } else { + setEmlContent(ReadEmlJson) + setEmlError(false) + if (ReadEmlJson.html) { + var sanitizedHtml = DOMPurify.sanitize(ReadEmlJson.html) + var parsedHtml = ReactHtmlParser(sanitizedHtml) + setMessageHtml(parsedHtml) + } else { + setMessageHtml(null) + } + } + }) + }, [emailSource, setMessageHtml, setEmailSource, setEmlError, setEmlContent]) + + var buttons = EmailButtons(emailSource) + + return ( + <> + {emlError && ( + + Unable to parse the EML file, email source is displayed below. + + + )} + + {emlContent && ( + <> + + <> + + +
+ + {emlContent?.from?.name} <{emlContent?.from?.email}> +
+ {emlContent?.to?.length > 0 && ( +
+ + To:{' '} + {emlContent?.to?.map((to) => to.name + ' <' + to.email + '>').join(', ')} + +
+ )} + {emlContent?.cc?.length > 0 && ( +
+ + CC:{' '} + {emlContent?.cc?.map((cc) => cc.name + ' <' + cc.email + '>').join(', ')} + +
+ )} +
+ +
+ + + {emlContent.date && isValidDate(emlContent.date) + ? emlContent.date.toLocaleDateString() + : 'Invalid Date'} + + {emlContent.date && isValidDate(emlContent.date) && ( + <> + () + + )} + +
+
+
+ + + {emlContent.attachments && emlContent.attachments.length > 0 && ( + + + {emlContent.attachments.map((attachment, index) => ( + + + + {attachment.name ?? 'No name'} + + + downloadAttachment(attachment)} + > + + Download + + {(attachment?.contentType === undefined || + attachment?.contentType?.includes('text') || + attachment?.contentType?.includes('pdf') || + attachment?.contentType?.includes('image') || + attachment?.contentType?.includes('rfc822')) && ( + downloadAttachment(attachment, true)} + > + + View + + )} + + + ))} + + + )} + + {(emlContent?.text || emlContent?.html) && ( + + + {messageHtml ? ( +
{messageHtml}
+ ) : ( +
+ +
+ )} +
+
+ )} +
+ + )} + + ) +} + +MessageViewer.propTypes = { + emlFile: PropTypes.string, +} + +const MessageViewerPage = () => { + const [emlFile, setEmlFile] = useState(null) + const onDrop = useCallback((acceptedFiles) => { + acceptedFiles.forEach((file) => { + const reader = new FileReader() + + reader.onabort = () => console.log('file reading was aborted') + reader.onerror = () => console.log('file reading has failed') + reader.onload = () => { + setEmlFile(reader.result) + } + reader.readAsText(file) + }) + }, []) + + return ( + + + {emlFile && } + + ) +} + +export default MessageViewerPage From 9b51796b206dde86252543a00c4bebaf690d81b1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Wed, 31 Jul 2024 23:33:12 +0200 Subject: [PATCH 13/37] upgrade user schedulder experience. --- src/views/identity/administration/AddUser.jsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/views/identity/administration/AddUser.jsx b/src/views/identity/administration/AddUser.jsx index 96186c358ed9..ad772b208d1a 100644 --- a/src/views/identity/administration/AddUser.jsx +++ b/src/views/identity/administration/AddUser.jsx @@ -37,8 +37,12 @@ import useQuery from 'src/hooks/useQuery' import Select from 'react-select' import { useNavigate } from 'react-router-dom' import { OnChange } from 'react-final-form-listeners' +import DatePicker from 'react-datepicker' +import 'react-datepicker/dist/react-datepicker.css' const AddUser = () => { + const currentDate = new Date() + const [startDate, setStartDate] = useState(currentDate) let navigate = useNavigate() const [addedAttributes, setAddedAttribute] = React.useState(0) const tenant = useSelector((state) => state.app.currentTenant) @@ -81,6 +85,8 @@ const AddUser = () => { values.addedAttributes.push({ Key: key, Value: values.defaultAttributes[key].Value }) }) } + const unixTime = Math.floor(startDate.getTime() / 1000) + const shippedValues = { AddedAliases: values.addedAliases ? values.addedAliases : '', BusinessPhone: values.businessPhones, @@ -106,6 +112,10 @@ const AddUser = () => { tenantID: tenantDomain, addedAttributes: values.addedAttributes, setManager: values.setManager, + Scheduled: values.Scheduled?.enabled ? { enabled: true, date: unixTime } : { enabled: false }, + PostExecution: values.Scheduled?.enabled + ? { webhook: values.webhook, psa: values.psa, email: values.email } + : '', ...values.license, } //window.alert(JSON.stringify(shippedValues)) @@ -408,6 +418,33 @@ const AddUser = () => { /> {usersError && Failed to load list of users} + + + + + + + + + setStartDate(date)} + /> + + + + + + + + + + From 3f20d802064b8322a32aa9fad0dfe129c321714a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 31 Jul 2024 19:42:03 -0400 Subject: [PATCH 14/37] message view bugfixes --- .../email-exchange/tools/MessageViewer.jsx | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/views/email-exchange/tools/MessageViewer.jsx b/src/views/email-exchange/tools/MessageViewer.jsx index 314762758830..52d7c3b637a4 100644 --- a/src/views/email-exchange/tools/MessageViewer.jsx +++ b/src/views/email-exchange/tools/MessageViewer.jsx @@ -23,9 +23,8 @@ import DOMPurify from 'dompurify' import ReactHtmlParser from 'react-html-parser' import CippDropzone from 'src/components/utilities/CippDropzone' -const MessageViewer = ({ emlFile }) => { +const MessageViewer = ({ emailSource }) => { const [emlContent, setEmlContent] = useState(null) - const [emailSource, setEmailSource] = useState(emlFile) const [emlError, setEmlError] = useState(false) const [messageHtml, setMessageHtml] = useState('') @@ -63,39 +62,57 @@ const MessageViewer = ({ emlFile }) => { } const downloadAttachment = (attachment, newTab = false) => { - if (attachment?.data) { - var contentType = attachment?.contentType?.split(';')[0] ?? 'text/plain' - var fileBytes = attachment.data - var fileName = attachment.name - downloadFileBytes(fileName, fileBytes, contentType, newTab) - } else { - downloadFile(attachment.name, attachment.data64) + var contentType = attachment?.contentType?.split(';')[0] ?? 'text/plain' + var fileBytes = attachment.data + if (fileBytes instanceof Uint8Array && attachment?.data64) { + fileBytes = new Uint8Array( + atob(attachment.data64) + .split('') + .map((c) => c.charCodeAt(0)), + ) } - } - - const downloadFile = (fileName, base64Content, newTab) => { - const link = document.createElement('a') - link.href = `data:application/octet-stream;base64,${base64Content}` - link.download = fileName - link.click() - } - - const downloadFileBytes = (fileName, fileBytes, contentType, newTab = false) => { + var fileName = attachment.name const blob = new Blob([fileBytes], { type: contentType ?? 'application/octet-stream' }) const url = URL.createObjectURL(blob) const link = document.createElement('a') if (newTab) { if (contentType.includes('rfc822')) { var content = fileBytes - const nestedMessage = + const nestedMessage = ModalService.open({ body: nestedMessage, title: fileName, size: 'lg', }) + } else if (contentType.includes('pdf')) { + const embeddedPdf = + ModalService.open({ + body: embeddedPdf, + title: fileName, + size: 'lg', + }) + } else if (contentType.includes('image')) { + const embeddedImage = {fileName} + ModalService.open({ + body: embeddedImage, + title: fileName, + size: 'lg', + }) + } else if (contentType.includes('text')) { + const textContent = fileBytes + ModalService.open({ + data: textContent, + componentType: 'codeblock', + title: fileName, + size: 'lg', + }) + setTimeout(() => { + URL.revokeObjectURL(url) + }, 1000) } else { const newWindow = window.open() newWindow.location.href = url + URL.revokeObjectURL(url) } } else { link.href = url @@ -145,7 +162,7 @@ const MessageViewer = ({ emlFile }) => { } } }) - }, [emailSource, setMessageHtml, setEmailSource, setEmlError, setEmlContent]) + }, [emailSource, setMessageHtml, setEmlError, setEmlContent]) var buttons = EmailButtons(emailSource) @@ -238,7 +255,7 @@ const MessageViewer = ({ emlFile }) => { className="dropdown-item" onClick={() => downloadAttachment(attachment, true)} > - + View )} @@ -274,7 +291,7 @@ const MessageViewer = ({ emlFile }) => { } MessageViewer.propTypes = { - emlFile: PropTypes.string, + emailSource: PropTypes.string, } const MessageViewerPage = () => { @@ -282,7 +299,6 @@ const MessageViewerPage = () => { const onDrop = useCallback((acceptedFiles) => { acceptedFiles.forEach((file) => { const reader = new FileReader() - reader.onabort = () => console.log('file reading was aborted') reader.onerror = () => console.log('file reading has failed') reader.onload = () => { @@ -302,7 +318,7 @@ const MessageViewerPage = () => { dropMessage="Drag an EML file or click to add" maxFiles={1} /> - {emlFile && } + {emlFile && } ) } From cff54a8fbd26c98f154a9cade3af684cba243d10 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 31 Jul 2024 20:00:02 -0400 Subject: [PATCH 15/37] add blob: to content-security-policy --- staticwebapp.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staticwebapp.config.json b/staticwebapp.config.json index 8a0fafca07d4..74988468595b 100644 --- a/staticwebapp.config.json +++ b/staticwebapp.config.json @@ -103,7 +103,7 @@ } }, "globalHeaders": { - "content-security-policy": "default-src https: 'unsafe-eval' 'unsafe-inline'; object-src 'none'; img-src 'self' data: *" + "content-security-policy": "default-src https: 'unsafe-eval' 'unsafe-inline'; object-src 'self' blob:; img-src 'self' blob: data: *" }, "mimeTypes": { ".json": "text/json" From ff56eef918f8e04122c6983bc67c51680185845a Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 31 Jul 2024 20:05:33 -0400 Subject: [PATCH 16/37] add blob to default-src --- staticwebapp.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staticwebapp.config.json b/staticwebapp.config.json index 74988468595b..0c36ddcac257 100644 --- a/staticwebapp.config.json +++ b/staticwebapp.config.json @@ -103,7 +103,7 @@ } }, "globalHeaders": { - "content-security-policy": "default-src https: 'unsafe-eval' 'unsafe-inline'; object-src 'self' blob:; img-src 'self' blob: data: *" + "content-security-policy": "default-src https: blob: 'unsafe-eval' 'unsafe-inline'; object-src 'self' blob:; img-src 'self' blob: data: *" }, "mimeTypes": { ".json": "text/json" From f978031b21ed9b612e57cbeb32b47ea122e28b44 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 31 Jul 2024 20:19:16 -0400 Subject: [PATCH 17/37] sanitize secure score html --- .../tenant/administration/SecureScore.jsx | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/views/tenant/administration/SecureScore.jsx b/src/views/tenant/administration/SecureScore.jsx index be036997339c..bfa9d2e751c2 100644 --- a/src/views/tenant/administration/SecureScore.jsx +++ b/src/views/tenant/administration/SecureScore.jsx @@ -28,6 +28,8 @@ import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGeneric import { CippCallout } from 'src/components/layout' import CippPrettyCard from 'src/components/contentcards/CippPrettyCard' import { TableModalButton } from 'src/components/buttons' +import DOMPurify from 'dompurify' +import ReactHtmlParser from 'react-html-parser' const SecureScore = () => { const textRef = useRef() @@ -66,6 +68,12 @@ const SecureScore = () => { }, }) + const sanitizeHtml = (html) => { + var sanitizedHtml = DOMPurify.sanitize(html) + var parsedHtml = ReactHtmlParser(sanitizedHtml) + return parsedHtml + } + useEffect(() => { if (isSuccess) { setTranslatedData(securescore.Results[0]) @@ -341,23 +349,16 @@ const SecureScore = () => {
Description
-
+
+ {sanitizeHtml(`${info.description} ${info.implementationStatus}`)} +
{info.scoreInPercentage !== 100 && (
Remediation Recommendation
- { -
- } + {
{sanitizeHtml(info.remediation)}
}
)} From e4e5ab15c86afa76f2e77028728e1bb599012c6b Mon Sep 17 00:00:00 2001 From: John Duprey Date: Wed, 31 Jul 2024 20:44:54 -0400 Subject: [PATCH 18/37] Fix revokesession bulk action --- src/views/identity/administration/Users.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/identity/administration/Users.jsx b/src/views/identity/administration/Users.jsx index 3bd663d7e063..669e401a3611 100644 --- a/src/views/identity/administration/Users.jsx +++ b/src/views/identity/administration/Users.jsx @@ -589,7 +589,7 @@ const Users = (row) => { label: 'Revoke sessions', color: 'info', modal: true, - modalUrl: `/api/ExecRevokeSessions?Enable=true&TenantFilter=!Tenant&ID=!userPrincipalName`, + modalUrl: `/api/ExecRevokeSessions?Enable=true&TenantFilter=!Tenant&ID=!id&Username=!userPrincipalName`, modalMessage: 'Are you sure you want to revoke all sessions for these users?', }, { From 8be3b6d85b95ef903ad1522d667483b4c845cbed Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 1 Aug 2024 01:11:50 -0400 Subject: [PATCH 19/37] SAM Roles --- .../cipp/app-settings/SettingsSuperAdmin.jsx | 38 +---- .../components/SettingsSAMRoles.jsx | 133 ++++++++++++++++++ 2 files changed, 135 insertions(+), 36 deletions(-) create mode 100644 src/views/cipp/app-settings/components/SettingsSAMRoles.jsx diff --git a/src/views/cipp/app-settings/SettingsSuperAdmin.jsx b/src/views/cipp/app-settings/SettingsSuperAdmin.jsx index 4e38038fb68c..18cb6b397980 100644 --- a/src/views/cipp/app-settings/SettingsSuperAdmin.jsx +++ b/src/views/cipp/app-settings/SettingsSuperAdmin.jsx @@ -7,6 +7,7 @@ import { CippCallout } from 'src/components/layout/index.js' import CippAccordionItem from 'src/components/contentcards/CippAccordionItem' import SettingsCustomRoles from 'src/views/cipp/app-settings/components/SettingsCustomRoles' import CippButtonCard from 'src/components/contentcards/CippButtonCard' +import SettingsSAMRoles from './components/SettingsSAMRoles' export function SettingsSuperAdmin() { const partnerConfig = useGenericGetRequestQuery({ @@ -65,46 +66,11 @@ export function SettingsSuperAdmin() {

- - -

Tenant Mode

-
( - <> - {partnerConfig.isFetching && } - - - - - - - )} - /> - {webhookCreateResult.isSuccess && ( - - {webhookCreateResult?.data?.results} - - )} - - + ) } diff --git a/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx b/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx new file mode 100644 index 000000000000..cb687914def3 --- /dev/null +++ b/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx @@ -0,0 +1,133 @@ +import React, { useRef, useState } from 'react' +import { + CButton, + CCallout, + CCol, + CForm, + CRow, + CAccordion, + CAccordionHeader, + CAccordionBody, + CAccordionItem, +} from '@coreui/react' +import { Field, Form, FormSpy } from 'react-final-form' +import { RFFCFormRadioList, RFFSelectSearch } from 'src/components/forms' +import { useGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { TenantSelectorMultiple, ModalService, CippOffcanvas } from 'src/components/utilities' +import PropTypes from 'prop-types' +import { OnChange } from 'react-final-form-listeners' +import { useListTenantsQuery } from 'src/store/api/tenants' +import { OffcanvasListSection } from 'src/components/utilities/CippListOffcanvas' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' +import GDAPRoles from 'src/data/GDAPRoles' + +const SettingsSAMRoles = () => { + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const [selectedTenant, setSelectedTenant] = useState([]) + const tenantSelectorRef = useRef() + const { data: tenants = [], tenantsFetching } = useListTenantsQuery({ + showAllTenantSelector: true, + }) + + const { + data: cippSAMRoles = [], + isFetching: roleListFetching, + isSuccess: roleListSuccess, + refetch: refetchRoleList, + } = useGenericGetRequestQuery({ + path: 'api/ExecSAMRoles', + }) + + const handleTenantChange = (e) => { + setSelectedTenant(e) + } + + const handleSubmit = async (values) => { + //filter on only objects that are 'true' + genericPostRequest({ + path: '/api/ExecSAMRoles?Action=Update', + values: { + Roles: values.Roles, + Tenants: selectedTenant.map((tenant) => tenant.value), + }, + }).then(() => { + refetchRoleList() + }) + } + + return ( + + <> +

+ Add your CIPP-SAM application Service Principal directly to Admin Roles in the tenant. + This is an advanced use case where you need access to additional Graph endpoints or + Exchange Cmdlets otherwise unavailable via Delegated permissions. +

+

+ This functionality is in + beta and should be treated as such. Roles are added during the Update Permissions process + or a CPV refresh. +

+ + { + return ( + + + +
+ ({ + name: role.Name, + value: role.ObjectId, + }))} + isLoading={roleListFetching} + multi={true} + refreshFunction={() => refetchRoleList()} + placeholder="Select admin roles" + /> +
+
+
Selected Tenants
+ handleTenantChange(e)} + /> +
+
+
+ + {postResults.isSuccess && ( + {postResults.data.Results} + )} + + + + + Save + + + + +
+ ) + }} + /> + +
+ ) +} + +export default SettingsSAMRoles From 8ac22e9abecb8a95a922bf88d0586dc21dc97509 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 1 Aug 2024 12:06:34 +0200 Subject: [PATCH 20/37] add device compliance alert --- src/data/alerts.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/data/alerts.json b/src/data/alerts.json index 2d635fb529f9..835216740c4d 100644 --- a/src/data/alerts.json +++ b/src/data/alerts.json @@ -94,5 +94,10 @@ "name": "SoftDeletedMailboxes", "label": "Alert on soft deleted mailboxes", "recommendedRunInterval": "1d" + }, + { + "name": "DeviceCompliance", + "label": "Alert on device compliance issues", + "recommendedRunInterval": "4h" } -] \ No newline at end of file +] From 2cb908a8d2aed95491cd7aad18773186d2da3ddc Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 1 Aug 2024 12:49:37 +0200 Subject: [PATCH 21/37] fixes https://github.com/KelvinTegelaar/CIPP/issues/2710 --- src/views/identity/administration/RiskyUsers.jsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/views/identity/administration/RiskyUsers.jsx b/src/views/identity/administration/RiskyUsers.jsx index d51a899bef85..0b8ae07e7e8d 100644 --- a/src/views/identity/administration/RiskyUsers.jsx +++ b/src/views/identity/administration/RiskyUsers.jsx @@ -88,6 +88,20 @@ const RiskyUsers = () => { } const columns = [ + { + name: 'Tenant', + selector: (row) => row['Tenant'], + sortable: true, + exportSelector: 'Tenant', + omit: tenant.defaultDomainName === 'allTenants' ? false : true, + }, + { + name: 'Status', + selector: (row) => row['CippStatus'], + sortable: true, + exportSelector: 'CippStatus', + omit: tenant.defaultDomainName === 'allTenants' ? false : true, + }, { name: 'Risk Last Updated Date', selector: (row) => row['riskLastUpdatedDateTime'], From f79e0c819db99857856e73a35ca2bdc0649326e4 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 1 Aug 2024 12:52:38 +0200 Subject: [PATCH 22/37] fixes https://github.com/KelvinTegelaar/CIPP/issues/2710 --- src/views/identity/administration/RiskyUsers.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/views/identity/administration/RiskyUsers.jsx b/src/views/identity/administration/RiskyUsers.jsx index 0b8ae07e7e8d..9d0b949b135a 100644 --- a/src/views/identity/administration/RiskyUsers.jsx +++ b/src/views/identity/administration/RiskyUsers.jsx @@ -93,14 +93,14 @@ const RiskyUsers = () => { selector: (row) => row['Tenant'], sortable: true, exportSelector: 'Tenant', - omit: tenant.defaultDomainName === 'allTenants' ? false : true, + omit: tenant.defaultDomainName === 'AllTenants' ? false : true, }, { name: 'Status', selector: (row) => row['CippStatus'], sortable: true, exportSelector: 'CippStatus', - omit: tenant.defaultDomainName === 'allTenants' ? false : true, + omit: tenant.defaultDomainName === 'AllTenants' ? false : true, }, { name: 'Risk Last Updated Date', From 06ba66bea0963ab7996d7dedbda428968daa9005 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 1 Aug 2024 08:35:25 -0400 Subject: [PATCH 23/37] Fix tenant selector --- .../components/SettingsSAMRoles.jsx | 125 ++++++++++-------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx b/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx index cb687914def3..ebc8310b6abd 100644 --- a/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx +++ b/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { CButton, CCallout, @@ -26,7 +26,11 @@ const SettingsSAMRoles = () => { const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [selectedTenant, setSelectedTenant] = useState([]) const tenantSelectorRef = useRef() - const { data: tenants = [], tenantsFetching } = useListTenantsQuery({ + const { + data: tenants = [], + isFetching: tenantsFetching, + isSuccess: tenantSuccess, + } = useListTenantsQuery({ showAllTenantSelector: true, }) @@ -56,8 +60,20 @@ const SettingsSAMRoles = () => { }) } + useEffect(() => { + if (roleListSuccess && cippSAMRoles.Tenants.length > 0) { + var selectedTenants = [] + tenants.map((tenant) => { + if (cippSAMRoles.Tenants.includes(tenant.customerId)) { + selectedTenants.push({ label: tenant.displayName, value: tenant.customerId }) + } + }) + tenantSelectorRef.current.setValue(selectedTenants) + } + }, [cippSAMRoles, roleListSuccess, tenantSuccess, tenantSelectorRef, tenants]) + return ( - + <>

Add your CIPP-SAM application Service Principal directly to Admin Roles in the tenant. @@ -70,61 +86,62 @@ const SettingsSAMRoles = () => { or a CPV refresh.

- { - return ( - - - -
- ({ - name: role.Name, - value: role.ObjectId, - }))} - isLoading={roleListFetching} - multi={true} - refreshFunction={() => refetchRoleList()} - placeholder="Select admin roles" - /> -
-
-
Selected Tenants
- handleTenantChange(e)} - /> -
-
-
- - {postResults.isSuccess && ( - {postResults.data.Results} - )} + {roleListSuccess && ( + { + return ( + - - - +
+ ({ + name: role.Name, + value: role.ObjectId, + }))} + multi={true} + refreshFunction={() => refetchRoleList()} + placeholder="Select admin roles" /> - Save - +
+
+
Selected Tenants
+ handleTenantChange(e)} + /> +
-
-
- ) - }} - /> + + {postResults.isSuccess && ( + {postResults.data.Results} + )} + + + + + Save + + + + + + ) + }} + /> + )}
) From 47e8e7f59feb14bdc2439a85b948df943317fbc1 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 1 Aug 2024 15:29:53 +0200 Subject: [PATCH 24/37] Add edit named locations --- .../tenant/conditional/NamedLocations.jsx | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/views/tenant/conditional/NamedLocations.jsx b/src/views/tenant/conditional/NamedLocations.jsx index b7f1ebb0f68c..817528a2078c 100644 --- a/src/views/tenant/conditional/NamedLocations.jsx +++ b/src/views/tenant/conditional/NamedLocations.jsx @@ -22,6 +22,88 @@ function DateNotNull(date) { return date.toString().trim() + 'Z' } +const Offcanvas = (row, rowIndex, formatExtraData) => { + const tenant = useSelector((state) => state.app.currentTenant) + const [ocVisible, setOCVisible] = useState(false) + return ( + <> + setOCVisible(true)}> + + + setOCVisible(false)} + /> + + ) +} const columns = [ { name: 'Name', @@ -62,6 +144,11 @@ const columns = [ exportSelector: 'modifiedDateTime', maxWidth: '150px', }, + { + name: 'Actions', + cell: Offcanvas, + maxWidth: '80px', + }, ] const NamedLocationsList = () => { From c70d679fe0570187774a9a98fb5a912feefd5ebc Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 1 Aug 2024 15:43:52 +0200 Subject: [PATCH 25/37] test --- src/views/cipp/ExtensionMappings.jsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/views/cipp/ExtensionMappings.jsx b/src/views/cipp/ExtensionMappings.jsx index 4de976555ccc..816a679e9905 100644 --- a/src/views/cipp/ExtensionMappings.jsx +++ b/src/views/cipp/ExtensionMappings.jsx @@ -218,11 +218,7 @@ export default function ExtensionMappings({ type, fieldMappings = false, autoMap { - return !Object.keys(listMappingBackendResult.data?.Mappings).includes( - tenant.customerId, - ) - }).map((tenant) => ({ + values={listMappingBackendResult.data?.Tenants.map((tenant) => ({ name: tenant.displayName, value: tenant.customerId, }))} From 930f99700d2b3d1c3c20c800d202d3ece43f458c Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 1 Aug 2024 15:48:47 +0200 Subject: [PATCH 26/37] test fix for extension multi mappings --- src/views/cipp/ExtensionMappings.jsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/views/cipp/ExtensionMappings.jsx b/src/views/cipp/ExtensionMappings.jsx index 816a679e9905..5bcf9e23e8e8 100644 --- a/src/views/cipp/ExtensionMappings.jsx +++ b/src/views/cipp/ExtensionMappings.jsx @@ -218,7 +218,11 @@ export default function ExtensionMappings({ type, fieldMappings = false, autoMap ({ + values={listMappingBackendResult.data?.Tenants.filter((tenant) => { + return !Object.keys(listMappingBackendResult.data?.Mappings).includes( + tenant.customerId, + ) + }).map((tenant) => ({ name: tenant.displayName, value: tenant.customerId, }))} @@ -247,9 +251,7 @@ export default function ExtensionMappings({ type, fieldMappings = false, autoMap if ( mappingValue.value !== undefined && mappingValue.value !== '-1' && - Object.values(mappingArray) - .map((item) => item.companyId) - .includes(mappingValue.value) === false + Object.values(mappingArray).map((item) => item.companyId) ) { setMappingArray([ ...mappingArray, From 1d94b1e20fa67bc9e2f54de62462cb2c61c65360 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Thu, 1 Aug 2024 15:57:45 +0200 Subject: [PATCH 27/37] fallback to actual id --- src/components/tables/CellLicense.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/tables/CellLicense.jsx b/src/components/tables/CellLicense.jsx index 50b88b978c4f..ff5df5329743 100644 --- a/src/components/tables/CellLicense.jsx +++ b/src/components/tables/CellLicense.jsx @@ -8,6 +8,8 @@ export function CellLicense({ cell }) { if (licenseAssignment.skuId == M365Licenses[x].GUID) { licenses.push(M365Licenses[x].Product_Display_Name) break + } else { + licenses.push(licenseAssignment.skuId) } } }) From ed1e05be4004d18af54533032feb522e000532b0 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 1 Aug 2024 14:14:25 -0400 Subject: [PATCH 28/37] Audit logs --- src/_nav.jsx | 9 +- src/importsMap.jsx | 3 +- src/routes.json | 10 +- .../tenant/administration/ListAlertsQueue.jsx | 2 +- .../tenant/administration/ListAuditLogs.jsx | 233 ++++++++++++++++++ 5 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 src/views/tenant/administration/ListAuditLogs.jsx diff --git a/src/_nav.jsx b/src/_nav.jsx index 59d2980e2ae1..bc1505aa763e 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -145,8 +145,13 @@ const _nav = [ }, { component: CNavItem, - name: 'Alerts', - to: '/tenant/administration/alertsqueue', + name: 'Alert Configuration', + to: '/tenant/administration/alert-configuration', + }, + { + component: CNavItem, + name: 'Audit Logs', + to: '/tenant/administration/audit-logs', }, { component: CNavItem, diff --git a/src/importsMap.jsx b/src/importsMap.jsx index f7c2a6e83234..3708a44f4ed9 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -42,7 +42,8 @@ import React from 'react' "/tenant/administration/domains": React.lazy(() => import('./views/tenant/administration/Domains')), "/tenant/administration/alertswizard": React.lazy(() => import('./views/tenant/administration/AlertWizard')), "/tenant/administration/alertrules": React.lazy(() => import('./views/tenant/administration/AlertRules')), - "/tenant/administration/alertsqueue": React.lazy(() => import('./views/tenant/administration/ListAlertsQueue')), + "/tenant/administration/alert-configuration": React.lazy(() => import('./views/tenant/administration/ListAlertsQueue')), + "/tenant/administration/audit-logs": React.lazy(() => import('./views/tenant/administration/ListAuditLogs')), "/tenant/administration/graph-explorer": React.lazy(() => import('./views/tenant/administration/GraphExplorer')), "/tenant/administration/service-health": React.lazy(() => import('./views/tenant/administration/ServiceHealth')), "/tenant/administration/enterprise-apps": React.lazy(() => import('./views/tenant/administration/ListEnterpriseApps')), diff --git a/src/routes.json b/src/routes.json index be956782af6d..73f0383bbd20 100644 --- a/src/routes.json +++ b/src/routes.json @@ -279,11 +279,17 @@ "allowedRoles": ["admin", "editor", "readonly"] }, { - "path": "/tenant/administration/alertsqueue", - "name": "Alerts Queue", + "path": "/tenant/administration/alert-configuration", + "name": "Alert Configuration", "component": "views/tenant/administration/ListAlertsQueue", "allowedRoles": ["admin", "editor", "readonly"] }, + { + "path": "/tenant/administration/audit-logs", + "name": "Audit Logs", + "component": "views/tenant/administration/ListAuditLogs", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/tenant/administration/graph-explorer", "name": "Graph Explorer", diff --git a/src/views/tenant/administration/ListAlertsQueue.jsx b/src/views/tenant/administration/ListAlertsQueue.jsx index 7efe849c13a4..7a715188a824 100644 --- a/src/views/tenant/administration/ListAlertsQueue.jsx +++ b/src/views/tenant/administration/ListAlertsQueue.jsx @@ -65,7 +65,7 @@ const ListClassicAlerts = () => { allTenants: true, helpContext: 'https://google.com', }} - title="Alerts List" + title="Alert Configuration" titleButton={ { + const [interval, setInterval] = React.useState('d') + const [time, setTime] = React.useState(1) + const [relativeTime, setRelativeTime] = React.useState('1d') + const [startDate, setStartDate] = React.useState(null) + const [endDate, setEndDate] = React.useState(null) + const [visibleA, setVisibleA] = React.useState(true) + const [tenantColumnSet, setTenantColumn] = React.useState(false) + const tenant = useSelector((state) => state.app.currentTenant) + + useEffect(() => { + if (tenant.defaultDomainName === 'AllTenants') { + setTenantColumn(false) + } + if (tenant.defaultDomainName !== 'AllTenants') { + setTenantColumn(true) + } + }, [tenant.defaultDomainName, tenantColumnSet]) + + const handleSearch = (values) => { + if (values.dateFilter === 'relative') { + setRelativeTime(`${values.Time}${values.Interval}`) + setStartDate(null) + setEndDate(null) + } else if (values.dateFilter === 'startEnd') { + setRelativeTime(null) + setStartDate(values.startDate) + setEndDate(values.endDate) + } + setVisibleA(false) + } + + const Actions = () => { + return ( + + + + + + ) + } + + const columns = [ + { + name: 'Timestamp', + selector: (row) => row['Timestamp'], + sortable: true, + exportSelector: 'Timestamp', + cell: cellDateFormatter({ format: 'short' }), + maxWidth: '200px', + }, + { + name: 'Tenant', + selector: (row) => row['Tenant'], + exportSelector: 'Tenant', + omit: !tenantColumnSet, + cell: cellGenericFormatter(), + maxWidth: '150px', + }, + { + name: 'Title', + selector: (row) => row['Title'], + exportSelector: 'Title', + cell: cellGenericFormatter(), + }, + { + name: 'Actions', + cell: Actions, + maxWidth: '100px', + }, + ] + return ( +
+ + + + + + Search Options + setVisibleA(!visibleA)} + > + + + + + + + + + + { + return ( + + + + Date Filter Type +
+ +
+
+
+
+ + + + Relative Time + + + Last + + + {({ input, meta }) => } + + + {({ input, meta }) => ( + + + + + + )} + + + + + + + + + + + + + + + + + + + + Search + + + +
+ ) + }} + /> +
+
+
+
+
+
+ +
+ ) +} + +export default ListAuditLogs From 70853600b9183487bffe959e8168ae93b8e6d8eb Mon Sep 17 00:00:00 2001 From: John Duprey Date: Thu, 1 Aug 2024 19:40:38 -0400 Subject: [PATCH 29/37] Add refresh config following a save --- src/views/cipp/Extensions.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/views/cipp/Extensions.jsx b/src/views/cipp/Extensions.jsx index eec80f5a1068..5923599a6f66 100644 --- a/src/views/cipp/Extensions.jsx +++ b/src/views/cipp/Extensions.jsx @@ -39,6 +39,8 @@ export default function CIPPExtensions() { setExtensionconfig({ path: 'api/ExecExtensionsConfig', values: values, + }).then((res) => { + listBackend({ path: 'api/ListExtensionsConfig' }) }) } From aae375a43a93b2ca62889330c6c7f9583407f824 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 2 Aug 2024 12:23:34 +0200 Subject: [PATCH 30/37] updates to layout --- .../tenant/standards/ListAppliedStandards.jsx | 36 ++++--------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index f60370975ee6..d93d6609148c 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -751,23 +751,15 @@ const ApplyNewStandard = () => { }, ].map((template, index) => ( - +
{template.name}
Deploy {template.name}
- -
Report
- -
- -
Alert
- -
Remediate
- +
Settings
{template.templates.isSuccess && ( {
))} - +
Autopilot Profile
Deploy Autopilot profile
- -
Report
- -
- -
Alert
- -
Remediate
- +
Settings
@@ -938,23 +922,15 @@ const ApplyNewStandard = () => { - +
Autopilot Status Page
Deploy Autopilot Status Page
- -
Report
- -
- -
Alert
- -
Remediate
- +
Settings
From fb51fb943a128b52c05ecc8fa68cef2db29e6ca9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 2 Aug 2024 13:31:04 +0200 Subject: [PATCH 31/37] new standards apply templates for intune --- .../tenant/standards/ListAppliedStandards.jsx | 123 ++++++++++++++++-- 1 file changed, 112 insertions(+), 11 deletions(-) diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index d93d6609148c..d591ff4b7168 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -29,7 +29,12 @@ import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery, } from 'src/store/api/app' -import { faCheck, faCircleNotch, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' +import { + faCheck, + faCircleNotch, + faExclamationTriangle, + faTrash, +} from '@fortawesome/free-solid-svg-icons' import { CippCallout, CippContentCard, CippPage } from 'src/components/layout' import { useSelector } from 'react-redux' import { ModalService, validateAlphabeticalSort } from 'src/components/utilities' @@ -368,6 +373,32 @@ const ApplyNewStandard = () => { setEnabledWarningsCount, ]) + const handleAddIntuneTemplate = (form) => { + const formvalues = form.getState().values + const newTemplate = { + label: formvalues.intunedataList.label, + value: formvalues.intunedataList.value, + AssignedTo: + formvalues.IntuneAssignto === 'customGroup' + ? formvalues.customGroup + : formvalues.IntuneAssignto, + } + const originalTemplates = formvalues.standards?.IntuneTemplate?.TemplateList || [] + const updatedTemplateList = [...originalTemplates, newTemplate] + + form.change('standards.IntuneTemplate.TemplateList', updatedTemplateList) + form.change('intunedataList', undefined) + form.change('intuneAssignTo', undefined) + form.change('customGroup', undefined) + } + const handleRemoveDeployedTemplate = (form, row) => { + console.log(row) + const formvalues = form.getState().values + const updatedTemplateList = formvalues.standards.IntuneTemplate.TemplateList.filter( + (template) => template.value !== row.value, + ) + form.change('standards.IntuneTemplate.TemplateList', updatedTemplateList) + } return ( <> @@ -484,7 +515,7 @@ const ApplyNewStandard = () => { }, }} onSubmit={handleSubmit} - render={({ handleSubmit, submitting, values }) => { + render={({ handleSubmit, submitting, values, form }) => { return ( @@ -728,6 +759,7 @@ const ApplyNewStandard = () => { switchName: 'standards.IntuneTemplate', assignable: true, templates: intuneTemplates, + table: true, }, { name: 'Transport Rule Template', @@ -761,11 +793,53 @@ const ApplyNewStandard = () => {
Settings
+ {template.table && ( + row['label'], + sortable: true, + exportSelector: 'name', + cell: cellGenericFormatter(), + }, + { + name: 'Assigned to', + selector: (row) => row['AssignedTo'], + sortable: true, + exportSelector: 'GUID', + }, + { + name: 'Actions', + cell: (row) => ( + + handleRemoveDeployedTemplate(form, row) + } + > + + + ), + }, + ]} + /> + )} {template.templates.isSuccess && ( { <> + handleAddIntuneTemplate(form)}> + Add to deployment + )}
From 5facbd7cd4fd8a2c6a1ced151439d7cfe38f3dd9 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 2 Aug 2024 13:50:31 +0200 Subject: [PATCH 32/37] add remove of old undefined. --- src/views/tenant/standards/ListAppliedStandards.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index d591ff4b7168..10eab27c96fd 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -385,7 +385,7 @@ const ApplyNewStandard = () => { } const originalTemplates = formvalues.standards?.IntuneTemplate?.TemplateList || [] const updatedTemplateList = [...originalTemplates, newTemplate] - + form.change('standards.IntuneTemplate.AssignTo', undefined) form.change('standards.IntuneTemplate.TemplateList', updatedTemplateList) form.change('intunedataList', undefined) form.change('intuneAssignTo', undefined) From c7b6f31f510dedb7e9b633e4986712463be08a33 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 2 Aug 2024 15:47:40 +0200 Subject: [PATCH 33/37] revert due to license column bug. --- src/components/tables/CellLicense.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/tables/CellLicense.jsx b/src/components/tables/CellLicense.jsx index ff5df5329743..50b88b978c4f 100644 --- a/src/components/tables/CellLicense.jsx +++ b/src/components/tables/CellLicense.jsx @@ -8,8 +8,6 @@ export function CellLicense({ cell }) { if (licenseAssignment.skuId == M365Licenses[x].GUID) { licenses.push(M365Licenses[x].Product_Display_Name) break - } else { - licenses.push(licenseAssignment.skuId) } } }) From fde2501818265f9e52bbc2209be01b52bc7d3423 Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 2 Aug 2024 11:36:59 -0400 Subject: [PATCH 34/37] Add message header parsing --- .../email-exchange/tools/MessageViewer.jsx | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/views/email-exchange/tools/MessageViewer.jsx b/src/views/email-exchange/tools/MessageViewer.jsx index 52d7c3b637a4..f010c0b37c4f 100644 --- a/src/views/email-exchange/tools/MessageViewer.jsx +++ b/src/views/email-exchange/tools/MessageViewer.jsx @@ -27,6 +27,7 @@ const MessageViewer = ({ emailSource }) => { const [emlContent, setEmlContent] = useState(null) const [emlError, setEmlError] = useState(false) const [messageHtml, setMessageHtml] = useState('') + const [emlHeaders, setEmlHeaders] = useState(null) const getAttachmentIcon = (contentType) => { if (contentType.includes('image')) { @@ -126,21 +127,32 @@ const MessageViewer = ({ emailSource }) => { return d instanceof Date && !isNaN(d) } - const showEmailModal = (emailSource) => { + const showEmailModal = (emailSource, title = 'Email Source') => { ModalService.open({ data: emailSource, componentType: 'codeblock', - title: 'Email Source', + title: title, size: 'lg', }) } - const EmailButtons = (emailSource) => { + const EmailButtons = (emailHeaders, emailSource) => { + const emailSourceBytes = new TextEncoder().encode(emailSource) + const blob = new Blob([emailSourceBytes], { type: 'message/rfc822' }) + const url = URL.createObjectURL(blob) return ( - showEmailModal(emailSource)}> - - View Source - + + {emailHeaders && ( + showEmailModal(emailHeaders, 'Email Headers')} className="me-2"> + + View Headers + + )} + showEmailModal(emailSource)}> + + View Source + + ) } @@ -150,6 +162,7 @@ const MessageViewer = ({ emailSource }) => { setEmlError(true) setEmlContent(null) setMessageHtml(null) + setEmlHeaders(null) } else { setEmlContent(ReadEmlJson) setEmlError(false) @@ -160,11 +173,14 @@ const MessageViewer = ({ emailSource }) => { } else { setMessageHtml(null) } + const header_regex = /(?:^[\w-]+:\s?.*(?:\r?\n[ \t].*)*\r?\n?)+/gm + const headers = emailSource.match(header_regex) + setEmlHeaders(headers ? headers[0] : null) } }) - }, [emailSource, setMessageHtml, setEmlError, setEmlContent]) + }, [emailSource, setMessageHtml, setEmlError, setEmlContent, setEmlHeaders]) - var buttons = EmailButtons(emailSource) + var buttons = EmailButtons(emlHeaders, emailSource) return ( <> From c755dce05a4775570b719b934a757166ba42d52f Mon Sep 17 00:00:00 2001 From: John Duprey Date: Fri, 2 Aug 2024 12:31:27 -0400 Subject: [PATCH 35/37] Audit Logs Add basic offcanvas --- .../tenant/administration/ListAuditLogs.jsx | 63 +++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/src/views/tenant/administration/ListAuditLogs.jsx b/src/views/tenant/administration/ListAuditLogs.jsx index 2ec02c53cf6c..4e1d30392b3d 100644 --- a/src/views/tenant/administration/ListAuditLogs.jsx +++ b/src/views/tenant/administration/ListAuditLogs.jsx @@ -26,8 +26,13 @@ import { import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { Condition, RFFCFormInput, RFFCFormRadioList } from 'src/components/forms' import { Field, Form } from 'react-final-form' +import { useSearchParams } from 'react-router-dom' +import { CippCodeBlock, CippOffcanvas } from 'src/components/utilities' const ListAuditLogs = () => { + // get query parameters + const [searchParams, setSearchParams] = useSearchParams() + const logId = searchParams.get('LogId') const [interval, setInterval] = React.useState('d') const [time, setTime] = React.useState(1) const [relativeTime, setRelativeTime] = React.useState('1d') @@ -59,13 +64,58 @@ const ListAuditLogs = () => { setVisibleA(false) } - const Actions = () => { + const Actions = (row) => { + const [visible, setVisible] = React.useState(false) return ( - - - - - + <> + setVisible(true)}> + + + + + setVisible(false)} + visible={visible} + addedClass="offcanvas-large" + placement="end" + > + + + +

Log Details

+
+
+ + {row?.Data?.ActionText && ( + + + + + {row?.Data?.ActionText} + + + + )} + + +

Raw Log

+ +
+
+
+
+
+ ) } @@ -219,6 +269,7 @@ const ListAuditLogs = () => { RelativeTime: relativeTime, StartDate: startDate, EndDate: endDate, + LogId: logId, }, tableProps: { selectableRows: true, From 53445e671a41ba4ffd0c7b36e8c095b2e1f89493 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 2 Aug 2024 18:39:50 +0200 Subject: [PATCH 36/37] release ready --- src/importsMap.jsx | 1 + src/routes.json | 6 + src/views/cipp/TemplateLibrary.jsx | 149 ++++++++++++++++++ .../intune/MEMListPolicyTemplates.jsx | 6 + .../tenant/conditional/ListCATemplates.jsx | 6 + 5 files changed, 168 insertions(+) create mode 100644 src/views/cipp/TemplateLibrary.jsx diff --git a/src/importsMap.jsx b/src/importsMap.jsx index 3708a44f4ed9..49f07b806f00 100644 --- a/src/importsMap.jsx +++ b/src/importsMap.jsx @@ -2,6 +2,7 @@ import React from 'react' export const importsMap = { "/home": React.lazy(() => import('./views/home/Home')), "/cipp/logs": React.lazy(() => import('./views/cipp/Logs')), + "/cipp/template-library": React.lazy(() => import('./views/cipp/TemplateLibrary')), "/cipp/scheduler": React.lazy(() => import('./views/cipp/Scheduler')), "/cipp/statistics": React.lazy(() => import('./views/cipp/Statistics')), "/cipp/404": React.lazy(() => import('./views/pages/page404/Page404')), diff --git a/src/routes.json b/src/routes.json index 73f0383bbd20..532343cc83e4 100644 --- a/src/routes.json +++ b/src/routes.json @@ -11,6 +11,12 @@ "component": "views/cipp/Logs", "allowedRoles": ["admin", "editor", "readonly"] }, + { + "path": "/cipp/template-library", + "name": "Logs", + "component": "views/cipp/TemplateLibrary", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/cipp/scheduler", "name": "Scheduler", diff --git a/src/views/cipp/TemplateLibrary.jsx b/src/views/cipp/TemplateLibrary.jsx new file mode 100644 index 000000000000..4dbf493d2293 --- /dev/null +++ b/src/views/cipp/TemplateLibrary.jsx @@ -0,0 +1,149 @@ +import React, { useState } from 'react' +import { CAlert, CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react' +import { useSelector } from 'react-redux' +import { Field, Form } from 'react-final-form' +import { RFFCFormInput, RFFCFormSwitch } from 'src/components/forms' +import { + useGenericGetRequestQuery, + useLazyGenericGetRequestQuery, + useLazyGenericPostRequestQuery, +} from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch, faEdit, faEye } from '@fortawesome/free-solid-svg-icons' +import { CippPage, CippPageList } from 'src/components/layout' +import 'react-datepicker/dist/react-datepicker.css' +import { ModalService, TenantSelector } from 'src/components/utilities' +import arrayMutators from 'final-form-arrays' +import { useListConditionalAccessPoliciesQuery } from 'src/store/api/tenants' +import CippButtonCard from 'src/components/contentcards/CippButtonCard' +import { CellTip, cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import { cellBadgeFormatter, cellDateFormatter } from 'src/components/tables' +import { Alert } from '@coreui/coreui' + +const TemplateLibrary = () => { + const [ExecuteGetRequest, getResults] = useLazyGenericGetRequestQuery() + const tenantDomain = useSelector((state) => state.app.currentTenant.defaultDomainName) + const [refreshState, setRefreshState] = useState(false) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + + const onSubmit = (values) => { + const startDate = new Date() + startDate.setHours(0, 0, 0, 0) + const unixTime = Math.floor(startDate.getTime() / 1000) - 45 + const shippedValues = { + TenantFilter: tenantDomain, + Name: `CIPP Template ${tenantDomain}`, + Command: { value: `New-CIPPTemplateRun` }, + Parameters: { TemplateSettings: { ...values } }, + ScheduledTime: unixTime, + Recurrence: { value: '4h' }, + } + genericPostRequest({ + path: '/api/AddScheduledItem?DisallowDuplicateName=true', + values: shippedValues, + }).then((res) => { + setRefreshState(res.requestId) + }) + } + + const { + data: caPolicies = [], + isFetching: caIsFetching, + error: caError, + } = useListConditionalAccessPoliciesQuery({ domain: tenantDomain }) + + return ( + + <> + + + + Set Tenant as Template Library + {postResults.isFetching && ( + + )} + + } + title="Add Template Library" + icon={faEdit} + > + { + return ( + +

+ Template libraries are tenants setup to retrieve the latest version of + policies from. By setting a tenant as a template library, automatic updates + will be made to the templates within CIPP based on this template library + every 4 hours. + + Enabling this feature will overwrite templates with the same name. + +

+ + + + + {(props) => } + + + + +
+
+ + +

Conditional Access

+ +

Intune

+ + + +
+
+ {postResults.isSuccess && ( + +
  • {postResults.data.Results}
  • +
    + )} + {getResults.isFetching && ( + + Loading + + )} + {getResults.isSuccess && ( + {getResults.data?.Results} + )} + {getResults.isError && ( + + Could not connect to API: {getResults.error.message} + + )} +
    + ) + }} + /> +
    +
    +
    + +
    + ) +} + +export default TemplateLibrary diff --git a/src/views/endpoint/intune/MEMListPolicyTemplates.jsx b/src/views/endpoint/intune/MEMListPolicyTemplates.jsx index 4ca9452daeff..1b389af653bc 100644 --- a/src/views/endpoint/intune/MEMListPolicyTemplates.jsx +++ b/src/views/endpoint/intune/MEMListPolicyTemplates.jsx @@ -16,6 +16,7 @@ import { useLazyGenericGetRequestQuery } from 'src/store/api/app' import { CippPage } from 'src/components/layout' import { ModalService } from 'src/components/utilities' import CippCodeOffCanvas from 'src/components/utilities/CippCodeOffcanvas' +import { TitleButton } from 'src/components/buttons' //todo: expandable with RAWJson property. @@ -106,6 +107,11 @@ const AutopilotListTemplates = () => { Endpoint Manager Templates + {getResults.isFetching && ( diff --git a/src/views/tenant/conditional/ListCATemplates.jsx b/src/views/tenant/conditional/ListCATemplates.jsx index 555a595c3ddb..13b652854a42 100644 --- a/src/views/tenant/conditional/ListCATemplates.jsx +++ b/src/views/tenant/conditional/ListCATemplates.jsx @@ -15,6 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { useLazyGenericGetRequestQuery } from 'src/store/api/app' import { CippPage } from 'src/components/layout' import { ModalService, CippCodeOffCanvas } from 'src/components/utilities' +import { TitleButton } from 'src/components/buttons' //todo: expandable with RAWJson property. @@ -87,6 +88,11 @@ const AutopilotListTemplates = () => { Results + {getResults.isFetching && ( From d0b1f4f956cfdb2e41469e9425cdf32f51933890 Mon Sep 17 00:00:00 2001 From: KelvinTegelaar Date: Fri, 2 Aug 2024 19:28:17 +0200 Subject: [PATCH 37/37] update --- 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 169a8fbc8a05..dde03c41dba7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "6.1.1", + "version": "6.2.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 f3b5af39e430..6abaeb2f9072 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -6.1.1 +6.2.0 diff --git a/version_latest.txt b/version_latest.txt index f3b5af39e430..6abaeb2f9072 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -6.1.1 +6.2.0