Skip to content

Commit

Permalink
Merge pull request #120 from OpenConext/handle-second-factor-test-res…
Browse files Browse the repository at this point in the history
…ponse

Handle second factor test response
  • Loading branch information
nicwortel committed Mar 7, 2017
2 parents 343d5ec + 87418d5 commit 7de4511
Show file tree
Hide file tree
Showing 13 changed files with 300 additions and 20 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,15 @@ This component is part of "Step-up Authentication as-a Service" and requires oth
Clone the repository or download the archive to a directory. Install the dependencies by running `composer install`.

Run `app/console mopa:bootstrap:symlink:less` to configure Bootstrap symlinks.

## Updating translations

Run the following command to extract translation strings from templates, form labels, etc:

```bash
bin/extract-translations.sh
```

Then, translate the strings using the web interface available at: https://ss-dev.stepup.coin.surf.net/app_dev.php/_trans/

For more information about the JMSTranslationBundle, see http://jmsyst.com/bundles/JMSTranslationBundle
19 changes: 17 additions & 2 deletions app/Resources/translations/messages.en_GB.xliff
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2016-11-02T14:23:44Z" source-language="en" target-language="en_GB" datatype="plaintext" original="not.available">
<file date="2017-03-03T14:45:53Z" source-language="en" target-language="en_GB" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>
Expand Down Expand Up @@ -597,10 +597,15 @@ An e-mail with your activation code has been sent to the e-mail address %email%.
<target>Your token has been removed.</target>
</trans-unit>
<trans-unit id="39bf5f5849cef0ac9b176c769c79169676fc7b96" resname="ss.second_factor.revoke.button.revoke">
<jms:reference-file line="44">views/SecondFactor/list.html.twig</jms:reference-file>
<jms:reference-file line="50">views/SecondFactor/list.html.twig</jms:reference-file>
<source>ss.second_factor.revoke.button.revoke</source>
<target>Remove</target>
</trans-unit>
<trans-unit id="17d18b6bcd8642fedf8e5371cb722efc604e8281" resname="ss.second_factor.revoke.button.test">
<jms:reference-file line="46">views/SecondFactor/list.html.twig</jms:reference-file>
<source>ss.second_factor.revoke.button.test</source>
<target>Test</target>
</trans-unit>
<trans-unit id="6fb0acf21df652aef6e8da27aefff89ad52b6e1a" resname="ss.second_factor.revoke.second_factor_type.biometric">
<jms:reference-file line="54">Resources/views/translations.twig</jms:reference-file>
<source>ss.second_factor.revoke.second_factor_type.biometric</source>
Expand Down Expand Up @@ -701,6 +706,16 @@ An e-mail with your activation code has been sent to the e-mail address %email%.
<source>ss.support_url_text</source>
<target>Help</target>
</trans-unit>
<trans-unit id="cbdf2c4bacdae4b97285b0201968c2d3a9de1be1" resname="ss.test_second_factor.verification_failed">
<jms:reference-file line="76">Resources/views/translations.twig</jms:reference-file>
<source>ss.test_second_factor.verification_failed</source>
<target>The second factor test failed.</target>
</trans-unit>
<trans-unit id="7c0f4f14de1d3c98032305d9af18fad28ec67456" resname="ss.test_second_factor.verification_successful">
<jms:reference-file line="75">Resources/views/translations.twig</jms:reference-file>
<source>ss.test_second_factor.verification_successful</source>
<target>The second factor test was successful.</target>
</trans-unit>
<trans-unit id="dc4478bd45a81c1a45020aac9d22d73abc5f1455" resname="ss.verify_yubikey_command.otp.otp_invalid">
<jms:reference-file line="34">Resources/views/translations.twig</jms:reference-file>
<source>ss.verify_yubikey_command.otp.otp_invalid</source>
Expand Down
19 changes: 17 additions & 2 deletions app/Resources/translations/messages.nl_NL.xliff
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:jms="urn:jms:translation" version="1.2">
<file date="2016-11-02T14:23:52Z" source-language="en" target-language="nl_NL" datatype="plaintext" original="not.available">
<file date="2017-03-03T14:45:51Z" source-language="en" target-language="nl_NL" datatype="plaintext" original="not.available">
<header>
<tool tool-id="JMSTranslationBundle" tool-name="JMSTranslationBundle" tool-version="1.1.0-DEV"/>
<note>The source node in most cases contains the sample message as written by the developer. If it looks like a dot-delimitted string such as "form.label.firstname", then the developer has not provided a default message.</note>
Expand Down Expand Up @@ -595,10 +595,15 @@ Er is een e-mail met activatiecode gestuurd naar het e-mailadres %email%. Volg d
<target>Je token is verwijderd.</target>
</trans-unit>
<trans-unit id="39bf5f5849cef0ac9b176c769c79169676fc7b96" resname="ss.second_factor.revoke.button.revoke">
<jms:reference-file line="44">views/SecondFactor/list.html.twig</jms:reference-file>
<jms:reference-file line="50">views/SecondFactor/list.html.twig</jms:reference-file>
<source>ss.second_factor.revoke.button.revoke</source>
<target>Verwijderen</target>
</trans-unit>
<trans-unit id="17d18b6bcd8642fedf8e5371cb722efc604e8281" resname="ss.second_factor.revoke.button.test">
<jms:reference-file line="46">views/SecondFactor/list.html.twig</jms:reference-file>
<source>ss.second_factor.revoke.button.test</source>
<target>Testen</target>
</trans-unit>
<trans-unit id="6fb0acf21df652aef6e8da27aefff89ad52b6e1a" resname="ss.second_factor.revoke.second_factor_type.biometric">
<jms:reference-file line="54">Resources/views/translations.twig</jms:reference-file>
<source>ss.second_factor.revoke.second_factor_type.biometric</source>
Expand Down Expand Up @@ -699,6 +704,16 @@ Er is een e-mail met activatiecode gestuurd naar het e-mailadres %email%. Volg d
<source>ss.support_url_text</source>
<target>Help</target>
</trans-unit>
<trans-unit id="cbdf2c4bacdae4b97285b0201968c2d3a9de1be1" resname="ss.test_second_factor.verification_failed">
<jms:reference-file line="76">Resources/views/translations.twig</jms:reference-file>
<source>ss.test_second_factor.verification_failed</source>
<target>De second factor test is mislukt.</target>
</trans-unit>
<trans-unit id="7c0f4f14de1d3c98032305d9af18fad28ec67456" resname="ss.test_second_factor.verification_successful">
<jms:reference-file line="75">Resources/views/translations.twig</jms:reference-file>
<source>ss.test_second_factor.verification_successful</source>
<target>De second factor test is geslaagd.</target>
</trans-unit>
<trans-unit id="dc4478bd45a81c1a45020aac9d22d73abc5f1455" resname="ss.verify_yubikey_command.otp.otp_invalid">
<jms:reference-file line="34">Resources/views/translations.twig</jms:reference-file>
<source>ss.verify_yubikey_command.otp.otp_invalid</source>
Expand Down
4 changes: 4 additions & 0 deletions app/config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ jms_translation:

surfnet_stepup_self_service_self_service:
enabled_second_factors: %enabled_second_factors%
second_factor_test_identity_provider:
entity_id: '%second_factor_test_idp_entity_id%'
sso_url: '%second_factor_test_idp_sso_url%'
certificate: '%second_factor_test_idp_certificate%'
session_lifetimes:
max_absolute_lifetime: "%session_max_absolute_lifetime%"
max_relative_lifetime: "%session_max_relative_lifetime%"
Expand Down
4 changes: 4 additions & 0 deletions app/config/parameters.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ parameters:
graylog_hostname: g2-dev.stepup.coin.surf.net
asset_version: 1

second_factor_test_idp_entity_id: ~
second_factor_test_idp_sso_url: ~
second_factor_test_idp_certificate: 'FOR CI ONLY, REPLACE WITH ACTUAL VALUE'

stepup_loa_loa1: https://gateway.tld/authentication/loa1
stepup_loa_loa2: https://gateway.tld/authentication/loa2
stepup_loa_loa3: https://gateway.tld/authentication/loa3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,111 @@

namespace Surfnet\StepupSelfService\SelfServiceBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Exception;
use Surfnet\SamlBundle\Http\XMLResponse;
use Symfony\Bundle\FrameworkBundle\Controller\Controller as FrameworkController;
use Surfnet\SamlBundle\SAML2\Response\Assertion\InResponseTo;
use Surfnet\StepupBundle\Value\SecondFactorType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class SamlController extends FrameworkController
class SamlController extends Controller
{
/**
* @Template
* @param string $secondFactorId
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @throws \Symfony\Component\Security\Core\Exception\AccessDeniedException
*/
public function testSecondFactorAction($secondFactorId)
{
$logger = $this->get('logger');
$logger->notice('Starting second factor test');

$secondFactorService = $this->get('surfnet_stepup_self_service_self_service.service.second_factor');
$identity = $this->getIdentity();

if (!$secondFactorService->identityHasSecondFactorOfStateWithId($identity->id, 'vetted', $secondFactorId)) {
$logger->error(
sprintf(
'Identity "%s" tried to test second factor "%s", but does not own that second factor or it is not vetted',
$identity->id,
$secondFactorId
)
);

throw new NotFoundHttpException();
}

$loaResolutionService = $this->get('surfnet_stepup.service.loa_resolution');
$authenticationRequestFactory = $this->get('self_service.test_second_factor_authentication_request_factory');

$secondFactor = $secondFactorService->findOneVetted($secondFactorId);
$secondFactorType = new SecondFactorType($secondFactor->type);

$authenticationRequest = $authenticationRequestFactory->createSecondFactorTestRequest(
$identity->nameId,
$loaResolutionService->getLoaByLevel($secondFactorType->getLevel())
);

$this->get('session')->set('second_factor_test_request_id', $authenticationRequest->getRequestId());

$samlLogger = $this->get('surfnet_saml.logger')->forAuthentication($authenticationRequest->getRequestId());
$samlLogger->notice('Sending authentication request to the second factor test IDP');

return $this->get('surfnet_saml.http.redirect_binding')->createRedirectResponseFor($authenticationRequest);
}

public function consumeAssertionAction(Request $httpRequest)
{
/** @var \Surfnet\SamlBundle\Http\PostBinding $postBinding */
$logger = $this->get('logger');

$logger->notice('Received an authentication response for testing a second factor');

$session = $this->get('session');

if (!$session->has('second_factor_test_request_id')) {
$logger->error(
'Received an authentication response for testing a second factor, but no second factor test response was expected'
);

throw new AccessDeniedHttpException('Did not expect an authentication response');
}

$initiatedRequestId = $session->get('second_factor_test_request_id');

$samlLogger = $this->get('surfnet_saml.logger')->forAuthentication($initiatedRequestId);

$session->remove('second_factor_test_request_id');

$postBinding = $this->get('surfnet_saml.http.post_binding');

/** @var \SAML2_Assertion $assertion */
$assertion = $postBinding->processResponse(
$httpRequest,
$this->get('surfnet_saml.remote.idp'),
$this->get('surfnet_saml.hosted.service_provider')
);
try {
$assertion = $postBinding->processResponse(
$httpRequest,
$this->get('self_service.second_factor_test_idp'),
$this->get('surfnet_saml.hosted.service_provider')
);

if (!InResponseTo::assertEquals($assertion, $initiatedRequestId)) {
$samlLogger->error(
sprintf(
'Expected a response to the request with ID "%s", but the SAMLResponse was a response to a different request',
$initiatedRequestId
)
);

throw new AuthenticationException('Unexpected InResponseTo in SAMLResponse');
}

$session->getFlashBag()->add('success', 'ss.test_second_factor.verification_successful');
} catch (Exception $exception) {
$session->getFlashBag()->add('error', 'ss.test_second_factor.verification_failed');
}

return $assertion->getAttributes();
return $this->redirectToRoute('ss_second_factor_list');
}

public function metadataAction()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function getConfigTreeBuilder()

$childNodes = $rootNode->children();
$this->appendEnabledSecondFactorTypesConfiguration($childNodes);
$this->appendSecondFactorTestIdentityProvider($childNodes);
$this->appendSessionConfiguration($childNodes);

return $treeBuilder;
Expand Down Expand Up @@ -83,6 +84,30 @@ function ($lifetime) {
->end();
}

private function appendSecondFactorTestIdentityProvider(NodeBuilder $childNodes)
{
$childNodes
->arrayNode('second_factor_test_identity_provider')
->isRequired()
->children()
->scalarNode('entity_id')
->isRequired()
->info('The EntityID of the remote identity provider')
->end()
->scalarNode('sso_url')
->isRequired()
->info('The name of the route to generate the SSO URL')
->end()
->scalarNode('certificate')
->info('The contents of the certificate used to sign the AuthnResponse with')
->end()
->scalarNode('certificate_file')
->info('A file containing the certificate used to sign the AuthnResponse with')
->end()
->end()
->end();
}

/**
* @param NodeBuilder $childNodes
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@

namespace Surfnet\StepupSelfService\SelfServiceBundle\DependencyInjection;

use Surfnet\SamlBundle\Entity\IdentityProvider;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;

Expand Down Expand Up @@ -56,5 +59,39 @@ public function load(array $configs, ContainerBuilder $container)
'self_service.security.authentication.session.maximum_relative_lifetime_in_seconds',
$config['session_lifetimes']['max_relative_lifetime']
);

$this->parseSecondFactorTestIdentityProviderConfiguration(
$config['second_factor_test_identity_provider'],
$container
);
}

/**
* @param array $identityProvider
* @param ContainerBuilder $container
*/
private function parseSecondFactorTestIdentityProviderConfiguration(
array $identityProvider,
ContainerBuilder $container
) {
$definition = new Definition('Surfnet\SamlBundle\Entity\IdentityProvider');
$configuration = [
'entityId' => $identityProvider['entity_id'],
'ssoUrl' => $identityProvider['sso_url'],
];

if (isset($identityProvider['certificate_file']) && !isset($identityProvider['certificate'])) {
$configuration['certificateFile'] = $identityProvider['certificate_file'];
} elseif (isset($identityProvider['certificate'])) {
$configuration['certificateData'] = $identityProvider['certificate'];
} else {
throw new InvalidConfigurationException(
'Either "certificate_file" or "certificate" must be set in the ' .
'surfnet_stepup_self_service_self_service.second_factor_test_identity_provider configuration.'
);
}

$definition->setArguments([$configuration]);
$container->setDefinition('self_service.second_factor_test_idp', $definition);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ ss_second_factor_list:
methods: [GET]
defaults: { _controller: SurfnetStepupSelfServiceSelfServiceBundle:SecondFactor:list }

ss_second_factor_test:
path: /second-factor/{secondFactorId}/test
methods: [GET]
defaults: { _controller: SurfnetStepupSelfServiceSelfServiceBundle:Saml:testSecondFactor }

ss_second_factor_revoke:
path: /second-factor/{state}/{secondFactorId}/revoke
methods: [GET,POST]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ services:
- "%locales%"
- "%support_url%"

self_service.test_second_factor_authentication_request_factory:
class: Surfnet\StepupSelfService\SelfServiceBundle\Service\TestSecondFactor\TestAuthenticationRequestFactory
arguments:
- '@surfnet_saml.hosted.service_provider'
- '@self_service.second_factor_test_idp'

self_service.event_listener.locale:
class: Surfnet\StepupSelfService\SelfServiceBundle\EventListener\LocaleListener
arguments: [ "@security.token_storage", "@translator" ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,17 @@
<td>{{ ('ss.second_factor.type.'~secondFactor.type)|trans }}</td>
<td>{{ secondFactor.secondFactorIdentifier }}</td>
<td>
<a class="btn btn-mini btn-warning pull-right"
href="{{ path('ss_second_factor_revoke', {'state': state, 'secondFactorId': secondFactor.id}) }}">
{{ 'ss.second_factor.revoke.button.revoke'|trans }}
</a>
<div class="btn-group pull-right" role="group">
{% if state == 'vetted' %}
<a class="btn btn-mini btn-default"
href="{{ path('ss_second_factor_test', {'secondFactorId': secondFactor.id}) }}">
{{ 'ss.second_factor.revoke.button.test'|trans }}</a>
{% endif %}
<a class="btn btn-mini btn-warning"
href="{{ path('ss_second_factor_revoke', {'state': state, 'secondFactorId': secondFactor.id}) }}">
{{ 'ss.second_factor.revoke.button.revoke'|trans }}
</a>
</div>
</td>
</tr>
{% endfor %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@
{# U2fController #}
{{ 'ss.registration.u2f.alert.device_reported_an_error'|trans }}
{{ 'ss.registration.u2f.alert.error'|trans }}

{# SamlController flash messages #}
{{ 'ss.test_second_factor.verification_successful'|trans }}
{{ 'ss.test_second_factor.verification_failed'|trans }}
Loading

0 comments on commit 7de4511

Please sign in to comment.