From ee3890695427093f0e9a09d2c138b8f122fe43ff Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Tue, 21 Mar 2023 20:00:34 +0100 Subject: [PATCH 01/20] added filter --- src/views/identity/administration/Users.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/views/identity/administration/Users.js b/src/views/identity/administration/Users.js index 7df07ee6de0f..b086ddee69d4 100644 --- a/src/views/identity/administration/Users.js +++ b/src/views/identity/administration/Users.js @@ -271,6 +271,7 @@ const Users = (row) => { datatable={{ filterlist: [ { filterName: 'Enabled users', filter: '"accountEnabled":true' }, + { filterName: 'Disabled users', filter: '"accountEnabled":false' }, { filterName: 'AAD users', filter: '"onPremisesSyncEnabled":false' }, { filterName: 'Synced users', filter: '"onPremisesSyncEnabled":true' }, { filterName: 'Guest users', filter: '"usertype":"guest"' }, From 7693782fa71085e8cdb63fc62665efbbac829fcb Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Wed, 22 Mar 2023 12:17:48 +0000 Subject: [PATCH 02/20] Update routes.js Changed Title of page from "Basic Auth Report" to "Inactive User Report" --- src/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes.js b/src/routes.js index 61176edabc93..34165aabe388 100644 --- a/src/routes.js +++ b/src/routes.js @@ -268,7 +268,7 @@ const routes = [ { path: '/identity/reports/mfa-report', name: 'MFA Report', component: MFAReport }, { path: '/identity/reports/inactive-users-report', - name: 'Basic Auth Report', + name: 'Inactive Users Report', component: BasicAuthReport, }, { From b8a08b82b10724f305a16190cd7a491f88a8b982 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Wed, 22 Mar 2023 12:30:33 +0000 Subject: [PATCH 03/20] Updated "BasicAuthReport" to "InActiveUserReport" Updated "BasicAuthReport" to "InActiveUserReport" --- src/routes.js | 4 ++-- src/views/identity/reports/InactiveUsers.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes.js b/src/routes.js index 34165aabe388..a71896e965cf 100644 --- a/src/routes.js +++ b/src/routes.js @@ -55,7 +55,7 @@ const DeployConditional = React.lazy(() => import('src/views/tenant/conditional/ const ListLicences = React.lazy(() => import('src/views/tenant/administration/ListLicences')) const ListAppConsent = React.lazy(() => import('src/views/tenant/administration/ListOauthApps')) -const BasicAuthReport = React.lazy(() => import('src/views/identity/reports/InactiveUsers')) +const InActiveUserReport = React.lazy(() => import('src/views/identity/reports/InactiveUsers')) const SignInReport = React.lazy(() => import('src/views/identity/reports/SignIns')) const AzureADConnectReport = React.lazy(() => @@ -269,7 +269,7 @@ const routes = [ { path: '/identity/reports/inactive-users-report', name: 'Inactive Users Report', - component: BasicAuthReport, + component: InActiveUserReport, }, { path: '/identity/reports/Signin-report', diff --git a/src/views/identity/reports/InactiveUsers.js b/src/views/identity/reports/InactiveUsers.js index d9d933c5d35c..4c64fc18386e 100644 --- a/src/views/identity/reports/InactiveUsers.js +++ b/src/views/identity/reports/InactiveUsers.js @@ -41,7 +41,7 @@ const Altcolumns = [ exportSelector: 'lastRefreshedDateTime', }, ] -const BasicAuthReport = () => { +const InActiveUserReport = () => { const tenant = useSelector((state) => state.app.currentTenant) return ( @@ -58,4 +58,4 @@ const BasicAuthReport = () => { ) } -export default BasicAuthReport +export default InActiveUserReport From 98a80d735203e020f7fd35de493896f92f63cada Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Fri, 24 Mar 2023 11:15:50 +0000 Subject: [PATCH 04/20] Added CopyForSentItems Function Added CopyForSentItems Function and added displayName to output when resetting password --- .../email-exchange/administration/MailboxesList.js | 14 ++++++++++++++ src/views/identity/administration/Users.js | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/views/email-exchange/administration/MailboxesList.js b/src/views/email-exchange/administration/MailboxesList.js index 0fc1535160d6..9126ba955d04 100644 --- a/src/views/email-exchange/administration/MailboxesList.js +++ b/src/views/email-exchange/administration/MailboxesList.js @@ -95,6 +95,20 @@ const MailboxList = () => { modalMessage: 'Are you sure you want to convert this shared mailbox to a user mailbox?', }, + { + label: 'Copy Sent Items to Shared Mailbox', + color: 'info', + modal: true, + modalUrl: `/api/ExecCopyForSent?TenantFilter=${tenant.defaultDomainName}&ID=${row.UPN}`, + modalMessage: 'Are you sure you want to enable Copy Sent Items to Shared Mailbox?', + }, + { + label: 'Disable Copy Sent Items to Shared Mailbox', + color: 'info', + modal: true, + modalUrl: `/api/ExecCopyForSent?TenantFilter=${tenant.defaultDomainName}&ID=${row.UPN}&MessageCopyForSentAsEnabled=false`, + modalMessage: 'Are you sure you want to disable Copy Sent Items to Shared Mailbox?', + }, { label: 'Hide from Global Address List', color: 'info', diff --git a/src/views/identity/administration/Users.js b/src/views/identity/administration/Users.js index b086ddee69d4..92f4ef1f6bd0 100644 --- a/src/views/identity/administration/Users.js +++ b/src/views/identity/administration/Users.js @@ -159,14 +159,14 @@ const Offcanvas = (row, rowIndex, formatExtraData) => { label: 'Reset Password (Must Change)', color: 'info', modal: true, - modalUrl: `/api/ExecResetPass?MustChange=true&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalUrl: `/api/ExecResetPass?MustChange=true&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&displayName=${row.displayName}`, modalMessage: 'Are you sure you want to reset the password for this user?', }, { label: 'Reset Password', color: 'info', modal: true, - modalUrl: `/api/ExecResetPass?MustChange=false&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalUrl: `/api/ExecResetPass?MustChange=false&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&displayName=${row.displayName}`, modalMessage: 'Are you sure you want to reset the password for this user?', }, { From b9ae134f89841a687f18f1746c9c9d6e95830965 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Fri, 24 Mar 2023 14:42:56 +0000 Subject: [PATCH 05/20] Added User Mailbox Rules to the Users Page Added User Mailbox Rules to the Users Page --- .../administration/UserMailboxRuleList.js | 65 +++++++++++++++++++ src/views/identity/administration/ViewUser.js | 4 ++ 2 files changed, 69 insertions(+) create mode 100644 src/views/identity/administration/UserMailboxRuleList.js diff --git a/src/views/identity/administration/UserMailboxRuleList.js b/src/views/identity/administration/UserMailboxRuleList.js new file mode 100644 index 000000000000..8d15e48d2960 --- /dev/null +++ b/src/views/identity/administration/UserMailboxRuleList.js @@ -0,0 +1,65 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { CellTip } from 'src/components/tables' +import { DatatableContentCard } from 'src/components/contentcards' +import { faEnvelope } from '@fortawesome/free-solid-svg-icons' + +const rowStyle = (row, rowIndex) => { + const style = {} + + return style +} + +export default function UserMailboxRuleList({ userId, tenantDomain, className = null }) { + const columns = [ + { + selector: (row) => row['Name'], + name: 'Display Name', + sortable: true, + cell: (row) => CellTip(row['Name']), + exportSelector: 'Name', + maxwidth: '350px', + }, + { + selector: (row) => row['Description'], + name: 'Description', + sortable: true, + cell: (row) => CellTip(row['Description']), + exportSelector: 'Description', + width: '600px', + }, + { + selector: (row) => row['ForwardTo'], + name: 'Forwards To', + sortable: true, + cell: (row) => CellTip(row['ForwardTo']), + exportSelector: 'ForwardTo', + width: '600px', + }, + ] + + return ( + + ) +} + +UserMailboxRuleList.propTypes = { + userId: PropTypes.string.isRequired, + tenantDomain: PropTypes.string.isRequired, + className: PropTypes.string, +} diff --git a/src/views/identity/administration/ViewUser.js b/src/views/identity/administration/ViewUser.js index 1f9363eb06b2..9e7f39feb4c8 100644 --- a/src/views/identity/administration/ViewUser.js +++ b/src/views/identity/administration/ViewUser.js @@ -19,6 +19,7 @@ import UserEmailSettings from 'src/views/identity/administration/UserEmailSettin import UserEmailPermissions from 'src/views/identity/administration/UserEmailPermissions' import UserGroups from 'src/views/identity/administration/UserGroups' import UserSigninLogs from 'src/views/identity/administration/UserSigninLogs' +import UserMailboxRuleList from 'src/views/identity/administration/UserMailboxRuleList' import { useListUserQuery } from 'src/store/api/users' const ViewUser = (props) => { @@ -90,6 +91,9 @@ const ViewUser = (props) => { + + + )} From cc5db48f7a6daa0a122ec60d3f8de6eb13c76210 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Fri, 24 Mar 2023 15:33:20 +0000 Subject: [PATCH 06/20] Update UserMailboxRuleList.js --- .../administration/UserMailboxRuleList.js | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/views/identity/administration/UserMailboxRuleList.js b/src/views/identity/administration/UserMailboxRuleList.js index 8d15e48d2960..acb19e3e44e1 100644 --- a/src/views/identity/administration/UserMailboxRuleList.js +++ b/src/views/identity/administration/UserMailboxRuleList.js @@ -1,6 +1,6 @@ import React from 'react' import PropTypes from 'prop-types' -import { CellTip } from 'src/components/tables' +import { cellBooleanFormatter, CellTip } from 'src/components/tables' import { DatatableContentCard } from 'src/components/contentcards' import { faEnvelope } from '@fortawesome/free-solid-svg-icons' @@ -11,6 +11,7 @@ const rowStyle = (row, rowIndex) => { } export default function UserMailboxRuleList({ userId, tenantDomain, className = null }) { + const formatter = (cell) => CellBoolean({ cell }) const columns = [ { selector: (row) => row['Name'], @@ -18,7 +19,7 @@ export default function UserMailboxRuleList({ userId, tenantDomain, className = sortable: true, cell: (row) => CellTip(row['Name']), exportSelector: 'Name', - maxwidth: '350px', + width: '200px', }, { selector: (row) => row['Description'], @@ -26,7 +27,7 @@ export default function UserMailboxRuleList({ userId, tenantDomain, className = sortable: true, cell: (row) => CellTip(row['Description']), exportSelector: 'Description', - width: '600px', + width: '350px', }, { selector: (row) => row['ForwardTo'], @@ -34,10 +35,42 @@ export default function UserMailboxRuleList({ userId, tenantDomain, className = sortable: true, cell: (row) => CellTip(row['ForwardTo']), exportSelector: 'ForwardTo', - width: '600px', + width: '250px', + }, + { + selector: (row) => row['RedirectTo'], + name: 'Redirect To', + sortable: true, + cell: (row) => CellTip(row['RedirectTo']), + exportSelector: 'RedirectTo', + maxwidth: '250px', + }, + { + selector: (row) => row['CopyToFolder'], + name: 'Copy To Folder', + sortable: true, + cell: (row) => CellTip(row['CopyToFolder']), + exportSelector: 'CopyToFolder', + maxwidth: '200px', + }, + { + selector: (row) => row['MoveToFolder'], + name: 'Move To Folder', + sortable: true, + cell: (row) => CellTip(row['MoveToFolder']), + exportSelector: 'MoveToFolder', + maxwidth: '200px', + }, + { + selector: (row) => row['DeleteMessage'], + name: 'Delete Message', + sortable: true, + cell: cellBooleanFormatter({ colourless: true }), + formatter, + exportSelector: 'DeleteMessage', + width: '200px', }, ] - return ( Date: Sat, 25 Mar 2023 06:08:00 +0100 Subject: [PATCH 07/20] Fixed GranularDelegetedAdminPrivileges spelling mistake Fixed spelling mistake as mentioned in https://github.com/KelvinTegelaar/CIPP/issues/1443 Left the spelling mistake in delegatedAndGranularDelegetedAdminPrivileges because Microsoft misspelled it in Graph itself. --- src/views/cipp/CIPPSettings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/cipp/CIPPSettings.js b/src/views/cipp/CIPPSettings.js index 94e2e17f3897..55de4cd0580d 100644 --- a/src/views/cipp/CIPPSettings.js +++ b/src/views/cipp/CIPPSettings.js @@ -656,7 +656,7 @@ const ExcludedTenantsSettings = () => { if (cell === 'delegatedAndGranularDelegetedAdminPrivileges') { return } - if (cell === 'GranularDelegetedAdminPrivileges') { + if (cell === 'granularDelegatedAdminPrivileges') { return } return From 22506385b92788d72f7adf410e13b3e56bea29d2 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Tue, 28 Mar 2023 21:20:02 +0100 Subject: [PATCH 08/20] Added "Guests" functionality @KelvinTegelaar This is slightly broken "View" guest does not work "Edit" guest does not work I had to add the additional "GuestDetails" and "GuestLastLoginDetails" because the "Userdetails" and "UserLastLoginDetails" both refer back to the "ListUsers" function which no longer contains "Guests" If we wanted to keep "Guests" showing in the main "User" list, then a lot of what i have done can be undone, and the "View" and "Edit" button are likely to then work, but i dont think we should keep the guests in the "User" list now that we have a separate place for them Once the "View" and "Edit" pages are working there may well be some clean up required on them which i am happy to do. But its hard to do that whilst its not working. Sorry, i hope this isnt too much of a mess! --- src/_nav.js | 5 + src/routes.js | 8 + src/store/api/users.js | 22 + src/views/identity/administration/AddGuest.js | 148 ++++++ .../identity/administration/EditGuest.js | 432 ++++++++++++++++++ .../identity/administration/GuestDetails.js | 54 +++ .../administration/GuestLastLoginDetails.js | 54 +++ src/views/identity/administration/Guests.js | 290 ++++++++++++ .../identity/administration/ViewGuest.js | 84 ++++ 9 files changed, 1097 insertions(+) create mode 100644 src/views/identity/administration/AddGuest.js create mode 100644 src/views/identity/administration/EditGuest.js create mode 100644 src/views/identity/administration/GuestDetails.js create mode 100644 src/views/identity/administration/GuestLastLoginDetails.js create mode 100644 src/views/identity/administration/Guests.js create mode 100644 src/views/identity/administration/ViewGuest.js diff --git a/src/_nav.js b/src/_nav.js index 3177cc44935d..c5f872216928 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -44,6 +44,11 @@ const _nav = [ name: 'Users', to: '/identity/administration/users', }, + { + component: CNavItem, + name: 'Guests', + to: '/identity/administration/guests', + }, { component: CNavItem, name: 'Groups', diff --git a/src/routes.js b/src/routes.js index a71896e965cf..167bf6d71cb2 100644 --- a/src/routes.js +++ b/src/routes.js @@ -3,11 +3,15 @@ import React from 'react' const Home = React.lazy(() => import('src/views/home/Home')) const Logs = React.lazy(() => import('src/views/cipp/Logs')) const Users = React.lazy(() => import('src/views/identity/administration/Users')) +const Guests = React.lazy(() => import('src/views/identity/administration/Guests')) const DeletedItems = React.lazy(() => import('src/views/identity/administration/Deleted')) const ViewBEC = React.lazy(() => import('src/views/identity/administration/ViewBEC')) const AddUser = React.lazy(() => import('src/views/identity/administration/AddUser')) +const AddGuest = React.lazy(() => import('src/views/identity/administration/AddGuest')) const EditUser = React.lazy(() => import('src/views/identity/administration/EditUser')) +const EditGuest = React.lazy(() => import('src/views/identity/administration/EditGuest')) const ViewUser = React.lazy(() => import('src/views/identity/administration/ViewUser')) +const ViewGuest = React.lazy(() => import('src/views/identity/administration/ViewGuest')) const Groups = React.lazy(() => import('src/views/identity/administration/Groups')) const AddGroup = React.lazy(() => import('src/views/identity/administration/AddGroup')) const AddGroupTemplates = React.lazy(() => @@ -232,6 +236,10 @@ const routes = [ { path: '/identity/administration', name: 'Administration' }, { path: '/identity/administration/users', name: 'Users', component: Users }, { path: '/identity/administration/groups/add', name: 'Add Group', component: AddGroup }, + { path: '/identity/administration/guests', name: 'Guests', component: Guests }, + { path: '/identity/administration/guests/view', name: 'View Guest', component: ViewGuest }, + { path: '/identity/administration/guests/edit', name: 'Edit Guest', component: EditGuest }, + { path: '/identity/administration/guests/add', name: 'Add Guest', component: AddGuest }, { path: '/identity/administration/group-templates', name: 'Group Templates', diff --git a/src/store/api/users.js b/src/store/api/users.js index 66cfd9bd48c6..8f6aa1eb93e6 100644 --- a/src/store/api/users.js +++ b/src/store/api/users.js @@ -17,6 +17,14 @@ export const usersApi = baseApi.injectEndpoints({ }, }), }), + listGuests: builder.query({ + query: ({ tenantDomain }) => ({ + path: '/api/ListGuests', + params: { + TenantFilter: tenantDomain, + }, + }), + }), listContacts: builder.query({ query: ({ tenantDomain }) => ({ path: '/api/ListContacts', @@ -37,6 +45,18 @@ export const usersApi = baseApi.injectEndpoints({ return {} }, }), + listGuest: builder.query({ + query: ({ tenantDomain, userId, IncludeLogonDetails }) => ({ + path: '/api/ListGuests', + params: { userId, TenantFilter: tenantDomain, IncludeLogonDetails }, + }), + transformResponse: (response) => { + if (response?.length > 0) { + return response[0] + } + return {} + }, + }), listUserConditionalAccessPolicies: builder.query({ query: ({ tenantDomain, userId }) => ({ path: '/api/ListUserConditionalAccessPolicies', @@ -117,6 +137,8 @@ export const { useEditUserMutation, useListUsersQuery, useListUserQuery, + useListGuestsQuery, + useListGuestQuery, useListContactsQuery, useListUserConditionalAccessPoliciesQuery, useListUserSigninLogsQuery, diff --git a/src/views/identity/administration/AddGuest.js b/src/views/identity/administration/AddGuest.js new file mode 100644 index 000000000000..b8c117ea7f2d --- /dev/null +++ b/src/views/identity/administration/AddGuest.js @@ -0,0 +1,148 @@ +import React from 'react' +import { + CButton, + CCard, + CCardBody, + CCardHeader, + CCardTitle, + CCol, + CForm, + CRow, + CCallout, +} from '@coreui/react' +import { Form } from 'react-final-form' +import { RFFCFormCheck, RFFCFormInput } from 'src/components/forms' +import { CippPage } from 'src/components/layout' +import { useListAdConnectSettingsQuery } from 'src/store/api/adconnect' +import { useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { useSelector } from 'react-redux' +import { required } from 'src/validators' +import useQuery from 'src/hooks/useQuery' +import { useNavigate } from 'react-router-dom' + +const AddGuest = () => { + let navigate = useNavigate() + + const tenant = useSelector((state) => state.app.currentTenant) + const { defaultDomainName: tenantDomain } = tenant + let query = useQuery() + const allQueryObj = {} + for (const [key, value] of query.entries()) { + allQueryObj[key] = value + } + const { + data: adconnectsettings = [], + isFetching: adcIsFetching, + error: adcError, + } = useListAdConnectSettingsQuery({ tenantDomain }) + + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const onSubmit = (values) => { + const shippedValues = { + DisplayName: values.displayName, + Domain: values.primDomain, + Mail: values.mail, + PostalCode: values.postalCode, + RedirectURL: values.RedirectURL, + SendInvite: values.SendInvite, + tenantID: tenantDomain, + } + //window.alert(JSON.stringify(shippedValues)) + genericPostRequest({ path: '/api/AddGuest', values: shippedValues }) + } + + return ( + + {postResults.isSuccess && ( + + {postResults.data?.Results.map((result, index) => ( +
  • {result}
  • + ))} +
    + )} + + + + + Account Details + + + {adcError && Unable to determine Azure AD Connect Settings} + {!adcIsFetching && adconnectsettings.dirSyncEnabled && ( + + Warning! {adconnectsettings.dirSyncEnabled} This tenant currently has Active + Directory Sync Enabled. This usually means users should be created in Active + Directory + + )} +
    { + return ( + + + + + + + + + + + + + + + + + + + + + Add User + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + + {postResults.data.Results.map((message, idx) => { + return
  • {message}
  • + })} +
    + )} +
    + ) + }} + /> + + + + + + ) +} + +export default AddGuest diff --git a/src/views/identity/administration/EditGuest.js b/src/views/identity/administration/EditGuest.js new file mode 100644 index 000000000000..6759d0018386 --- /dev/null +++ b/src/views/identity/administration/EditGuest.js @@ -0,0 +1,432 @@ +import React, { useEffect, useState } from 'react' +import { CButton, CCallout, CCol, CForm, CFormLabel, CRow, CSpinner } from '@coreui/react' +import useQuery from 'src/hooks/useQuery' +import { useDispatch, useSelector } from 'react-redux' +import { Form } from 'react-final-form' +import { + Condition, + RFFCFormCheck, + RFFCFormInput, + RFFCFormSelect, + RFFCFormSwitch, + RFFCFormTextarea, + RFFSelectSearch, +} from 'src/components/forms' +import countryList from 'src/data/countryList' +import { useListGuestQuery, useListGuestsQuery } from 'src/store/api/users' +import { useListDomainsQuery } from 'src/store/api/domains' +import { useListLicensesQuery } from 'src/store/api/licenses' +import { CippCodeBlock, ModalService } from 'src/components/utilities' +import { 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 } from 'src/components/layout' +import { password } from 'src/validators' + +const EditGuest = () => { + const dispatch = useDispatch() + let query = useQuery() + const userId = query.get('userId') + const tenantDomain = query.get('tenantDomain') + + const [queryError, setQueryError] = useState(false) + + //const [editUser, { error: editUserError, isFetching: editUserIsFetching }] = useEditUserMutation() + + const { + data: user = {}, + isFetching: userIsFetching, + error: userError, + } = useListGuestQuery({ tenantDomain, userId }) + + const { + data: users = [], + isFetching: usersIsFetching, + error: usersError, + } = useListGuestsQuery({ tenantDomain }) + + const { + data: domains = [], + isFetching: domainsIsFetching, + error: domainsError, + } = useListDomainsQuery({ tenantDomain, userId }) + + const { + data: licenses = [], + isFetching: licensesIsFetching, + error: licensesError, + } = useListLicensesQuery({ tenantDomain }) + + useEffect(() => { + if (!userId || !tenantDomain) { + ModalService.open({ + body: 'Error invalid request, could not load requested user.', + title: 'Invalid Request', + }) + setQueryError(true) + } else { + setQueryError(false) + } + }, [userId, tenantDomain, dispatch]) + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const onSubmit = (values) => { + const shippedValues = { + BusinessPhone: values.businessPhones, + City: values.city, + CompanyName: values.companyName, + CopyFrom: values.CopyFrom ? values.CopyFrom.value : '', + Country: values.country, + Department: values.department, + DisplayName: values.displayName, + Domain: values.primDomain, + firstName: values.givenName, + Jobtitle: values.jobTitle, + LastName: values.surname, + License: values.licenses, + MobilePhone: values.mobilePhone, + Password: values.password, + PostalCode: values.postalCode, + usageLocation: values.usageLocation ? values.usageLocation.value : '', + UserID: userId, + Username: values.mailNickname, + streetAddress: values.streetAddress, + tenantID: tenantDomain, + mustchangepass: values.RequirePasswordChange, + } + //window.alert(JSON.stringify(shippedValues)) + genericPostRequest({ path: '/api/EditUser', values: shippedValues }) + } + const usageLocation = useSelector((state) => state.app.usageLocation) + const precheckedLicenses = user.assignedLicenses + ? user.assignedLicenses.reduce( + (o, key) => Object.assign(o, { [`License_${key.skuId}`]: true }), + {}, + ) + : '' + const initialState = { + keepLicenses: true, + ...user, + usageLocation: { + value: user.usageLocation ? user.usageLocation : usageLocation?.value, + label: user.usageLocation ? user.usageLocation : usageLocation?.label, + }, + license: precheckedLicenses, + } + + const formDisabled = queryError === true || !!userError || !user || Object.keys(user).length === 0 + const RawUser = JSON.stringify(user, null, 2) + return ( + + {!queryError && ( + <> + {postResults.isSuccess && ( + {postResults.data?.Results} + )} + {queryError && ( + + + + {/* @todo add more descriptive help message here */} + Failed to load user + + + + )} + + + + {userIsFetching && } + {userError && Error loading user} + {!userIsFetching && ( + { + return ( + + + + + + + + + + + + + + + + + + + + {domainsIsFetching && } + {!domainsIsFetching && ( + ({ + value: domain.id, + label: domain.id, + }))} + /> + )} + {domainsError && Failed to load list of domains} + + + + + + + + + + Settings + + + + + + + + + + + + ({ + value: Code, + name: Name, + }))} + disabled={formDisabled} + name="usageLocation" + placeholder="Type to search..." + label="Usage Location" + /> + + + + + + + Licenses +
    + {licensesIsFetching && } + {licensesError && Error loading licenses} + + {licensesIsFetching && } + {licensesError && Error loading licenses} + {!licensesIsFetching && + licenses?.map((license) => ( + + ))} +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ({ + value: user.mail, + name: user.displayName, + }))} + placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} + name="CopyFrom" + /> + {usersError && Failed to load list of users} + + + + + + Edit User + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + + {postResults.data.Results.map((message, idx) => { + return
  • {message}
  • + })} +
    + )} +
    + ) + }} + /> + )} +
    +
    + + + {userIsFetching && } + {userError && Error loading user} + {!userIsFetching && ( + <> + This is the (raw) information for this account. + + + )} + + +
    + + )} +
    + ) +} + +export default EditGuest diff --git a/src/views/identity/administration/GuestDetails.js b/src/views/identity/administration/GuestDetails.js new file mode 100644 index 000000000000..9f36fc0d250d --- /dev/null +++ b/src/views/identity/administration/GuestDetails.js @@ -0,0 +1,54 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { faPortrait } from '@fortawesome/free-solid-svg-icons' +import { useListGuestQuery } from 'src/store/api/users' +import { ListGroupContentCard } from 'src/components/contentcards' + +export default function GuestDetails({ tenantDomain, userId, className = null }) { + const { data: user = {}, isFetching, error } = useListGuestQuery({ tenantDomain, userId }) + + const content = [ + { + heading: 'First Name', + body: user.givenName, + }, + { + heading: 'Last Name', + body: user.surname, + }, + { + heading: 'User Principal Name', + body: user.userPrincipalName, + }, + { + heading: 'Job Title', + body: user.jobTitle, + }, + { + heading: 'Mobile Phone', + body: user.mobilePhone, + }, + { + heading: 'Business Phone', + body: user.businessPhones, + }, + ] + + return ( + + ) +} + +GuestDetails.propTypes = { + tenantDomain: PropTypes.string.isRequired, + userId: PropTypes.string.isRequired, + className: PropTypes.string, +} diff --git a/src/views/identity/administration/GuestLastLoginDetails.js b/src/views/identity/administration/GuestLastLoginDetails.js new file mode 100644 index 000000000000..bb439c9e96aa --- /dev/null +++ b/src/views/identity/administration/GuestLastLoginDetails.js @@ -0,0 +1,54 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { faClock } from '@fortawesome/free-solid-svg-icons' +import { ListGroupContentCard } from 'src/components/contentcards' +import { useListGuestQuery } from 'src/store/api/users' + +export default function GuestLastLoginDetails({ tenantDomain, userId, className = null }) { + const { + data: user = {}, + isFetching, + error, + } = useListGuestQuery({ tenantDomain, userId, IncludeLogonDetails: true }) + + const content = [ + { + heading: 'Last Sign in Date (UTC)', + body: user.LastSigninDate, + }, + { + heading: 'Last Sign in Application', + body: user.LastSigninApplication, + }, + { + heading: 'Last Sign in Status', + body: user.LastSigninStatus, + }, + { + heading: 'Last Sign in Result', + body: user.LastSigninResult, + }, + { + heading: 'Last Sign in Failure Reason', + body: user.LastSigninFailureReason, + }, + ] + + return ( + + ) +} + +GuestLastLoginDetails.propTypes = { + tenantDomain: PropTypes.string.isRequired, + userId: PropTypes.string.isRequired, + className: PropTypes.string, +} diff --git a/src/views/identity/administration/Guests.js b/src/views/identity/administration/Guests.js new file mode 100644 index 000000000000..603463eedcc6 --- /dev/null +++ b/src/views/identity/administration/Guests.js @@ -0,0 +1,290 @@ +import React, { useState } from 'react' +import { CButton } from '@coreui/react' +import { Link } from 'react-router-dom' +import { useSelector } from 'react-redux' +import { faEdit, faEllipsisV, faEye } from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { cellBooleanFormatter, CellTip } from 'src/components/tables' +import { CippPageList } from 'src/components/layout' +import { TitleButton } from 'src/components/buttons' +import { CippActionsOffcanvas } from 'src/components/utilities' + +const Offcanvas = (row, rowIndex, formatExtraData) => { + const tenant = useSelector((state) => state.app.currentTenant) + const [ocVisible, setOCVisible] = useState(false) + const viewLink = `/identity/administration/Guests/view?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}&userEmail=${row.userPrincipalName}` + const editLink = `/identity/administration/Guests/edit?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}` + //console.log(row) + return ( + <> + + + + + + + + + + + setOCVisible(true)}> + + + , + label: 'View User', + link: viewLink, + color: 'success', + }, + { + icon: , + label: 'Edit User', + link: editLink, + color: 'info', + }, + { + label: 'Research Compromised Account', + link: `/identity/administration/ViewBec?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}`, + color: 'info', + }, + { + label: 'Create Temporary Access Password', + color: 'info', + modal: true, + modalUrl: `/api/ExecCreateTAP?TenantFilter=${tenant.defaultDomainName}&ID=${row.userPrincipalName}`, + modalMessage: 'Are you sure you want to create a Temporary Access Pass?', + }, + { + label: 'Rerequire MFA registration', + color: 'info', + modal: true, + modalUrl: `/api/ExecResetMFA?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalMessage: 'Are you sure you want to enable MFA for this user?', + }, + { + label: 'Send MFA Push', + color: 'info', + modal: true, + modalUrl: `/api/ExecSendPush?TenantFilter=${tenant.defaultDomainName}&UserEmail=${row.userPrincipalName}`, + modalMessage: 'Are you sure you want to send a MFA request?', + }, + { + label: 'Block Sign In', + color: 'info', + modal: true, + modalUrl: `/api/ExecDisableUser?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalMessage: 'Are you sure you want to block the sign in for this user?', + }, + { + label: 'Unblock Sign In', + color: 'info', + modal: true, + modalUrl: `/api/ExecDisableUser?Enable=true&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalMessage: 'Are you sure you want to enable this user?', + }, + { + label: 'Reset Password (Must Change)', + color: 'info', + modal: true, + modalUrl: `/api/ExecResetPass?MustChange=true&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&displayName=${row.displayName}`, + modalMessage: 'Are you sure you want to reset the password for this user?', + }, + { + label: 'Reset Password', + color: 'info', + modal: true, + modalUrl: `/api/ExecResetPass?MustChange=false&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&displayName=${row.displayName}`, + modalMessage: 'Are you sure you want to reset the password for this user?', + }, + { + label: 'Clear ImmutableId', + color: 'warning', + modal: true, + modalUrl: `/api/ExecClrImmId?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalMessage: 'Are you sure you want to clear the ImmutableId for this user?', + }, + { + label: 'Revoke all user sessions', + color: 'danger', + modal: true, + modalUrl: `/api/ExecRevokeSessions?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalMessage: 'Are you sure you want to revoke this users sessions?', + }, + { + label: 'Delete User', + color: 'danger', + modal: true, + modalUrl: `/api/RemoveUser?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, + modalMessage: 'Are you sure you want to delete this user?', + }, + ]} + placement="end" + visible={ocVisible} + id={row.id} + hideFunction={() => setOCVisible(false)} + /> + + ) +} + +const columns = [ + { + name: 'Display Name', + selector: (row) => row['displayName'], + sortable: true, + cell: (row) => CellTip(row['displayName']), + exportSelector: 'displayName', + minWidth: '300px', + }, + { + name: 'Email', + selector: (row) => row['mail'], + sortable: true, + cell: (row) => CellTip(row['mail']), + exportSelector: 'mail', + minWidth: '250px', + }, + { + name: 'User Type', + selector: (row) => row['userType'], + sortable: true, + exportSelector: 'userType', + minWidth: '140px', + }, + { + name: 'Enabled', + selector: (row) => row['accountEnabled'], + cell: cellBooleanFormatter({ colourless: true }), + sortable: true, + exportSelector: 'accountEnabled', + minWidth: '100px', + }, + { + name: 'AD Synced', + selector: (row) => row['onPremisesSyncEnabled'], + cell: cellBooleanFormatter({ colourless: true }), + sortable: true, + exportSelector: 'onPremisesSyncEnabled', + minWidth: '120px', + }, + { + name: 'Licenses', + selector: (row) => row['LicJoined'], + exportSelector: 'LicJoined', + sortable: true, + grow: 5, + wrap: true, + minWidth: '200px', + }, + { + name: 'id', + selector: (row) => row['id'], + omit: true, + }, + { + name: 'Actions', + cell: Offcanvas, + }, +] + +const Guests = (row) => { + const tenant = useSelector((state) => state.app.currentTenant) + const titleButton = + return ( + + ) +} + +export default Guests diff --git a/src/views/identity/administration/ViewGuest.js b/src/views/identity/administration/ViewGuest.js new file mode 100644 index 000000000000..7388d890b459 --- /dev/null +++ b/src/views/identity/administration/ViewGuest.js @@ -0,0 +1,84 @@ +import React, { useEffect, useState } from 'react' +import { CSpinner } from '@coreui/react' +import PropTypes from 'prop-types' +import useQuery from 'src/hooks/useQuery' +import { useDispatch } from 'react-redux' +import { CippPage } from 'src/components/layout' +import { CippMasonry, CippMasonryItem } from 'src/components/layout' +import { ModalService } from 'src/components/utilities' +import UserDevices from 'src/views/identity/administration/UserDevices' +import GuestDetails from 'src/views/identity/administration/GuestDetails' +import GuestLastLoginDetails from 'src/views/identity/administration/GuestLastLoginDetails' +import UserCAPs from 'src/views/identity/administration/UserCAPs' +import UserActions from 'src/views/identity/administration/UserActions' +import User365Management from 'src/views/identity/administration/User365Management' +import UserGroups from 'src/views/identity/administration/UserGroups' +import UserSigninLogs from 'src/views/identity/administration/UserSigninLogs' +import { useListGuestQuery } from 'src/store/api/users' + +const ViewGuest = (props) => { + const dispatch = useDispatch() + let query = useQuery() + const userId = query.get('userId') + const tenantDomain = query.get('tenantDomain') + const userEmail = query.get('userEmail') + const [queryError, setQueryError] = useState(false) + + const { + data: user = {}, + isFetching: userFetching, + error: userError, + } = useListGuestQuery({ tenantDomain, userId }) + + useEffect(() => { + if (!userId || !tenantDomain) { + ModalService.open({ + body: 'Error invalid request, could not load requested user.', + title: 'Invalid Request', + }) + setQueryError(true) + } + }, [tenantDomain, userId, dispatch]) + + return ( + + {userFetching && } + {!userFetching && userError && Error loading user} + {!queryError && !userFetching && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + )} + + ) +} + +ViewGuest.propTypes = { + params: PropTypes.object, + location: PropTypes.object, +} + +export default ViewGuest From 2cf767ed0a9164b17010881884aadbdc8e0a56e8 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Tue, 28 Mar 2023 21:40:05 +0100 Subject: [PATCH 09/20] Revert "Added "Guests" functionality" This reverts commit 22506385b92788d72f7adf410e13b3e56bea29d2. --- src/_nav.js | 5 - src/routes.js | 8 - src/store/api/users.js | 22 - src/views/identity/administration/AddGuest.js | 148 ------ .../identity/administration/EditGuest.js | 432 ------------------ .../identity/administration/GuestDetails.js | 54 --- .../administration/GuestLastLoginDetails.js | 54 --- src/views/identity/administration/Guests.js | 290 ------------ .../identity/administration/ViewGuest.js | 84 ---- 9 files changed, 1097 deletions(-) delete mode 100644 src/views/identity/administration/AddGuest.js delete mode 100644 src/views/identity/administration/EditGuest.js delete mode 100644 src/views/identity/administration/GuestDetails.js delete mode 100644 src/views/identity/administration/GuestLastLoginDetails.js delete mode 100644 src/views/identity/administration/Guests.js delete mode 100644 src/views/identity/administration/ViewGuest.js diff --git a/src/_nav.js b/src/_nav.js index c5f872216928..3177cc44935d 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -44,11 +44,6 @@ const _nav = [ name: 'Users', to: '/identity/administration/users', }, - { - component: CNavItem, - name: 'Guests', - to: '/identity/administration/guests', - }, { component: CNavItem, name: 'Groups', diff --git a/src/routes.js b/src/routes.js index 167bf6d71cb2..a71896e965cf 100644 --- a/src/routes.js +++ b/src/routes.js @@ -3,15 +3,11 @@ import React from 'react' const Home = React.lazy(() => import('src/views/home/Home')) const Logs = React.lazy(() => import('src/views/cipp/Logs')) const Users = React.lazy(() => import('src/views/identity/administration/Users')) -const Guests = React.lazy(() => import('src/views/identity/administration/Guests')) const DeletedItems = React.lazy(() => import('src/views/identity/administration/Deleted')) const ViewBEC = React.lazy(() => import('src/views/identity/administration/ViewBEC')) const AddUser = React.lazy(() => import('src/views/identity/administration/AddUser')) -const AddGuest = React.lazy(() => import('src/views/identity/administration/AddGuest')) const EditUser = React.lazy(() => import('src/views/identity/administration/EditUser')) -const EditGuest = React.lazy(() => import('src/views/identity/administration/EditGuest')) const ViewUser = React.lazy(() => import('src/views/identity/administration/ViewUser')) -const ViewGuest = React.lazy(() => import('src/views/identity/administration/ViewGuest')) const Groups = React.lazy(() => import('src/views/identity/administration/Groups')) const AddGroup = React.lazy(() => import('src/views/identity/administration/AddGroup')) const AddGroupTemplates = React.lazy(() => @@ -236,10 +232,6 @@ const routes = [ { path: '/identity/administration', name: 'Administration' }, { path: '/identity/administration/users', name: 'Users', component: Users }, { path: '/identity/administration/groups/add', name: 'Add Group', component: AddGroup }, - { path: '/identity/administration/guests', name: 'Guests', component: Guests }, - { path: '/identity/administration/guests/view', name: 'View Guest', component: ViewGuest }, - { path: '/identity/administration/guests/edit', name: 'Edit Guest', component: EditGuest }, - { path: '/identity/administration/guests/add', name: 'Add Guest', component: AddGuest }, { path: '/identity/administration/group-templates', name: 'Group Templates', diff --git a/src/store/api/users.js b/src/store/api/users.js index 8f6aa1eb93e6..66cfd9bd48c6 100644 --- a/src/store/api/users.js +++ b/src/store/api/users.js @@ -17,14 +17,6 @@ export const usersApi = baseApi.injectEndpoints({ }, }), }), - listGuests: builder.query({ - query: ({ tenantDomain }) => ({ - path: '/api/ListGuests', - params: { - TenantFilter: tenantDomain, - }, - }), - }), listContacts: builder.query({ query: ({ tenantDomain }) => ({ path: '/api/ListContacts', @@ -45,18 +37,6 @@ export const usersApi = baseApi.injectEndpoints({ return {} }, }), - listGuest: builder.query({ - query: ({ tenantDomain, userId, IncludeLogonDetails }) => ({ - path: '/api/ListGuests', - params: { userId, TenantFilter: tenantDomain, IncludeLogonDetails }, - }), - transformResponse: (response) => { - if (response?.length > 0) { - return response[0] - } - return {} - }, - }), listUserConditionalAccessPolicies: builder.query({ query: ({ tenantDomain, userId }) => ({ path: '/api/ListUserConditionalAccessPolicies', @@ -137,8 +117,6 @@ export const { useEditUserMutation, useListUsersQuery, useListUserQuery, - useListGuestsQuery, - useListGuestQuery, useListContactsQuery, useListUserConditionalAccessPoliciesQuery, useListUserSigninLogsQuery, diff --git a/src/views/identity/administration/AddGuest.js b/src/views/identity/administration/AddGuest.js deleted file mode 100644 index b8c117ea7f2d..000000000000 --- a/src/views/identity/administration/AddGuest.js +++ /dev/null @@ -1,148 +0,0 @@ -import React from 'react' -import { - CButton, - CCard, - CCardBody, - CCardHeader, - CCardTitle, - CCol, - CForm, - CRow, - CCallout, -} from '@coreui/react' -import { Form } from 'react-final-form' -import { RFFCFormCheck, RFFCFormInput } from 'src/components/forms' -import { CippPage } from 'src/components/layout' -import { useListAdConnectSettingsQuery } from 'src/store/api/adconnect' -import { useLazyGenericPostRequestQuery } from 'src/store/api/app' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' -import { useSelector } from 'react-redux' -import { required } from 'src/validators' -import useQuery from 'src/hooks/useQuery' -import { useNavigate } from 'react-router-dom' - -const AddGuest = () => { - let navigate = useNavigate() - - const tenant = useSelector((state) => state.app.currentTenant) - const { defaultDomainName: tenantDomain } = tenant - let query = useQuery() - const allQueryObj = {} - for (const [key, value] of query.entries()) { - allQueryObj[key] = value - } - const { - data: adconnectsettings = [], - isFetching: adcIsFetching, - error: adcError, - } = useListAdConnectSettingsQuery({ tenantDomain }) - - const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() - const onSubmit = (values) => { - const shippedValues = { - DisplayName: values.displayName, - Domain: values.primDomain, - Mail: values.mail, - PostalCode: values.postalCode, - RedirectURL: values.RedirectURL, - SendInvite: values.SendInvite, - tenantID: tenantDomain, - } - //window.alert(JSON.stringify(shippedValues)) - genericPostRequest({ path: '/api/AddGuest', values: shippedValues }) - } - - return ( - - {postResults.isSuccess && ( - - {postResults.data?.Results.map((result, index) => ( -
  • {result}
  • - ))} -
    - )} - - - - - Account Details - - - {adcError && Unable to determine Azure AD Connect Settings} - {!adcIsFetching && adconnectsettings.dirSyncEnabled && ( - - Warning! {adconnectsettings.dirSyncEnabled} This tenant currently has Active - Directory Sync Enabled. This usually means users should be created in Active - Directory - - )} - { - return ( - - - - - - - - - - - - - - - - - - - - - Add User - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - - {postResults.data.Results.map((message, idx) => { - return
  • {message}
  • - })} -
    - )} -
    - ) - }} - /> -
    -
    -
    -
    -
    - ) -} - -export default AddGuest diff --git a/src/views/identity/administration/EditGuest.js b/src/views/identity/administration/EditGuest.js deleted file mode 100644 index 6759d0018386..000000000000 --- a/src/views/identity/administration/EditGuest.js +++ /dev/null @@ -1,432 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { CButton, CCallout, CCol, CForm, CFormLabel, CRow, CSpinner } from '@coreui/react' -import useQuery from 'src/hooks/useQuery' -import { useDispatch, useSelector } from 'react-redux' -import { Form } from 'react-final-form' -import { - Condition, - RFFCFormCheck, - RFFCFormInput, - RFFCFormSelect, - RFFCFormSwitch, - RFFCFormTextarea, - RFFSelectSearch, -} from 'src/components/forms' -import countryList from 'src/data/countryList' -import { useListGuestQuery, useListGuestsQuery } from 'src/store/api/users' -import { useListDomainsQuery } from 'src/store/api/domains' -import { useListLicensesQuery } from 'src/store/api/licenses' -import { CippCodeBlock, ModalService } from 'src/components/utilities' -import { 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 } from 'src/components/layout' -import { password } from 'src/validators' - -const EditGuest = () => { - const dispatch = useDispatch() - let query = useQuery() - const userId = query.get('userId') - const tenantDomain = query.get('tenantDomain') - - const [queryError, setQueryError] = useState(false) - - //const [editUser, { error: editUserError, isFetching: editUserIsFetching }] = useEditUserMutation() - - const { - data: user = {}, - isFetching: userIsFetching, - error: userError, - } = useListGuestQuery({ tenantDomain, userId }) - - const { - data: users = [], - isFetching: usersIsFetching, - error: usersError, - } = useListGuestsQuery({ tenantDomain }) - - const { - data: domains = [], - isFetching: domainsIsFetching, - error: domainsError, - } = useListDomainsQuery({ tenantDomain, userId }) - - const { - data: licenses = [], - isFetching: licensesIsFetching, - error: licensesError, - } = useListLicensesQuery({ tenantDomain }) - - useEffect(() => { - if (!userId || !tenantDomain) { - ModalService.open({ - body: 'Error invalid request, could not load requested user.', - title: 'Invalid Request', - }) - setQueryError(true) - } else { - setQueryError(false) - } - }, [userId, tenantDomain, dispatch]) - const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() - const onSubmit = (values) => { - const shippedValues = { - BusinessPhone: values.businessPhones, - City: values.city, - CompanyName: values.companyName, - CopyFrom: values.CopyFrom ? values.CopyFrom.value : '', - Country: values.country, - Department: values.department, - DisplayName: values.displayName, - Domain: values.primDomain, - firstName: values.givenName, - Jobtitle: values.jobTitle, - LastName: values.surname, - License: values.licenses, - MobilePhone: values.mobilePhone, - Password: values.password, - PostalCode: values.postalCode, - usageLocation: values.usageLocation ? values.usageLocation.value : '', - UserID: userId, - Username: values.mailNickname, - streetAddress: values.streetAddress, - tenantID: tenantDomain, - mustchangepass: values.RequirePasswordChange, - } - //window.alert(JSON.stringify(shippedValues)) - genericPostRequest({ path: '/api/EditUser', values: shippedValues }) - } - const usageLocation = useSelector((state) => state.app.usageLocation) - const precheckedLicenses = user.assignedLicenses - ? user.assignedLicenses.reduce( - (o, key) => Object.assign(o, { [`License_${key.skuId}`]: true }), - {}, - ) - : '' - const initialState = { - keepLicenses: true, - ...user, - usageLocation: { - value: user.usageLocation ? user.usageLocation : usageLocation?.value, - label: user.usageLocation ? user.usageLocation : usageLocation?.label, - }, - license: precheckedLicenses, - } - - const formDisabled = queryError === true || !!userError || !user || Object.keys(user).length === 0 - const RawUser = JSON.stringify(user, null, 2) - return ( - - {!queryError && ( - <> - {postResults.isSuccess && ( - {postResults.data?.Results} - )} - {queryError && ( - - - - {/* @todo add more descriptive help message here */} - Failed to load user - - - - )} - - - - {userIsFetching && } - {userError && Error loading user} - {!userIsFetching && ( - { - return ( - - - - - - - - - - - - - - - - - - - - {domainsIsFetching && } - {!domainsIsFetching && ( - ({ - value: domain.id, - label: domain.id, - }))} - /> - )} - {domainsError && Failed to load list of domains} - - - - - - - - - - Settings - - - - - - - - - - - - ({ - value: Code, - name: Name, - }))} - disabled={formDisabled} - name="usageLocation" - placeholder="Type to search..." - label="Usage Location" - /> - - - - - - - Licenses -
    - {licensesIsFetching && } - {licensesError && Error loading licenses} - - {licensesIsFetching && } - {licensesError && Error loading licenses} - {!licensesIsFetching && - licenses?.map((license) => ( - - ))} -
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ({ - value: user.mail, - name: user.displayName, - }))} - placeholder={!usersIsFetching ? 'Select user' : 'Loading...'} - name="CopyFrom" - /> - {usersError && Failed to load list of users} - - - - - - Edit User - {postResults.isFetching && ( - - )} - - - - {postResults.isSuccess && ( - - {postResults.data.Results.map((message, idx) => { - return
  • {message}
  • - })} -
    - )} -
    - ) - }} - /> - )} -
    -
    - - - {userIsFetching && } - {userError && Error loading user} - {!userIsFetching && ( - <> - This is the (raw) information for this account. - - - )} - - -
    - - )} -
    - ) -} - -export default EditGuest diff --git a/src/views/identity/administration/GuestDetails.js b/src/views/identity/administration/GuestDetails.js deleted file mode 100644 index 9f36fc0d250d..000000000000 --- a/src/views/identity/administration/GuestDetails.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { faPortrait } from '@fortawesome/free-solid-svg-icons' -import { useListGuestQuery } from 'src/store/api/users' -import { ListGroupContentCard } from 'src/components/contentcards' - -export default function GuestDetails({ tenantDomain, userId, className = null }) { - const { data: user = {}, isFetching, error } = useListGuestQuery({ tenantDomain, userId }) - - const content = [ - { - heading: 'First Name', - body: user.givenName, - }, - { - heading: 'Last Name', - body: user.surname, - }, - { - heading: 'User Principal Name', - body: user.userPrincipalName, - }, - { - heading: 'Job Title', - body: user.jobTitle, - }, - { - heading: 'Mobile Phone', - body: user.mobilePhone, - }, - { - heading: 'Business Phone', - body: user.businessPhones, - }, - ] - - return ( - - ) -} - -GuestDetails.propTypes = { - tenantDomain: PropTypes.string.isRequired, - userId: PropTypes.string.isRequired, - className: PropTypes.string, -} diff --git a/src/views/identity/administration/GuestLastLoginDetails.js b/src/views/identity/administration/GuestLastLoginDetails.js deleted file mode 100644 index bb439c9e96aa..000000000000 --- a/src/views/identity/administration/GuestLastLoginDetails.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { faClock } from '@fortawesome/free-solid-svg-icons' -import { ListGroupContentCard } from 'src/components/contentcards' -import { useListGuestQuery } from 'src/store/api/users' - -export default function GuestLastLoginDetails({ tenantDomain, userId, className = null }) { - const { - data: user = {}, - isFetching, - error, - } = useListGuestQuery({ tenantDomain, userId, IncludeLogonDetails: true }) - - const content = [ - { - heading: 'Last Sign in Date (UTC)', - body: user.LastSigninDate, - }, - { - heading: 'Last Sign in Application', - body: user.LastSigninApplication, - }, - { - heading: 'Last Sign in Status', - body: user.LastSigninStatus, - }, - { - heading: 'Last Sign in Result', - body: user.LastSigninResult, - }, - { - heading: 'Last Sign in Failure Reason', - body: user.LastSigninFailureReason, - }, - ] - - return ( - - ) -} - -GuestLastLoginDetails.propTypes = { - tenantDomain: PropTypes.string.isRequired, - userId: PropTypes.string.isRequired, - className: PropTypes.string, -} diff --git a/src/views/identity/administration/Guests.js b/src/views/identity/administration/Guests.js deleted file mode 100644 index 603463eedcc6..000000000000 --- a/src/views/identity/administration/Guests.js +++ /dev/null @@ -1,290 +0,0 @@ -import React, { useState } from 'react' -import { CButton } from '@coreui/react' -import { Link } from 'react-router-dom' -import { useSelector } from 'react-redux' -import { faEdit, faEllipsisV, faEye } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { cellBooleanFormatter, CellTip } from 'src/components/tables' -import { CippPageList } from 'src/components/layout' -import { TitleButton } from 'src/components/buttons' -import { CippActionsOffcanvas } from 'src/components/utilities' - -const Offcanvas = (row, rowIndex, formatExtraData) => { - const tenant = useSelector((state) => state.app.currentTenant) - const [ocVisible, setOCVisible] = useState(false) - const viewLink = `/identity/administration/Guests/view?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}&userEmail=${row.userPrincipalName}` - const editLink = `/identity/administration/Guests/edit?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}` - //console.log(row) - return ( - <> - - - - - - - - - - - setOCVisible(true)}> - - - , - label: 'View User', - link: viewLink, - color: 'success', - }, - { - icon: , - label: 'Edit User', - link: editLink, - color: 'info', - }, - { - label: 'Research Compromised Account', - link: `/identity/administration/ViewBec?userId=${row.id}&tenantDomain=${tenant.defaultDomainName}`, - color: 'info', - }, - { - label: 'Create Temporary Access Password', - color: 'info', - modal: true, - modalUrl: `/api/ExecCreateTAP?TenantFilter=${tenant.defaultDomainName}&ID=${row.userPrincipalName}`, - modalMessage: 'Are you sure you want to create a Temporary Access Pass?', - }, - { - label: 'Rerequire MFA registration', - color: 'info', - modal: true, - modalUrl: `/api/ExecResetMFA?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, - modalMessage: 'Are you sure you want to enable MFA for this user?', - }, - { - label: 'Send MFA Push', - color: 'info', - modal: true, - modalUrl: `/api/ExecSendPush?TenantFilter=${tenant.defaultDomainName}&UserEmail=${row.userPrincipalName}`, - modalMessage: 'Are you sure you want to send a MFA request?', - }, - { - label: 'Block Sign In', - color: 'info', - modal: true, - modalUrl: `/api/ExecDisableUser?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, - modalMessage: 'Are you sure you want to block the sign in for this user?', - }, - { - label: 'Unblock Sign In', - color: 'info', - modal: true, - modalUrl: `/api/ExecDisableUser?Enable=true&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, - modalMessage: 'Are you sure you want to enable this user?', - }, - { - label: 'Reset Password (Must Change)', - color: 'info', - modal: true, - modalUrl: `/api/ExecResetPass?MustChange=true&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&displayName=${row.displayName}`, - modalMessage: 'Are you sure you want to reset the password for this user?', - }, - { - label: 'Reset Password', - color: 'info', - modal: true, - modalUrl: `/api/ExecResetPass?MustChange=false&TenantFilter=${tenant.defaultDomainName}&ID=${row.id}&displayName=${row.displayName}`, - modalMessage: 'Are you sure you want to reset the password for this user?', - }, - { - label: 'Clear ImmutableId', - color: 'warning', - modal: true, - modalUrl: `/api/ExecClrImmId?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, - modalMessage: 'Are you sure you want to clear the ImmutableId for this user?', - }, - { - label: 'Revoke all user sessions', - color: 'danger', - modal: true, - modalUrl: `/api/ExecRevokeSessions?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, - modalMessage: 'Are you sure you want to revoke this users sessions?', - }, - { - label: 'Delete User', - color: 'danger', - modal: true, - modalUrl: `/api/RemoveUser?TenantFilter=${tenant.defaultDomainName}&ID=${row.id}`, - modalMessage: 'Are you sure you want to delete this user?', - }, - ]} - placement="end" - visible={ocVisible} - id={row.id} - hideFunction={() => setOCVisible(false)} - /> - - ) -} - -const columns = [ - { - name: 'Display Name', - selector: (row) => row['displayName'], - sortable: true, - cell: (row) => CellTip(row['displayName']), - exportSelector: 'displayName', - minWidth: '300px', - }, - { - name: 'Email', - selector: (row) => row['mail'], - sortable: true, - cell: (row) => CellTip(row['mail']), - exportSelector: 'mail', - minWidth: '250px', - }, - { - name: 'User Type', - selector: (row) => row['userType'], - sortable: true, - exportSelector: 'userType', - minWidth: '140px', - }, - { - name: 'Enabled', - selector: (row) => row['accountEnabled'], - cell: cellBooleanFormatter({ colourless: true }), - sortable: true, - exportSelector: 'accountEnabled', - minWidth: '100px', - }, - { - name: 'AD Synced', - selector: (row) => row['onPremisesSyncEnabled'], - cell: cellBooleanFormatter({ colourless: true }), - sortable: true, - exportSelector: 'onPremisesSyncEnabled', - minWidth: '120px', - }, - { - name: 'Licenses', - selector: (row) => row['LicJoined'], - exportSelector: 'LicJoined', - sortable: true, - grow: 5, - wrap: true, - minWidth: '200px', - }, - { - name: 'id', - selector: (row) => row['id'], - omit: true, - }, - { - name: 'Actions', - cell: Offcanvas, - }, -] - -const Guests = (row) => { - const tenant = useSelector((state) => state.app.currentTenant) - const titleButton = - return ( - - ) -} - -export default Guests diff --git a/src/views/identity/administration/ViewGuest.js b/src/views/identity/administration/ViewGuest.js deleted file mode 100644 index 7388d890b459..000000000000 --- a/src/views/identity/administration/ViewGuest.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { CSpinner } from '@coreui/react' -import PropTypes from 'prop-types' -import useQuery from 'src/hooks/useQuery' -import { useDispatch } from 'react-redux' -import { CippPage } from 'src/components/layout' -import { CippMasonry, CippMasonryItem } from 'src/components/layout' -import { ModalService } from 'src/components/utilities' -import UserDevices from 'src/views/identity/administration/UserDevices' -import GuestDetails from 'src/views/identity/administration/GuestDetails' -import GuestLastLoginDetails from 'src/views/identity/administration/GuestLastLoginDetails' -import UserCAPs from 'src/views/identity/administration/UserCAPs' -import UserActions from 'src/views/identity/administration/UserActions' -import User365Management from 'src/views/identity/administration/User365Management' -import UserGroups from 'src/views/identity/administration/UserGroups' -import UserSigninLogs from 'src/views/identity/administration/UserSigninLogs' -import { useListGuestQuery } from 'src/store/api/users' - -const ViewGuest = (props) => { - const dispatch = useDispatch() - let query = useQuery() - const userId = query.get('userId') - const tenantDomain = query.get('tenantDomain') - const userEmail = query.get('userEmail') - const [queryError, setQueryError] = useState(false) - - const { - data: user = {}, - isFetching: userFetching, - error: userError, - } = useListGuestQuery({ tenantDomain, userId }) - - useEffect(() => { - if (!userId || !tenantDomain) { - ModalService.open({ - body: 'Error invalid request, could not load requested user.', - title: 'Invalid Request', - }) - setQueryError(true) - } - }, [tenantDomain, userId, dispatch]) - - return ( - - {userFetching && } - {!userFetching && userError && Error loading user} - {!queryError && !userFetching && ( - - - - - - - - - - - - - - - - - - - - - - - - - - - )} - - ) -} - -ViewGuest.propTypes = { - params: PropTypes.object, - location: PropTypes.object, -} - -export default ViewGuest From 0fcc0ea2ed3c8554bf9e82df94fce3c94540b79d Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Tue, 28 Mar 2023 23:16:35 +0200 Subject: [PATCH 10/20] Removed ExchangeRefreshToken --- deployment/AzureDeploymentTemplate.json | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/deployment/AzureDeploymentTemplate.json b/deployment/AzureDeploymentTemplate.json index 620700934567..bc7eb0ba9efe 100644 --- a/deployment/AzureDeploymentTemplate.json +++ b/deployment/AzureDeploymentTemplate.json @@ -115,18 +115,6 @@ "dependsOn": [ "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" ] - }, - { - "type": "secrets", - "name": "exchangerefreshtoken", - "apiVersion": "2015-06-01", - "properties": { - "contentType": "text/plain", - "value": "ExchangeRefreshToken" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" - ] } ], "dependsOn": ["[resourceId('Microsoft.Web/sites', variables('funcAppName'))]"] @@ -178,10 +166,6 @@ "name": "RefreshToken", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/RefreshToken)')]" }, - { - "name": "ExchangeRefreshtoken", - "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/ExchangeRefreshToken)')]" - }, { "name": "TenantID", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/tenantid)')]" From f9feeb461c6ccd124b75a6f1fddff9d8cd4e6fc1 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Tue, 28 Mar 2023 23:17:15 +0200 Subject: [PATCH 11/20] Removed ExchangeRefreshToken --- .../AzureDeploymentTemplate_regionoptions.json | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/deployment/AzureDeploymentTemplate_regionoptions.json b/deployment/AzureDeploymentTemplate_regionoptions.json index c187bb7dc914..11fccba7350c 100644 --- a/deployment/AzureDeploymentTemplate_regionoptions.json +++ b/deployment/AzureDeploymentTemplate_regionoptions.json @@ -115,18 +115,6 @@ "dependsOn": [ "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" ] - }, - { - "type": "secrets", - "name": "exchangerefreshtoken", - "apiVersion": "2015-06-01", - "properties": { - "contentType": "text/plain", - "value": "ExchangeRefreshToken" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" - ] } ], "dependsOn": ["[resourceId('Microsoft.Web/sites', variables('funcAppName'))]"] @@ -178,10 +166,6 @@ "name": "RefreshToken", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/RefreshToken)')]" }, - { - "name": "ExchangeRefreshtoken", - "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/ExchangeRefreshToken)')]" - }, { "name": "TenantID", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/tenantid)')]" From 414d55782473a3ee383c087f72fd22a355cdf68c Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Tue, 28 Mar 2023 23:18:36 +0200 Subject: [PATCH 12/20] Removed ExchangeRefreshToken --- deployment/DevAzureDeploymentTemplate.json | 23 ---------------------- 1 file changed, 23 deletions(-) diff --git a/deployment/DevAzureDeploymentTemplate.json b/deployment/DevAzureDeploymentTemplate.json index a653cebc66dd..dee70ee11871 100644 --- a/deployment/DevAzureDeploymentTemplate.json +++ b/deployment/DevAzureDeploymentTemplate.json @@ -37,13 +37,6 @@ "description": "The Refresh token for your Secure Application Model." } }, - "ExchangeRefreshToken": { - "defaultValue": "LongRefreshtoken", - "type": "string", - "metadata": { - "description": "The Exchange Refresh token for your Secure Application Model." - } - }, "GithubRepository": { "defaultValue": "https://github.com/KelvinTegelaar/CIPP", "type": "string", @@ -150,18 +143,6 @@ "dependsOn": [ "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" ] - }, - { - "type": "secrets", - "name": "exchangerefreshtoken", - "apiVersion": "2015-06-01", - "properties": { - "contentType": "text/plain", - "value": "[parameters('exchangerefreshtoken')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" - ] } ], "dependsOn": ["[resourceId('Microsoft.Web/sites', variables('funcAppName'))]"] @@ -213,10 +194,6 @@ "name": "RefreshToken", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/RefreshToken)')]" }, - { - "name": "ExchangeRefreshtoken", - "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/ExchangeRefreshToken)')]" - }, { "name": "TenantID", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/tenantid)')]" From 3c71cdd879a60931d8026717d8dff299275bcd1a Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Tue, 28 Mar 2023 23:19:24 +0200 Subject: [PATCH 13/20] Removed ExchangeRefreshToken --- ...AzureDeploymentTemplate_regionoptions.json | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/deployment/DevAzureDeploymentTemplate_regionoptions.json b/deployment/DevAzureDeploymentTemplate_regionoptions.json index 7af2b9af4905..301d50d5437a 100644 --- a/deployment/DevAzureDeploymentTemplate_regionoptions.json +++ b/deployment/DevAzureDeploymentTemplate_regionoptions.json @@ -37,13 +37,6 @@ "description": "The Refresh token for your Secure Application Model." } }, - "ExchangeRefreshToken": { - "defaultValue": "LongRefreshtoken", - "type": "string", - "metadata": { - "description": "The Exchange Refresh token for your Secure Application Model." - } - }, "GithubRepository": { "defaultValue": "https://github.com/KelvinTegelaar/CIPP", "type": "string", @@ -150,18 +143,6 @@ "dependsOn": [ "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" ] - }, - { - "type": "secrets", - "name": "exchangerefreshtoken", - "apiVersion": "2015-06-01", - "properties": { - "contentType": "text/plain", - "value": "[parameters('exchangerefreshtoken')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', variables('uniqueResourceNameBase'))]" - ] } ], "dependsOn": ["[resourceId('Microsoft.Web/sites', variables('funcAppName'))]"] @@ -213,10 +194,6 @@ "name": "RefreshToken", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/RefreshToken)')]" }, - { - "name": "ExchangeRefreshtoken", - "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/ExchangeRefreshToken)')]" - }, { "name": "TenantID", "value": "[concat('@Microsoft.KeyVault(SecretUri=https://',variables('uniqueResourceNameBase'), '.vault.azure.net/secrets/tenantid)')]" From 953cd338f025932c2324a6adc27f68eacf1bdcf4 Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Tue, 28 Mar 2023 23:08:17 +0100 Subject: [PATCH 14/20] Added "Invite Guest" function Added "Invite Guest" function Take two! :) --- src/routes.js | 6 + .../identity/administration/InviteGuest.js | 148 ++++++++++++++++++ src/views/identity/administration/Users.js | 15 +- 3 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 src/views/identity/administration/InviteGuest.js diff --git a/src/routes.js b/src/routes.js index a71896e965cf..0290e512fbca 100644 --- a/src/routes.js +++ b/src/routes.js @@ -6,6 +6,7 @@ const Users = React.lazy(() => import('src/views/identity/administration/Users') const DeletedItems = React.lazy(() => import('src/views/identity/administration/Deleted')) const ViewBEC = React.lazy(() => import('src/views/identity/administration/ViewBEC')) const AddUser = React.lazy(() => import('src/views/identity/administration/AddUser')) +const InviteGuest = React.lazy(() => import('src/views/identity/administration/InviteGuest')) const EditUser = React.lazy(() => import('src/views/identity/administration/EditUser')) const ViewUser = React.lazy(() => import('src/views/identity/administration/ViewUser')) const Groups = React.lazy(() => import('src/views/identity/administration/Groups')) @@ -228,6 +229,11 @@ const routes = [ { path: '/identity/administration/users/add', name: 'Add User', component: AddUser }, { path: '/identity/administration/users/edit', name: 'Edit User', component: EditUser }, { path: '/identity/administration/users/view', name: 'View User', component: ViewUser }, + { + path: '/identity/administration/users/InviteGuest', + name: 'Invite Guest', + component: InviteGuest, + }, { path: '/identity/administration/ViewBec', name: 'View BEC', component: ViewBEC }, { path: '/identity/administration', name: 'Administration' }, { path: '/identity/administration/users', name: 'Users', component: Users }, diff --git a/src/views/identity/administration/InviteGuest.js b/src/views/identity/administration/InviteGuest.js new file mode 100644 index 000000000000..2ea2ccce47a3 --- /dev/null +++ b/src/views/identity/administration/InviteGuest.js @@ -0,0 +1,148 @@ +import React from 'react' +import { + CButton, + CCard, + CCardBody, + CCardHeader, + CCardTitle, + CCol, + CForm, + CRow, + CCallout, +} from '@coreui/react' +import { Form } from 'react-final-form' +import { RFFCFormCheck, RFFCFormInput } from 'src/components/forms' +import { CippPage } from 'src/components/layout' +import { useListAdConnectSettingsQuery } from 'src/store/api/adconnect' +import { useLazyGenericPostRequestQuery } from 'src/store/api/app' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleNotch } from '@fortawesome/free-solid-svg-icons' +import { useSelector } from 'react-redux' +import { required } from 'src/validators' +import useQuery from 'src/hooks/useQuery' +import { useNavigate } from 'react-router-dom' + +const InviteGuest = () => { + let navigate = useNavigate() + + const tenant = useSelector((state) => state.app.currentTenant) + const { defaultDomainName: tenantDomain } = tenant + let query = useQuery() + const allQueryObj = {} + for (const [key, value] of query.entries()) { + allQueryObj[key] = value + } + const { + data: adconnectsettings = [], + isFetching: adcIsFetching, + error: adcError, + } = useListAdConnectSettingsQuery({ tenantDomain }) + + const [genericPostRequest, postResults] = useLazyGenericPostRequestQuery() + const onSubmit = (values) => { + const shippedValues = { + DisplayName: values.displayName, + Domain: values.primDomain, + Mail: values.mail, + PostalCode: values.postalCode, + RedirectURL: values.RedirectURL, + SendInvite: values.SendInvite, + tenantID: tenantDomain, + } + //window.alert(JSON.stringify(shippedValues)) + genericPostRequest({ path: '/api/AddGuest', values: shippedValues }) + } + + return ( + + {postResults.isSuccess && ( + + {postResults.data?.Results.map((result, index) => ( +
  • {result}
  • + ))} +
    + )} + + + + + Guest Details + + + {adcError && Unable to determine Azure AD Connect Settings} + {!adcIsFetching && adconnectsettings.dirSyncEnabled && ( + + Warning! {adconnectsettings.dirSyncEnabled} This tenant currently has Active + Directory Sync Enabled. This usually means users should be created in Active + Directory + + )} + { + return ( + + + + + + + + + + + + + + + + + + + + + Invite Guest + {postResults.isFetching && ( + + )} + + + + {postResults.isSuccess && ( + + {postResults.data.Results.map((message, idx) => { + return
  • {message}
  • + })} +
    + )} +
    + ) + }} + /> +
    +
    +
    +
    +
    + ) +} + +export default InviteGuest diff --git a/src/views/identity/administration/Users.js b/src/views/identity/administration/Users.js index 92f4ef1f6bd0..3b7a0e3a04e1 100644 --- a/src/views/identity/administration/Users.js +++ b/src/views/identity/administration/Users.js @@ -262,12 +262,23 @@ const columns = [ const Users = (row) => { const tenant = useSelector((state) => state.app.currentTenant) - const titleButton = + const titleButtons = ( +
    + +
    + +
    +
    + ) return ( Date: Wed, 29 Mar 2023 15:10:58 +0100 Subject: [PATCH 15/20] Added SharedMailbox with Account Enabled Report Added Feature Request: https://github.com/KelvinTegelaar/CIPP/issues/1440 --- src/_nav.js | 5 ++ src/routes.js | 8 +++ .../reports/SharedMailboxEnabledAccount.js | 65 +++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 src/views/email-exchange/reports/SharedMailboxEnabledAccount.js diff --git a/src/_nav.js b/src/_nav.js index 3177cc44935d..e5a5d4f3f548 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -606,6 +606,11 @@ const _nav = [ name: 'Phishing Policies', to: '/email/reports/phishing-policies', }, + { + component: CNavItem, + name: 'Shared Mailbox with Enabled Account', + to: '/email/reports/SharedMailboxEnabledAccount', + }, ], }, { diff --git a/src/routes.js b/src/routes.js index 0290e512fbca..bac1f9bf996f 100644 --- a/src/routes.js +++ b/src/routes.js @@ -173,6 +173,9 @@ const MailboxClientAccessSettingsList = React.lazy(() => const MailboxStatisticsList = React.lazy(() => import('src/views/email-exchange/reports/MailboxStatisticsList'), ) +const SharedMailboxEnabledAccount = React.lazy(() => + import('src/views/email-exchange/reports/SharedMailboxEnabledAccount'), +) const MessageTrace = React.lazy(() => import('src/views/email-exchange/reports/MessageTrace')) const PhishingPoliciesList = React.lazy(() => import('src/views/email-exchange/reports/PhishingPoliciesList'), @@ -574,6 +577,11 @@ const routes = [ path: '/email/reports/mailbox-statistics', component: MailboxStatisticsList, }, + { + name: 'Shared Mailbox Enabled Account', + path: '/email/reports/SharedMailboxEnabledAccount', + component: SharedMailboxEnabledAccount, + }, { name: 'Mailbox Client Access Settings', path: '/email/reports/mailbox-cas-settings', diff --git a/src/views/email-exchange/reports/SharedMailboxEnabledAccount.js b/src/views/email-exchange/reports/SharedMailboxEnabledAccount.js new file mode 100644 index 000000000000..8135b38a3655 --- /dev/null +++ b/src/views/email-exchange/reports/SharedMailboxEnabledAccount.js @@ -0,0 +1,65 @@ +import React from 'react' +import { useSelector } from 'react-redux' +import { CellTip, cellBooleanFormatter } from 'src/components/tables' +import { CippPageList } from 'src/components/layout' + +const columns = [ + { + selector: (row) => row['UserPrincipalName'], + name: 'User Prinicipal Name', + sortable: true, + cell: (row) => CellTip(row['UserPrincipalName']), + exportSelector: 'UserPrincipalName', + minWidth: '200px', + }, + { + selector: (row) => row['displayName'], + name: 'Display Name', + sortable: true, + cell: (row) => CellTip(row['displayName']), + exportSelector: 'displayName', + minWidth: '200px', + }, + { + selector: (row) => row['givenName'], + name: 'First Name', + sortable: true, + cell: (row) => CellTip(row['givenName']), + exportSelector: 'givenName', + minWidth: '200px', + }, + { + selector: (row) => row['surname'], + name: 'Surname', + sortable: true, + cell: (row) => CellTip(row['surname']), + exportSelector: 'surname', + minWidth: '200px', + }, + { + selector: (row) => row['accountEnabled'], + name: 'Account Enabled', + sortable: true, + cell: (row) => CellTip(row['accountEnabled']), + exportSelector: 'accountEnabled', + }, +] + +const SharedMailboxEnabledAccount = () => { + const tenant = useSelector((state) => state.app.currentTenant) + + return ( + + ) +} + +export default SharedMailboxEnabledAccount From 36d48d508efe1223def88559dd5ad35e07c391bc Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Wed, 29 Mar 2023 17:29:07 +0100 Subject: [PATCH 16/20] Update InviteGuest.js Made Redirect URL optional and added some place holder text --- src/views/identity/administration/InviteGuest.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/views/identity/administration/InviteGuest.js b/src/views/identity/administration/InviteGuest.js index 2ea2ccce47a3..c1b46cbdf6fc 100644 --- a/src/views/identity/administration/InviteGuest.js +++ b/src/views/identity/administration/InviteGuest.js @@ -100,8 +100,11 @@ const InviteGuest = () => { From 36e512867c38ca2e13ec85ea84e361c0bfd74d9d Mon Sep 17 00:00:00 2001 From: BNWEIN Date: Wed, 29 Mar 2023 18:00:54 +0100 Subject: [PATCH 17/20] Update Logs.js Changed order of columns and fixed so it shows newest to oldest by default --- src/views/cipp/Logs.js | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/views/cipp/Logs.js b/src/views/cipp/Logs.js index 73a956b18b05..e7733a152fff 100644 --- a/src/views/cipp/Logs.js +++ b/src/views/cipp/Logs.js @@ -20,6 +20,20 @@ import { CippDatatable, cellDateFormatter, CellTip } from 'src/components/tables import { useNavigate } from 'react-router-dom' import DatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' +const reverseSort = (rowA, rowB) => { + const a = rowA.DateTime.toLowerCase() + const b = rowB.DateTime.toLowerCase() + + if (a > b) { + return -1 + } + + if (b > a) { + return 1 + } + + return 0 +} const columns = [ { @@ -30,6 +44,7 @@ const columns = [ exportSelector: 'DateTime', minWidth: '145px', maxWidth: '145px', + sortFunction: reverseSort, }, { name: 'Tenant', @@ -41,11 +56,11 @@ const columns = [ maxWidth: '145px', }, { - name: 'API', - selector: (row) => row['API'], + name: 'User', + selector: (row) => row['User'], sortable: true, - cell: (row) => CellTip(row['API']), - exportSelector: 'API', + cell: (row) => CellTip(row['User']), + exportSelector: 'User', minWidth: '145px', maxWidth: '145px', }, @@ -57,11 +72,11 @@ const columns = [ exportSelector: 'Message', }, { - name: 'User', - selector: (row) => row['User'], + name: 'API', + selector: (row) => row['API'], sortable: true, - cell: (row) => CellTip(row['User']), - exportSelector: 'User', + cell: (row) => CellTip(row['API']), + exportSelector: 'API', minWidth: '145px', maxWidth: '145px', }, From 392b30eba7fb2b1abff118b32dfda469f49394e5 Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Fri, 31 Mar 2023 08:57:36 +0200 Subject: [PATCH 18/20] Removed ExchangeRefreshToken input during setup ExchangeRefreshToken has been deprecated --- src/views/cipp/Setup.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/views/cipp/Setup.js b/src/views/cipp/Setup.js index 5dd85f962c53..61f5c0f21db4 100644 --- a/src/views/cipp/Setup.js +++ b/src/views/cipp/Setup.js @@ -281,16 +281,6 @@ const Setup = () => { /> - - - - -
    From b28b177d33b02e8d2044a245df4dcd740b8c17cf Mon Sep 17 00:00:00 2001 From: Roel van der Wegen Date: Fri, 31 Mar 2023 09:00:17 +0200 Subject: [PATCH 19/20] Removed ExchangeRefreshToken from home ExchangeRefreshToken has been deprecated --- src/views/home/Home.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/views/home/Home.js b/src/views/home/Home.js index 974ab575da30..f0fc78f8e10a 100644 --- a/src/views/home/Home.js +++ b/src/views/home/Home.js @@ -80,14 +80,10 @@ const Home = () => { - +
    Refresh Token: {!isLoadingDash ? dashboard?.RefreshTokenDate : ''}
    - -
    - Exchange Token: {!isLoadingDash ? dashboard?.ExchangeTokenDate : ''} -
    From af08a60c4f1cf3c1a68b98ad312039929409dfca Mon Sep 17 00:00:00 2001 From: KelvinTegelaar <49186168+KelvinTegelaar@users.noreply.github.com> Date: Fri, 31 Mar 2023 12:39:25 +0200 Subject: [PATCH 20/20] version up for release --- package-lock.json | 57 +++++++++++++++++++++------------------ public/version_latest.txt | 2 +- version_latest.txt | 2 +- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index 51ada863cb0a..08e6a9e247c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7107,8 +7107,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.9.2", - "license": "MIT", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -8824,7 +8825,8 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", - "license": "BSD-2-Clause" + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/global-modules": { "version": "2.0.0", @@ -11851,10 +11853,6 @@ "node": ">=4" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "license": "MIT" @@ -17528,8 +17526,9 @@ } }, "node_modules/watchpack": { - "version": "2.3.1", - "license": "MIT", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -17555,32 +17554,33 @@ } }, "node_modules/webpack": { - "version": "5.69.1", - "license": "MIT", + "version": "5.77.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.77.0.tgz", + "integrity": "sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "bin": { @@ -22755,7 +22755,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.9.2", + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -23851,7 +23853,9 @@ } }, "glob-to-regexp": { - "version": "0.4.1" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "global-modules": { "version": "2.0.0", @@ -25730,9 +25734,6 @@ "jsesc": { "version": "2.5.2" }, - "json-parse-better-errors": { - "version": "1.0.2" - }, "json-parse-even-better-errors": { "version": "2.3.1" }, @@ -29235,7 +29236,9 @@ } }, "watchpack": { - "version": "2.3.1", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -29253,31 +29256,33 @@ "dev": true }, "webpack": { - "version": "5.69.1", + "version": "5.77.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.77.0.tgz", + "integrity": "sha512-sbGNjBr5Ya5ss91yzjeJTLKyfiwo5C628AFjEa6WSXcZa4E+F57om3Cc8xLb1Jh0b243AWuSYRf3dn7HVeFQ9Q==", "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.8.3", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { diff --git a/public/version_latest.txt b/public/version_latest.txt index a4f52a5dbb5a..0fa4ae489037 100644 --- a/public/version_latest.txt +++ b/public/version_latest.txt @@ -1 +1 @@ -3.2.0 \ No newline at end of file +3.3.0 \ No newline at end of file diff --git a/version_latest.txt b/version_latest.txt index 0444f3207675..0fa4ae489037 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -3.2.1 \ No newline at end of file +3.3.0 \ No newline at end of file