diff --git a/api/api-iam/iam-external/src/main/java/fr/gouv/vitamui/iam/external/server/service/IdentityProviderExternalService.java b/api/api-iam/iam-external/src/main/java/fr/gouv/vitamui/iam/external/server/service/IdentityProviderExternalService.java index 6f19689a52c..0b7bb38477d 100644 --- a/api/api-iam/iam-external/src/main/java/fr/gouv/vitamui/iam/external/server/service/IdentityProviderExternalService.java +++ b/api/api-iam/iam-external/src/main/java/fr/gouv/vitamui/iam/external/server/service/IdentityProviderExternalService.java @@ -120,8 +120,8 @@ public IdentityProviderDto mapToIdentityProviderDto( dto.getIdentifierAttribute(), dto.getMaximumAuthenticationLifetime(), dto.getAuthnRequestBinding(), - Objects.isNull(dto.getWantsAssertionsSigned()) ? false : dto.getWantsAssertionsSigned(), - Objects.isNull(dto.getAuthnRequestSigned()) ? false : dto.getAuthnRequestSigned(), + Objects.isNull(dto.getWantsAssertionsSigned()) ? true : dto.getWantsAssertionsSigned(), + Objects.isNull(dto.getAuthnRequestSigned()) ? true : dto.getAuthnRequestSigned(), dto.isPropagateLogout(), dto.isAutoProvisioningEnabled(), dto.getClientId(), diff --git a/cas/cas-server/README.md b/cas/cas-server/README.md index 52428ab3116..33e6400ba0a 100644 --- a/cas/cas-server/README.md +++ b/cas/cas-server/README.md @@ -266,7 +266,7 @@ To set up Saml V2 authentication with vitamui, please follow these steps: keytool -importcert -keystore environments/keystores/server/my_server/keystore_cas-server.jks -storepass xxx -alias orga-saml -file environments/certs/orga-SAML.crt ``` - In Vitamui interface, we create an external provider of SAML type with the following informations: - - Email attribute: keep it empty, except if the idp provide the email after authentication + - Email attribute: The attribute containing the user email sent by the idp after authentication, please check that the attribute 'nameid-format' to 'emailAddress' instead of 'transient' - Upload the CAS keystore file (with the associated password) (keystore_cas-server.jks) - Upload the IDP metadata file (e.g., FederationMetadata.xml) - After provider creation, we need to download the metadata file of the vitamui provider (spmetadata.xml), and @@ -347,7 +347,7 @@ To test the saml authentication, bellow an example with an external CAS on versi - Login in into Vitamui as a superadmin, create a SAML provider with the following information: - Pattern: email domain configured before: ```mydomainmail.fr``` - Type: ```SAML``` - - Email attribute: empty (except if the cas send the email after authentication) + - Email attribute: The attribute containing the user email sent by the idp after authentication, please check that the attribute 'nameid-format' to 'emailAddress' instead of 'transient' - CAS Keystore: for testing: you upload any keystore with the right passowrd. - IDP Metadata: upload the file generated by running external CAS, from the path ```/etc/cas/saml/idp-metadata.xml``` - Assertions : false diff --git a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/Pac4jClientIdentityProviderDto.java b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/Pac4jClientIdentityProviderDto.java index 305b77ed676..04b2908fe36 100644 --- a/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/Pac4jClientIdentityProviderDto.java +++ b/cas/cas-server/src/main/java/fr/gouv/vitamui/cas/provider/Pac4jClientIdentityProviderDto.java @@ -39,6 +39,8 @@ import fr.gouv.vitamui.iam.common.dto.IdentityProviderDto; import org.pac4j.core.client.IndirectClient; +import java.util.Objects; + /** * Pac4j client identity provider. * @@ -61,7 +63,7 @@ public Pac4jClientIdentityProviderDto(final IdentityProviderDto dto, final Indir setIdentifierAttribute(dto.getIdentifierAttribute()); setAutoProvisioningEnabled(dto.isAutoProvisioningEnabled()); setProtocoleType(dto.getProtocoleType()); - setPropagateLogout(dto.isPropagateLogout()); + setPropagateLogout(Objects.isNull(dto.isPropagateLogout()) ? false : dto.isPropagateLogout()); setKeystoreBase64(dto.getKeystoreBase64()); setKeystorePassword(dto.getKeystorePassword()); @@ -70,8 +72,10 @@ public Pac4jClientIdentityProviderDto(final IdentityProviderDto dto, final Indir setSpMetadata(dto.getSpMetadata()); setMaximumAuthenticationLifetime(dto.getMaximumAuthenticationLifetime()); setAuthnRequestBinding(dto.getAuthnRequestBinding()); - setWantsAssertionsSigned(dto.getWantsAssertionsSigned()); - setAuthnRequestSigned(dto.getAuthnRequestSigned()); + setWantsAssertionsSigned( + Objects.isNull(dto.getWantsAssertionsSigned()) ? true : dto.getWantsAssertionsSigned() + ); + setAuthnRequestSigned(Objects.isNull(dto.getAuthnRequestSigned()) ? true : dto.getAuthnRequestSigned()); setClientId(dto.getClientId()); setClientSecret(dto.getClientSecret()); diff --git a/deployment/scripts/mongod/v7.1/62_update_existing_external_providers_models.js.j2 b/deployment/scripts/mongod/v7.1/62_update_existing_external_providers_models.js.j2 new file mode 100644 index 00000000000..d5918e0ce86 --- /dev/null +++ b/deployment/scripts/mongod/v7.1/62_update_existing_external_providers_models.js.j2 @@ -0,0 +1,82 @@ +print("START 61_update_existing_external_providers_models.js.j2"); + +db = db.getSiblingDB('iam'); + +db.providers.updateMany( + { + internal: false, + $or: [ + { protocoleType: "SAML" }, + { protocoleType: "OIDC" } + ], + wantsAssertionsSigned: { $exists: false } + }, + [ + { + $set: { + wantsAssertionsSigned: { + $cond: { + if: { $eq: [{ $type: "$spMetadata" }, "missing"] }, + then: false, + else: { + $cond: { + if: { $regexMatch: { input: "$spMetadata", regex: /WantAssertionsSigned="true"/ } }, + then: true, + else: false + } + } + } + } + } + } + ] +); + + + +db.providers.updateMany( + { + internal: false, + $or: [ + { protocoleType: "SAML" }, + { protocoleType: "OIDC" } + ], + authnRequestSigned: { $exists: false } + }, + [ + { + $set: { + authnRequestSigned: { + $cond: { + if: { $eq: [{ $type: "$spMetadata" }, "missing"] }, + then: false, + else: { + $cond: { + if: { $regexMatch: { input: "$spMetadata", regex: /AuthnRequestsSigned="true"/ } }, + then: true, + else: false + } + } + } + } + } + } + ] +); + + +db.providers.updateMany( + { + internal: false, + $or: [ + { protocoleType: "SAML" }, + { protocoleType: "OIDC" } + ], + propagateLogout: { $exists: false } + }, + { + $set: { propagateLogout: false } + } +); + +print("END 61_update_existing_external_providers_models.js.j2"); diff --git a/docs/developeurs/vitamui-conf-dev/README.md b/docs/developeurs/vitamui-conf-dev/README.md index 44de2a32672..7055e3c86e5 100644 --- a/docs/developeurs/vitamui-conf-dev/README.md +++ b/docs/developeurs/vitamui-conf-dev/README.md @@ -24,7 +24,7 @@ of our environment trusted by the remote environment, to do that, we follow thes ``` 3. Download an external public certificate (external_pub.pem && external_key.pem) provided in this - path: https://webdav.dev.programmevitam.fr/webdav/Certificats_vitam/ + path: https://rec.part.programmevitam.fr/ 4. Generate a keystore, for Vitam context, with this certificate. (example password: customer-password-ks , example in P12 format): ```shell script diff --git a/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.spec.ts b/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.spec.ts index 9751cd5d376..f2023f63730 100644 --- a/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.spec.ts +++ b/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.spec.ts @@ -49,6 +49,7 @@ describe('IdentityProviderService', () => { let httpTestingController: HttpTestingController; let identityProviderService: IdentityProviderService; let identityProviders: any[]; + let externalIdentityProviders: any[]; let keystore: File; let idpMetadata: File; @@ -68,6 +69,19 @@ describe('IdentityProviderService', () => { idpMetadata, }, ]; + externalIdentityProviders = [ + { + id: '44', + customerId: '1234', + name: 'Test IDP', + internal: false, + keystorePassword: 'testpassword1234', + patterns: ['test.com', 'test.fr'], + enabled: true, + keystore, + idpMetadata, + }, + ]; const snackBarSpy = jasmine.createSpyObj(['open']); TestBed.configureTestingModule({ @@ -108,6 +122,23 @@ describe('IdentityProviderService', () => { req.flush(identityProviders[0]); }); + it('should call /fake-api/providers and display a succes message to asking to restart service', () => { + const snackBar = TestBed.inject(VitamUISnackBarService); + identityProviderService.create(externalIdentityProviders[0]).subscribe((response: IdentityProvider) => { + expect(response).toEqual(externalIdentityProviders[0]); + expect(snackBar.open).toHaveBeenCalledTimes(1); + expect(snackBar.open).toHaveBeenCalledWith({ + message: 'SHARED.SNACKBAR.PROVIDER_CREATE_RESTART_NEED', + translateParams: { + param1: externalIdentityProviders[0].name, + }, + }); + }, fail); + const req = httpTestingController.expectOne('/fake-api/providers'); + expect(req.request.method).toEqual('POST'); + req.flush(externalIdentityProviders[0]); + }); + it('should display an error message', () => { const snackBar = TestBed.inject(VitamUISnackBarService); identityProviderService.create(identityProviders[0]).subscribe(fail, () => { @@ -153,6 +184,27 @@ describe('IdentityProviderService', () => { req.flush(identityProviders[0]); }); + it('should call PATCH with specific message /fake-api/providers/44', () => { + const snackBar = TestBed.inject(VitamUISnackBarService); + identityProviderService.updated.subscribe( + (provider: IdentityProvider) => expect(provider).toEqual(externalIdentityProviders[0]), + fail, + ); + identityProviderService.patch(externalIdentityProviders[0]).subscribe((provider: IdentityProvider) => { + expect(provider).toEqual(externalIdentityProviders[0]); + expect(snackBar.open).toHaveBeenCalledTimes(1); + expect(snackBar.open).toHaveBeenCalledWith({ + message: 'SHARED.SNACKBAR.PROVIDER_UPDATE_RESTART_NEED', + translateParams: { + param1: externalIdentityProviders[0].name, + }, + }); + }, fail); + const req = httpTestingController.expectOne('/fake-api/providers/44'); + expect(req.request.method).toEqual('PATCH'); + expect(req.request.body).toEqual(externalIdentityProviders[0]); + req.flush(externalIdentityProviders[0]); + }); it('should display an error message', () => { const snackBar = TestBed.inject(VitamUISnackBarService); identityProviderService.patch(identityProviders[0]).subscribe(fail, () => { diff --git a/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.ts b/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.ts index fe80e2f4f9d..46c6bd91f9a 100644 --- a/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.ts +++ b/ui/ui-frontend/projects/identity/src/app/customer/customer-preview/sso-tab/identity-provider.service.ts @@ -60,8 +60,10 @@ export class IdentityProviderService { map((updatedIdp: IdentityProvider) => this.addSpMetadataUrl(updatedIdp)), tap( (newIDP: IdentityProvider) => { + let messageKey = newIDP.internal ? 'SHARED.SNACKBAR.PROVIDER_CREATE' : 'SHARED.SNACKBAR.PROVIDER_CREATE_RESTART_NEED'; + this.snackBarService.open({ - message: 'SHARED.SNACKBAR.PROVIDER_CREATE', + message: messageKey, translateParams: { param1: newIDP.name, }, @@ -81,8 +83,9 @@ export class IdentityProviderService { tap((updatedIdp: IdentityProvider) => this.updated.next(updatedIdp)), tap( (updatedIdp: IdentityProvider) => { + let messageKey = updatedIdp.internal ? 'SHARED.SNACKBAR.PROVIDER_UPDATE' : 'SHARED.SNACKBAR.PROVIDER_UPDATE_RESTART_NEED'; this.snackBarService.open({ - message: 'SHARED.SNACKBAR.PROVIDER_UPDATE', + message: messageKey, translateParams: { param1: updatedIdp.name, }, @@ -102,8 +105,9 @@ export class IdentityProviderService { tap((updatedIdp: IdentityProvider) => this.updated.next(updatedIdp)), tap( (updatedIdp: IdentityProvider) => { + let messageKey = updatedIdp.internal ? 'SHARED.SNACKBAR.PROVIDER_UPDATE' : 'SHARED.SNACKBAR.PROVIDER_UPDATE_RESTART_NEED'; this.snackBarService.open({ - message: 'SHARED.SNACKBAR.PROVIDER_UPDATE', + message: messageKey, translateParams: { param1: updatedIdp.name, }, @@ -122,8 +126,9 @@ export class IdentityProviderService { tap((updatedIdp: IdentityProvider) => this.updated.next(updatedIdp)), tap( (updatedIdp: IdentityProvider) => { + let messageKey = updatedIdp.internal ? 'SHARED.SNACKBAR.PROVIDER_UPDATE' : 'SHARED.SNACKBAR.PROVIDER_UPDATE_RESTART_NEED'; this.snackBarService.open({ - message: 'SHARED.SNACKBAR.PROVIDER_UPDATE', + message: messageKey, translateParams: { param1: updatedIdp.name, }, @@ -135,27 +140,6 @@ export class IdentityProviderService { ), ); } - - updateSpMetadataFile(id: string, spMetadata: File): Observable { - return this.providerApi.patchProviderSpMetadata(id, spMetadata).pipe( - map((updatedIdp: IdentityProvider) => this.addSpMetadataUrl(updatedIdp)), - tap((updatedIdp: IdentityProvider) => this.updated.next(updatedIdp)), - tap( - (updatedIdp: IdentityProvider) => { - this.snackBarService.open({ - message: 'SHARED.SNACKBAR.PROVIDER_UPDATE', - translateParams: { - param1: updatedIdp.name, - }, - }); - }, - (error) => { - this.snackBarService.open({ message: error.error.message, translate: false }); - }, - ), - ); - } - getAll(customerId?: string): Observable { const criterionArray: Criterion[] = []; if (customerId) { diff --git a/ui/ui-frontend/projects/identity/src/assets/i18n/en.json b/ui/ui-frontend/projects/identity/src/assets/i18n/en.json index c5e7aae3ec0..debd24d1800 100644 --- a/ui/ui-frontend/projects/identity/src/assets/i18n/en.json +++ b/ui/ui-frontend/projects/identity/src/assets/i18n/en.json @@ -61,7 +61,9 @@ "CUSTOMER_CREATE_ERROR": "The organization could not be created, please contact an administrator", "CUSTOMER_UPDATE": "The organization {{ param1 }} has been successfully modified", "PROVIDER_CREATE": "IDP {{ param1 }} has been successfully created", + "PROVIDER_CREATE_RESTART_NEED": "IDP {{ param1 }} has been successfully created, taking these parameter changes into account requires a restart of the authentication system", "PROVIDER_UPDATE": "IDP {{ param1 }} has been successfully modified", + "PROVIDER_UPDATE_RESTART_NEED": "IDP {{ param1 }} has been successfully modified,, taking these parameter changes into account requires a restart of the authentication system", "OWNER_CREATE": "The owner {{ param1 }} has been successfully created", "OWNER_UPDATE": "The owner {{ param1 }} has been successfully modified", "SAFE_CREATE": "The safe {{ param1 }} of owner {{ param2 }} has been successfully created", diff --git a/ui/ui-frontend/projects/identity/src/assets/i18n/fr.json b/ui/ui-frontend/projects/identity/src/assets/i18n/fr.json index 9fbfdc56100..d655694d5b4 100644 --- a/ui/ui-frontend/projects/identity/src/assets/i18n/fr.json +++ b/ui/ui-frontend/projects/identity/src/assets/i18n/fr.json @@ -60,7 +60,9 @@ "CUSTOMER_CREATE_ERROR": "L'organisation n'a pas pu être créé, veuillez contacter un administrateur", "CUSTOMER_UPDATE": "L'organisation {{ param1 }} a bien été modifié", "PROVIDER_CREATE": "L'IDP {{ param1 }} a bien été créé", + "PROVIDER_CREATE_RESTART_NEED": "L'IDP {{ param1 }} a bien été créé, la prise en compte du changement de ces paramètres nécessite un redémarrage du système d'authentification", "PROVIDER_UPDATE": "L'IDP {{ param1 }} a bien été modifié", + "PROVIDER_UPDATE_RESTART_NEED": "L'IDP {{ param1 }} a bien été modifié, la prise en compte du changement de ces paramètres nécessite un redémarrage du système d'authentification", "OWNER_CREATE": "Le propriétaire {{ param1 }} a bien été créé", "OWNER_UPDATE": "Le propriétaire {{ param1 }} a bien été modifié", "SAFE_CREATE": "Le coffre {{ param1 }} du propriétaire {{ param2 }} a bien été créé",