diff --git a/composer.json b/composer.json index fcd8bd9e6..e7388fd8c 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "prefer-stable": true, "require": { "php": "~7.2", + "ext-json": "*", "guzzlehttp/guzzle": "^6", "incenteev/composer-parameter-handler": "~2.0", "jms/translation-bundle": "^1.3.0", diff --git a/composer.lock b/composer.lock index a4b07d656..5c0d5f2a3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "aab81bd4f242211643f8d19d14a71bf9", + "content-hash": "200510546a7bbe5e1b4893100aa58fa0", "packages": [ { "name": "beberlei/assert", @@ -9055,7 +9055,8 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "~7.2" + "php": "~7.2", + "ext-json": "*" }, "platform-dev": [], "platform-overrides": { diff --git a/config/legacy/parameters.yaml.dist b/config/legacy/parameters.yaml.dist index 79ce5c091..a8b1f7661 100644 --- a/config/legacy/parameters.yaml.dist +++ b/config/legacy/parameters.yaml.dist @@ -62,6 +62,8 @@ parameters: # remote vetting + remote_vetting_entity_id: https://selfservice.stepup.example.com/rv/metadata + # For each remote vetting IdP we require some parameters. remote_vetting_idps: # The display name @@ -82,7 +84,6 @@ parameters: ssoUrl: https://selfservice.stepup.example.com/second-factor/mock/sso # Certificates for the remote vetting IdP certificateFile: "%saml_rv_publickey%" - privateKey: "%saml_rv_privatekey%" # The attribute mapping should map the institute IdP attributes with the ones received from the remote vetting # IdP. attributeMapping: @@ -97,7 +98,6 @@ parameters: entityId: https://selfservice.stepup.example.com/mock/metadata ssoUrl: https://selfservice.stepup.example.com/second-factor/mock/sso certificateFile: "%saml_rv_publickey%" - privateKey: "%saml_rv_privatekey%" attributeMapping: givenName: firstName surname: lastName @@ -110,16 +110,21 @@ parameters: entityId: https://selfservice.stepup.example.com/mock/metadata ssoUrl: https://selfservice.stepup.example.com/second-factor/mock/sso certificateFile: '%saml_rv_publickey%' - privateKey: '%saml_rv_privatekey%' attributeMapping: givenName: firstName surname: lastName - # SelfService acts as the remote vetting SP, this metadata is used by the remote vetting IdP's to post their SAML - # responses to - remote_vetting_sp: - entityId: https://selfservice.stepup.example.com/saml/metadata - assertionConsumerUrl: https://selfservice.stepup.example.com/second-factor/acs - privateKey: '%saml_rv_privatekey%' + - slug: mock + name: 'Mock IDP' + logo: /images/remote-vetting/mock.png + description: + nl_NL: 'This is an integration test IdP.' + en_GB: 'This is an integration test IdP.' + entityId: 'https://selfservice.stepup.example.com/mock/metadata' + ssoUrl: 'https://selfservice.stepup.example.com/second-factor/mock/sso' + certificateFile: '%saml_rv_publickey%' + attributeMapping: + givenName: firstName + surname: lastName identity_encryption_configuration: # The public key used to encrypt the remote vetting user data. The private key matching this is used to decrypt, @@ -147,6 +152,8 @@ parameters: # The location on disk where the encrypted remote vetting user data is stored storage_location: '%kernel.project_dir%/var/rv' - # For test, we use a mock remote vetting IdP, these certificates are used for that. - saml_rv_publickey: /src/Stepup-SelfService/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/test.crt - saml_rv_privatekey: /src/Stepup-SelfService/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/test.key \ No newline at end of file + + # Saml Remote Vetting SP public key + saml_rv_publickey: '%kernel.project_dir%/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/test.crt' + # Saml Remote Vetting Mock IdP private key (used for development and testing, this value should be omitted in production) + saml_rv_privatekey: '%kernel.project_dir%/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/test.key' \ No newline at end of file diff --git a/config/legacy/samlstepupproviders.yaml b/config/legacy/samlstepupproviders.yaml index 1b9bcfa57..e1efe352d 100644 --- a/config/legacy/samlstepupproviders.yaml +++ b/config/legacy/samlstepupproviders.yaml @@ -7,56 +7,6 @@ surfnet_stepup_self_service_saml_stepup_provider: metadata: ss_registration_gssf_saml_metadata providers: - tiqr: - hosted: - service_provider: - public_key: "%gssp_tiqr_sp_publickey%" - private_key: "%gssp_tiqr_sp_privatekey%" - metadata: - public_key: "%gssp_tiqr_metadata_publickey%" - private_key: "%gssp_tiqr_metadata_privatekey%" - remote: - entity_id: "%gssp_tiqr_remote_entity_id%" - sso_url: "%gssp_tiqr_remote_sso_url%" - certificate: "%gssp_tiqr_remote_certificate%" - view_config: - loa: "%gssp_tiqr_loa%" - logo: "%gssp_tiqr_logo%" - alt: "%gssp_tiqr_alt%" - title: "%gssp_tiqr_title%" - description: "%gssp_tiqr_description%" - button_use: "%gssp_tiqr_button_use%" - initiate_title: "%gssp_tiqr_initiate_title%" - initiate_button: "%gssp_tiqr_initiate_button%" - explanation: "%gssp_tiqr_explanation%" - authn_failed: "%gssp_tiqr_authn_failed%" - pop_failed: "%gssp_tiqr_pop_failed%" - app_android_url: "%gssp_tiqr_app_android_url%" - app_ios_url: "%gssp_tiqr_app_ios_url%" - biometric: - hosted: - service_provider: - public_key: "%gssp_biometric_sp_publickey%" - private_key: "%gssp_biometric_sp_privatekey%" - metadata: - public_key: "%gssp_biometric_metadata_publickey%" - private_key: "%gssp_biometric_metadata_privatekey%" - remote: - entity_id: "%gssp_biometric_remote_entity_id%" - sso_url: "%gssp_biometric_remote_sso_url%" - certificate: "%gssp_biometric_remote_certificate%" - view_config: - loa: "%gssp_biometric_loa%" - logo: "%gssp_biometric_logo%" - alt: "%gssp_biometric_alt%" - title: "%gssp_biometric_title%" - description: "%gssp_biometric_description%" - button_use: "%gssp_biometric_button_use%" - initiate_title: "%gssp_biometric_initiate_title%" - initiate_button: "%gssp_biometric_initiate_button%" - explanation: "%gssp_biometric_explanation%" - authn_failed: "%gssp_biometric_authn_failed%" - pop_failed: "%gssp_biometric_pop_failed%" webauthn: hosted: service_provider: @@ -83,32 +33,58 @@ surfnet_stepup_self_service_saml_stepup_provider: pop_failed: "%gssp_webauthn_pop_failed%" app_android_url: "%gssp_webauthn_app_android_url%" app_ios_url: "%gssp_webauthn_app_ios_url%" - azuremfa: + demo_gssp_2: hosted: service_provider: - public_key: "%gssp_azuremfa_sp_publickey%" - private_key: "%gssp_azuremfa_sp_privatekey%" + public_key: "%gssp_demo_gssp_2_sp_publickey%" + private_key: "%gssp_demo_gssp_2_sp_privatekey%" metadata: - public_key: "%gssp_azuremfa_metadata_publickey%" - private_key: "%gssp_azuremfa_metadata_privatekey%" + public_key: "%gssp_demo_gssp_2_metadata_publickey%" + private_key: "%gssp_demo_gssp_2_metadata_privatekey%" remote: - entity_id: "%gssp_azuremfa_remote_entity_id%" - sso_url: "%gssp_azuremfa_remote_sso_url%" - certificate: "%gssp_azuremfa_remote_certificate%" + entity_id: "%gssp_demo_gssp_2_remote_entity_id%" + sso_url: "%gssp_demo_gssp_2_remote_sso_url%" + certificate: "%gssp_demo_gssp_2_remote_certificate%" + view_config: + loa: 3 + logo: "%gssp_demo_gssp_2_logo%" + alt: "%gssp_demo_gssp_2_alt%" + title: "%gssp_demo_gssp_2_title%" + description: "%gssp_demo_gssp_2_description%" + button_use: "%gssp_demo_gssp_2_button_use%" + initiate_title: "%gssp_demo_gssp_2_initiate_title%" + initiate_button: "%gssp_demo_gssp_2_initiate_button%" + explanation: "%gssp_demo_gssp_2_explanation%" + authn_failed: "%gssp_demo_gssp_2_authn_failed%" + pop_failed: "%gssp_demo_gssp_2_pop_failed%" + app_android_url: "%gssp_demo_gssp_2_app_android_url%" + app_ios_url: "%gssp_demo_gssp_2_app_ios_url%" + tiqr: + hosted: + service_provider: + public_key: "%gssp_tiqr_sp_publickey%" + private_key: "%gssp_tiqr_sp_privatekey%" + metadata: + public_key: "%gssp_tiqr_metadata_publickey%" + private_key: "%gssp_tiqr_metadata_privatekey%" + remote: + entity_id: "%gssp_tiqr_remote_entity_id%" + sso_url: "%gssp_tiqr_remote_sso_url%" + certificate: "%gssp_tiqr_remote_certificate%" view_config: loa: 2 - logo: "%gssp_azuremfa_logo%" - alt: "%gssp_azuremfa_alt%" - title: "%gssp_azuremfa_title%" - description: "%gssp_azuremfa_description%" - button_use: "%gssp_azuremfa_button_use%" - initiate_title: "%gssp_azuremfa_initiate_title%" - initiate_button: "%gssp_azuremfa_initiate_button%" - explanation: "%gssp_azuremfa_explanation%" - authn_failed: "%gssp_azuremfa_authn_failed%" - pop_failed: "%gssp_azuremfa_pop_failed%" - app_android_url: "%gssp_azuremfa_app_android_url%" - app_ios_url: "%gssp_azuremfa_app_ios_url%" + logo: "%gssp_tiqr_logo%" + alt: "%gssp_tiqr_alt%" + title: "%gssp_tiqr_title%" + description: "%gssp_tiqr_description%" + button_use: "%gssp_tiqr_button_use%" + initiate_title: "%gssp_tiqr_initiate_title%" + initiate_button: "%gssp_tiqr_initiate_button%" + explanation: "%gssp_tiqr_explanation%" + authn_failed: "%gssp_tiqr_authn_failed%" + pop_failed: "%gssp_tiqr_pop_failed%" + app_android_url: "%gssp_tiqr_app_android_url%" + app_ios_url: "%gssp_tiqr_app_ios_url%" demo_gssp: hosted: service_provider: @@ -135,29 +111,3 @@ surfnet_stepup_self_service_saml_stepup_provider: pop_failed: "%gssp_demo_gssp_pop_failed%" app_android_url: "%gssp_demo_gssp_app_android_url%" app_ios_url: "%gssp_demo_gssp_app_ios_url%" - demo_gssp_2: - hosted: - service_provider: - public_key: "%gssp_demo_gssp_2_sp_publickey%" - private_key: "%gssp_demo_gssp_2_sp_privatekey%" - metadata: - public_key: "%gssp_demo_gssp_2_metadata_publickey%" - private_key: "%gssp_demo_gssp_2_metadata_privatekey%" - remote: - entity_id: "%gssp_demo_gssp_2_remote_entity_id%" - sso_url: "%gssp_demo_gssp_2_remote_sso_url%" - certificate: "%gssp_demo_gssp_2_remote_certificate%" - view_config: - loa: 3 - logo: "%gssp_demo_gssp_2_logo%" - alt: "%gssp_demo_gssp_2_alt%" - title: "%gssp_demo_gssp_2_title%" - description: "%gssp_demo_gssp_2_description%" - button_use: "%gssp_demo_gssp_2_button_use%" - initiate_title: "%gssp_demo_gssp_2_initiate_title%" - initiate_button: "%gssp_demo_gssp_2_initiate_button%" - explanation: "%gssp_demo_gssp_2_explanation%" - authn_failed: "%gssp_demo_gssp_2_authn_failed%" - pop_failed: "%gssp_demo_gssp_2_pop_failed%" - app_android_url: "%gssp_demo_gssp_2_app_android_url%" - app_ios_url: "%gssp_demo_gssp_2_app_ios_url%" diff --git a/config/services_dev.yaml b/config/services_dev.yaml new file mode 100644 index 000000000..409535619 --- /dev/null +++ b/config/services_dev.yaml @@ -0,0 +1,28 @@ +# Use this service definition file to override services and parameters in the dev environment. +# For example to mock certain services, or override a password for test. +services: + # A remote vetting mock IdP is used in order to mock the attributes released from a remote RV (Remote Vetting) IdP. + # This IdP is utilized as a drop in replacement to test the attribute matching between a remote IdP to vet with and the local + # OpenConext IdP. + Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration: + arguments: + $privateKey: '%saml_rv_privatekey%' + $configurationSettings: '%identity_encryption_configuration%' + $remoteVettingIdpConfig: '%remote_vetting_idps%' + + Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockConfiguration: + arguments: + $identityProviderEntityId: 'https://selfservice.stepup.example.com/mock/idp/metadata' + $serviceProviderEntityId: 'https://selfservice.stepup.example.com/rv/metadata' + $privateKeyPath: '%saml_rv_privatekey%' + $publicCertPath: '%saml_rv_publickey%' + + Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockRemoteVetController: + public: true + arguments: + - '@Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockGateway' + - '@twig' + + Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockGateway: + arguments: + - '@Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockConfiguration' \ No newline at end of file diff --git a/config/services_test.yaml b/config/services_test.yaml index 3dca58ddc..e95bc7d05 100644 --- a/config/services_test.yaml +++ b/config/services_test.yaml @@ -4,10 +4,6 @@ parameters: middleware_credentials_password: secret - saml_rv_publickey: '%kernel.project_dir%/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/test.crt' - saml_rv_privatekey: '%kernel.project_dir%/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/test.key' - - services: surfnet_stepup_self_service_self_service.service.sms_second_factor: class: Surfnet\StepupSelfService\SelfServiceBundle\Tests\TestDouble\Service\SmsSecondFactorService @@ -35,8 +31,28 @@ services: - "%middleware_url_command_api%" - Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Encryption\IdentityEncrypter: - class: \Surfnet\StepupSelfService\SelfServiceBundle\Tests\Service\RemoteVetting\Encryption\FakeIdentityEncrypter + # A remote vetting mock IdP is used in order to mock the attributes released from a remote RV (Remote Vetting) IdP. + # This IdP is utilized as a drop in replacement to test the attribute matching between a remote IdP to vet with and the local + # OpenConext IdP. + Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration: + arguments: + $privateKey: '%saml_rv_privatekey%' + $configurationSettings: '%identity_encryption_configuration%' + $remoteVettingIdpConfig: '%remote_vetting_idps%' + + Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockConfiguration: + arguments: + $identityProviderEntityId: 'https://selfservice.stepup.example.com/mock/idp/metadata' + $serviceProviderEntityId: 'https://selfservice.stepup.example.com/rv/metadata' + $privateKeyPath: '%saml_rv_privatekey%' + $publicCertPath: '%saml_rv_publickey%' + + Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockRemoteVetController: + public: true + arguments: + - '@Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockGateway' + - '@twig' + + Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockGateway: arguments: - $configuration: '@Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration' - $writer: '@Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Encryption\IdentityFilesystemWriter' + - '@Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockConfiguration' \ No newline at end of file diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/remote_vetting.yml b/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/remote_vetting.yml index 2dbce9a16..7753b605f 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/remote_vetting.yml +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/remote_vetting.yml @@ -1,15 +1,20 @@ services: Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\IdentityProviderFactory: + public: true arguments: - - '%remote_vetting_idps%' + $configuration: '@Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration' Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\ServiceProviderFactory: arguments: - - '%remote_vetting_sp%' + $router: '@router' + $entityId: "%remote_vetting_entity_id%" + $assertionConsumerUrlSlug: "ss_second_factor_remote_vet_acs" + $privateKey: "%saml_rv_privatekey%" Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration: arguments: $configurationSettings: '%identity_encryption_configuration%' + $remoteVettingIdpConfig: '%remote_vetting_idps%' Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\RemoteVettingContext: arguments: @@ -23,7 +28,7 @@ services: $logger: '@logger' Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\AttributeMapper: - $identityProviderFactory: '@Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\IdentityProviderFactory' + $configuration: '@Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration' Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVettingService: public: true @@ -43,23 +48,6 @@ services: - '@surfnet_stepup.registration_expiration_helper' - '@logger' - Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockConfiguration: - arguments: - $identityProviderEntityId: 'https://selfservice.stepup.example.com/mock/idp/metadata' - $serviceProviderEntityId: 'https://selfservice.stepup.example.com/saml/metadata' - $privateKeyPath: '%saml_rv_privatekey%' - $publicCertPath: '%saml_rv_publickey%' - - Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockRemoteVetController: - public: true - arguments: - - '@Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockGateway' - - '@twig' - - Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockGateway: - arguments: - - '@Surfnet\StepupSelfService\SelfServiceBundle\Mock\RemoteVetting\MockConfiguration' - Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Encryption\IdentityFilesystemWriter: arguments: - '@Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration' @@ -67,7 +55,7 @@ services: Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\RemoteVettingViewHelper: public: true arguments: - $remoteVettingIdpConfig: "%remote_vetting_idps%" + $configuration: '@Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration' Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Encryption\IdentityEncrypter: arguments: diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/services.yml b/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/services.yml index 4f14a92c8..1c61ecb8b 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/services.yml +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Resources/config/services.yml @@ -82,7 +82,7 @@ services: self_service.service.application: class: Surfnet\StepupSelfService\SelfServiceBundle\Service\ApplicationHelper arguments: - $kernelRootDir: "%kernel.root_dir%" + $kernelProjectDir: "%kernel.project_dir%" self_service.service.identity: class: Surfnet\StepupSelfService\SelfServiceBundle\Service\IdentityService diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/ApplicationHelper.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/ApplicationHelper.php index cdce984a3..ad0a03250 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/ApplicationHelper.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/ApplicationHelper.php @@ -22,15 +22,15 @@ class ApplicationHelper { - private $kernelRootDir; + private $kernelProjectDir; /** - * @param string $kernelRootDir + * @param string $kernelProjectDir */ - public function __construct($kernelRootDir) + public function __construct($kernelProjectDir) { - Assert::string($kernelRootDir, 'Kernel root directory must have a string value'); - $this->kernelRootDir = $kernelRootDir; + Assert::string($kernelProjectDir, 'Kernel project directory must have a string value'); + $this->kernelProjectDir = $kernelProjectDir; } /** @@ -44,7 +44,7 @@ public function __construct($kernelRootDir) public function getApplicationVersion() { // The buildPath (version string) is the installation directory of the project. And is derived from the - // kernel.root_dir (which is the app folder). - return basename(realpath($this->kernelRootDir . '/../')); + // kernel.project_dir (which is the app folder). + return basename(realpath($this->kernelProjectDir)); } } diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/AttributeMapper.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/AttributeMapper.php index c94f11cb1..3a09069b1 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/AttributeMapper.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/AttributeMapper.php @@ -18,6 +18,7 @@ namespace Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting; use Surfnet\StepupSelfService\SelfServiceBundle\Exception\InvalidRemoteVettingMappingException; +use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration; use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Dto\AttributeListDto; use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Value\AttributeMatch; use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Value\AttributeMatchCollection; @@ -25,25 +26,25 @@ class AttributeMapper { /** - * @var IdentityProviderFactory + * @var RemoteVettingConfiguration */ - private $identityProviderFactory; + private $configuration; - public function __construct(IdentityProviderFactory $identityProviderFactory) + public function __construct(RemoteVettingConfiguration $configuration) { - $this->identityProviderFactory = $identityProviderFactory; + $this->configuration = $configuration; } /** * @param string $identityProviderName * @param AttributeListDto $localAttributes - * @param AttributeListDto $externalAttributes + * @param AttributeListDto $remoteAttributes * @return AttributeMatchCollection * @throws InvalidRemoteVettingMappingException */ public function map($identityProviderName, AttributeListDto $localAttributes, AttributeListDto $remoteAttributes) { - $attributeMapping = $this->identityProviderFactory->getAttributeMapping($identityProviderName); + $attributeMapping = $this->configuration->getAttributeMapping($identityProviderName); $localMap = $this->attributeListToMap($localAttributes); $remoteMap = $this->attributeListToMap($remoteAttributes); diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Configuration/RemoteVettingConfiguration.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Configuration/RemoteVettingConfiguration.php index b409b3142..b53458c27 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Configuration/RemoteVettingConfiguration.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Configuration/RemoteVettingConfiguration.php @@ -18,16 +18,43 @@ namespace Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration; +use Surfnet\StepupSelfService\SelfServiceBundle\Assert; +use Surfnet\StepupSelfService\SelfServiceBundle\Exception\InvalidRemoteVettingIdentityProviderException; +use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Dto\RemoteVettingIdenityProviderDto; + class RemoteVettingConfiguration { private $publicKey; private $location; - public function __construct($configurationSettings) + private $idps = []; + + private $attributeMapping = []; + + public function __construct(string $privateKey, array $configurationSettings, array $remoteVettingIdpConfig) { + Assert::string($privateKey, 'privateKey should be a string'); + Assert::file($privateKey, 'privateKey should be a file'); + + Assert::string($configurationSettings['encryption_public_key'], 'identity_encryption_configuration.encryption_public_key should be a string'); + Assert::string($configurationSettings['storage_location'], 'identity_encryption_configuration.storage_location should be a string'); + $this->publicKey = $configurationSettings['encryption_public_key']; $this->location = $configurationSettings['storage_location']; + + foreach ($remoteVettingIdpConfig as $idpConfig) { + $idpConfig['privateKey'] = $privateKey; + + $idp = RemoteVettingIdenityProviderDto::create($idpConfig); + $this->idps[$idp->getSlug()] = $idp; + + Assert::keyExists($idpConfig, 'attributeMapping', sprintf('attributeMapping should be set: %s', $idp->getSlug())); + Assert::isArray($idpConfig['attributeMapping'], 'attributeMapping should be an array'); + Assert::allString($idpConfig['attributeMapping'], 'attributeMapping should consist of strings'); + + $this->attributeMapping[$idp->getSlug()] = $idpConfig['attributeMapping']; + } } public function getLocation() @@ -39,4 +66,25 @@ public function getPublicKey() { return $this->publicKey; } + + /** + * @return RemoteVettingIdenityProviderDto[] + */ + public function getRemoteVettingIdps(): array + { + return $this->idps; + } + + /** + * @param string + * @return array + */ + public function getAttributeMapping($name) + { + if (array_key_exists($name, $this->attributeMapping)) { + return $this->attributeMapping[$name]; + } + + throw new InvalidRemoteVettingIdentityProviderException(sprintf("Invalid IdP requested '%s'", $name)); + } } diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingIdenityProviderDto.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingIdenityProviderDto.php index c2b11ca82..a4e75dc07 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingIdenityProviderDto.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingIdenityProviderDto.php @@ -41,6 +41,27 @@ class RemoteVettingIdenityProviderDto */ private $slug; + + /** + * @var string + */ + private $entityId; + + /** + * @var string + */ + private $ssoUrl; + + /** + * @var string + */ + private $privateKey; + + /** + * @var string + */ + private $certificateFile; + /** * @param string $identityId * @param string $secondFactorId @@ -56,12 +77,27 @@ public static function create(array $configData) Assert::string($configData['slug'], 'The slug of a remote identity provider must be of type string'); Assert::alnum($configData['slug'], 'The slug must be alphanumeric'); + Assert::keyExists($configData, 'entityId', 'entityId should be set'); + Assert::keyExists($configData, 'ssoUrl', 'ssoUrl should be set'); + Assert::keyExists($configData, 'privateKey', 'privateKey should be set'); + Assert::keyExists($configData, 'certificateFile', 'certificateFile should be set'); + + Assert::url($configData['entityId'], 'entityId should be an url'); + Assert::url($configData['ssoUrl'], 'ssoUrl should be an url'); + Assert::string($configData['privateKey'], 'privateKey should be a string'); + Assert::string($configData['certificateFile'], 'certificateFile should be a string'); + $identityProvider = new self(); $identityProvider->name = $configData['name']; $identityProvider->descriptions = $configData['description']; $identityProvider->logo = $configData['logo']; $identityProvider->slug = $configData['slug']; + $identityProvider->entityId = $configData['entityId']; + $identityProvider->ssoUrl = $configData['ssoUrl']; + $identityProvider->privateKey = $configData['privateKey']; + $identityProvider->certificateFile = $configData['certificateFile']; + return $identityProvider; } @@ -97,4 +133,36 @@ public function getSlug() { return $this->slug; } + + /** + * @return string + */ + public function getSsoUrl(): string + { + return $this->ssoUrl; + } + + /** + * @return string + */ + public function getPrivateKey(): string + { + return $this->privateKey; + } + + /** + * @return string + */ + public function getCertificateFile(): string + { + return $this->certificateFile; + } + + /** + * @return string + */ + public function getEntityId(): string + { + return $this->entityId; + } } diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingProcessDto.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingProcessDto.php index d30203be9..4924c1292 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingProcessDto.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Dto/RemoteVettingProcessDto.php @@ -164,13 +164,20 @@ public function serialize() { $stateClass = !is_null($this->state) ? get_class($this->state) : null; - return json_encode([ - 'processId' => $this->processId->getProcessId(), + $data = [ + 'processId' => json_encode($this->processId->getProcessId()), 'token' => $this->token->serialize(), - 'state' => $stateClass, + 'state' => json_encode($stateClass), 'attributes' => $this->attributes->serialize(), - 'identityProvider' => $this->identityProviderName, - ]); + 'identityProvider' => json_encode($this->identityProviderName), + ]; + + $params = []; + foreach ($data as $key => $value) { + $params[] = json_encode($key).":{$value}"; + } + + return '{'.implode(',', $params).'}'; } /** @@ -183,9 +190,9 @@ public function unserialize($serialized) $stateClass = !is_null($data['state']) ? new $data['state']() : null; $this->processId = ProcessId::create($data['processId']); - $this->token = RemoteVettingTokenDto::deserialize($data['token']); + $this->token = RemoteVettingTokenDto::deserialize(json_encode($data['token'])); $this->state = $stateClass; - $this->attributes = AttributeListDto::deserialize($data['attributes']); + $this->attributes = AttributeListDto::deserialize(json_encode($data['attributes'])); $this->identityProviderName = $data['identityProvider']; } } diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Encryption/IdentityFilesystemWriter.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Encryption/IdentityFilesystemWriter.php index b217c104c..112ae454f 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Encryption/IdentityFilesystemWriter.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/Encryption/IdentityFilesystemWriter.php @@ -40,7 +40,7 @@ public function __construct(RemoteVettingConfiguration $configuration) public function write($data) { $fileName = $this->createFileName($this->configuration->getLocation()); - file_put_contents($fileName, json_encode($data)); + file_put_contents($fileName, $data."\n"); } private function createFileName($location) diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/IdentityProviderFactory.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/IdentityProviderFactory.php index b6acb209b..67af546a0 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/IdentityProviderFactory.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/IdentityProviderFactory.php @@ -21,6 +21,7 @@ use Surfnet\SamlBundle\Entity\IdentityProvider; use Surfnet\StepupSelfService\SelfServiceBundle\Assert; use Surfnet\StepupSelfService\SelfServiceBundle\Exception\InvalidRemoteVettingIdentityProviderException; +use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration; class IdentityProviderFactory { @@ -30,40 +31,24 @@ class IdentityProviderFactory private $identityProviders = []; /** - * @var array + * @param RemoteVettingConfiguration $configuration */ - private $attributeMapping = []; - - /** - * @param array $configuration - */ - public function __construct(array $configuration) + public function __construct(RemoteVettingConfiguration $configuration) { - foreach ($configuration as $idpConfiguration) { - Assert::keyExists($idpConfiguration, 'name', 'name should be set'); - Assert::keyExists($idpConfiguration, 'entityId', 'entityId should be set'); - Assert::keyExists($idpConfiguration, 'ssoUrl', 'ssoUrl should be set'); - Assert::keyExists($idpConfiguration, 'privateKey', 'privateKey should be set'); - Assert::keyExists($idpConfiguration, 'certificateFile', 'certificateFile should be set'); - Assert::isArray($idpConfiguration, 'attributeMapping should be an array'); - Assert::allString($idpConfiguration['attributeMapping'], 'attributeMapping should consist of strings'); - - Assert::string($idpConfiguration['name'], 'name should be a string'); - Assert::url($idpConfiguration['entityId'], 'entityId should be an url'); - Assert::url($idpConfiguration['ssoUrl'], 'ssoUrl should be an url'); - Assert::file($idpConfiguration['privateKey'], 'privateKey should be a file'); - Assert::file($idpConfiguration['certificateFile'], 'certificateFile should be a file'); + foreach ($configuration->getRemoteVettingIdps() as $idp) { + Assert::file($idp->getPrivateKey(), 'privateKey should be a file'); + Assert::file($idp->getCertificateFile(), 'certificateFile should be a file'); - $idpConfiguration['privateKeys'] = [new PrivateKey($idpConfiguration['privateKey'], PrivateKey::NAME_DEFAULT)]; - unset($idpConfiguration['privateKey']); + $idpConfiguration = [ + 'name' => $idp->getName(), + 'entityId' => $idp->getEntityId(), + 'ssoUrl' => $idp->getSsoUrl(), + 'certificateFile' => $idp->getCertificateFile(), + 'privateKeys' => [new PrivateKey($idp->getPrivateKey(), PrivateKey::NAME_DEFAULT)], + ]; // set idp - $this->identityProviders[$idpConfiguration['slug']] = new IdentityProvider($idpConfiguration); - - // set mapping - foreach ($idpConfiguration['attributeMapping'] as $key => $value) { - $this->attributeMapping[$idpConfiguration['slug']][$key] = $value; - } + $this->identityProviders[$idp->getSlug()] = new IdentityProvider($idpConfiguration); } } @@ -79,20 +64,4 @@ public function create($name) throw new InvalidRemoteVettingIdentityProviderException(sprintf("Invalid IdP requested '%s'", $name)); } - - - /** - * @param string $name - * @return array - */ - public function getAttributeMapping($name) - { - if (array_key_exists($name, $this->attributeMapping)) { - return $this->attributeMapping[$name]; - } - - throw new InvalidRemoteVettingIdentityProviderException( - sprintf("Unable to find the attribute mapping for an unknown IdP identified by '%s'", $name) - ); - } } diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/RemoteVettingViewHelper.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/RemoteVettingViewHelper.php index 5623ed22f..4baba48e5 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/RemoteVettingViewHelper.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/RemoteVettingViewHelper.php @@ -17,24 +17,22 @@ namespace Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting; -use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Dto\RemoteVettingIdenityProviderDto; +use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration; class RemoteVettingViewHelper { /** - * @var RemoteVettingIdenityProviderDto[] + * @var RemoteVettingConfiguration */ - private $identityProviders; + private $configuration; - public function __construct(array $remoteVettingIdpConfig) + public function __construct(RemoteVettingConfiguration $configuration) { - foreach ($remoteVettingIdpConfig as $idenityProvider) { - $this->identityProviders[] = RemoteVettingIdenityProviderDto::create($idenityProvider); - } + $this->configuration = $configuration; } public function getIdentityProviders() { - return $this->identityProviders; + return $this->configuration->getRemoteVettingIdps(); } } diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/ServiceProviderFactory.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/ServiceProviderFactory.php index e7c9e70d2..d033c4487 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/ServiceProviderFactory.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Service/RemoteVetting/ServiceProviderFactory.php @@ -20,6 +20,9 @@ use SAML2\Configuration\PrivateKey; use Surfnet\SamlBundle\Entity\ServiceProvider; use Surfnet\StepupSelfService\SelfServiceBundle\Assert; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Router; +use Symfony\Component\Routing\RouterInterface; class ServiceProviderFactory { @@ -28,14 +31,16 @@ class ServiceProviderFactory */ private $serviceProvider; - /** - * @param array $configuration - */ - public function __construct(array $configuration) + public function __construct(RouterInterface $router, string $entityId, string $assertionConsumerUrlSlug, string $privateKey) { - Assert::keyExists($configuration, 'entityId'); - Assert::keyExists($configuration, 'assertionConsumerUrl'); - Assert::keyExists($configuration, 'privateKey'); + Assert::notEmpty($entityId, 'entityId'); + Assert::notEmpty($assertionConsumerUrlSlug, 'assertionConsumerUrl'); + Assert::notEmpty($privateKey, 'privateKey'); + + $configuration = []; + $configuration['entityId'] = $entityId; + $configuration['assertionConsumerUrl'] = $router->generate($assertionConsumerUrlSlug, [], UrlGeneratorInterface::ABSOLUTE_URL); + $configuration['privateKey'] = $privateKey; Assert::url($configuration['entityId']); Assert::url($configuration['assertionConsumerUrl']); diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Mock/RemoteVetting/MockRemoteVetControllerTest.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Mock/RemoteVetting/MockRemoteVetControllerTest.php index 655d65000..b767e8fb8 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Mock/RemoteVetting/MockRemoteVetControllerTest.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Mock/RemoteVetting/MockRemoteVetControllerTest.php @@ -25,7 +25,6 @@ use SAML2\Configuration\PrivateKey; use SAML2\Response\Processor; use SAML2\XML\saml\NameID; -use Surfnet\SamlBundle\Entity\IdentityProvider; use Surfnet\SamlBundle\Entity\ServiceProvider; use Surfnet\SamlBundle\Http\PostBinding; use Surfnet\SamlBundle\SAML2\Attribute\Attribute; @@ -102,7 +101,6 @@ protected function setUp(): void $this->mockSecondFactorOverviewPage(); $projectDir = self::$kernel->getProjectDir(); - $keyPath = '/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources'; $this->publicKey = $projectDir . $keyPath . '/test.crt'; $this->privateKey = $projectDir . $keyPath . '/test.key'; @@ -124,8 +122,8 @@ protected function setUp(): void public function the_mock_remote_vetting_idp_should_present_us_with_possible_results_for_testing_purposes() { $this->logIn(); - $this->remoteVettingService->start('irma', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); - $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('MockIdP'); + $this->remoteVettingService->start('mock', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); + $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('mock'); $crawler = $this->client->request('GET', $authnRequestUrl); @@ -141,8 +139,8 @@ public function the_mock_remote_vetting_idp_should_present_us_with_possible_resu public function a_succesful_response_from_a_remote_vetting_idp_should_succeed() { $this->logIn(); - $this->remoteVettingService->start('irma', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); - $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('MockIdP'); + $this->remoteVettingService->start('mock', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); + $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('mock'); $crawler = $this->client->request('GET', $authnRequestUrl); @@ -150,7 +148,6 @@ public function a_succesful_response_from_a_remote_vetting_idp_should_succeed() $this->postMockIdpForm($crawler, 'success'); // Test if on manual matching form - $c = $this->client->getResponse()->getContent(); $this->assertEquals(200, $this->client->getResponse()->getStatusCode()); $this->assertStringStartsWith('https://selfservice.stepup.example.com/second-factor/remote-vetting/match/', $this->client->getRequest()->getUri()); $this->assertStringContainsString('Validate information', $this->client->getResponse()->getContent()); @@ -163,8 +160,8 @@ public function a_succesful_response_from_a_remote_vetting_idp_should_succeed() public function a_user_cancelled_response_from_a_remote_vetting_idp_should_fail() { $this->logIn(); - $this->remoteVettingService->start('irma', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); - $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('MockIdP'); + $this->remoteVettingService->start('mock', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); + $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('mock'); $crawler = $this->client->request('GET', $authnRequestUrl); @@ -184,8 +181,8 @@ public function a_user_cancelled_response_from_a_remote_vetting_idp_should_fail( public function an_unsuccessful_response_from_a_remote_vetting_idp_should_fail() { $this->logIn(); - $this->remoteVettingService->start('irma', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); - $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('MockIdP'); + $this->remoteVettingService->start('mock', RemoteVettingTokenDto::create('identity-id-123456', 'second-factor-id-56789')); + $authnRequestUrl = $this->samlCalloutHelper->createAuthnRequest('mock'); $crawler = $this->client->request('GET', $authnRequestUrl); @@ -224,14 +221,14 @@ public function a_verified_token_must_be_vetted_with_external_idp_e2e() $this->assertSame('https://selfservice.stepup.example.com/second-factor/second-factor-id-56789/vetting-types', $link->getUri()); $crawler = $this->client->click($link); - // Select 'irma' as vetting type + // Select 'mock' as vetting type $this->assertSame('https://selfservice.stepup.example.com/second-factor/second-factor-id-56789/vetting-types', $this->client->getRequest()->getUri()); - $button = $crawler->selectButton('select-rv-idp-irma'); + $button = $crawler->selectButton('select-rv-idp-mock'); $form = $button->form(); $crawler = $this->client->submit($form); // Accept sending info to IdP on consent screen - $this->assertSame('https://selfservice.stepup.example.com/second-factor/second-factor-id-56789/remote-vet/irma', $this->client->getRequest()->getUri()); + $this->assertSame('https://selfservice.stepup.example.com/second-factor/second-factor-id-56789/remote-vet/mock', $this->client->getRequest()->getUri()); $button = $crawler->selectButton('Validate identity'); $form = $button->form(); $crawler = $this->client->submit($form); @@ -265,11 +262,7 @@ public function a_verified_token_must_be_vetted_with_external_idp_e2e() */ private function setupSamlCalloutHelper() { - $identityProviderFactory = m::mock(IdentityProviderFactory::class); - $identityProviderFactory->shouldReceive('create') - ->with('MockIdP') - ->once() - ->andReturn($this->createIdentityProvider()); + $identityProviderFactory = $this->client->getKernel()->getContainer()->get(IdentityProviderFactory::class); $serviceProviderFactory = m::mock(ServiceProviderFactory::class); $serviceProviderFactory->shouldReceive('create') @@ -297,7 +290,7 @@ private function createServiceProvider() { return new ServiceProvider( [ - 'entityId' => 'https://selfservice.stepup.example.com/saml/metadata', + 'entityId' => 'https://selfservice.stepup.example.com/rv/metadata', 'assertionConsumerUrl' => 'https://selfservice.stepup.example.com/second-factor/acs', 'certificateFile' => $this->publicKey, 'privateKeys' => [ @@ -311,24 +304,6 @@ private function createServiceProvider() ); } - private function createIdentityProvider() - { - return new IdentityProvider( - [ - 'entityId' => 'https://selfservice.stepup.example.com/mock/idp/metadata', - 'ssoUrl' => 'https://selfservice.stepup.example.com/second-factor/mock/sso', - 'certificateFile' => $this->publicKey, - 'privateKeys' => [ - new PrivateKey( - $this->privateKey, - 'default' - ), - ], - 'sharedKey' => $this->publicKey - ] - ); - } - /** * @param Crawler $crawler * @param string $state State button to press diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.crt b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.crt new file mode 100644 index 000000000..0859998f9 --- /dev/null +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.crt @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC6jCCAdICCQC9cRx5wiwWOjANBgkqhkiG9w0BAQsFADA3MRwwGgYDVQQDDBNT +ZWxmU2VydmljZSBTQU1MIFNQMRcwFQYDVQQKDA5EZXZlbG9wbWVudCBWTTAeFw0x +ODA3MzAxMjMwNDdaFw0yMzA3MjkxMjMwNDdaMDcxHDAaBgNVBAMME1NlbGZTZXJ2 +aWNlIFNBTUwgU1AxFzAVBgNVBAoMDkRldmVsb3BtZW50IFZNMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqhbI0Xy682DuvWchg6FYnI+DNwLXef2XExM4 +YVRBaMMsOZ3rBtQUTMSqYan6SK/BOEXLs0rNiJjyM0dn+F98wg3fv5zIADlvfk3L +BVdcGsrpVfFUWtSa73yMgbROy8/RJADbUJE/HUB3ZmdjdiuD2Cui2aoWwT2HR8uk +Jwmoxiu45IWFPbqPQ7/1mH644JPOWTPLTv4OGGLQo8MNrP1oRCiZ0IEL4CQeGOOj +u5rfIJ0bTVm0UmelT4hGaqZovBMwXp3QV41akJ7UEMEBK2YMnLQy47Xuzi7aTDhJ +lvHcJ8mfH2NbjRh7hJoACVRTvQloxajgkr1iGMiWiiqT0e+YYwIDAQABMA0GCSqG +SIb3DQEBCwUAA4IBAQBwZ0gRHvR8B8KivrXrhWNL9uLvWhEAH7OiDqo+fywkBp5K +EuDJcbbvEPftHunSAGylg7M2xKuBIGamFpp74WDJccrtZ1jJ4qqnacUDRQrTLqqM +ZKqGpFOU0xjKkSxSGRuMtGN9/7er/TeonjQ0XBvjYvTomy3b5aCLVWRvEfKu2g1s +Dd8uhr62RY/HfMgidEt7LHDolkCVg+6JzY3OTcgeHga3cvYObOYPplxw1YPq5+Bq +qxaUW4nfb5DtK33bZBYMeyV6BZtSggc5Z/19aPx/s0bf6ySTUyB3lRqe5d3etCns +4bGidORCl/6EZiXwVcPvmYmxYXqmuNWfps7isUvo +-----END CERTIFICATE----- diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.key b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.key new file mode 100644 index 000000000..a7ab91723 --- /dev/null +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAqhbI0Xy682DuvWchg6FYnI+DNwLXef2XExM4YVRBaMMsOZ3r +BtQUTMSqYan6SK/BOEXLs0rNiJjyM0dn+F98wg3fv5zIADlvfk3LBVdcGsrpVfFU +WtSa73yMgbROy8/RJADbUJE/HUB3ZmdjdiuD2Cui2aoWwT2HR8ukJwmoxiu45IWF +PbqPQ7/1mH644JPOWTPLTv4OGGLQo8MNrP1oRCiZ0IEL4CQeGOOju5rfIJ0bTVm0 +UmelT4hGaqZovBMwXp3QV41akJ7UEMEBK2YMnLQy47Xuzi7aTDhJlvHcJ8mfH2Nb +jRh7hJoACVRTvQloxajgkr1iGMiWiiqT0e+YYwIDAQABAoIBAF+J5Msm0Kwcan2h +DEYvvuJSClZAFmDDfLSOO0EQXp1F4/WJKpbvUWe9oCazn45sio/dRIo1HjX4EzOS +jGgK2rz1phSvL/hQSrwbXkplw6qZB2/q2oMaoNycjR/d89Svqr4abRZYP6diqq6u +rEOYNbqa6CJzU8y/jtlZHZ9/4XlN8035QNJ3YIi3qVe3cCr6IOahUGOayWNaW+0q +vLBhWdbaER5aHiUdcZPrJfNhepb2Ob9djizqpWo8u9WyYNpiExjm1Ov6IAQhxkc7 +uAvJIE7W39Ag4wHNHHj+WkctG+KBEym3/i2SDAddUP5H6FGMzPQPdoJK2XArrE0B +p5Tun0ECgYEA1ot9Vz7YbMOqGvok/GQyVuV8MTRC12iPlwoOV3HKNG9TfclglRzg +csp83rJ13tz8NyN93GQpjOkCvdQinJGk/kR6h9eCi2l2HPGNMrZH7qY+2cQvf6J5 +KTGI1sAi4DqHJ9u0AyaQdu2ieh3HwgI8+PWBFn3dBR5xKeHIh/59hRsCgYEAyvRG +W+xpVRlM1XoLPMn5Z2yUpI6mieaD3jmNQSC0OuxdxlIZVtyqBF3rFQw1V/74bS3X +aOxtwelGQ2PfWnjo4uLoWqUoIN0ZAn+9yKzMla/5y1jEhyFcaUQc8QGmp+wOjDgQ +NHM23VSAr7Q+G3EMQmjlURC45Il66mnrkcZUFlkCgYEAoAMzPZHauuwH/8zXLwLP +5K2Nvej7fUs35O+UGLX+mLL7M1KxXSVHZXYOQc4aSVjKJ5mp8mkl8DmNWOVR1zJt +O1L5jD042R+T/yxNIih/Z8fIEoTW5DvaX9XY+Eoe+NvOF/UtwjfOAVVlG+0AInum +3AvG9m5zHLFCt3j1JjCxj0cCgYEAv4IrFjiJ2DwsbVBhZDYt+nLR/EmDSqLTEhH6 +gVcr2mIJxsbXlEhawg4hctX3TBaTMurL1f0rQIwvug12yDdJgjadDFPF/uTC4cHK +Qp8T2beZHVGg+OX4/nfAW4a0TMYJoDSSzftd7RH88E9DP7+30r6KjKkb3sL/0kyq +df7Qf9kCgYAi1vf0bc6GgWf0CA+7NtZivl4Pw1aZEZI7tKY2cC95KKTycPhxSpq5 +g72XdHAp+gaJoSBledEYMJfE5Xsdf5r0F1v5xDe87Dn+zT7UXpw4JrDE16jBKwv1 +pTLyJ51aerY27qJEtZ3JqbCux853aa2cxLIoje+5Kxso33bPe0EXGg== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Security/Session/SessionStorageTest.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Security/Session/SessionStorageTest.php index 236189465..cc67925d7 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Security/Session/SessionStorageTest.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Security/Session/SessionStorageTest.php @@ -47,6 +47,8 @@ public function the_authentication_moment_can_be_logged() $sessionStorage = new SessionStorage(new FakeSession()); $sessionStorage->logAuthenticationMoment(); + + $this->assertInstanceOf(SessionStorage::class, $sessionStorage); } /** @@ -122,6 +124,8 @@ public function an_interaction_can_be_logged() $sessionStorage = new SessionStorage(new FakeSession()); $sessionStorage->updateLastInteractionMoment(); + + $this->assertInstanceOf(SessionStorage::class, $sessionStorage); } /** @@ -293,6 +297,8 @@ public function a_session_can_be_invalidated() $sessionStorage = new SessionStorage($session); $sessionStorage->invalidate(); + + $this->assertInstanceOf(SessionStorage::class, $sessionStorage); } /** @@ -309,6 +315,8 @@ public function a_session_can_be_migrated() $sessionStorage = new SessionStorage($session); $sessionStorage->migrate(); + + $this->assertInstanceOf(SessionStorage::class, $sessionStorage); } /** diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/AttributeMapperTest.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/AttributeMapperTest.php index d6fe3f7ec..1e077dd33 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/AttributeMapperTest.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/AttributeMapperTest.php @@ -21,15 +21,15 @@ use PHPUnit\Framework\TestCase; use Surfnet\StepupSelfService\SelfServiceBundle\Exception\InvalidRemoteVettingMappingException; use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\AttributeMapper; +use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Configuration\RemoteVettingConfiguration; use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\Dto\AttributeListDto; -use Surfnet\StepupSelfService\SelfServiceBundle\Service\RemoteVetting\IdentityProviderFactory; class AttributeMapperTest extends TestCase { /** - * @var IdentityProviderFactory|m\Mock + * @var RemoteVettingConfiguration|m\Mock */ - private $identityProviderFactory; + private $configuration; /** * @var AttributeMapper */ @@ -37,8 +37,8 @@ class AttributeMapperTest extends TestCase public function setUp(): void { - $this->identityProviderFactory = m::mock(IdentityProviderFactory::class); - $this->attributeMapper = new AttributeMapper($this->identityProviderFactory); + $this->configuration = m::mock(RemoteVettingConfiguration::class); + $this->attributeMapper = new AttributeMapper($this->configuration); } public function test_attribute_mapping() @@ -60,7 +60,8 @@ public function test_attribute_mapping() 'baz3' => ['foobar3'], ]; - $this->identityProviderFactory->shouldReceive('getAttributeMapping') + $this->configuration->shouldReceive('getAttributeMapping') + ->with('idp-name') ->andReturn($config); $localAttributes = new AttributeListDto($local, $nameId); @@ -96,7 +97,8 @@ public function test_attribute_mapping_missing_local() 'baz3' => ['foobar3'], ]; - $this->identityProviderFactory->shouldReceive('getAttributeMapping') + $this->configuration->shouldReceive('getAttributeMapping') + ->with('idp-name') ->andReturn($config); $localAttributes = new AttributeListDto($local, $nameId); @@ -129,7 +131,8 @@ public function test_attribute_mapping_missing_external() 'baz3' => ['foobar3'], ]; - $this->identityProviderFactory->shouldReceive('getAttributeMapping') + $this->configuration->shouldReceive('getAttributeMapping') + ->with('idp-name') ->andReturn($config); $localAttributes = new AttributeListDto($local, $nameId); diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Dto/RemoteVettingProcessDtoTest.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Dto/RemoteVettingProcessDtoTest.php index 44ef37edf..9ad3202a8 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Dto/RemoteVettingProcessDtoTest.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Dto/RemoteVettingProcessDtoTest.php @@ -36,7 +36,7 @@ class RemoteVettingProcessDtoTest extends TestCase public function a_process_could_be_serialized_and_deserialized($name, $state, $expected) { $attributes = new AttributeListDto(['foo' => ['bar']], 'nameId'); - $expected = sprintf('{"processId":"process id","token":"{\"identityId\":\"identityId\",\"secondFactorId\":\"secondFactorId\"}","state":%s,"attributes":"{\"nameId\":\"nameId\",\"attributes\":{\"foo\":[\"bar\"]}}","identityProvider":"IRMA"}', json_encode($expected)); + $expected = sprintf('{"processId":"process id","token":{"identityId":"identityId","secondFactorId":"secondFactorId"},"state":%s,"attributes":{"nameId":"nameId","attributes":{"foo":["bar"]}},"identityProvider":"IRMA"}', json_encode($expected)); $processId = ProcessId::create('process id'); $token = RemoteVettingTokenDto::create('identityId', 'secondFactorId'); diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterIntegrationTest.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterIntegrationTest.php index df3059b8c..4d79fc35e 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterIntegrationTest.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterIntegrationTest.php @@ -35,65 +35,23 @@ class IdentityEncrypterIntegrationTest extends TestCase private $writer; - private $publicKey = <<publicKey = file_get_contents($testKeyPath. '/encryption.crt'); + $this->privateKey = file_get_contents($testKeyPath . '/encryption.key'); + + $rvPrivateKey = realpath($testKeyPath . '/test.key'); $config = [ 'encryption_public_key' => $this->publicKey, 'storage_location' => '/tmp', ]; - $this->config = new RemoteVettingConfiguration($config, 'v0.0'); + $this->config = new RemoteVettingConfiguration($rvPrivateKey, $config, []); $this->writer = new FakeIdentityWriter(); $this->encrypter = new IdentityEncrypter($this->config, $this->writer); } diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterTest.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterTest.php index b8921867d..587028ff0 100644 --- a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterTest.php +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVetting/Encryption/IdentityEncrypterTest.php @@ -34,81 +34,17 @@ class IdentityEncrypterTest extends TestCase private $writer; - private $privateKey = <<publicKey = file_get_contents($testKeyPath. '/encryption.crt'); + $this->privateKey = file_get_contents($testKeyPath . '/encryption.key'); + $this->config = m::mock(RemoteVettingConfiguration::class); $this->writer = m::mock(IdentityWriterInterface::class); $this->encrypter = new IdentityEncrypter($this->config, $this->writer); @@ -121,7 +57,7 @@ public function happy_flow_should_succeed() { $this->config ->shouldReceive('getPublicKey') - ->andReturn($this->cert); + ->andReturn($this->publicKey); $this->writer ->shouldReceive('write') @@ -147,7 +83,7 @@ public function a_large_chunk_should_succeed() { $this->config ->shouldReceive('getPublicKey') - ->andReturn($this->cert); + ->andReturn($this->publicKey); $this->writer ->shouldReceive('write') @@ -157,9 +93,8 @@ public function a_large_chunk_should_succeed() }); $nameId = 'a-random-nameid@something.else'; - $raw = $this->generateRandomString(5000); - $data = new AttributeListDto(['email' => ['johndoe@example.com'], 'firstName' => ['John']], $nameId, $raw); + $data = new AttributeListDto(['email' => ['johndoe@example.com'], 'firstName' => ['John']], $nameId); $this->encrypter->encrypt($data->serialize()); // Assert result @@ -177,8 +112,7 @@ public function an_invalid_key_should_fail_non_string() ->andReturn(8373292782); $nameId = 'a-random-nameid@something.else'; - $raw = 'the raw message we could incorporate'; - $data = new AttributeListDto(['email' => ['johndoe@example.com'], 'firstName' => ['John']], $nameId, $raw); + $data = new AttributeListDto(['email' => ['johndoe@example.com'], 'firstName' => ['John']], $nameId); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Invalid input was provided to the encrypt method'); @@ -195,9 +129,8 @@ public function an_invalid_key_should_fail_bad_key_format() ->andReturn('invalid key'); $nameId = 'a-random-nameid@something.else'; - $raw = 'the raw message we could incorporate'; - $data = new AttributeListDto(['email' => ['johndoe@example.com'], 'firstName' => ['John']], $nameId, $raw); + $data = new AttributeListDto(['email' => ['johndoe@example.com'], 'firstName' => ['John'],'a-lot-of-data' => [$this->generateRandomString(5000)]], $nameId); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Reading RSA public key failed'); diff --git a/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVettingServiceTestTest.php b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVettingServiceTestTest.php new file mode 100644 index 000000000..efe4b38a2 --- /dev/null +++ b/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Service/RemoteVettingServiceTestTest.php @@ -0,0 +1,190 @@ +publicKey = file_get_contents($kernelRootPath. '/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.crt'); + $this->privateKey = file_get_contents($kernelRootPath . '/src/Surfnet/StepupSelfService/SelfServiceBundle/Tests/Resources/encryption.key'); + + $this->remoteVettingConfiguration = m::mock(RemoteVettingConfiguration::class); + $this->applicationHelper = new ApplicationHelper($kernelRootPath); + $this->fakeIdentityWriter = new FakeIdentityWriter(); + $this->logger = new TestLogger(); + $session = new Session(new MockFileSessionStorage()); + + $identityEncrypter = new IdentityEncrypter($this->remoteVettingConfiguration, $this->fakeIdentityWriter); + $this->attributeMapper = new AttributeMapper($this->remoteVettingConfiguration); + $remoteVettingContext = new RemoteVettingContext($session); + + $now = new DateTime(); + $mockTime = new DateTime('@1614004537',$now->getTimezone()); + DateTimeHelper::setCurrentTime($mockTime); + + $this->service = new RemoteVettingService( + $remoteVettingContext, + $this->attributeMapper, + $identityEncrypter, + $this->applicationHelper, + $this->logger + ); + } + + public function test_happy_flow() + { + $processId = '4a46493c-7387-4c04-b491-a41f7323d73a'; + $identityId = '95515f44-71a2-4dc1-8e8f-e7e4021ee65b'; + $secondFactorId = 'dace3819-35e9-4205-8538-04bb09bdd479'; + + $nameId = "john.doe@example.com"; + $institution = "stepup.example.com"; + $email = "johndoe@example.com"; + $commonName = "John Doe"; + $preferredLocale = "nl_NL"; + + $processId = ProcessId::create($processId); + + $identity = Identity::fromData([ + 'id' => $identityId, + 'name_id' => $nameId, + 'institution' => $institution, + 'email' => $email, + 'common_name' => $commonName, + 'preferred_locale' => $preferredLocale, + ]); + + $localAttributes = new AttributeListDto(['email' => ['john@example.com'], 'firstName' => ['Johnie'], "familyName" => ["Doe"], "telephone" => ["0612345678"]], $nameId); + $externalAttributes = new AttributeListDto(['emailAddress' => ['johndoe@example.com'], 'givenName' => ['John'], "familyName" => ["Doe"], "fullName" => ["John Doe"]], $nameId); + $remarks = "This seems a pretty decent match"; + + $this->remoteVettingConfiguration->shouldReceive("getAttributeMapping") + ->with('mock') + ->andReturn([ + 'email' => 'emailAddress', + 'firstName' => 'givenName', + 'familyName' => 'familyName', + ]); + + $this->remoteVettingConfiguration->shouldReceive("getPublicKey") + ->andReturn($this->publicKey); + + $matches = $this->attributeMapper->map('mock', $localAttributes, $externalAttributes); + + // todo: add matching result! + + $token = RemoteVettingTokenDto::create($identityId, $secondFactorId); + $this->service->start('mock', $token); + $this->service->startValidation($processId); + $this->service->finishValidation($processId, $externalAttributes); + $remoteVettingToken = $this->service->done($processId, $identity, $localAttributes, $matches, $remarks); + + // test token result + $this->assertSame($identityId, $remoteVettingToken->getIdentityId()); + $this->assertSame($secondFactorId, $remoteVettingToken->getSecondFactorId()); + + // test encrypted result + $result = Decrypter::decrypt($this->fakeIdentityWriter->getData(), $this->privateKey); + $this->assertSame('{"attribute-data":{"local-attributes":{"nameId":"john.doe@example.com","attributes":{"email":["john@example.com"],"firstName":["Johnie"],"familyName":["Doe"],"telephone":["0612345678"]}},"remote-attributes":{"nameId":"john.doe@example.com","attributes":{"emailAddress":["johndoe@example.com"],"givenName":["John"],"familyName":["Doe"],"fullName":["John Doe"]}},"matching-results":{"email":{"local":{"name":"email","value":["john@example.com"]},"remote":{"name":"emailAddress","value":["johndoe@example.com"]},"is-valid":false,"remarks":""},"firstName":{"local":{"name":"firstName","value":["Johnie"]},"remote":{"name":"givenName","value":["John"]},"is-valid":false,"remarks":""},"familyName":{"local":{"name":"familyName","value":["Doe"]},"remote":{"name":"familyName","value":["Doe"]},"is-valid":false,"remarks":""}}},"remarks":"This seems a pretty decent match","name-id":"john.doe@example.com","institution":"stepup.example.com","remote-vetting-source":"mock","application-version":"Stepup-SelfService","time":"2021-02-22T14:35:37+00:00"}', $result); + + // test logs + $this->assertSame([ + [ + 'level' => 'info', + 'message' => 'Starting an remote vetting process for the provided token', + 'context' => [], + ],[ + 'level' => 'info', + 'message' => 'Starting an remote vetting authentication for the current process', + 'context' => [], + ],[ + 'level' => 'info', + 'message' => 'Finishing a remote vetting authentication for the current process', + 'context' => [], + ],[ + 'level' => 'info', + 'message' => 'Saving the encrypted assertion to the filesystem', + 'context' => [], + ],[ + 'level' => 'info', + 'message' => 'Finished the remote vetting process for the current process', + 'context' => [], + ], + ], $this->logger->records); + } +}