diff --git a/app/frontend/src/javascript/components/authentication-provider/data-mapping-form.tsx b/app/frontend/src/javascript/components/authentication-provider/data-mapping-form.tsx index 64b4a12f7..1b2cd034f 100644 --- a/app/frontend/src/javascript/components/authentication-provider/data-mapping-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/data-mapping-form.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react'; import { UseFormRegister, useFieldArray, ArrayPath, useWatch, Path } from 'react-hook-form'; import { FieldValues } from 'react-hook-form/dist/types/fields'; import AuthProviderAPI from '../../api/auth-provider'; -import { MappingFields, mappingType, ProvidableType } from '../../models/authentication-provider'; -import { Control } from 'react-hook-form/dist/types/form'; +import { AuthenticationProviderMapping, MappingFields, mappingType, ProvidableType } from '../../models/authentication-provider'; +import { Control, UseFormSetValue } from 'react-hook-form/dist/types/form'; import { FormSelect } from '../form/form-select'; import { FormInput } from '../form/form-input'; import { useTranslation } from 'react-i18next'; @@ -17,6 +17,8 @@ export interface DataMappingFormProps { register: UseFormRegister, control: Control, providerType: ProvidableType, + setValue: UseFormSetValue, + currentFormValues: Array, } type selectModelFieldOption = { value: string, label: string }; @@ -24,7 +26,7 @@ type selectModelFieldOption = { value: string, label: string }; /** * Partial form to define the mapping of the data between the API of the authentication provider and the application internals. */ -export const DataMappingForm = ({ register, control, providerType }: DataMappingFormProps) => { +export const DataMappingForm = ({ register, control, providerType, setValue, currentFormValues }: DataMappingFormProps) => { const { t } = useTranslation('admin'); const [dataMapping, setDataMapping] = useState(null); const [isOpenTypeMappingModal, updateIsOpenTypeMappingModal] = useImmer>(new Map()); @@ -128,7 +130,10 @@ export const DataMappingForm =
{providerType === 'OAuth2Provider' && } - {providerType === 'OpenIdConnectProvider' && } + {providerType === 'OpenIdConnectProvider' && }
diff --git a/app/frontend/src/javascript/components/authentication-provider/openid-connect-data-mapping-form.tsx b/app/frontend/src/javascript/components/authentication-provider/openid-connect-data-mapping-form.tsx index 445016caa..8a8eb410d 100644 --- a/app/frontend/src/javascript/components/authentication-provider/openid-connect-data-mapping-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/openid-connect-data-mapping-form.tsx @@ -1,18 +1,59 @@ import React from 'react'; -import { UseFormRegister } from 'react-hook-form'; +import { Path, UseFormRegister } from 'react-hook-form'; import { FieldValues } from 'react-hook-form/dist/types/fields'; import { FormInput } from '../form/form-input'; import { HtmlTranslate } from '../base/html-translate'; import { useTranslation } from 'react-i18next'; +import { UnpackNestedValue, UseFormSetValue } from 'react-hook-form/dist/types/form'; +import { FabButton } from '../base/fab-button'; +import { FieldPathValue } from 'react-hook-form/dist/types/path'; +import { AuthenticationProviderMapping } from '../../models/authentication-provider'; interface OpenidConnectDataMappingFormProps { register: UseFormRegister, + setValue: UseFormSetValue, + currentFormValues: Array, index: number, } -export const OpenidConnectDataMappingForm = ({ register, index }: OpenidConnectDataMappingFormProps) => { +export const OpenidConnectDataMappingForm = ({ register, setValue, currentFormValues, index }: OpenidConnectDataMappingFormProps) => { const { t } = useTranslation('admin'); + const standardConfiguration = { + 'user.uid': { api_field: 'sub' }, + 'user.email': { api_field: 'email' }, + 'user.username': { api_field: 'preferred_username' }, + 'profile.first_name': { api_field: 'given_name' }, + 'profile.last_name': { api_field: 'family_name' }, + 'profile.avatar': { api_field: 'picture' }, + 'profile.website': { api_field: 'website' }, + 'profile.gender': { api_field: 'gender', transformation: { true_value: 'male', false_value: 'female' } }, + 'profile.birthday': { api_field: 'birthdate', transformation: { format: 'iso8601' } }, + 'profile.phone': { api_field: 'phone_number' }, + 'profile.address': { api_field: 'address.formatted' } + }; + + /** + * Set the data mapping according to the standard OpenID Connect specification + */ + const openIdStandardConfiguration = (): void => { + const model = currentFormValues[index]?.local_model; + const field = currentFormValues[index]?.local_field; + const configuration = standardConfiguration[`${model}.${field}`]; + setValue( + `auth_provider_mappings_attributes.${index}.api_field` as Path, + configuration.api_field as UnpackNestedValue>> + ); + if (configuration.transformation) { + Object.keys(configuration.transformation).forEach((key) => { + setValue( + `auth_provider_mappings_attributes.${index}.transformation.${key}` as Path, + configuration.transformation[key] as UnpackNestedValue>> + ); + }); + } + }; + return (
({ placeholder="claim..." tooltip={} label={t('app.admin.authentication.openid_connect_data_mapping_form.api_field')} /> + } + className="auto-configure-button" + onClick={openIdStandardConfiguration} + tooltip={t('app.admin.authentication.openid_connect_data_mapping_form.openid_standard_configuration')} />
); }; diff --git a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx index b2e937c13..8aef0474c 100644 --- a/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx +++ b/app/frontend/src/javascript/components/authentication-provider/provider-form.tsx @@ -2,7 +2,12 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useForm, SubmitHandler, useWatch } from 'react-hook-form'; import { react2angular } from 'react2angular'; import { debounce as _debounce } from 'lodash'; -import { AuthenticationProvider, OpenIdConnectProvider, ProvidableType } from '../../models/authentication-provider'; +import { + AuthenticationProvider, + AuthenticationProviderMapping, + OpenIdConnectProvider, + ProvidableType +} from '../../models/authentication-provider'; import { Loader } from '../base/loader'; import { IApplication } from '../../models/application'; import { FormInput } from '../form/form-input'; @@ -108,7 +113,11 @@ export const ProviderForm: React.FC = ({ action, provider, on currentFormValues={output.providable_attributes as OpenIdConnectProvider} formState={formState} setValue={setValue} />} - {providableType && providableType !== 'DatabaseProvider' && } + {providableType && providableType !== 'DatabaseProvider' && } />}
{t('app.admin.authentication.provider_form.save')}
diff --git a/app/frontend/src/stylesheets/application.scss b/app/frontend/src/stylesheets/application.scss index bb2b11026..10fbf65f7 100644 --- a/app/frontend/src/stylesheets/application.scss +++ b/app/frontend/src/stylesheets/application.scss @@ -15,8 +15,9 @@ @import "app.components"; @import "app.plugins"; -@import "modules/authentication-provider/data-mapping-form"; @import "modules/authentication-provider/array-mapping-form"; +@import "modules/authentication-provider/data-mapping-form"; +@import "modules/authentication-provider/openid-connect-data-mapping-form"; @import "modules/authentication-provider/provider-form"; @import "modules/authentication-provider/type-mapping-modal"; @import "modules/base/fab-alert"; diff --git a/app/frontend/src/stylesheets/modules/authentication-provider/openid-connect-data-mapping-form.scss b/app/frontend/src/stylesheets/modules/authentication-provider/openid-connect-data-mapping-form.scss new file mode 100644 index 000000000..f41303584 --- /dev/null +++ b/app/frontend/src/stylesheets/modules/authentication-provider/openid-connect-data-mapping-form.scss @@ -0,0 +1,7 @@ +.openid-connect-data-mapping-form { + .auto-configure-button { + align-self: center; + margin-top: 0.8rem; + margin-left: 20px; + } +} diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index e021eda91..7e6d85c13 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -1084,6 +1084,7 @@ en: openid_connect_data_mapping_form: api_field: "Userinfo claim" api_field_help_html: 'Set the field providing the corresponding data through the userinfo endpoint.
JsonPath syntax is supported. If many fields are selected, the first one will be used.
Example: $.data[*].name' + openid_standard_configuration: "Use the OpenID standard configuration" type_mapping_modal: data_mapping: "Data mapping" TYPE_expected: "{TYPE} expected" diff --git a/doc/sso_open_id_connect.md b/doc/sso_open_id_connect.md new file mode 100644 index 000000000..e69de29bb