diff --git a/public/version_latest.txt b/public/version_latest.txt index 7cbea073bea1..804440660c71 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -5.2.0 \ No newline at end of file +5.2.1 \ No newline at end of file diff --git a/src/components/forms/RFFComponents.jsx b/src/components/forms/RFFComponents.jsx index 2d315826b4f2..834c9688d2cc 100644 --- a/src/components/forms/RFFComponents.jsx +++ b/src/components/forms/RFFComponents.jsx @@ -11,6 +11,7 @@ import { CTooltip, } from '@coreui/react' import Select from 'react-select' +import Creatable, { useCreatable } from 'react-select/creatable' import { Field } from 'react-final-form' import { FieldArray } from 'react-final-form-arrays' import React, { useState, useMemo, useRef } from 'react' @@ -393,6 +394,7 @@ export const RFFSelectSearch = ({ disabled = false, retainInput = true, isLoading = false, + allowCreate = false, refreshFunction, props, }) => { @@ -433,7 +435,7 @@ export const RFFSelectSearch = ({ )} - {onChange && ( + {!allowCreate && onChange && ( )} + {allowCreate && onChange && ( + + )} + {allowCreate && !onChange && ( + + )} {meta.error && meta.touched && {meta.error}} ) diff --git a/src/views/identity/reports/MFAReport.jsx b/src/views/identity/reports/MFAReport.jsx index 5dbae89bb4ad..70ece62c2b4e 100644 --- a/src/views/identity/reports/MFAReport.jsx +++ b/src/views/identity/reports/MFAReport.jsx @@ -25,12 +25,6 @@ const columns = [ cell: cellBooleanFormatter({ colourless: true }), exportSelector: 'isLicensed', }, - { - selector: (row) => row['PerUser'], - name: 'Per user MFA Status', - sortable: true, - exportSelector: 'PerUser', - }, { selector: (row) => row['MFARegistration'], name: 'Registered for Conditional MFA', @@ -38,6 +32,13 @@ const columns = [ cell: cellBooleanFormatter(), exportSelector: 'MFARegistration', }, + { + selector: (row) => row['CoveredBySD'], + name: 'Enforced via Security Defaults', + sortable: true, + cell: cellBooleanFormatter({ colourless: true }), + exportSelector: 'CoveredBySD', + }, { selector: (row) => row['CoveredByCA'], name: 'Enforced via Conditional Access', @@ -46,11 +47,10 @@ const columns = [ exportSelector: 'CoveredByCA', }, { - selector: (row) => row['CoveredBySD'], - name: 'Enforced via Security Defaults', + selector: (row) => row['PerUser'], + name: 'Per user MFA Status', sortable: true, - cell: cellBooleanFormatter({ colourless: true }), - exportSelector: 'CoveredBySD', + exportSelector: 'PerUser', }, ] @@ -134,7 +134,17 @@ const MFAList = () => { datatable={{ filterlist: [ { filterName: 'Enabled users', filter: '"accountEnabled":true' }, + { filterName: 'Non-guest users', filter: 'Complex: UPN notlike #EXT#' }, { filterName: 'Licensed users', filter: 'Complex: IsLicensed eq true' }, + { + filterName: 'Enabled, licensed non-guest users missing MFA', + filter: + 'Complex: UPN notlike #EXT#; IsLicensed eq true; accountEnabled eq true; MFARegistration eq false', + }, + { + filterName: 'No MFA methods registered', + filter: 'Complex: MFARegistration eq false', + }, ], columns: tenant.defaultDomainName === 'AllTenants' ? Altcolumns : columns, path: '/api/ListMFAUsers', diff --git a/src/views/tenant/administration/GraphExplorer.jsx b/src/views/tenant/administration/GraphExplorer.jsx index 3664f42caed6..8f38821ec387 100644 --- a/src/views/tenant/administration/GraphExplorer.jsx +++ b/src/views/tenant/administration/GraphExplorer.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from 'react' +import React, { useEffect, useState, useRef, useMemo } from 'react' import { CAlert, CButton, @@ -31,6 +31,7 @@ import { OnChange } from 'react-final-form-listeners' import { cellGenericFormatter } from 'src/components/tables/CellGenericFormat' import PropTypes from 'prop-types' import { CippCodeOffCanvas, ModalService } from 'src/components/utilities' +import { debounce } from 'lodash-es' const GraphExplorer = () => { const tenant = useSelector((state) => state.app.currentTenant) @@ -39,6 +40,7 @@ const GraphExplorer = () => { const [alertVisible, setAlertVisible] = useState() const [random, setRandom] = useState('') const [random2, setRandom2] = useState('') + const [random3, setRandom3] = useState('') const [ocVisible, setOCVisible] = useState(false) const [searchNow, setSearchNow] = useState(false) const [visibleA, setVisibleA] = useState(true) @@ -49,6 +51,7 @@ const GraphExplorer = () => { } const [execGraphRequest, graphrequest] = useLazyGenericGetRequestQuery() const [execPostRequest, postResults] = useLazyGenericPostRequestQuery() + const [execPropRequest, availableProperties] = useLazyGenericGetRequestQuery() const { data: customPresets = [], isFetching: presetsIsFetching, @@ -56,6 +59,22 @@ const GraphExplorer = () => { } = useGenericGetRequestQuery({ path: '/api/ListGraphExplorerPresets', params: { random2 } }) const QueryColumns = { set: false, data: [] } + function endpointChange(value) { + execPropRequest({ + path: '/api/ListGraphRequest', + params: { + Endpoint: value, + ListProperties: true, + TenantFilter: tenant.defaultDomainName, + IgnoreErrors: true, + random: (Math.random() + 1).toString(36).substring(7), + }, + }) + } + const debounceEndpointChange = useMemo(() => { + return debounce(endpointChange, 1000) + }, [endpointChange]) + if (graphrequest.isSuccess) { if (graphrequest.data?.Results?.length > 0) { //set columns @@ -217,10 +236,15 @@ const GraphExplorer = () => { useEffect(() => { if (params?.endpoint) { + var select = '' + if (params?.$select) { + select = params.$select.map((p) => p.value).join(',') + } execGraphRequest({ path: 'api/ListGraphRequest', params: { ...params, + $select: select, random: random, }, }) @@ -237,15 +261,36 @@ const GraphExplorer = () => { {({ form }) => ( {(value) => { + if (field == 'endpoint') { + debounceEndpointChange(value) + } if (value?.value) { let preset = presets.filter(function (obj) { return obj.id === value.value }) if (preset[0]?.id !== '') { - if (preset[0]?.params[set]) { - onChange(preset[0]?.params[set]) + if (set == 'endpoint') { + debounceEndpointChange(preset[0]?.params[set]) + } + if (set == '$select') { + if (preset[0]?.params[set]) { + var properties = preset[0].params[set].split(',') + var selectedProps = properties.map((prop) => { + return { + label: prop, + value: prop, + } + }) + onChange(selectedProps) + } else { + onChange('') + } } else { - onChange(preset[0][set]) + if (preset[0]?.params[set]) { + onChange(preset[0]?.params[set]) + } else { + onChange(preset[0][set]) + } } } } @@ -263,6 +308,10 @@ const GraphExplorer = () => { function getPresetProps(values) { var newvals = Object.assign({}, values) + console.log(newvals) + if (newvals?.$select !== undefined && Array.isArray(newvals?.$select)) { + newvals.$select = newvals?.$select.map((p) => p.value).join(',') + } delete newvals['reportTemplate'] delete newvals['tenantFilter'] delete newvals['IsShared'] @@ -454,6 +503,7 @@ const GraphExplorer = () => { placeholder="Enter the Graph Endpoint you'd like to run the custom report for." /> + { placeholder="Enter the filter string for the Graph query" /> - +
+ { + return { + name: prop, + value: prop, + } + }) + : [] + } + allowCreate={true} + refreshFunction={() => + setRandom3((Math.random() + 1).toString(36).substring(7)) + } + isLoading={availableProperties.isFetching} + /> +
{
{!searchNow && Execute a search to get started.} + {graphrequest.isFetching && ( +
+ Loading Data +
+ )} {graphrequest.isSuccess && QueryColumns.set && searchNow && ( diff --git a/version_latest.txt b/version_latest.txt index 7cbea073bea1..804440660c71 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -5.2.0 \ No newline at end of file +5.2.1 \ No newline at end of file