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 0d19ddc025c7..dde03c41dba7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cipp",
- "version": "6.1.0",
+ "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": {
@@ -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/public/version_latest.txt b/public/version_latest.txt
index dfda3e0b4f01..6abaeb2f9072 100644
--- a/public/version_latest.txt
+++ b/public/version_latest.txt
@@ -1 +1 @@
-6.1.0
+6.2.0
diff --git a/src/_nav.jsx b/src/_nav.jsx
index a3910a2f0018..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,
@@ -326,7 +331,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,
@@ -682,6 +692,11 @@ const _nav = [
name: 'Mail Test',
to: '/email/tools/mail-test',
},
+ {
+ component: CNavItem,
+ name: 'Message Viewer',
+ to: '/email/tools/message-viewer',
+ },
],
},
{
@@ -790,8 +805,23 @@ const _nav = [
},
{
component: CNavItem,
- name: 'Phishing Policies',
- to: '/email/reports/phishing-policies',
+ name: 'Anti-Phishing Filters',
+ to: '/email/reports/antiphishing-filters',
+ },
+ {
+ component: CNavItem,
+ name: 'Malware Filters',
+ to: '/email/reports/malware-filters',
+ },
+ {
+ component: CNavItem,
+ name: 'Safe Links Filters',
+ to: '/email/reports/safelinks-filters',
+ },
+ {
+ component: CNavItem,
+ name: 'Safe Attachments Filters',
+ to: '/email/reports/safeattachments-filters',
},
{
component: CNavItem,
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/data/Extensions.json b/src/data/Extensions.json
index 6d4b8ab242f0..6524c9236657 100644
--- a/src/data/Extensions.json
+++ b/src/data/Extensions.json
@@ -186,6 +186,11 @@
"label": "Hudu API Key",
"placeholder": "Enter your Hudu API Key"
},
+ {
+ "type": "checkbox",
+ "name": "Hudu.Enabled",
+ "label": "Enable Integration"
+ },
{
"type": "checkbox",
"name": "Hudu.CreateMissingUsers",
@@ -196,10 +201,20 @@
"name": "Hudu.CreateMissingDevices",
"label": "Create missing devices in Hudu"
},
+ {
+ "type": "text",
+ "name": "Hudu.ExcludeSerials",
+ "label": "Exclude device serials (comma separated)"
+ },
{
"type": "checkbox",
- "name": "Hudu.Enabled",
- "label": "Enable Integration"
+ "name": "Hudu.ImportDomains",
+ "label": "Import domains from M365"
+ },
+ {
+ "type": "checkbox",
+ "name": "Hudu.MonitorDomains",
+ "label": "Monitor domains in Hudu"
}
],
"mappingRequired": true,
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
+]
diff --git a/src/data/standards.json b/src/data/standards.json
index c018b25b8a8e..ef14f236cd1a 100644
--- a/src/data/standards.json
+++ b/src/data/standards.json
@@ -5,11 +5,6 @@
"tag": ["lowimpact"],
"helpText": "Defines the email address to receive general updates and information related to M365 subscriptions. Leave a contact field blank if you do not want to update the contact information.",
"docsDescription": "",
- "disabledFeatures": {
- "report": false,
- "warn": false,
- "remediate": false
- },
"addedComponent": [
{
"type": "input",
@@ -589,7 +584,7 @@
{
"name": "standards.OauthConsentLowSec",
"cat": "Entra (AAD) Standards",
- "tag": ["mediumimpact"],
+ "tag": ["mediumimpact", "IntegratedApps"],
"helpText": "Sets the default oauth consent level so users can consent to applications that have low risks.",
"docsDescription": "Allows users to consent to applications with low assigned risk.",
"label": "Allow users to consent to applications with low security risk (Prevent OAuth phishing. Lower impact, less secure)",
@@ -645,7 +640,9 @@
"addedComponent": [],
"label": "Disables Voice call as an MFA method",
"impact": "High Impact",
- "impactColour": "danger"
+ "impactColour": "danger",
+ "powershellEquivalent": "Update-MgBetaPolicyAuthenticationMethodPolicyAuthenticationMethodConfiguration",
+ "recommendedBy": []
},
{
"name": "standards.DisableEmail",
@@ -1281,6 +1278,19 @@
"powershellEquivalent": "Get-Mailbox & Update-MgUser",
"recommendedBy": ["CIS"]
},
+ {
+ "name": "standards.EXODisableAutoForwarding",
+ "cat": "Exchange Standards",
+ "tag": ["highimpact", "CIS", "mdo_autoforwardingmode", "mdo_blockmailforward"],
+ "helpText": "Disables the ability for users to automatically forward e-mails to external recipients.",
+ "docsDescription": "Disables the ability for users to automatically forward e-mails to external recipients. This is to prevent data exfiltration. Please check if there are any legitimate use cases for this feature before implementing, like forwarding invoices and such.",
+ "addedComponent": [],
+ "label": "Disable automatic forwarding to external recipients",
+ "impact": "High Impact",
+ "impactColour": "danger",
+ "powershellEquivalent": "Set-HostedOutboundSpamFilterPolicy -AutoForwardingMode 'Off'",
+ "recommendedBy": ["CIS"]
+ },
{
"name": "standards.QuarantineRequestAlert",
"cat": "Defender Standards",
@@ -1339,7 +1349,8 @@
"mdo_highconfidencephishaction",
"mdo_phisspamacation",
"mdo_spam_notifications_only_for_admins",
- "mdo_antiphishingpolicies"
+ "mdo_antiphishingpolicies",
+ "mdo_phishthresholdlevel"
],
"helpText": "This creates a Anti-Phishing policy that automatically enables Mailbox Intelligence and spoofing, optional switches for Mailtips.",
"addedComponent": [
@@ -1635,6 +1646,11 @@
}
]
},
+ {
+ "type": "input",
+ "name": "standards.MalwareFilterPolicy.OptionalFileTypes",
+ "label": "Optional File Types, Comma separated"
+ },
{
"type": "Select",
"label": "QuarantineTag",
@@ -1687,18 +1703,24 @@
"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",
"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"
}
]
},
@@ -1721,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",
@@ -1740,6 +1777,21 @@
}
]
},
+ {
+ "type": "Select",
+ "label": "Bulk Spam Action",
+ "name": "standards.SpamFilterPolicy.BulkSpamAction",
+ "values": [
+ {
+ "label": "Quarantine the message",
+ "value": "Quarantine"
+ },
+ {
+ "label": "Move message to Junk Email folder",
+ "value": "MoveToJmf"
+ }
+ ]
+ },
{
"type": "Select",
"label": "Bulk Quarantine Tag",
@@ -1759,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",
@@ -2081,23 +2148,62 @@
"name": "standards.DisableAddShortcutsToOneDrive",
"cat": "SharePoint Standards",
"tag": ["mediumimpact"],
- "helpText": "When the feature is disabled the option Add shortcut to OneDrive will be removed. Any folders that have already been added will remain on the user's computer.",
- "disabledFeatures": {
- "report": true,
- "warn": true,
- "remediate": false
- },
- "addedComponent": [],
- "label": "Disable Add Shortcuts To OneDrive",
+ "helpText": "If disabled, the button Add shortcut to OneDrive will be removed and users in the tenant will no longer be able to add new shortcuts to their OneDrive. Existing shortcuts will remain functional",
+ "addedComponent": [
+ {
+ "type": "Select",
+ "label": "Add Shortcuts To OneDrive button state",
+ "name": "standards.DisableAddShortcutsToOneDrive.state",
+ "values": [
+ {
+ "label": "Disabled",
+ "value": "true"
+ },
+ {
+ "label": "Enabled",
+ "value": "false"
+ }
+ ]
+ }
+ ],
+ "label": "Set Add Shortcuts To OneDrive button state",
"impact": "Medium Impact",
"impactColour": "warning",
- "powershellEquivalent": "Graph API or Portal",
+ "powershellEquivalent": "Set-SPOTenant -DisableAddShortcutsToOneDrive $true or $false",
+ "recommendedBy": []
+ },
+ {
+ "name": "standards.SPSyncButtonState",
+ "cat": "SharePoint Standards",
+ "tag": ["mediumimpact"],
+ "helpText": "If disabled, users in the tenant will no longer be able to use the Sync button to sync SharePoint content on all sites. However, existing synced content will remain functional on the user's computer.",
+ "addedComponent": [
+ {
+ "type": "Select",
+ "label": "SharePoint Sync Button state",
+ "name": "standards.SPSyncButtonState.state",
+ "values": [
+ {
+ "label": "Disabled",
+ "value": "true"
+ },
+ {
+ "label": "Enabled",
+ "value": "false"
+ }
+ ]
+ }
+ ],
+ "label": "Set SharePoint sync button state",
+ "impact": "Medium Impact",
+ "impactColour": "warning",
+ "powershellEquivalent": "Set-SPOTenant -HideSyncButtonOnTeamSite $true or $false",
"recommendedBy": []
},
{
"name": "standards.DisableSharePointLegacyAuth",
"cat": "SharePoint Standards",
- "tag": ["mediumimpact", "CIS"],
+ "tag": ["mediumimpact", "CIS", "spo_legacy_auth"],
"helpText": "Disables the ability to authenticate with SharePoint using legacy authentication methods. Any applications that use legacy authentication will need to be updated to use modern authentication.",
"docsDescription": "Disables the ability for users and applications to access SharePoint via legacy basic authentication. This will likely not have any user impact, but will block systems/applications depending on basic auth or the SharePointOnlineCredentials class.",
"addedComponent": [],
@@ -2247,5 +2353,179 @@
"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"]
+ },
+ {
+ "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",
+ "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"]
+ },
+ {
+ "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": []
+ },
+ {
+ "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": []
}
]
diff --git a/src/importsMap.jsx b/src/importsMap.jsx
index a8435a662dbd..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')),
@@ -42,7 +43,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')),
@@ -115,6 +117,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')),
@@ -133,7 +136,10 @@ import React from 'react'
"/email/reports/mailbox-cas-settings": React.lazy(() => import('./views/email-exchange/reports/MailboxClientAccessSettingsList')),
"/email/reports/message-trace": React.lazy(() => import('./views/email-exchange/reports/MessageTrace')),
"/cipp/user-settings": React.lazy(() => import('./views/cipp/UserSettings')),
- "/email/reports/phishing-policies": React.lazy(() => import('./views/email-exchange/reports/PhishingPoliciesList')),
+ "/email/reports/antiphishing-filters": React.lazy(() => import('./views/email-exchange/reports/AntiPhishingFilters')),
+ "/email/reports/malware-filters": React.lazy(() => import('./views/email-exchange/reports/MalwareFilters')),
+ "/email/reports/safelinks-filters": React.lazy(() => import('./views/email-exchange/reports/SafeLinksFilters')),
+ "/email/reports/safeattachments-filters": React.lazy(() => import('./views/email-exchange/reports/SafeAttachmentsFilters')),
"/security/incidents/list-alerts": React.lazy(() => import('./views/security/incidents/ListAlerts')),
"/security/incidents/list-incidents": React.lazy(() => import('./views/security/incidents/ListIncidents')),
"/security/reports/list-device-compliance": React.lazy(() => import('./views/security/reports/ListDeviceComplianceReport')),
@@ -144,7 +150,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 9817ebc8aabd..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",
@@ -279,11 +285,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",
@@ -776,6 +788,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",
@@ -890,9 +908,27 @@
"allowedRoles": ["admin", "editor", "readonly"]
},
{
- "name": "Phishing Policies",
- "path": "/email/reports/phishing-policies",
- "component": "views/email-exchange/reports/PhishingPoliciesList",
+ "path": "/email/reports/antiphishing-filters",
+ "name": "Anti Phishing Filters",
+ "component": "views/email-exchange/reports/AntiPhishingFilters",
+ "allowedRoles": ["admin", "editor", "readonly"]
+ },
+ {
+ "path": "/email/reports/malware-filters",
+ "name": "Malware Filters",
+ "component": "views/email-exchange/reports/MalwareFilters",
+ "allowedRoles": ["admin", "editor", "readonly"]
+ },
+ {
+ "path": "/email/reports/safelinks-filters",
+ "name": "Safe Links Filters",
+ "component": "views/email-exchange/reports/SafeLinksFilters",
+ "allowedRoles": ["admin", "editor", "readonly"]
+ },
+ {
+ "path": "/email/reports/safeattachments-filters",
+ "name": "Safe Attachment Filters",
+ "component": "views/email-exchange/reports/SafeAttachmentsFilters",
"allowedRoles": ["admin", "editor", "readonly"]
},
{
@@ -981,11 +1017,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/cipp/ExtensionMappings.jsx b/src/views/cipp/ExtensionMappings.jsx
index 4de976555ccc..5bcf9e23e8e8 100644
--- a/src/views/cipp/ExtensionMappings.jsx
+++ b/src/views/cipp/ExtensionMappings.jsx
@@ -251,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,
diff --git a/src/views/cipp/ExtensionSync.jsx b/src/views/cipp/ExtensionSync.jsx
index c9937921aa86..f121bf08e513 100644
--- a/src/views/cipp/ExtensionSync.jsx
+++ b/src/views/cipp/ExtensionSync.jsx
@@ -19,49 +19,49 @@ const ExtensionSync = () => {
const columns = [
{
name: 'Tenant',
- selector: (row) => row['Tenant'],
+ selector: (row) => row?.Tenant,
sortable: true,
cell: cellGenericFormatter(),
exportSelector: 'Tenants',
},
{
name: 'Sync Type',
- selector: (row) => row['SyncType'],
+ selector: (row) => row?.SyncType,
sortable: true,
cell: cellBadgeFormatter({ color: 'info' }),
exportSelector: 'SyncType',
},
{
name: 'Task',
- selector: (row) => row['Name'],
+ selector: (row) => row?.Name,
sortable: true,
cell: cellGenericFormatter(),
exportSelector: 'Name',
},
{
name: 'Scheduled Time',
- selector: (row) => row['ScheduledTime'],
+ selector: (row) => row?.ScheduledTime,
sortable: true,
cell: cellDateFormatter({ format: 'short' }),
exportSelector: 'ScheduledTime',
},
{
name: 'Last Run',
- selector: (row) => row['ExecutedTime'],
+ selector: (row) => row?.ExecutedTime,
sortable: true,
cell: cellDateFormatter({ format: 'short' }),
exportSelector: 'ExecutedTime',
},
{
name: 'Repeats every',
- selector: (row) => row['RepeatsEvery'],
+ selector: (row) => row?.RepeatsEvery,
sortable: true,
cell: (row) => CellTip(row['RepeatsEvery']),
exportSelector: 'RepeatsEvery',
},
{
name: 'Results',
- selector: (row) => row['Results'],
+ selector: (row) => row?.Results,
sortable: true,
cell: cellGenericFormatter(),
exportSelector: 'Results',
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' })
})
}
diff --git a/src/views/cipp/Setup.jsx b/src/views/cipp/Setup.jsx
index bf09e8b04baf..7f18e7c6a661 100644
--- a/src/views/cipp/Setup.jsx
+++ b/src/views/cipp/Setup.jsx
@@ -286,7 +286,7 @@ const Setup = () => {
type="text"
name="TenantID"
label="Tenant ID"
- placeholder="Enter the Tenant ID. e.g. mymsp.onmicrosoft.com. Leave blank to retain a previous key if this exists."
+ placeholder="Enter the Tenant ID. e.g. 1111-1111-1111-1111-11111. Leave blank to retain a previous key if this exists."
/>
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}
+ >
+
+
+
+ >
+
+ )
+}
+
+export default TemplateLibrary
diff --git a/src/views/cipp/UserSettings.jsx b/src/views/cipp/UserSettings.jsx
index cedc16b9e672..a7fcaa628f3e 100644
--- a/src/views/cipp/UserSettings.jsx
+++ b/src/views/cipp/UserSettings.jsx
@@ -156,28 +156,36 @@ const UserSettings = () => {
Offboarding Defaults
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/cipp/app-settings/CIPPSettings.jsx b/src/views/cipp/app-settings/CIPPSettings.jsx
index 5a610170e49b..b4dd85cba84f 100644
--- a/src/views/cipp/app-settings/CIPPSettings.jsx
+++ b/src/views/cipp/app-settings/CIPPSettings.jsx
@@ -10,6 +10,7 @@ import { SettingsNotifications } from 'src/views/cipp/app-settings/SettingsNotif
import { SettingsLicenses } from 'src/views/cipp/app-settings/SettingsLicenses.jsx'
import { SettingsMaintenance } from 'src/views/cipp/app-settings/SettingsMaintenance.jsx'
import { SettingsPartner } from 'src/views/cipp/app-settings/SettingsPartner.jsx'
+import { SettingsWebhookSubscriptions } from 'src/views/cipp/app-settings/SettingsWebhookSubscriptions.jsx'
import useQuery from 'src/hooks/useQuery.jsx'
import { SettingsSuperAdmin } from './SettingsSuperAdmin.jsx'
import { useLoadClientPrincipalQuery } from 'src/store/api/auth.js'
@@ -48,16 +49,19 @@ export default function CIPPSettings() {
Notifications
setActive(5)} href="#">
- Partner Webhooks
+ Log Subscriptions
setActive(6)} href="#">
- Licenses
+ Partner Webhooks
setActive(7)} href="#">
+ Licenses
+
+ setActive(8)} href="#">
Maintenance
{superAdmin && (
- setActive(8)} href="#">
+ setActive(9)} href="#">
SuperAdmin Settings
)}
@@ -84,23 +88,29 @@ export default function CIPPSettings() {
-
+
-
-
-
+
+
-
+
-
+
+ {superAdmin && (
+
+
+
+
+
+ )}
)
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
-
-
>
>
+
>
)
}
diff --git a/src/views/cipp/app-settings/SettingsTenants.jsx b/src/views/cipp/app-settings/SettingsTenants.jsx
index c425f3c1ae97..bbced7def29a 100644
--- a/src/views/cipp/app-settings/SettingsTenants.jsx
+++ b/src/views/cipp/app-settings/SettingsTenants.jsx
@@ -7,7 +7,7 @@ import { useLazyGenericGetRequestQuery } from 'src/store/api/app.js'
import React, { useEffect, useRef } from 'react'
import { ModalService, TenantSelectorMultiple } from 'src/components/utilities/index.js'
import { setCurrentTenant } from 'src/store/features/app.js'
-import { CAlert, CButton, CCallout, CSpinner, CTooltip } from '@coreui/react'
+import { CButton, CSpinner, CTooltip } from '@coreui/react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
faCheckCircle,
@@ -261,6 +261,16 @@ export function SettingsTenants() {
modalMessage:
'Are you sure you want to reset the CPV permissions for these tenants? (This will delete the Service Principal and re-add it.)',
},
+ {
+ label: 'Remove Tenant',
+ modal: true,
+ modalType: 'POST',
+ modalUrl: `/api/ExecRemoveTenant`,
+ modalBody: {
+ TenantID: '!customerId',
+ },
+ modalMessage: 'Are you sure you want to remove this tenant?',
+ },
],
},
isModal: true,
diff --git a/src/views/cipp/app-settings/SettingsWebhookSubscriptions.jsx b/src/views/cipp/app-settings/SettingsWebhookSubscriptions.jsx
new file mode 100644
index 000000000000..965e13b57b90
--- /dev/null
+++ b/src/views/cipp/app-settings/SettingsWebhookSubscriptions.jsx
@@ -0,0 +1,78 @@
+import React from 'react'
+import { CippPageList } from 'src/components/layout/index.js'
+import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
+import { cellBadgeFormatter, cellDateFormatter } from 'src/components/tables'
+
+/**
+ * SettingsWebhookSubscriptions component is used to manage webhook subscriptions in a settings page.
+ *
+ * @returns {JSX.Element} The generated settings page component.
+ */
+export function SettingsWebhookSubscriptions() {
+ const columns = [
+ {
+ name: 'Tenant',
+ selector: (row) => row['PartitionKey'],
+ exportSelector: 'PartitionKey',
+ sortable: true,
+ cell: cellGenericFormatter(),
+ },
+ {
+ name: 'Resource',
+ selector: (row) => row['Resource'],
+ exportSelector: 'Resource',
+ sortable: true,
+ cell: cellGenericFormatter(),
+ },
+ {
+ name: 'Status',
+ selector: (row) => row['Status'],
+ exportSelector: 'Status',
+ sortable: true,
+ cell: cellBadgeFormatter({ color: 'info' }),
+ },
+ {
+ name: 'Last Update',
+ selector: (row) => row['Timestamp'],
+ sortable: true,
+ cell: cellDateFormatter({ format: 'short' }),
+ exportSelector: 'Timestamp',
+ },
+ ]
+ return (
+ <>
+
+ >
+ )
+}
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..ebc8310b6abd
--- /dev/null
+++ b/src/views/cipp/app-settings/components/SettingsSAMRoles.jsx
@@ -0,0 +1,150 @@
+import React, { useEffect, 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 = [],
+ isFetching: tenantsFetching,
+ isSuccess: tenantSuccess,
+ } = 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()
+ })
+ }
+
+ 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.
+ 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.
+
+
+ {roleListSuccess && (
+
+ )
+}
+
+export default SettingsSAMRoles
diff --git a/src/views/email-exchange/reports/AntiPhishingFilters.jsx b/src/views/email-exchange/reports/AntiPhishingFilters.jsx
new file mode 100644
index 000000000000..ed68929c2005
--- /dev/null
+++ b/src/views/email-exchange/reports/AntiPhishingFilters.jsx
@@ -0,0 +1,293 @@
+import { CButton } from '@coreui/react'
+import { faBan, faBook, faCheck, faEllipsisV, faTrash } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React, { useState } from 'react'
+import { CippActionsOffcanvas } from 'src/components/utilities'
+import { useSelector } from 'react-redux'
+import { CippPageList } from 'src/components/layout'
+import { cellDateFormatter, cellBooleanFormatter, CellTip } from 'src/components/tables'
+import { cellTableFormatter } from 'src/components/tables/CellTable'
+
+const ListAntiPhishingFilters = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
+
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+ ,
+ modal: true,
+ modalUrl: `/api/EditAntiPhishingFilter?State=Enable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to enable this rule?',
+ },
+ {
+ label: 'Disable Rule',
+ color: 'info',
+ icon: ,
+ modal: true,
+ modalUrl: `/api/EditAntiPhishingFilter?State=Disable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to disable this rule?',
+ },
+ /*{
+ label: 'Delete Rule',
+ color: 'danger',
+ modal: true,
+ icon: ,
+ modalUrl: `/api/RemoveAntiPhishingFilter?TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to delete this rule?',
+ },*/
+ ]}
+ placement="end"
+ visible={ocVisible}
+ id={row.id}
+ hideFunction={() => setOCVisible(false)}
+ />
+ >
+ )
+ }
+
+ const columns = [
+ {
+ name: 'Rule Name',
+ selector: (row) => row['RuleName'],
+ sortable: true,
+ exportSelector: 'RuleName',
+ },
+ {
+ name: 'Policy Name',
+ selector: (row) => row['Name'],
+ sortable: true,
+ exportSelector: 'Name',
+ },
+ {
+ name: 'Enabled',
+ selector: (row) => row['State'],
+ exportSelector: 'State',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Priority',
+ selector: (row) => row['Priority'],
+ sortable: true,
+ exportSelector: 'Priority',
+ maxWidth: '40px',
+ },
+ {
+ name: 'Recipient Domains',
+ selector: (row) => row['RecipientDomainIs'],
+ sortable: true,
+ exportSelector: 'RecipientDomainIs',
+ cell: cellTableFormatter('RecipientDomainIs'),
+ },
+ {
+ name: 'Excluded Domains',
+ selector: (row) => row['ExcludedDomains'],
+ sortable: true,
+ exportSelector: 'ExcludedDomains',
+ cell: cellTableFormatter('ExcludedDomains'),
+ },
+ {
+ name: 'Excluded Senders',
+ selector: (row) => row['ExcludedSenders'],
+ sortable: true,
+ exportSelector: 'ExcludedSenders',
+ cell: cellTableFormatter('ExcludedSenders'),
+ },
+ {
+ name: 'PhishThresholdLevel',
+ selector: (row) => row['PhishThresholdLevel'],
+ sortable: true,
+ exportSelector: 'PhishThresholdLevel',
+ },
+ {
+ name: 'Mailbox Intelligence',
+ selector: (row) => row['EnableMailboxIntelligence'],
+ exportSelector: 'EnableMailboxIntelligence',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Mailbox Intelligence Protection',
+ selector: (row) => row['EnableMailboxIntelligenceProtection'],
+ exportSelector: 'EnableMailboxIntelligenceProtection',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Spoof Intelligence',
+ selector: (row) => row['EnableSpoofIntelligence'],
+ exportSelector: 'EnableSpoofIntelligence',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'First Contact Safety Tips',
+ selector: (row) => row['EnableFirstContactSafetyTips'],
+ exportSelector: 'EnableFirstContactSafetyTips',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Similar Users Safety Tips',
+ selector: (row) => row['EnableSimilarUsersSafetyTips'],
+ exportSelector: 'EnableSimilarUsersSafetyTips',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Similar Domain Safety Tips',
+ selector: (row) => row['EnableSimilarDomainsSafetyTips'],
+ exportSelector: 'EnableSimilarDomainsSafetyTips',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Unusual Characters Safety Tips',
+ selector: (row) => row['EnableUnusualCharactersSafetyTips'],
+ exportSelector: 'EnableUnusualCharactersSafetyTips',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Unauthenticated Sender',
+ selector: (row) => row['EnableUnauthenticatedSender'],
+ exportSelector: 'EnableUnauthenticatedSender',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'ViaTag',
+ selector: (row) => row['EnableViaTag'],
+ exportSelector: 'EnableViaTag',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Organization Domains Protection',
+ selector: (row) => row['EnableOrganizationDomainsProtection'],
+ exportSelector: 'EnableOrganizationDomainsProtection',
+ cell: cellBooleanFormatter(),
+ },
+ {
+ name: 'Authentication Fail Action',
+ selector: (row) => row['AuthenticationFailAction'],
+ exportSelector: 'AuthenticationFailAction',
+ maxWidt: '100px',
+ },
+ {
+ name: 'Spoof Quarantine Tag',
+ selector: (row) => row['SpoofQuarantineTag'],
+ exportSelector: 'SpoofQuarantineTag',
+ maxWidth: '100px',
+ },
+ {
+ name: 'MailboxIntelligence Protection Action',
+ selector: (row) => row['MailboxIntelligenceProtectionAction'],
+ exportSelector: 'MailboxIntelligenceProtectionAction',
+ maxWidth: '100px',
+ },
+ {
+ name: 'Mailbox Intelligence Quarantine Tag',
+ selector: (row) => row['MailboxIntelligenceQuarantineTag'],
+ exportSelector: 'MailboxIntelligenceQuarantineTag',
+ maxWidth: '100px',
+ },
+ {
+ name: 'Targeted UserProtection Action',
+ selector: (row) => row['TargetedUserProtectionAction'],
+ exportSelector: 'TargetedUserProtectionAction',
+ maxWidth: '100px',
+ },
+ {
+ name: 'Targeted UserQuarantine Tag',
+ selector: (row) => row['TargetedUserQuarantineTag'],
+ exportSelector: 'TargetedUserQuarantineTag',
+ maxWidth: '100px',
+ },
+ {
+ name: 'Targeted Domain Protection Action',
+ selector: (row) => row['TargetedDomainProtectionAction'],
+ exportSelector: 'TargetedDomainProtectionAction',
+ maxWidth: '100px',
+ },
+ {
+ name: 'Targeted Domain Quarantine Tag',
+ selector: (row) => row['TargetedDomainQuarantineTag'],
+ exportSelector: 'TargetedDomainQuarantineTag',
+ maxWidth: '100px',
+ },
+ {
+ name: 'Creation Date',
+ selector: (row) => row['WhenCreated'],
+ sortable: true,
+ exportSelector: 'WhenCreated',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Last Modified Date',
+ selector: (row) => row['WhenChanged'],
+ sortable: true,
+ exportSelector: 'WhenChanged',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default ListAntiPhishingFilters
diff --git a/src/views/email-exchange/reports/MalwareFilters.jsx b/src/views/email-exchange/reports/MalwareFilters.jsx
new file mode 100644
index 000000000000..ebd9d5ad354c
--- /dev/null
+++ b/src/views/email-exchange/reports/MalwareFilters.jsx
@@ -0,0 +1,217 @@
+import { CButton } from '@coreui/react'
+import { faBan, faBook, faCheck, faEllipsisV, faTrash } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React, { useState } from 'react'
+import { CippActionsOffcanvas } from 'src/components/utilities'
+import { useSelector } from 'react-redux'
+import { CippPageList } from 'src/components/layout'
+import { cellDateFormatter, cellBooleanFormatter, CellTip } from 'src/components/tables'
+import { cellTableFormatter } from 'src/components/tables/CellTable'
+
+const ListMalwareFilters = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
+
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+ ,
+ modal: true,
+ modalUrl: `/api/EditMalwareFilter?State=Enable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to enable this rule?',
+ },
+ {
+ label: 'Disable Rule',
+ color: 'info',
+ icon: ,
+ modal: true,
+ modalUrl: `/api/EditMalwareFilter?State=Disable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to disable this rule?',
+ },
+ /*{
+ label: 'Delete Rule',
+ color: 'danger',
+ modal: true,
+ icon: ,
+ modalUrl: `/api/RemoveMalwareFilter?TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to delete this rule?',
+ },*/
+ ]}
+ placement="end"
+ visible={ocVisible}
+ id={row.id}
+ hideFunction={() => setOCVisible(false)}
+ />
+ >
+ )
+ }
+
+ const columns = [
+ {
+ name: 'Rule Name',
+ selector: (row) => row['RuleName'],
+ sortable: true,
+ exportSelector: 'RuleName',
+ },
+ {
+ name: 'Policy Name',
+ selector: (row) => row['Name'],
+ sortable: true,
+ exportSelector: 'Name',
+ },
+ {
+ name: 'Enabled',
+ selector: (row) => row['State'],
+ exportSelector: 'State',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Priority',
+ selector: (row) => row['Priority'],
+ sortable: true,
+ exportSelector: 'Priority',
+ maxWidth: '40px',
+ },
+ {
+ name: 'Recipient Domains',
+ selector: (row) => row['RecipientDomainIs'],
+ sortable: true,
+ exportSelector: 'RecipientDomainIs',
+ cell: cellTableFormatter('RecipientDomainIs'),
+ },
+ {
+ name: 'File Filter',
+ selector: (row) => row['EnableFileFilter'],
+ exportSelector: 'EnableFileFilter',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'File Types',
+ selector: (row) => row['FileTypes'],
+ sortable: true,
+ exportSelector: 'FileTypes',
+ cell: cellTableFormatter('FileTypes'),
+ },
+ {
+ name: 'File Type Action',
+ selector: (row) => row['FileTypeAction'],
+ sortable: true,
+ exportSelector: 'FileTypeAction',
+ maxWidth: '200px',
+ },
+ {
+ name: 'Zap',
+ selector: (row) => row['ZapEnabled'],
+ exportSelector: 'ZapEnabled',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Quarantine Tag',
+ selector: (row) => row['QuarantineTag'],
+ sortable: true,
+ exportSelector: 'QuarantineTag',
+ maxWidth: '200px',
+ },
+ {
+ name: 'Internal Admin Notifications',
+ selector: (row) => row['EnableInternalSenderAdminNotifications'],
+ exportSelector: 'EnableInternalSenderAdminNotifications',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Internal Sender Admin Address',
+ selector: (row) => row['InternalSenderAdminAddress'],
+ sortable: true,
+ exportSelector: 'InternalSenderAdminAddress',
+ },
+ {
+ name: 'External Admin Notifications',
+ selector: (row) => row['EnableExternalSenderAdminNotifications'],
+ exportSelector: 'EnableExternalSenderAdminNotifications',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'External Sender Admin Address',
+ selector: (row) => row['ExternalSenderAdminAddress'],
+ sortable: true,
+ exportSelector: 'ExternalSenderAdminAddress',
+ },
+ {
+ name: 'Creation Date',
+ selector: (row) => row['WhenCreated'],
+ sortable: true,
+ exportSelector: 'WhenCreated',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Last Modified Date',
+ selector: (row) => row['WhenChanged'],
+ sortable: true,
+ exportSelector: 'WhenChanged',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default ListMalwareFilters
diff --git a/src/views/email-exchange/reports/PhishingPoliciesList.jsx b/src/views/email-exchange/reports/PhishingPoliciesList.jsx
deleted file mode 100644
index b4cd5ba23a01..000000000000
--- a/src/views/email-exchange/reports/PhishingPoliciesList.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react'
-import { useSelector } from 'react-redux'
-import { CippPageList } from 'src/components/layout/CippPage'
-import { cellBooleanFormatter, cellDateFormatter } from 'src/components/tables'
-
-//TODO: Add CellBoolean
-const columns = [
- {
- selector: (row) => row['Name'],
- name: 'Name',
- sortable: true,
- exportSelector: 'Name',
- },
- {
- selector: (row) => row['PhishThresholdLevel'],
- name: 'Phish Threshold Level',
- sortable: true,
- exportSelector: 'PhishThresholdLevel',
- },
- {
- selector: (row) => row['Enabled'],
- name: 'Enabled',
- sortable: true,
- cell: cellBooleanFormatter({ warning: true, colourless: true }),
- exportSelector: 'Enabled',
- },
- {
- selector: (row) => row['ExcludedSenders'],
- name: 'Excluded Senders',
- sortable: true,
- cell: cellBooleanFormatter({ warning: true, colourless: true, noDataIsFalse: true }),
- exportSelector: 'ExcludedSenders',
- },
- {
- selector: (row) => row['ExcludedDomains'],
- name: 'Excluded Domains',
- sortable: true,
- cell: cellBooleanFormatter(),
- exportSelector: 'ExcludedDomains',
- },
- {
- selector: (row) => row['WhenChangedUTC'],
- name: 'Last Change Date (Local)',
- sortable: true,
- cell: cellDateFormatter(),
- exportSelector: 'WhenChangedUTC',
- },
- {
- selector: (row) => row['Priority'],
- name: 'Priority',
- sortable: true,
- exportSelector: 'Priority',
- },
-]
-
-const MailboxList = () => {
- const tenant = useSelector((state) => state.app.currentTenant)
-
- return (
-
- )
-}
-
-export default MailboxList
diff --git a/src/views/email-exchange/reports/SafeAttachmentsFilters.jsx b/src/views/email-exchange/reports/SafeAttachmentsFilters.jsx
new file mode 100644
index 000000000000..a9257edfd7e4
--- /dev/null
+++ b/src/views/email-exchange/reports/SafeAttachmentsFilters.jsx
@@ -0,0 +1,181 @@
+import { CButton } from '@coreui/react'
+import { faBan, faBook, faCheck, faEllipsisV, faTrash } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React, { useState } from 'react'
+import { CippActionsOffcanvas } from 'src/components/utilities'
+import { useSelector } from 'react-redux'
+import { CippPageList } from 'src/components/layout'
+import { cellDateFormatter, cellBooleanFormatter, CellTip } from 'src/components/tables'
+import { cellTableFormatter } from 'src/components/tables/CellTable'
+
+const ListSafeAttachmentsFilters = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
+
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+ ,
+ modal: true,
+ modalUrl: `/api/EditSafeAttachmentsFilter?State=Enable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to enable this rule?',
+ },
+ {
+ label: 'Disable Rule',
+ color: 'info',
+ icon: ,
+ modal: true,
+ modalUrl: `/api/EditSafeAttachmentsFilter?State=Disable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to disable this rule?',
+ },
+ /*{
+ label: 'Delete Rule',
+ color: 'danger',
+ modal: true,
+ icon: ,
+ modalUrl: `/api/RemoveSafeAttachmentsFilter?TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to delete this rule?',
+ },*/
+ ]}
+ placement="end"
+ visible={ocVisible}
+ id={row.id}
+ hideFunction={() => setOCVisible(false)}
+ />
+ >
+ )
+ }
+
+ const columns = [
+ {
+ name: 'Rule Name',
+ selector: (row) => row['RuleName'],
+ sortable: true,
+ exportSelector: 'RuleName',
+ },
+ {
+ name: 'Policy Name',
+ selector: (row) => row['Name'],
+ sortable: true,
+ exportSelector: 'Name',
+ },
+ {
+ name: 'Enabled',
+ selector: (row) => row['State'],
+ exportSelector: 'State',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Priority',
+ selector: (row) => row['Priority'],
+ sortable: true,
+ exportSelector: 'Priority',
+ maxWidth: '40px',
+ },
+ {
+ name: 'Recipient Domains',
+ selector: (row) => row['RecipientDomainIs'],
+ sortable: true,
+ exportSelector: 'RecipientDomainIs',
+ cell: cellTableFormatter('RecipientDomainIs'),
+ },
+ {
+ name: 'Action',
+ selector: (row) => row['Action'],
+ sortable: true,
+ exportSelector: 'Action',
+ },
+ {
+ name: 'QuarantineTag',
+ selector: (row) => row['QuarantineTag'],
+ sortable: true,
+ exportSelector: 'QuarantineTag',
+ },
+ {
+ name: 'Redirect',
+ selector: (row) => row['Redirect'],
+ exportSelector: 'Redirect',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Redirect Address',
+ selector: (row) => row['RedirectAddress'],
+ sortable: true,
+ exportSelector: 'RedirectAddress',
+ },
+ {
+ name: 'Creation Date',
+ selector: (row) => row['WhenCreated'],
+ sortable: true,
+ exportSelector: 'WhenCreated',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Last Modified Date',
+ selector: (row) => row['WhenChanged'],
+ sortable: true,
+ exportSelector: 'WhenChanged',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default ListSafeAttachmentsFilters
diff --git a/src/views/email-exchange/reports/SafeLinksFilters.jsx b/src/views/email-exchange/reports/SafeLinksFilters.jsx
new file mode 100644
index 000000000000..68c9d07305ff
--- /dev/null
+++ b/src/views/email-exchange/reports/SafeLinksFilters.jsx
@@ -0,0 +1,226 @@
+import { CButton } from '@coreui/react'
+import { faBan, faBook, faCheck, faEllipsisV, faTrash } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import React, { useState } from 'react'
+import { CippActionsOffcanvas } from 'src/components/utilities'
+import { useSelector } from 'react-redux'
+import { CippPageList } from 'src/components/layout'
+import { cellDateFormatter, cellBooleanFormatter, CellTip } from 'src/components/tables'
+import { cellTableFormatter } from 'src/components/tables/CellTable'
+
+const ListSafeLinksFilters = () => {
+ const tenant = useSelector((state) => state.app.currentTenant)
+
+ const Offcanvas = (row, rowIndex, formatExtraData) => {
+ const [ocVisible, setOCVisible] = useState(false)
+
+ return (
+ <>
+ setOCVisible(true)}>
+
+
+ ,
+ modal: true,
+ modalUrl: `/api/EditSafeLinksFilter?State=Enable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to enable this rule?',
+ },
+ {
+ label: 'Disable Rule',
+ color: 'info',
+ icon: ,
+ modal: true,
+ modalUrl: `/api/EditSafeLinksFilter?State=Disable&TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to disable this rule?',
+ },
+ /*{
+ label: 'Delete Rule',
+ color: 'danger',
+ modal: true,
+ icon: ,
+ modalUrl: `/api/RemoveSafeLinksFilter?TenantFilter=${tenant.defaultDomainName}&RuleName=${row.RuleName}`,
+ modalMessage: 'Are you sure you want to delete this rule?',
+ },*/
+ ]}
+ placement="end"
+ visible={ocVisible}
+ id={row.id}
+ hideFunction={() => setOCVisible(false)}
+ />
+ >
+ )
+ }
+
+ const columns = [
+ {
+ name: 'Rule Name',
+ selector: (row) => row['RuleName'],
+ sortable: true,
+ exportSelector: 'RuleName',
+ },
+ {
+ name: 'Policy Name',
+ selector: (row) => row['Name'],
+ sortable: true,
+ exportSelector: 'Name',
+ },
+ {
+ name: 'Enabled',
+ selector: (row) => row['State'],
+ exportSelector: 'State',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Priority',
+ selector: (row) => row['Priority'],
+ sortable: true,
+ exportSelector: 'Priority',
+ maxWidth: '40px',
+ },
+ {
+ name: 'Recipient Domains',
+ selector: (row) => row['RecipientDomainIs'],
+ sortable: true,
+ exportSelector: 'RecipientDomainIs',
+ cell: cellTableFormatter('RecipientDomainIs'),
+ },
+ {
+ name: 'SafeLinks For Email',
+ selector: (row) => row['EnableSafeLinksForEmail'],
+ exportSelector: 'EnableSafeLinksForEmail',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'SafeLinks For Teams',
+ selector: (row) => row['EnableSafeLinksForTeams'],
+ exportSelector: 'EnableSafeLinksForTeams',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'SafeLinks For Office',
+ selector: (row) => row['EnableSafeLinksForOffice'],
+ exportSelector: 'EnableSafeLinksForOffice',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Track Clicks',
+ selector: (row) => row['TrackClicks'],
+ exportSelector: 'TrackClicks',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Scan Urls',
+ selector: (row) => row['ScanUrls'],
+ exportSelector: 'ScanUrls',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Enable For Internal Senders',
+ selector: (row) => row['EnableForInternalSenders'],
+ exportSelector: 'EnableForInternalSenders',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Deliver Message After Scan',
+ selector: (row) => row['DeliverMessageAfterScan'],
+ exportSelector: 'DeliverMessageAfterScan',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Allow Click Through',
+ selector: (row) => row['AllowClickThrough'],
+ exportSelector: 'AllowClickThrough',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Disable Url Rewrite',
+ selector: (row) => row['DisableUrlRewrite'],
+ exportSelector: 'DisableUrlRewrite',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Organization Branding',
+ selector: (row) => row['EnableOrganizationBranding'],
+ exportSelector: 'EnableOrganizationBranding',
+ cell: cellBooleanFormatter(),
+ maxWidth: '40px',
+ },
+ {
+ name: 'Creation Date',
+ selector: (row) => row['WhenCreated'],
+ sortable: true,
+ exportSelector: 'WhenCreated',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Last Modified Date',
+ selector: (row) => row['WhenChanged'],
+ sortable: true,
+ exportSelector: 'WhenChanged',
+ cell: cellDateFormatter(),
+ maxWidth: '150px',
+ },
+ {
+ name: 'Actions',
+ cell: Offcanvas,
+ maxWidth: '80px',
+ },
+ ]
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default ListSafeLinksFilters
diff --git a/src/views/email-exchange/tools/MessageViewer.jsx b/src/views/email-exchange/tools/MessageViewer.jsx
new file mode 100644
index 000000000000..f010c0b37c4f
--- /dev/null
+++ b/src/views/email-exchange/tools/MessageViewer.jsx
@@ -0,0 +1,342 @@
+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 = ({ 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')) {
+ 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) => {
+ 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)),
+ )
+ }
+ 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 =
+ 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 =
+ 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
+ link.download = fileName
+ link.click()
+ URL.revokeObjectURL(url)
+ }
+ }
+
+ function isValidDate(d) {
+ return d instanceof Date && !isNaN(d)
+ }
+
+ const showEmailModal = (emailSource, title = 'Email Source') => {
+ ModalService.open({
+ data: emailSource,
+ componentType: 'codeblock',
+ title: title,
+ size: 'lg',
+ })
+ }
+
+ const EmailButtons = (emailHeaders, emailSource) => {
+ const emailSourceBytes = new TextEncoder().encode(emailSource)
+ const blob = new Blob([emailSourceBytes], { type: 'message/rfc822' })
+ const url = URL.createObjectURL(blob)
+ return (
+
+ {emailHeaders && (
+ showEmailModal(emailHeaders, 'Email Headers')} className="me-2">
+
+ View Headers
+
+ )}
+ showEmailModal(emailSource)}>
+
+ View Source
+
+
+ )
+ }
+
+ useEffect(() => {
+ readEml(emailSource, (err, ReadEmlJson) => {
+ if (err) {
+ setEmlError(true)
+ setEmlContent(null)
+ setMessageHtml(null)
+ setEmlHeaders(null)
+ } else {
+ setEmlContent(ReadEmlJson)
+ setEmlError(false)
+ if (ReadEmlJson.html) {
+ var sanitizedHtml = DOMPurify.sanitize(ReadEmlJson.html)
+ var parsedHtml = ReactHtmlParser(sanitizedHtml)
+ setMessageHtml(parsedHtml)
+ } 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, setEmlHeaders])
+
+ var buttons = EmailButtons(emlHeaders, 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 = {
+ emailSource: 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
diff --git a/src/views/endpoint/intune/Devices.jsx b/src/views/endpoint/intune/Devices.jsx
index 85111eab9967..01ab8dd14071 100644
--- a/src/views/endpoint/intune/Devices.jsx
+++ b/src/views/endpoint/intune/Devices.jsx
@@ -283,6 +283,44 @@ const DevicesList = () => {
path: '/api/ListDevices',
reportName: `${tenant?.defaultDomainName}-Device-List`,
params: { TenantFilter: tenant?.defaultDomainName },
+ tableProps: {
+ keyField: 'id',
+ selectableRows: true,
+ actionsList: [
+ {
+ label: 'Sync Device',
+ modal: true,
+ modalUrl: `/api/ExecDeviceAction?TenantFilter=${tenant.defaultDomainName}&GUID=!id&Action=syncDevice`,
+ modalMessage: 'Are you sure you want to Sync these device(s)?',
+ },
+ {
+ label: 'Reboot Device(s)',
+ modal: true,
+ modalUrl: `/api/ExecDeviceAction?TenantFilter=${tenant.defaultDomainName}&GUID=!id&Action=rebootNow`,
+ modalMessage: 'Are you sure you want to reboot these device(s)?',
+ },
+ {
+ label: 'Update Windows Defender',
+ modal: true,
+ modalUrl: `/api/ExecDeviceAction?TenantFilter=${tenant.defaultDomainName}&GUID=!id&Action=windowsDefenderUpdateSignatures`,
+ modalMessage:
+ 'Are you sure you want to update the Windows Defender signatures for these device(s)?',
+ },
+ {
+ label: 'Rotate Local Admin Password',
+ modal: true,
+ modalUrl: `/api/ExecDeviceAction?TenantFilter=${tenant.defaultDomainName}&GUID=!id&Action=RotateLocalAdminPassword`,
+ modalMessage: 'Are you sure you want to rotate the password for these devices(s)?',
+ },
+ {
+ label: 'Rotate Bitlocker Keys',
+ modal: true,
+ modalUrl: `/api/ExecDeviceAction?TenantFilter=${tenant.defaultDomainName}&GUID=!id&Action=rotateBitLockerKeys`,
+ modalMessage:
+ 'Are you sure you want to rotate the Bitlocker Keys for these device(s)?',
+ },
+ ],
+ },
}}
/>
)
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/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 }
+
+
+
+
+
+
+
+ Scheduled creation Date
+ setStartDate(date)}
+ />
+
+
+
+ Send results to
+
+
+
+
+
+
diff --git a/src/views/identity/administration/OffboardingWizard.jsx b/src/views/identity/administration/OffboardingWizard.jsx
index d7008c071724..208190c00ee9 100644
--- a/src/views/identity/administration/OffboardingWizard.jsx
+++ b/src/views/identity/administration/OffboardingWizard.jsx
@@ -167,17 +167,17 @@ const OffboardingWizard = () => {
-
-
+
+
+
+
+
+
-
-
+
-
-
-
diff --git a/src/views/identity/administration/RiskyUsers.jsx b/src/views/identity/administration/RiskyUsers.jsx
index 727ee8dd896d..9d0b949b135a 100644
--- a/src/views/identity/administration/RiskyUsers.jsx
+++ b/src/views/identity/administration/RiskyUsers.jsx
@@ -77,15 +77,6 @@ const RiskyUsers = () => {
modalMessage: 'Are you sure you want to dismiss this users risk?',
icon: ,
},
- /* TODO Add action for Compromised
- {
- label: 'Confirm Compromised',
- color: 'info',
- modal: true,
- modalUrl: `/api/ExecBECRemediate?TenantFilter=${tenant.defaultDomainName}&userid=${row.id}`,
- modalMessage: 'Are you sure you want to confirm this user as compromised?',
- icon: ,
- },*/
]}
placement="end"
visible={ocVisible}
@@ -97,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'],
@@ -186,10 +191,12 @@ const RiskyUsers = () => {
path: `api/ListGraphRequest`,
reportName: `${tenant?.defaultDomainName}-ListRiskyUsers`,
params: {
- TenantFilter: tenant?.defaultDomainName,
+ TenantFilter: tenant.defaultDomainName,
Endpoint: `identityProtection/riskyUsers`,
$count: true,
$orderby: 'riskLastUpdatedDateTime desc',
+ NoPagination: true,
+ $top: 500,
},
tableProps: {
selectableRows: true,
@@ -200,13 +207,6 @@ const RiskyUsers = () => {
model: true,
modalUrl: `/api/ExecDismissRiskyUser?TenantFilter=${tenant.defaultDomainName}&userid=!id&userDisplayName=!userDisplayName`,
},
- /* TODO Add action for Compromised
- {
- label: 'Confirm Compromised',
- color: 'danger',
- model: true,
- modalUrl: `/api/ExecBECRemediate?TenantFilter=${tenant.defaultDomainName}&userid=!id`,
- },*/
],
},
}}
diff --git a/src/views/identity/administration/UserMailboxRuleList.jsx b/src/views/identity/administration/UserMailboxRuleList.jsx
index 4ab999081ee8..ae2592866e85 100644
--- a/src/views/identity/administration/UserMailboxRuleList.jsx
+++ b/src/views/identity/administration/UserMailboxRuleList.jsx
@@ -10,7 +10,7 @@ const rowStyle = (row, rowIndex) => {
return style
}
-export default function UserMailboxRuleList({ userId, tenantDomain, className = null }) {
+export default function UserMailboxRuleList({ userId, userEmail, tenantDomain, className = null }) {
const formatter = (cell) => CellBoolean({ cell })
const columns = [
{
@@ -79,7 +79,7 @@ export default function UserMailboxRuleList({ userId, tenantDomain, className =
datatable={{
reportName: 'ListUserMailboxRules',
path: '/api/ListUserMailboxRules',
- params: { tenantFilter: tenantDomain, userId },
+ params: { tenantFilter: tenantDomain, userId, userEmail },
columns,
keyField: 'id',
responsive: true,
@@ -93,6 +93,7 @@ export default function UserMailboxRuleList({ userId, tenantDomain, className =
UserMailboxRuleList.propTypes = {
userId: PropTypes.string.isRequired,
+ userEmail: PropTypes.string,
tenantDomain: PropTypes.string.isRequired,
className: PropTypes.string,
}
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?',
},
{
diff --git a/src/views/identity/administration/ViewUser.jsx b/src/views/identity/administration/ViewUser.jsx
index ae0e44510693..72341f5cda9f 100644
--- a/src/views/identity/administration/ViewUser.jsx
+++ b/src/views/identity/administration/ViewUser.jsx
@@ -91,7 +91,11 @@ const ViewUser = (props) => {
-
+
)}
diff --git a/src/views/identity/reports/MFAReport.jsx b/src/views/identity/reports/MFAReport.jsx
index 27f09aecfa3b..7eb6859a3b07 100644
--- a/src/views/identity/reports/MFAReport.jsx
+++ b/src/views/identity/reports/MFAReport.jsx
@@ -3,6 +3,7 @@ import { useSelector } from 'react-redux'
import { cellBooleanFormatter, CellTip } from 'src/components/tables'
import { CippPageList } from 'src/components/layout'
import { Row } from 'react-bootstrap'
+import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat'
const columns = [
{
@@ -57,9 +58,23 @@ const columns = [
selector: (row) => row['CoveredByCA'],
name: 'Enforced via Conditional Access',
sortable: true,
- cell: (row) => CellTip(row['CoveredByCA']),
+ cell: cellGenericFormatter(),
exportSelector: 'CoveredByCA',
},
+ {
+ selector: (row) => row['MFAMethods'],
+ name: 'MFA Methods',
+ sortable: true,
+ cell: cellGenericFormatter(),
+ exportSelector: 'MFAMethods',
+ },
+ {
+ selector: (row) => row['CAPolicies'],
+ name: 'CA Policies',
+ sortable: true,
+ cell: cellGenericFormatter(),
+ exportSelector: 'CAPolicies',
+ },
]
const Altcolumns = [
diff --git a/src/views/identity/reports/RiskDetections.jsx b/src/views/identity/reports/RiskDetections.jsx
index b139fa058161..b031a9d9fc6b 100644
--- a/src/views/identity/reports/RiskDetections.jsx
+++ b/src/views/identity/reports/RiskDetections.jsx
@@ -214,10 +214,12 @@ const RiskDetections = () => {
path: `api/ListGraphRequest`,
reportName: `${tenant?.defaultDomainName}-RiskDetections-Report`,
params: {
- TenantFilter: tenant?.defaultDomainName,
+ TenantFilter: tenant.defaultDomainName,
Endpoint: `identityProtection/riskDetections`,
$count: true,
$orderby: 'detectedDateTime desc',
+ NoPagination: true,
+ $top: 500,
},
}}
/>
diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx
index 5f303a3c827f..a3c38dbf7dc6 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,33 @@ 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))
+
+ 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, ensure there is only one group mapping for the listed roles to continue: ${duplicates.join(
+ ', ',
+ )}`
+ } else {
+ return undefined
+ }
+ } else {
+ return 'You must select at least one GDAP Role'
+ }
+}
const GDAPInviteWizard = () => {
const defaultRolesArray = [
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={
{
+ // 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')
+ 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 = (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
+
+
+
+
+
+
+ >
+ )
+ }
+
+ 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)}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default ListAuditLogs
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
diff --git a/src/views/tenant/administration/SecureScore.jsx b/src/views/tenant/administration/SecureScore.jsx
index 76c77d584296..bfa9d2e751c2 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,9 @@ 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'
+import DOMPurify from 'dompurify'
+import ReactHtmlParser from 'react-html-parser'
const SecureScore = () => {
const textRef = useRef()
@@ -65,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])
@@ -192,6 +201,11 @@ const SecureScore = () => {
cell: cellGenericFormatter(),
exportSelector: 'actionUrl',
},
+ {
+ name: 'Updates',
+ selector: (row) => row?.controlStateUpdates,
+ cell: cellGenericFormatter(),
+ },
]
return (
@@ -278,7 +292,7 @@ const SecureScore = () => {
- {viewMode && translateData.controlScores.length > 1 && isSuccess && isSuccessTranslation && (
+ {viewMode && translateData.controlScores?.length > 1 && isSuccess && isSuccessTranslation && (
Best Practice Report
@@ -286,7 +300,7 @@ const SecureScore = () => {
{
Description
-
+
+ {sanitizeHtml(`${info.description} ${info.implementationStatus}`)}
+
{info.scoreInPercentage !== 100 && (
Remediation Recommendation
- {
-
- }
+ {{sanitizeHtml(info.remediation)}
}
)}
@@ -392,6 +399,12 @@ const SecureScore = () => {
openResolution(info)} className="me-3">
Change Status
+
+
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 && (
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 = () => {
diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx
index f60370975ee6..10eab27c96fd 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.AssignTo', undefined)
+ 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',
@@ -751,29 +783,63 @@ const ApplyNewStandard = () => {
},
].map((template, index) => (
-
+
{template.name}
Deploy {template.name}
-
- Report
-
-
-
- Alert
-
-
Remediate
-
+
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
+
>
)}
))}
-
+
Autopilot Profile
Deploy Autopilot profile
-
- Report
-
-
-
- Alert
-
-
Remediate
-
+
Settings
@@ -938,23 +1023,15 @@ const ApplyNewStandard = () => {
-
+
Autopilot Status Page
Deploy Autopilot Status Page
-
- Report
-
-
-
- Alert
-
-
Remediate
-
+
Settings
diff --git a/staticwebapp.config.json b/staticwebapp.config.json
index 8a0fafca07d4..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 'none'; img-src 'self' 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"
diff --git a/version_latest.txt b/version_latest.txt
index dfda3e0b4f01..6abaeb2f9072 100644
--- a/version_latest.txt
+++ b/version_latest.txt
@@ -1 +1 @@
-6.1.0
+6.2.0