diff --git a/package-lock.json b/package-lock.json index 490fb9a392b6..bc2ed9783377 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cipp", - "version": "5.6.1", + "version": "5.8.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cipp", - "version": "5.6.1", + "version": "5.8.5", "license": "AGPL-3.0", "dependencies": { "@coreui/chartjs": "^3.0.0", diff --git a/package.json b/package.json index f92f847989c0..039c0b97dd85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cipp", - "version": "5.8.5", + "version": "5.9.3", "description": "The CyberDrain Improved Partner Portal is a portal to help manage administration for Microsoft Partners.", "homepage": "https://cipp.app/", "bugs": { diff --git a/public/version_latest.txt b/public/version_latest.txt index a075da200b96..99a8b57b6f85 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -5.8.5 +5.9.3 diff --git a/src/_nav.jsx b/src/_nav.jsx index ada800050fc5..874b6e28eb42 100644 --- a/src/_nav.jsx +++ b/src/_nav.jsx @@ -45,6 +45,11 @@ const _nav = [ name: 'Users', to: '/identity/administration/users', }, + { + component: CNavItem, + name: 'Risky Users', + to: '/identity/administration/risky-users', + }, { component: CNavItem, name: 'Groups', @@ -114,6 +119,11 @@ const _nav = [ name: 'AAD Connect Report', to: '/identity/reports/azure-ad-connect-report', }, + { + component: CNavItem, + name: 'Risk Detections', + to: '/identity/reports/risk-detections', + }, ], }, { diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index 516098532900..14163a033d62 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -470,19 +470,25 @@ RFFCFormSelect.propTypes = { export function Condition({ when, is, children, like, regex }) { return ( <> - {is && ( + {is !== undefined && ( - {({ input: { value } }) => (value === is ? children : null)} + {({ input: { value } }) => { + return value === is ? children : null + }} )} - {like && ( + {like !== undefined && ( - {({ input: { value } }) => (value.includes(like) ? children : null)} + {({ input: { value } }) => { + return value.includes(like) ? children : null + }} )} - {regex && ( + {regex !== undefined && ( - {({ input: { value } }) => (value.match(regex) ? children : null)} + {({ input: { value } }) => { + return value.match(regex) ? children : null + }} )} diff --git a/src/components/layout/AppHeader.jsx b/src/components/layout/AppHeader.jsx index 620a2c9332c7..3d3636d0e426 100644 --- a/src/components/layout/AppHeader.jsx +++ b/src/components/layout/AppHeader.jsx @@ -15,7 +15,12 @@ import { AppHeaderSearch } from 'src/components/header' import { CippActionsOffcanvas, TenantSelector } from '../utilities' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faBars } from '@fortawesome/free-solid-svg-icons' -import { setCurrentTheme, setUserSettings, toggleSidebarShow } from 'src/store/features/app' +import { + setCurrentTheme, + setSetupCompleted, + setUserSettings, + toggleSidebarShow, +} from 'src/store/features/app' import { useMediaPredicate } from 'react-media-hook' import { useGenericGetRequestQuery, @@ -92,6 +97,25 @@ const AppHeader = () => { } }, [delay, state]) } + //useEffect to check if any of the dashboard alerts contained the key "setupCompleted" and if so, + //check if the value of this key is false. If so, set the setupCompleted state to false + //if none is found, set the setupCompleted state to true + useEffect(() => { + if (dashboard && Array.isArray(dashboard) && dashboard.length >= 1) { + console.log('Finding if setup is completed.') + const setupCompleted = dashboard.find((alert) => alert && alert.setupCompleted === false) + if (setupCompleted) { + console.log("Setup isn't completed yet, we found a match with false.") + dispatch(setSetupCompleted({ setupCompleted: false })) + } else { + console.log('Setup is completed.') + dispatch(setSetupCompleted({ setupCompleted: true })) + } + } else { + console.log('Setup is completed.') + dispatch(setSetupCompleted({ setupCompleted: true })) + } + }, [dashboard, dispatch]) useEffect(() => { if (cippQueueList.isUninitialized && (cippQueueList.isFetching || cippQueueList.isLoading)) { diff --git a/src/components/tables/CippTable.jsx b/src/components/tables/CippTable.jsx index e858c5e28fc6..6eee3a54e611 100644 --- a/src/components/tables/CippTable.jsx +++ b/src/components/tables/CippTable.jsx @@ -637,6 +637,7 @@ export default function CippTable({ // Define the flatten function const flatten = (obj, prefix = '') => { + if (obj === null) return {} return Object.keys(obj).reduce((output, key) => { const newKey = prefix ? `${prefix}.${key}` : key const value = obj[key] === null ? '' : obj[key] @@ -644,7 +645,17 @@ export default function CippTable({ if (typeof value === 'object' && !Array.isArray(value)) { Object.assign(output, flatten(value, newKey)) } else { - output[newKey] = value + if (Array.isArray(value)) { + if (typeof value[0] === 'object') { + value.map((item, idx) => { + Object.assign(output, flatten(item, `${newKey}[${idx}]`)) + }) + } else { + output[newKey] = value + } + } else { + output[newKey] = value + } } return output }, {}) @@ -677,8 +688,7 @@ export default function CippTable({ }) return Array.isArray(exportData) && exportData.length > 0 ? exportData.map((obj) => { - const flattenedObj = flatten(obj) - return applyFormatter(flattenedObj) + return flatten(applyFormatter(obj)) }) : [] } @@ -689,8 +699,7 @@ export default function CippTable({ // Adjusted dataFlat processing to include formatting let dataFlat = Array.isArray(data) ? data.map((item) => { - const flattenedItem = flatten(item) - return applyFormatter(flattenedItem) + return flatten(applyFormatter(item)) }) : [] if (!disablePDFExport) { diff --git a/src/components/utilities/CippActionsOffcanvas.jsx b/src/components/utilities/CippActionsOffcanvas.jsx index 63febc746bce..c335bae5db5a 100644 --- a/src/components/utilities/CippActionsOffcanvas.jsx +++ b/src/components/utilities/CippActionsOffcanvas.jsx @@ -345,7 +345,7 @@ export default function CippActionsOffcanvas(props) { } let actionsSelectorsContent try { - actionsSelectorsContent = props.actionsSelect.map((action, index) => ( + actionsSelectorsContent = props?.actionsSelect?.map((action, index) => ( {action.label} import('./views/identity/administration/DeployJITAdmin')), "/identity/administration/ViewBec": React.lazy(() => import('./views/identity/administration/ViewBEC')), "/identity/administration/users": React.lazy(() => import('./views/identity/administration/Users')), + "/identity/administration/risky-users": React.lazy(() => import('./views/identity/administration/RiskyUsers')), "/identity/administration/devices": React.lazy(() => import('./views/identity/administration/Devices')), "/identity/administration/groups/add": React.lazy(() => import('./views/identity/administration/AddGroup')), "/identity/administration/group-templates": React.lazy(() => import('./views/identity/administration/GroupTemplates')), @@ -32,6 +33,7 @@ import React from 'react' "/identity/reports/inactive-users-report": React.lazy(() => import('./views/identity/reports/InactiveUsers')), "/identity/reports/Signin-report": React.lazy(() => import('./views/identity/reports/SignIns')), "/identity/reports/azure-ad-connect-report": React.lazy(() => import('./views/identity/reports/AzureADConnectReport')), + "/identity/reports/risk-detections": React.lazy(() => import('./views/identity/reports/RiskDetections')), "/tenant/administration/tenants": React.lazy(() => import('./views/tenant/administration/Tenants')), "/tenant/administration/tenants/edit": React.lazy(() => import('./views/tenant/administration/EditTenant')), "/tenant/administration/partner-relationships": React.lazy(() => import('./views/tenant/administration/PartnerRelationships')), diff --git a/src/routes.json b/src/routes.json index f27aaa5b148e..2975b4c168ac 100644 --- a/src/routes.json +++ b/src/routes.json @@ -99,6 +99,12 @@ "component": "views/identity/administration/Users", "allowedRoles": ["admin", "editor", "readonly"] }, + { + "path": "/identity/administration/risky-users", + "name": "Risky Users", + "component": "views/identity/administration/RiskyUsers", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/identity/administration/devices", "name": "Devices", @@ -206,6 +212,12 @@ "component": "views/identity/reports/AzureADConnectReport", "allowedRoles": ["admin", "editor", "readonly"] }, + { + "path": "/identity/reports/risk-detections", + "name": "Risk Detections", + "component": "views/identity/reports/RiskDetections", + "allowedRoles": ["admin", "editor", "readonly"] + }, { "path": "/tenant", "name": "Tenant", diff --git a/src/store/features/app.js b/src/store/features/app.js index 91b0e1f5bf86..3f8ce3bcfb0f 100644 --- a/src/store/features/app.js +++ b/src/store/features/app.js @@ -14,6 +14,7 @@ const initialState = { defaultColumns: {}, newUserDefaults: {}, recentPages: [], + setupCompleted: false, } export const appSlice = createSlice({ @@ -62,6 +63,9 @@ export const appSlice = createSlice({ setRecentPages: (state, action) => { state.recentPages = action.payload?.recentPages }, + setSetupCompleted: (state, action) => { + state.setupCompleted = action.payload?.setupCompleted + }, }, }) @@ -80,6 +84,7 @@ export const { setDefaultColumns, setNewUserDefaults, setRecentPages, + setSetupCompleted, } = appSlice.actions export default persistReducer( diff --git a/src/store/middleware/errorMiddleware.js b/src/store/middleware/errorMiddleware.js index 14e868882709..074acb4f47c2 100644 --- a/src/store/middleware/errorMiddleware.js +++ b/src/store/middleware/errorMiddleware.js @@ -2,17 +2,25 @@ // set action.hideToastError to `true` to ignore this middleware import { showToast } from 'src/store/features/toasts' import { isRejectedWithValue } from '@reduxjs/toolkit' +import { store } from '../store' export const errorMiddleware = ({ dispatch }) => (next) => (action) => { + const { getState } = store + const state = getState() + const setupCompleted = state.app?.setupCompleted + let SamWizardError = false + if (action?.meta?.arg?.originalArgs?.path === '/api/ExecSamSetup') { + SamWizardError = true + } if ( isRejectedWithValue(action) && !action.error?.hideToastError && - action.payload.message !== 'canceled' + action.payload.message !== 'canceled' && + (setupCompleted || SamWizardError) ) { - console.error(action) if (action.payload.data === 'Backend call failure') { action.payload.data = 'The Azure Function has taken too long to respond. Try selecting a different report or a single tenant instead' diff --git a/src/views/email-exchange/administration/QuarantineList.jsx b/src/views/email-exchange/administration/QuarantineList.jsx index 7453af66b1a7..6c5de828afec 100644 --- a/src/views/email-exchange/administration/QuarantineList.jsx +++ b/src/views/email-exchange/administration/QuarantineList.jsx @@ -135,6 +135,20 @@ const QuarantineList = () => { capabilities={{ allTenants: false, helpContext: 'https://google.com' }} title="Quarantine Management" datatable={{ + filterlist: [ + { filterName: 'Status: Not Released', filter: '"ReleaseStatus":"NotReleased"' }, + { filterName: 'Status: Released', filter: '"ReleaseStatus":"Released"' }, + { filterName: 'Status: Denied', filter: '"ReleaseStatus":"Denied"' }, + { + filterName: 'Reason: High Confidence Phishing', + filter: '"QuarantineTypes":"HighConfPhish"', + }, + { filterName: 'Reason: Phishing', filter: '"QuarantineTypes":"Phish"' }, + { filterName: 'Reason: Spam', filter: '"QuarantineTypes":"Spam"' }, + { filterName: 'Reason: Malware', filter: '"QuarantineTypes":"Malware"' }, + { filterName: 'Reason: FileTypeBlock', filter: '"QuarantineTypes":"FileTypeBlock"' }, + { filterName: 'Reason: Bulk', filter: '"QuarantineTypes":"Bulk"' }, + ], keyField: 'id', reportName: `${tenant?.defaultDomainName}-Mailbox-Quarantine`, path: '/api/ListMailQuarantine', diff --git a/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx b/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx index c45b21e3fb0d..c2135ec9c833 100644 --- a/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx +++ b/src/views/email-exchange/spamfilter/DeploySpamfilter.jsx @@ -6,7 +6,7 @@ import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons' import { CippWizard } from 'src/components/layout' import { WizardTableField } from 'src/components/tables' import PropTypes from 'prop-types' -import { RFFCFormSelect, RFFCFormTextarea } from 'src/components/forms' +import { RFFCFormSelect, RFFCFormTextarea, RFFCFormInput } from 'src/components/forms' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { OnChange } from 'react-final-form-listeners' @@ -151,6 +151,11 @@ const SpamFilterAdd = () => { /> + + + + +
@@ -179,6 +184,8 @@ const SpamFilterAdd = () => {
Rule Settings
{props.values.PowerShellCommand} +
Priority
+ {props.values.Priority} diff --git a/src/views/identity/administration/DeployJITAdmin.jsx b/src/views/identity/administration/DeployJITAdmin.jsx index b87ca67dd8f3..9d16549f9867 100644 --- a/src/views/identity/administration/DeployJITAdmin.jsx +++ b/src/views/identity/administration/DeployJITAdmin.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { CButton, CCallout, CCol, CForm, CRow, CSpinner, CTooltip } from '@coreui/react' import { useSelector } from 'react-redux' -import { Field, Form } from 'react-final-form' +import { Field, Form, FormSpy } from 'react-final-form' import { Condition, RFFCFormInput, @@ -9,7 +9,11 @@ import { RFFCFormSwitch, RFFSelectSearch, } from 'src/components/forms' -import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' +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 { CippContentCard, CippPage, CippPageList } from 'src/components/layout' @@ -45,6 +49,7 @@ const DeployJITAdmin = () => { useraction: values.useraction, AdminRoles: values.AdminRoles?.map((role) => role.value), StartDate: startTime, + UseTAP: values.useTap, EndDate: endTime, ExpireAction: values.expireAction.value, PostExecution: { @@ -62,7 +67,17 @@ const DeployJITAdmin = () => { data: users = [], isFetching: usersIsFetching, error: usersError, - } = useListUsersQuery({ tenantDomain }) + } = useGenericGetRequestQuery({ + path: '/api/ListGraphRequest', + params: { + TenantFilter: tenantDomain, + Endpoint: 'users', + $select: 'id,displayName,userPrincipalName,accountEnabled', + $count: true, + $top: 999, + $orderby: 'displayName', + }, + }) return ( @@ -129,7 +144,7 @@ const DeployJITAdmin = () => { ({ + values={users?.Results?.map((user) => ({ value: user.id, name: `${user.displayName} <${user.userPrincipalName}>`, }))} @@ -137,6 +152,23 @@ const DeployJITAdmin = () => { name="UserId" isLoading={usersIsFetching} /> + + {({ values }) => { + return users?.Results?.map((user, key) => { + if ( + user.id === values?.UserId?.value && + user.accountEnabled === false + ) { + return ( + + This user is currently disabled, they will automatically be + enabled when JIT is executed. + + ) + } + }) + }} + @@ -195,6 +227,15 @@ const DeployJITAdmin = () => { /> + + + +
+ +
+
+
+
@@ -258,6 +299,13 @@ const DeployJITAdmin = () => { cell: cellGenericFormatter(), exportSelector: 'userPrincipalName', }, + { + name: 'Account Enabled', + selector: (row) => row['accountEnabled'], + sortable: true, + cell: cellGenericFormatter(), + exportSelector: 'accountEnabled', + }, { name: 'JIT Enabled', selector: (row) => row['jitAdminEnabled'], diff --git a/src/views/identity/administration/EditGroup.jsx b/src/views/identity/administration/EditGroup.jsx index 928962af5b83..7e81fc66306e 100644 --- a/src/views/identity/administration/EditGroup.jsx +++ b/src/views/identity/administration/EditGroup.jsx @@ -111,7 +111,7 @@ const EditGroup = () => { allowExternal: values.allowExternal, sendCopies: values.sendCopies, mail: group[0].mail, - groupName: group[0].DisplayName, + groupName: group[0].displayName, } //window.alert(JSON.stringify(shippedValues)) genericPostRequest({ path: '/api/EditGroup', values: shippedValues }).then((res) => { diff --git a/src/views/identity/administration/RiskyUsers.jsx b/src/views/identity/administration/RiskyUsers.jsx new file mode 100644 index 000000000000..d1ffc4ad0499 --- /dev/null +++ b/src/views/identity/administration/RiskyUsers.jsx @@ -0,0 +1,103 @@ +import { useSelector } from 'react-redux' +import { CippPageList } from 'src/components/layout' + +const columns = [ + { + name: 'Risk Last Updated Date', + selector: (row) => row['riskLastUpdatedDateTime'], + sortable: true, + exportSelector: 'riskLastUpdatedDateTime', + }, + { + name: 'User Principal Name', + selector: (row) => row['userPrincipalName'], + sortable: true, + exportSelector: 'userPrincipalName', + }, + { + name: 'Risk Level', + selector: (row) => row['riskLevel'], + sortable: true, + exportSelector: 'riskLevel', + }, + { + name: 'Risk State', + selector: (row) => row['riskState'], + sortable: true, + exportSelector: 'riskState', + }, + { + name: 'Risk Detail', + selector: (row) => row['riskDetail'], + sortable: true, + exportSelector: 'riskDetail', + }, + { + name: 'isProcessing', + selector: (row) => row['isProcessing'], + sortable: true, + exportSelector: 'isProcessing', + }, + { + name: 'isDeleted', + selector: (row) => row['isDeleted'], + sortable: true, + exportSelector: 'isDeleted', + }, +] + +const RiskyUsers = () => { + const tenant = useSelector((state) => state.app.currentTenant) + + return ( + <> + + + ) +} + +export default RiskyUsers diff --git a/src/views/identity/administration/Users.jsx b/src/views/identity/administration/Users.jsx index 93adcb0f1de8..0b0ea525497e 100644 --- a/src/views/identity/administration/Users.jsx +++ b/src/views/identity/administration/Users.jsx @@ -293,7 +293,7 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { label: 'Revoke all user sessions', color: 'danger', modal: true, - modalUrl: `/api/ExecRevokeSessions?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalUrl: `/api/ExecRevokeSessions?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&Username=${row.userPrincipalName}`, modalMessage: 'Are you sure you want to revoke this users sessions?', }, { diff --git a/src/views/identity/reports/RiskDetections.jsx b/src/views/identity/reports/RiskDetections.jsx new file mode 100644 index 000000000000..ed1604acda6e --- /dev/null +++ b/src/views/identity/reports/RiskDetections.jsx @@ -0,0 +1,125 @@ +import { useSelector } from 'react-redux' +import { CippPageList } from 'src/components/layout' +import { CellTip } from 'src/components/tables' + +const columns = [ + { + name: 'Detected Date', + selector: (row) => row['detectedDateTime'], + sortable: true, + exportSelector: 'detectedDateTime', + }, + { + name: 'User Principal Name', + selector: (row) => row['userPrincipalName'], + sortable: true, + exportSelector: 'userPrincipalName', + }, + { + name: 'Location', + selector: (row) => `${row.location?.city} - ${row.location?.countryOrRegion}`, + sortable: true, + exportSelector: 'Location', + cell: (row) => CellTip(`${row.location?.city} - ${row.location?.countryOrRegion}`), + }, + { + name: 'IP Address', + selector: (row) => row['ipAddress'], + sortable: true, + exportSelector: 'ipAddress', + }, + { + name: 'Risk State', + selector: (row) => row['riskState'], + sortable: true, + exportSelector: 'riskState', + }, + { + name: 'Risk Detail', + selector: (row) => row['riskDetail'], + sortable: true, + exportSelector: 'riskDetail', + }, + { + name: 'Risk Level', + selector: (row) => row['riskLevel'], + sortable: true, + exportSelector: 'riskLevel', + }, + { + name: 'Risk Type', + selector: (row) => row['riskType'], + sortable: true, + exportSelector: 'riskType', + }, + { + name: 'Risk Event Type', + selector: (row) => row['riskEventType'], + sortable: true, + exportSelector: 'riskEventType', + }, + { + name: 'Detection Type', + selector: (row) => row['detectionTimingType'], + sortable: true, + exportSelector: 'detectionTimingType', + }, + { + name: 'Activity', + selector: (row) => row['activity'], + sortable: true, + exportSelector: 'activity', + }, +] + +const RiskDetections = () => { + const tenant = useSelector((state) => state.app.currentTenant) + + return ( + <> + + + ) +} + +export default RiskDetections diff --git a/src/views/identity/reports/SignIns.jsx b/src/views/identity/reports/SignIns.jsx index bcd14acc0c07..45934e5de02b 100644 --- a/src/views/identity/reports/SignIns.jsx +++ b/src/views/identity/reports/SignIns.jsx @@ -16,7 +16,7 @@ import React, { useState } from 'react' import { Form } from 'react-final-form' import { useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' -import { RFFCFormCheck, RFFCFormInput } from 'src/components/forms' +import { Condition, RFFCFormCheck, RFFCFormInput } from 'src/components/forms' import { CippPageList } from 'src/components/layout' import { CellTip } from 'src/components/tables' import useQuery from 'src/hooks/useQuery' @@ -95,11 +95,10 @@ const columns = [ const SignInsReport = () => { const tenant = useSelector((state) => state.app.currentTenant) - let navigate = useNavigate() let query = useQuery() const filter = query.get('filter') const DateFilter = query.get('DateFilter') - const searchparams = query.toString() + const [searchParams, setSearchParams] = useState({}) const [visibleA, setVisibleA] = useState(true) const handleSubmit = async (values) => { @@ -113,11 +112,7 @@ const SignInsReport = () => { SearchNow: true, ...values, } - var queryString = Object.keys(shippedValues) - .map((key) => key + '=' + shippedValues[key]) - .join('&') - - navigate(`?${queryString}`) + setSearchParams(shippedValues) } return ( @@ -152,6 +147,11 @@ const SignInsReport = () => { render={({ handleSubmit, submitting, values }) => { return ( + + + + + { + + + + + + + @@ -189,10 +201,16 @@ const SignInsReport = () => { title="Sign Ins Report" capabilities={{ allTenants: false, helpContext: 'https://google.com' }} datatable={{ + filterlist: [ + { + filterName: 'Risky sign-ins', + filter: 'Complex: riskState ne none', + }, + ], columns: columns, - path: `/api/ListSignIns?${searchparams}`, + path: `/api/ListSignIns`, reportName: `${tenant?.defaultDomainName}-SignIns-Report`, - params: { TenantFilter: tenant?.defaultDomainName }, + params: { TenantFilter: tenant?.defaultDomainName, ...searchParams }, }} /> diff --git a/src/views/teams-share/onedrive/OneDriveList.jsx b/src/views/teams-share/onedrive/OneDriveList.jsx index cacd7d39d0fc..f8bb4936e8d6 100644 --- a/src/views/teams-share/onedrive/OneDriveList.jsx +++ b/src/views/teams-share/onedrive/OneDriveList.jsx @@ -37,7 +37,7 @@ const OneDriveList = () => { TenantFilter: tenant.defaultDomainName, RemovePermission: false, }, - modalUrl: `/api/ExecSharePointOwner`, + modalUrl: `/api/ExecSharePointPerms`, modalDropdown: { url: `/api/listUsers?TenantFilter=${tenant.defaultDomainName}`, labelField: 'displayName', @@ -55,7 +55,7 @@ const OneDriveList = () => { TenantFilter: tenant.defaultDomainName, RemovePermission: true, }, - modalUrl: `/api/ExecSharePointOwner`, + modalUrl: `/api/ExecSharePointPerms`, modalDropdown: { url: `/api/listUsers?TenantFilter=${tenant.defaultDomainName}`, labelField: 'displayName', diff --git a/src/views/teams-share/sharepoint/SharepointList.jsx b/src/views/teams-share/sharepoint/SharepointList.jsx index e4ba2d74db6f..a93bb7c01250 100644 --- a/src/views/teams-share/sharepoint/SharepointList.jsx +++ b/src/views/teams-share/sharepoint/SharepointList.jsx @@ -79,7 +79,7 @@ const SharepointList = () => { RemovePermission: false, URL: row.URL, }, - modalUrl: `/api/ExecSharePointOwner`, + modalUrl: `/api/ExecSharePointPerms`, modalDropdown: { url: `/api/listUsers?TenantFilter=${tenant.defaultDomainName}`, labelField: 'displayName', @@ -98,7 +98,7 @@ const SharepointList = () => { RemovePermission: true, URL: row.URL, }, - modalUrl: `/api/ExecSharePointOwner`, + modalUrl: `/api/ExecSharePointPerms`, modalDropdown: { url: `/api/listUsers?TenantFilter=${tenant.defaultDomainName}`, labelField: 'displayName', diff --git a/src/views/tenant/administration/GDAPInviteWizard.jsx b/src/views/tenant/administration/GDAPInviteWizard.jsx index 2377ae2e0239..956b8a374f00 100644 --- a/src/views/tenant/administration/GDAPInviteWizard.jsx +++ b/src/views/tenant/administration/GDAPInviteWizard.jsx @@ -19,6 +19,7 @@ import { TitleButton } from 'src/components/buttons' import PropTypes from 'prop-types' import { useLazyGenericGetRequestQuery, useLazyGenericPostRequestQuery } from 'src/store/api/app' import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' +import { Condition, RFFCFormSwitch } from 'src/components/forms' const Error = ({ name }) => ( (value && value.length !== 0 ? undefined : 'Required') const GDAPInviteWizard = () => { + const defaultRolesArray = [ + { + Name: 'User Administrator', + ObjectId: 'fe930be7-5e62-47db-91af-98c3a49a38b1', + }, + { + Name: 'Teams Administrator', + ObjectId: '69091246-20e8-4a56-aa4d-066075b2a7a8', + }, + { + Name: 'SharePoint Administrator', + ObjectId: 'f28a1f50-f6e7-4571-818b-6a12f2af6b6c', + }, + { + Name: 'Security Administrator', + ObjectId: '194ae4cb-b126-40b2-bd5b-6091b380977d', + }, + { + Name: 'Privileged Role Administrator', + ObjectId: 'e8611ab8-c189-46e8-94e1-60213ab1f814', + }, + { + Name: 'Privileged Authentication Administrator', + ObjectId: '7be44c8a-adaf-4e2a-84d6-ab2649e08a13', + }, + { + Name: 'Intune Administrator', + ObjectId: '3a2c62db-5318-420d-8d74-23affee5d9d5', + }, + { + Name: 'Exchange Administrator', + ObjectId: '29232cdf-9323-42fd-ade2-1d097af3e4de', + }, + { + Name: 'Cloud Device Administrator', + ObjectId: '7698a772-787b-4ac8-901f-60d6b08affd2', + }, + { + Name: 'Cloud App Security Administrator', + ObjectId: '892c5842-a9a6-463a-8041-72aa08ca3cf6', + }, + { + Name: 'Authentication Policy Administrator', + ObjectId: '0526716b-113d-4c15-b2c8-68e3c22b9f80', + }, + { + Name: 'Application Administrator', + ObjectId: '9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3', + }, + ] const [inviteCount, setInviteCount] = useState(1) const [loopRunning, setLoopRunning] = React.useState(false) const [massResults, setMassResults] = React.useState([]) const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() const [genericGetRequest, getResults] = useLazyGenericGetRequestQuery() + const [easyModeDone, setEasyMode] = useState(false) + const [easyModeProgress, setEasyModeProgress] = useState(null) const handleSubmit = async (values) => { - const resultsarr = [] - setLoopRunning(true) - for (var x = 0; x < inviteCount; x++) { - const results = await genericPostRequest({ path: '/api/ExecGDAPInvite', values: values }) - resultsarr.push(results.data) - setMassResults(resultsarr) + if (values.easyMode === true) { + if (easyModeDone === false) { + const defaultRoles = { + gdapRoles: defaultRolesArray, + } + const easyModeValues = { ...defaultRoles } + try { + await genericPostRequest({ path: '/api/ExecAddGDAPRole', values: easyModeValues }) + const results = await genericGetRequest({ path: '/api/ListGDAPRoles' }) + const filteredResults = results.data.filter((role) => + defaultRolesArray.some((defaultRole) => defaultRole.ObjectId === role.roleDefinitionId), + ) + setEasyMode(true) + const resultsarr = [] + setLoopRunning(true) + for (var x = 0; x < inviteCount; x++) { + const results = await genericPostRequest({ + path: '/api/ExecGDAPInvite', + values: { ...values, gdapRoles: filteredResults }, + }) + resultsarr.push(results.data) + setMassResults(resultsarr) + } + setLoopRunning(false) + } catch (error) { + setEasyModeProgress(`Failed to create GDAP roles or invite users ${error}`) + setLoopRunning(false) + } + } + } else { + // Normal mode execution + const resultsarr = [] + setLoopRunning(true) + for (var y = 0; y < inviteCount; y++) { + const results = await genericPostRequest({ path: '/api/ExecGDAPInvite', values: values }) + resultsarr.push(results.data) + setMassResults(resultsarr) + } + setLoopRunning(false) } - setLoopRunning(false) } - const formValues = {} + const formValues = { easyMode: true } const inviteColumns = [ { @@ -99,10 +184,7 @@ const GDAPInviteWizard = () => { onSubmit={handleSubmit} wizardTitle="GDAP Invite Wizard" > - +

Step 1

@@ -111,39 +193,66 @@ const GDAPInviteWizard = () => {

- - CIPP will create a single relationship with all roles you've selected for the maximum - duration of 730 days using a GUID as a random name for the relationship. -
It is recommend to put CIPP user in the correct GDAP Role Groups to manage your - environment secure after deployment of GDAP. -
-
- -
+ + + +

+ CIPP will create 12 new groups in your Azure AD environment if they do not exist, + and add the CIPP user to these 12 groups. The CIPP user will be added to the + following groups: +

+
    +
  • M365 GDAP Application Administrator
  • +
  • M365 GDAP Authentication Policy Administrator
  • +
  • M365 GDAP Cloud App Security Administrator
  • +
  • M365 GDAP Cloud Device Administrator
  • +
  • M365 GDAP Exchange Administrator
  • +
  • M365 GDAP Intune Administrator
  • +
  • M365 GDAP Privileged Authentication Administrator
  • +
  • M365 GDAP Privileged Role Administrator
  • +
  • M365 GDAP Security Administrator
  • +
  • M365 GDAP SharePoint Administrator
  • +
  • M365 GDAP Teams Administrator
  • +
  • M365 GDAP User Administrator
  • +
+ Any other user that needs to gain access to your Microsoft CSP Tenants will need to be + manually added to these groups. +
+
+ + + CIPP will create a single relationship with all roles you've selected for the maximum + duration of 730 days using a GUID as a random name for the relationship. + + +
+ +
- - {(props) => ( - row['RoleName'], - sortable: true, - exportselector: 'Name', - }, - { - name: 'Group', - selector: (row) => row['GroupName'], - sortable: true, - }, - ]} - fieldProps={props} - /> - )} - + + {(props) => ( + row['RoleName'], + sortable: true, + exportselector: 'Name', + }, + { + name: 'Group', + selector: (row) => row['GroupName'], + sortable: true, + }, + ]} + fieldProps={props} + /> + )} + +

@@ -190,25 +299,57 @@ const GDAPInviteWizard = () => { -
Roles and group names
- {props.values.gdapRoles.map((role, idx) => ( + {props.values.easyMode === false && ( + <> +
Roles and group names
+ {props.values.gdapRoles.map((role, idx) => ( + + {role.RoleName === 'Company Administrator' && ( + + WARNING: The Company Administrator role will prevent GDAP + relationships from automatically extending. We recommend against + using this in any GDAP relationship. + + )} + + ))} + +
    + {props.values.gdapRoles.map((role, idx) => ( +
  • + {role.RoleName} - {role.GroupName} +
  • + ))} +
+
+ + )} + {props.values.easyMode === true && ( <> - {role.RoleName === 'Company Administrator' && ( - - WARNING: The Company Administrator role will prevent GDAP - relationships from automatically extending. We recommend against using - this in any GDAP relationship. - - )} + +

+ You have selected CIPP to manage your roles and groups. Invites will + contain the following roles and groups +

+
    +
  • M365 GDAP Application Administrator
  • +
  • M365 GDAP Authentication Policy Administrator
  • +
  • M365 GDAP Cloud App Security Administrator
  • +
  • M365 GDAP Cloud Device Administrator
  • +
  • M365 GDAP Exchange Administrator
  • +
  • M365 GDAP Intune Administrator
  • +
  • M365 GDAP Privileged Authentication Administrator
  • +
  • M365 GDAP Privileged Role Administrator
  • +
  • M365 GDAP Security Administrator
  • +
  • M365 GDAP SharePoint Administrator
  • +
  • M365 GDAP Teams Administrator
  • +
  • M365 GDAP User Administrator
  • +
+
- ))} - - {props.values.gdapRoles.map((role, idx) => ( -
  • - {role.RoleName} - {role.GroupName} -
  • - ))} -
    + )} + {easyModeProgress && {easyModeProgress}} + {getResults.isFetching && }
    @@ -218,6 +359,14 @@ const GDAPInviteWizard = () => { )} {(massResults.length >= 1 || loopRunning) && ( <> + +

    + The invites have been generated. You can view the results below. The + invite link is to be used by a Global Administrator + of your clients Tenant. Theonboardinglink is to be + used by a CIPP administrator to finish the process inside of CIPP. +

    +
    {loopRunning ? ( diff --git a/src/views/tenant/conditional/DeployVacation.jsx b/src/views/tenant/conditional/DeployVacation.jsx index 572aee8bb83d..6f43e2ddda30 100644 --- a/src/views/tenant/conditional/DeployVacation.jsx +++ b/src/views/tenant/conditional/DeployVacation.jsx @@ -50,7 +50,17 @@ const ListClassicAlerts = () => { data: users = [], isFetching: usersIsFetching, error: usersError, - } = useListUsersQuery({ tenantDomain }) + } = useGenericGetRequestQuery({ + path: '/api/ListGraphRequest', + params: { + TenantFilter: tenantDomain, + Endpoint: 'users', + $select: 'id,displayName,userPrincipalName,accountEnabled', + $count: true, + $top: 999, + $orderby: 'displayName', + }, + }) const { data: caPolicies = [], @@ -90,12 +100,13 @@ const ListClassicAlerts = () => { ({ + values={users?.Results?.map((user) => ({ value: user.id, name: `${user.displayName} <${user.userPrincipalName}>`, }))} placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} name="UserId" + isLoading={usersIsFetching} /> @@ -109,6 +120,7 @@ const ListClassicAlerts = () => { }))} placeholder={!caIsFetching ? 'Select policy' : 'Loading...'} name="PolicyId" + isLoading={caIsFetching} /> diff --git a/src/views/tenant/standards/BPAReportBuilder.jsx b/src/views/tenant/standards/BPAReportBuilder.jsx index 584958f9ec40..eabe4d1bdc63 100644 --- a/src/views/tenant/standards/BPAReportBuilder.jsx +++ b/src/views/tenant/standards/BPAReportBuilder.jsx @@ -157,6 +157,12 @@ const BPAReportBuilder = () => { newBPATemplate({ path: '/api/AddBPATemplate', values: data }) } + const handleDelete = async (event) => { + event.preventDefault() + const data = formData.name + newBPATemplate({ path: `/api/RemoveBPATemplate?TemplateName=${data}` }) + } + const options = { wordWrap: true, } @@ -228,17 +234,21 @@ const BPAReportBuilder = () => { - - - + + + Publish - - + + handleDelete(e)}> + + Delete + + {newTemplateResult.isFetching && } {newTemplateResult.isSuccess && ( diff --git a/src/views/tenant/standards/BestPracticeAnalyser.jsx b/src/views/tenant/standards/BestPracticeAnalyser.jsx index 801cd1eb6838..a0e1cb00b101 100644 --- a/src/views/tenant/standards/BestPracticeAnalyser.jsx +++ b/src/views/tenant/standards/BestPracticeAnalyser.jsx @@ -179,9 +179,12 @@ const BestPracticeAnalyser = () => { if (graphrequest.data.length === 0) { graphrequest.data = [{ data: 'No Data Found' }] } - const flatObj = graphrequest.data.Columns ? graphrequest.data.Columns : [] + const flatObj = graphrequest.data.Columns.length >= 0 ? graphrequest.data.Columns : [] flatObj.map((col) => { + if (col === null) { + return + } // Determine the cell selector based on the 'formatter' property let cellSelector if (col.formatter) { diff --git a/src/views/tenant/standards/ListAppliedStandards.jsx b/src/views/tenant/standards/ListAppliedStandards.jsx index 6548ebb25848..f60370975ee6 100644 --- a/src/views/tenant/standards/ListAppliedStandards.jsx +++ b/src/views/tenant/standards/ListAppliedStandards.jsx @@ -548,7 +548,7 @@ const ApplyNewStandard = () => { /> -
    Optional Input
    +
    Settings
    @@ -647,7 +647,7 @@ const ApplyNewStandard = () => { /> -
    Optional Input
    +
    Settings
    {obj.addedComponent && obj.addedComponent.map((component) => ( <> @@ -768,7 +768,7 @@ const ApplyNewStandard = () => {
    -
    Optional Input
    +
    Settings
    {template.templates.isSuccess && ( {
    -
    Optional Input
    +
    Settings
    { -
    Optional Input
    +
    Settings