feat: introduce role mappings form on authentication page (#1256)
This commit is contained in:
9
packages/types/index.d.ts
vendored
9
packages/types/index.d.ts
vendored
@@ -430,6 +430,13 @@ type TSamlAuthProvider = {
|
|||||||
loginUrl: string;
|
loginUrl: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TSamlAuthProviderRole = {
|
||||||
|
id: string;
|
||||||
|
samlAuthProviderId: string;
|
||||||
|
roleId: string;
|
||||||
|
remoteRoleName: string;
|
||||||
|
};
|
||||||
|
|
||||||
type AppConfig = {
|
type AppConfig = {
|
||||||
id: string;
|
id: string;
|
||||||
key: string;
|
key: string;
|
||||||
@@ -453,7 +460,7 @@ type Notification = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
documentationUrl: string;
|
documentationUrl: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
declare module 'axios' {
|
declare module 'axios' {
|
||||||
interface AxiosResponse {
|
interface AxiosResponse {
|
||||||
|
@@ -4,7 +4,6 @@ import Button from '@mui/material/Button';
|
|||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
|
||||||
import useSamlAuthProviders from 'hooks/useSamlAuthProviders.ee';
|
import useSamlAuthProviders from 'hooks/useSamlAuthProviders.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
|
@@ -0,0 +1,14 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const UPSERT_SAML_AUTH_PROVIDERS_ROLE_MAPPINGS = gql`
|
||||||
|
mutation UpsertSamlAuthProvidersRoleMappings(
|
||||||
|
$input: UpsertSamlAuthProvidersRoleMappingsInput
|
||||||
|
) {
|
||||||
|
upsertSamlAuthProvidersRoleMappings(input: $input) {
|
||||||
|
id
|
||||||
|
samlAuthProviderId
|
||||||
|
roleId
|
||||||
|
remoteRoleName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@@ -0,0 +1,12 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const GET_SAML_AUTH_PROVIDER_ROLE_MAPPINGS = gql`
|
||||||
|
query GetSamlAuthProviderRoleMappings($id: String!) {
|
||||||
|
getSamlAuthProviderRoleMappings(id: $id) {
|
||||||
|
id
|
||||||
|
samlAuthProviderId
|
||||||
|
roleId
|
||||||
|
remoteRoleName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@@ -3,6 +3,7 @@ import { gql } from '@apollo/client';
|
|||||||
export const GET_SAML_AUTH_PROVIDER = gql`
|
export const GET_SAML_AUTH_PROVIDER = gql`
|
||||||
query GetSamlAuthProvider {
|
query GetSamlAuthProvider {
|
||||||
getSamlAuthProvider {
|
getSamlAuthProvider {
|
||||||
|
id
|
||||||
name
|
name
|
||||||
certificate
|
certificate
|
||||||
signatureAlgorithm
|
signatureAlgorithm
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
import { useQuery } from '@apollo/client';
|
import { QueryResult, useQuery } from '@apollo/client';
|
||||||
|
|
||||||
import { TSamlAuthProvider } from '@automatisch/types';
|
import { TSamlAuthProvider } from '@automatisch/types';
|
||||||
import { GET_SAML_AUTH_PROVIDER } from 'graphql/queries/get-saml-auth-provider';
|
import { GET_SAML_AUTH_PROVIDER } from 'graphql/queries/get-saml-auth-provider';
|
||||||
|
|
||||||
type UseSamlAuthProviderReturn = {
|
type UseSamlAuthProviderReturn = {
|
||||||
provider: TSamlAuthProvider;
|
provider?: TSamlAuthProvider;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
|
refetch: QueryResult<TSamlAuthProvider | undefined>['refetch'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useSamlAuthProvider(): UseSamlAuthProviderReturn {
|
export default function useSamlAuthProvider(): UseSamlAuthProviderReturn {
|
||||||
const { data, loading } = useQuery(GET_SAML_AUTH_PROVIDER, {
|
const { data, loading, refetch } = useQuery(GET_SAML_AUTH_PROVIDER, {
|
||||||
context: { autoSnackbar: false },
|
context: { autoSnackbar: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
provider: data?.getSamlAuthProvider,
|
provider: data?.getSamlAuthProvider,
|
||||||
loading,
|
loading,
|
||||||
|
refetch,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
29
packages/web/src/hooks/useSamlAuthProviderRoleMappings.ts
Normal file
29
packages/web/src/hooks/useSamlAuthProviderRoleMappings.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { useLazyQuery } from '@apollo/client';
|
||||||
|
import { TSamlAuthProviderRole } from '@automatisch/types';
|
||||||
|
|
||||||
|
import { GET_SAML_AUTH_PROVIDER_ROLE_MAPPINGS } from 'graphql/queries/get-saml-auth-provider-role-mappings';
|
||||||
|
|
||||||
|
type QueryResponse = {
|
||||||
|
getSamlAuthProviderRoleMappings: TSamlAuthProviderRole[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function useSamlAuthProviderRoleMappings(providerId?: string) {
|
||||||
|
const [getSamlAuthProviderRoleMappings, { data, loading }] =
|
||||||
|
useLazyQuery<QueryResponse>(GET_SAML_AUTH_PROVIDER_ROLE_MAPPINGS);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (providerId) {
|
||||||
|
getSamlAuthProviderRoleMappings({
|
||||||
|
variables: {
|
||||||
|
id: providerId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [providerId]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
roleMappings: data?.getSamlAuthProviderRoleMappings || [],
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
}
|
@@ -238,5 +238,11 @@
|
|||||||
"authenticationForm.roleAttributeName": "Role attribute name",
|
"authenticationForm.roleAttributeName": "Role attribute name",
|
||||||
"authenticationForm.defaultRole": "Default role",
|
"authenticationForm.defaultRole": "Default role",
|
||||||
"authenticationForm.successfullySaved": "The provider has been saved.",
|
"authenticationForm.successfullySaved": "The provider has been saved.",
|
||||||
"authenticationForm.save": "Save"
|
"authenticationForm.save": "Save",
|
||||||
|
"roleMappingsForm.title": "Role mappings",
|
||||||
|
"roleMappingsForm.remoteRoleName": "Remote role name",
|
||||||
|
"roleMappingsForm.role": "Role",
|
||||||
|
"roleMappingsForm.appendRoleMapping": "Append",
|
||||||
|
"roleMappingsForm.save": "Save",
|
||||||
|
"roleMappingsForm.successfullySaved": "Role mappings have been saved."
|
||||||
}
|
}
|
||||||
|
109
packages/web/src/pages/Authentication/RoleMappings.tsx
Normal file
109
packages/web/src/pages/Authentication/RoleMappings.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import { TSamlAuthProvider, TSamlAuthProviderRole } from '@automatisch/types';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import { useMutation } from '@apollo/client';
|
||||||
|
import { useSnackbar } from 'notistack';
|
||||||
|
|
||||||
|
import { UPSERT_SAML_AUTH_PROVIDERS_ROLE_MAPPINGS } from 'graphql/mutations/upsert-saml-auth-providers-role-mappings';
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import useSamlAuthProviderRoleMappings from 'hooks/useSamlAuthProviderRoleMappings';
|
||||||
|
import Form from 'components/Form';
|
||||||
|
|
||||||
|
import RoleMappingsFieldArray from './RoleMappingsFieldsArray';
|
||||||
|
|
||||||
|
type RoleMappingsProps = {
|
||||||
|
provider?: TSamlAuthProvider;
|
||||||
|
providerLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateFormRoleMappings(roleMappings: TSamlAuthProviderRole[]) {
|
||||||
|
if (roleMappings.length === 0) {
|
||||||
|
return [{ roleId: '', remoteRoleName: '' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return roleMappings.map(({ roleId, remoteRoleName }) => ({
|
||||||
|
roleId,
|
||||||
|
remoteRoleName,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function RoleMappings({ provider, providerLoading }: RoleMappingsProps) {
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
const { roleMappings, loading: roleMappingsLoading } =
|
||||||
|
useSamlAuthProviderRoleMappings(provider?.id);
|
||||||
|
const [
|
||||||
|
upsertSamlAuthProvidersRoleMappings,
|
||||||
|
{ loading: upsertRoleMappingsLoading },
|
||||||
|
] = useMutation(UPSERT_SAML_AUTH_PROVIDERS_ROLE_MAPPINGS);
|
||||||
|
|
||||||
|
const handleRoleMappingsUpdate = async (values: any) => {
|
||||||
|
try {
|
||||||
|
if (provider?.id) {
|
||||||
|
await upsertSamlAuthProvidersRoleMappings({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
samlAuthProviderId: provider.id,
|
||||||
|
samlAuthProvidersRoleMappings: values.roleMappings.map(
|
||||||
|
({
|
||||||
|
roleId,
|
||||||
|
remoteRoleName,
|
||||||
|
}: {
|
||||||
|
roleId: string;
|
||||||
|
remoteRoleName: string;
|
||||||
|
}) => ({
|
||||||
|
roleId,
|
||||||
|
remoteRoleName,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
enqueueSnackbar(formatMessage('roleMappingsForm.successfullySaved'), {
|
||||||
|
variant: 'success',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Failed while saving!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultValues = useMemo(
|
||||||
|
() => ({
|
||||||
|
roleMappings: generateFormRoleMappings(roleMappings),
|
||||||
|
}),
|
||||||
|
[roleMappings]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (providerLoading || !provider?.id || roleMappingsLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Divider sx={{ pt: 2 }} />
|
||||||
|
<Typography variant="h3">
|
||||||
|
{formatMessage('roleMappingsForm.title')}
|
||||||
|
</Typography>
|
||||||
|
<Form defaultValues={defaultValues} onSubmit={handleRoleMappingsUpdate}>
|
||||||
|
<Stack direction="column" spacing={2}>
|
||||||
|
<RoleMappingsFieldArray />
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ boxShadow: 2 }}
|
||||||
|
loading={upsertRoleMappingsLoading}
|
||||||
|
>
|
||||||
|
{formatMessage('roleMappingsForm.save')}
|
||||||
|
</LoadingButton>
|
||||||
|
</Stack>
|
||||||
|
</Form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoleMappings;
|
@@ -0,0 +1,92 @@
|
|||||||
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||||
|
import { IRole } from '@automatisch/types';
|
||||||
|
import MuiTextField from '@mui/material/TextField';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
|
||||||
|
import useRoles from 'hooks/useRoles.ee';
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
|
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||||
|
import TextField from 'components/TextField';
|
||||||
|
import { Divider } from '@mui/material';
|
||||||
|
|
||||||
|
function generateRoleOptions(roles: IRole[]) {
|
||||||
|
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function RoleMappingsFieldArray() {
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
const { control } = useFormContext();
|
||||||
|
const { roles, loading: rolesLoading } = useRoles();
|
||||||
|
const { fields, append, remove } = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: 'roleMappings',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleAppendMapping = () => append({ roleId: '', remoteRoleName: '' });
|
||||||
|
const handleRemoveMapping = (index: number) => () => remove(index);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{fields.map((field, index) => (
|
||||||
|
<div key={field.id}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
alignItems="flex-start"
|
||||||
|
pb={1.5}
|
||||||
|
pt={0.5}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
direction={{ xs: 'column', md: 'row' }}
|
||||||
|
spacing={2}
|
||||||
|
alignItems="stretch"
|
||||||
|
sx={{ flex: 1 }}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
name={`roleMappings.${index}.remoteRoleName`}
|
||||||
|
label={formatMessage('roleMappingsForm.remoteRoleName')}
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<ControlledAutocomplete
|
||||||
|
name={`roleMappings.${index}.roleId`}
|
||||||
|
fullWidth
|
||||||
|
disablePortal
|
||||||
|
disableClearable
|
||||||
|
options={generateRoleOptions(roles)}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<MuiTextField
|
||||||
|
{...params}
|
||||||
|
label={formatMessage('roleMappingsForm.role')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
loading={rolesLoading}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<IconButton
|
||||||
|
aria-label="delete"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
sx={{ alignSelf: 'flex-start' }}
|
||||||
|
onClick={handleRemoveMapping(index)}
|
||||||
|
disabled={fields.length === 1}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
{index < fields.length - 1 && <Divider />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<Button fullWidth onClick={handleAppendMapping}>
|
||||||
|
{formatMessage('roleMappingsForm.appendRoleMapping')}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoleMappingsFieldArray;
|
211
packages/web/src/pages/Authentication/SamlConfiguration.tsx
Normal file
211
packages/web/src/pages/Authentication/SamlConfiguration.tsx
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
import MuiTextField from '@mui/material/TextField';
|
||||||
|
import LoadingButton from '@mui/lab/LoadingButton';
|
||||||
|
import { IRole } from '@automatisch/types';
|
||||||
|
import { useSnackbar } from 'notistack';
|
||||||
|
import { TSamlAuthProvider } from '@automatisch/types';
|
||||||
|
import { QueryResult, useMutation } from '@apollo/client';
|
||||||
|
|
||||||
|
import Form from 'components/Form';
|
||||||
|
import TextField from 'components/TextField';
|
||||||
|
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||||
|
import Switch from 'components/Switch';
|
||||||
|
|
||||||
|
import { UPSERT_SAML_AUTH_PROVIDER } from 'graphql/mutations/upsert-saml-auth-provider';
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
import useRoles from 'hooks/useRoles.ee';
|
||||||
|
|
||||||
|
type SamlConfigurationProps = {
|
||||||
|
provider?: TSamlAuthProvider;
|
||||||
|
providerLoading: boolean;
|
||||||
|
refetchProvider: QueryResult<TSamlAuthProvider | undefined>['refetch'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
active: false,
|
||||||
|
name: '',
|
||||||
|
certificate: '',
|
||||||
|
signatureAlgorithm: 'sha1',
|
||||||
|
issuer: '',
|
||||||
|
entryPoint: '',
|
||||||
|
firstnameAttributeName: '',
|
||||||
|
surnameAttributeName: '',
|
||||||
|
emailAttributeName: '',
|
||||||
|
roleAttributeName: '',
|
||||||
|
defaultRoleId: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateRoleOptions(roles: IRole[]) {
|
||||||
|
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function SamlConfiguration({
|
||||||
|
provider,
|
||||||
|
providerLoading,
|
||||||
|
refetchProvider,
|
||||||
|
}: SamlConfigurationProps) {
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
const { roles, loading: rolesLoading } = useRoles();
|
||||||
|
const { enqueueSnackbar } = useSnackbar();
|
||||||
|
const [upsertSamlAuthProvider, { loading }] = useMutation(
|
||||||
|
UPSERT_SAML_AUTH_PROVIDER
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleProviderUpdate = async (
|
||||||
|
providerDataToUpdate: Partial<TSamlAuthProvider>
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
certificate,
|
||||||
|
signatureAlgorithm,
|
||||||
|
issuer,
|
||||||
|
entryPoint,
|
||||||
|
firstnameAttributeName,
|
||||||
|
surnameAttributeName,
|
||||||
|
emailAttributeName,
|
||||||
|
roleAttributeName,
|
||||||
|
active,
|
||||||
|
defaultRoleId,
|
||||||
|
} = providerDataToUpdate;
|
||||||
|
|
||||||
|
await upsertSamlAuthProvider({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
name,
|
||||||
|
certificate,
|
||||||
|
signatureAlgorithm,
|
||||||
|
issuer,
|
||||||
|
entryPoint,
|
||||||
|
firstnameAttributeName,
|
||||||
|
surnameAttributeName,
|
||||||
|
emailAttributeName,
|
||||||
|
roleAttributeName,
|
||||||
|
active,
|
||||||
|
defaultRoleId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!provider?.id) {
|
||||||
|
await refetchProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueueSnackbar(formatMessage('authenticationForm.successfullySaved'), {
|
||||||
|
variant: 'success',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Failed while saving!');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (providerLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
defaultValues={provider || defaultValues}
|
||||||
|
onSubmit={handleProviderUpdate}
|
||||||
|
>
|
||||||
|
<Stack direction="column" gap={2}>
|
||||||
|
<Switch
|
||||||
|
name="active"
|
||||||
|
label={formatMessage('authenticationForm.active')}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="name"
|
||||||
|
label={formatMessage('authenticationForm.name')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="certificate"
|
||||||
|
label={formatMessage('authenticationForm.certificate')}
|
||||||
|
fullWidth
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
<ControlledAutocomplete
|
||||||
|
name="signatureAlgorithm"
|
||||||
|
fullWidth
|
||||||
|
disablePortal
|
||||||
|
disableClearable={true}
|
||||||
|
options={[
|
||||||
|
{ label: 'SHA1', value: 'sha1' },
|
||||||
|
{ label: 'SHA256', value: 'sha256' },
|
||||||
|
{ label: 'SHA512', value: 'sha512' },
|
||||||
|
]}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<MuiTextField
|
||||||
|
{...params}
|
||||||
|
label={formatMessage('authenticationForm.signatureAlgorithm')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="issuer"
|
||||||
|
label={formatMessage('authenticationForm.issuer')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="entryPoint"
|
||||||
|
label={formatMessage('authenticationForm.entryPoint')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="firstnameAttributeName"
|
||||||
|
label={formatMessage('authenticationForm.firstnameAttributeName')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="surnameAttributeName"
|
||||||
|
label={formatMessage('authenticationForm.surnameAttributeName')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="emailAttributeName"
|
||||||
|
label={formatMessage('authenticationForm.emailAttributeName')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
required={true}
|
||||||
|
name="roleAttributeName"
|
||||||
|
label={formatMessage('authenticationForm.roleAttributeName')}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
<ControlledAutocomplete
|
||||||
|
name="defaultRoleId"
|
||||||
|
fullWidth
|
||||||
|
disablePortal
|
||||||
|
disableClearable={true}
|
||||||
|
options={generateRoleOptions(roles)}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<MuiTextField
|
||||||
|
{...params}
|
||||||
|
label={formatMessage('authenticationForm.defaultRole')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
loading={rolesLoading}
|
||||||
|
/>
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
sx={{ boxShadow: 2 }}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{formatMessage('authenticationForm.save')}
|
||||||
|
</LoadingButton>
|
||||||
|
</Stack>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SamlConfiguration;
|
@@ -1,95 +1,22 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
import MuiTextField from '@mui/material/TextField';
|
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
|
||||||
import { IRole } from '@automatisch/types';
|
|
||||||
import { useSnackbar } from 'notistack';
|
|
||||||
import { TSamlAuthProvider } from '@automatisch/types';
|
|
||||||
import { useMutation } from '@apollo/client';
|
|
||||||
|
|
||||||
import PageTitle from 'components/PageTitle';
|
import PageTitle from 'components/PageTitle';
|
||||||
import Container from 'components/Container';
|
import Container from 'components/Container';
|
||||||
import Form from 'components/Form';
|
|
||||||
import TextField from 'components/TextField';
|
|
||||||
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
|
||||||
import Switch from 'components/Switch';
|
|
||||||
|
|
||||||
import { UPSERT_SAML_AUTH_PROVIDER } from 'graphql/mutations/upsert-saml-auth-provider';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
import useRoles from 'hooks/useRoles.ee';
|
|
||||||
import useSamlAuthProvider from 'hooks/useSamlAuthProvider';
|
import useSamlAuthProvider from 'hooks/useSamlAuthProvider';
|
||||||
|
|
||||||
const defaultValues = {
|
import SamlConfiguration from './SamlConfiguration';
|
||||||
active: false,
|
import RoleMappings from './RoleMappings';
|
||||||
name: '',
|
|
||||||
certificate: '',
|
|
||||||
signatureAlgorithm: 'sha1',
|
|
||||||
issuer: '',
|
|
||||||
entryPoint: '',
|
|
||||||
firstnameAttributeName: '',
|
|
||||||
surnameAttributeName: '',
|
|
||||||
emailAttributeName: '',
|
|
||||||
roleAttributeName: '',
|
|
||||||
defaultRoleId: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
function generateRoleOptions(roles: IRole[]) {
|
|
||||||
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
|
||||||
}
|
|
||||||
|
|
||||||
function AuthenticationPage() {
|
function AuthenticationPage() {
|
||||||
const formatMessage = useFormatMessage();
|
const formatMessage = useFormatMessage();
|
||||||
const { roles, loading: rolesLoading } = useRoles();
|
const {
|
||||||
const { provider, loading: providerLoading } = useSamlAuthProvider();
|
provider,
|
||||||
const { enqueueSnackbar } = useSnackbar();
|
loading: providerLoading,
|
||||||
const [upsertSamlAuthProvider, { loading }] = useMutation(
|
refetch: refetchProvider,
|
||||||
UPSERT_SAML_AUTH_PROVIDER
|
} = useSamlAuthProvider();
|
||||||
);
|
|
||||||
|
|
||||||
const handleProviderUpdate = async (
|
|
||||||
providerDataToUpdate: Partial<TSamlAuthProvider>
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
certificate,
|
|
||||||
signatureAlgorithm,
|
|
||||||
issuer,
|
|
||||||
entryPoint,
|
|
||||||
firstnameAttributeName,
|
|
||||||
surnameAttributeName,
|
|
||||||
emailAttributeName,
|
|
||||||
roleAttributeName,
|
|
||||||
active,
|
|
||||||
defaultRoleId,
|
|
||||||
} = providerDataToUpdate;
|
|
||||||
|
|
||||||
await upsertSamlAuthProvider({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
name,
|
|
||||||
certificate,
|
|
||||||
signatureAlgorithm,
|
|
||||||
issuer,
|
|
||||||
entryPoint,
|
|
||||||
firstnameAttributeName,
|
|
||||||
surnameAttributeName,
|
|
||||||
emailAttributeName,
|
|
||||||
roleAttributeName,
|
|
||||||
active,
|
|
||||||
defaultRoleId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('authenticationForm.successfullySaved'), {
|
|
||||||
variant: 'success',
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Failed while saving!');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
<Container sx={{ py: 3, display: 'flex', justifyContent: 'center' }}>
|
||||||
@@ -98,114 +25,17 @@ function AuthenticationPage() {
|
|||||||
<PageTitle>{formatMessage('authenticationPage.title')}</PageTitle>
|
<PageTitle>{formatMessage('authenticationPage.title')}</PageTitle>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
<Grid item xs={12} sx={{ pt: 5, pb: 5 }}>
|
||||||
{!providerLoading && (
|
<Stack spacing={5}>
|
||||||
<Form
|
<SamlConfiguration
|
||||||
defaultValues={provider || defaultValues}
|
provider={provider}
|
||||||
onSubmit={handleProviderUpdate}
|
providerLoading={providerLoading}
|
||||||
>
|
refetchProvider={refetchProvider}
|
||||||
<Stack direction="column" gap={2}>
|
/>
|
||||||
<Switch
|
<RoleMappings
|
||||||
name="active"
|
provider={provider}
|
||||||
label={formatMessage('authenticationForm.active')}
|
providerLoading={providerLoading}
|
||||||
/>
|
/>
|
||||||
<TextField
|
</Stack>
|
||||||
required={true}
|
|
||||||
name="name"
|
|
||||||
label={formatMessage('authenticationForm.name')}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
required={true}
|
|
||||||
name="certificate"
|
|
||||||
label={formatMessage('authenticationForm.certificate')}
|
|
||||||
fullWidth
|
|
||||||
multiline
|
|
||||||
/>
|
|
||||||
<ControlledAutocomplete
|
|
||||||
name="signatureAlgorithm"
|
|
||||||
fullWidth
|
|
||||||
disablePortal
|
|
||||||
disableClearable={true}
|
|
||||||
options={[
|
|
||||||
{ label: 'SHA1', value: 'sha1' },
|
|
||||||
{ label: 'SHA256', value: 'sha256' },
|
|
||||||
{ label: 'SHA512', value: 'sha512' },
|
|
||||||
]}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<MuiTextField
|
|
||||||
{...params}
|
|
||||||
label={formatMessage(
|
|
||||||
'authenticationForm.signatureAlgorithm'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
required={true}
|
|
||||||
name="issuer"
|
|
||||||
label={formatMessage('authenticationForm.issuer')}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
required={true}
|
|
||||||
name="entryPoint"
|
|
||||||
label={formatMessage('authenticationForm.entryPoint')}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
required={true}
|
|
||||||
name="firstnameAttributeName"
|
|
||||||
label={formatMessage(
|
|
||||||
'authenticationForm.firstnameAttributeName'
|
|
||||||
)}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
required={true}
|
|
||||||
name="surnameAttributeName"
|
|
||||||
label={formatMessage(
|
|
||||||
'authenticationForm.surnameAttributeName'
|
|
||||||
)}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
required={true}
|
|
||||||
name="emailAttributeName"
|
|
||||||
label={formatMessage('authenticationForm.emailAttributeName')}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
required={true}
|
|
||||||
name="roleAttributeName"
|
|
||||||
label={formatMessage('authenticationForm.roleAttributeName')}
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
<ControlledAutocomplete
|
|
||||||
name="defaultRoleId"
|
|
||||||
fullWidth
|
|
||||||
disablePortal
|
|
||||||
disableClearable={true}
|
|
||||||
options={generateRoleOptions(roles)}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<MuiTextField
|
|
||||||
{...params}
|
|
||||||
label={formatMessage('authenticationForm.defaultRole')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
loading={rolesLoading}
|
|
||||||
/>
|
|
||||||
<LoadingButton
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
sx={{ boxShadow: 2 }}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{formatMessage('authenticationForm.save')}
|
|
||||||
</LoadingButton>
|
|
||||||
</Stack>
|
|
||||||
</Form>
|
|
||||||
)}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
|
Reference in New Issue
Block a user