Passed
Pull Request — develop (#295)
by Peter
04:30
created

ServiceProviderController   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 113
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 67
dl 0
loc 113
rs 10
c 1
b 0
f 0
wmc 8

3 Methods

Rating   Name   Duplication   Size   Complexity  
A adfsSsoAction() 0 45 1
A __construct() 0 4 1
B acsAction() 0 46 6
1
<?php
2
3
namespace Surfnet\StepupGateway\Behat\Controller;
4
5
use Exception;
6
use Psr\Log\LoggerInterface;
7
use RobRichards\XMLSecLibs\XMLSecurityKey;
8
use RuntimeException;
9
use SAML2\AuthnRequest;
10
use SAML2\Certificate\PrivateKeyLoader;
11
use SAML2\Configuration\PrivateKey;
12
use SAML2\Constants;
13
use SAML2\HTTPPost;
14
use SAML2\HTTPRedirect;
15
use SAML2\Response as SAMLResponse;
16
use SAML2\XML\saml\Issuer;
17
use SAML2\XML\saml\NameID;
18
use Surfnet\SamlBundle\Http\XMLResponse;
19
use Surfnet\StepupGateway\Behat\Repository\SamlEntityRepository;
20
use Surfnet\StepupGateway\Behat\ServiceProviderContext;
21
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Adfs\ValueObject\Response as AdfsResponse;
22
use Symfony\Component\HttpFoundation\Request;
23
use Symfony\Component\HttpFoundation\Response;
24
use Twig\Environment;
25
use function base64_encode;
26
27
class ServiceProviderController
28
{
29
    private $twig;
30
31
    private $logger;
32
33
    public function __construct(Environment $twig, LoggerInterface $logger)
34
    {
35
        $this->logger = $logger;
36
        $this->twig = $twig;
37
    }
38
39
    /**
40
     * Simply dumps the SAMLResponse XML
41
     */
42
    public function acsAction(Request $request)
43
    {
44
        $this->logger->notice('Getting ready to consume the assertion on the test SP');
45
        $isAdfs = false;
46
        if ($request->request->has('_SAMLResponse')) {
47
            $this->logger->notice('Handling a test ADFS assertion');
48
            // The ADFS saml response is hidden in the _SAMLResponse input, in order to get the
49
            $request->request->set('SAMLResponse', $request->request->get('_SAMLResponse'));
50
            $_POST['SAMLResponse'] = $request->request->get('_SAMLResponse');
51
            $isAdfs = true;
52
        }
53
        libxml_disable_entity_loader(true);
54
        try {
55
            $this->logger->notice('Process the assertion on the test SP (try POST binding)');
56
            $httpPostBinding = new HTTPPost();
57
            $message = $httpPostBinding->receive();
58
        } catch (Exception $e1) {
59
            try {
60
                $this->logger->alert('Processing failed on the test SP');
61
                $httpRedirectBinding = new HTTPRedirect();
62
                $message = $httpRedirectBinding->receive();
63
            } catch (Exception $e2) {
64
                throw new RuntimeException('Unable to retrieve SAML message?', 1, $e1);
65
            }
66
        }
67
68
        if (!$message instanceof SAMLResponse) {
69
            throw new RuntimeException(sprintf('Unrecognized message type received: "%s"', get_class($message)));
70
        }
71
72
        $xml = base64_decode($request->get('SAMLResponse'));
0 ignored issues
show
Bug introduced by
It seems like $request->get('SAMLResponse') can also be of type null; however, parameter $string of base64_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

72
        $xml = base64_decode(/** @scrutinizer ignore-type */ $request->get('SAMLResponse'));
Loading history...
73
        $this->logger->notice(sprintf('Received SAMLResponse with status "%s"', implode($message->getStatus())));
74
        $this->logger->notice('The XML received', ['response-xml' => $xml]);
75
76
        if ($isAdfs) {
77
            $html = $this->twig->render(
78
                '@test_resources/adfs_acs.html.twig',
79
                [
80
                    'samlResponse' => $xml,
81
                    'context' => $request->request->get('Context'),
82
                    'authMethod' => $request->request->get('AuthMethod'),
83
                ]
84
            );
85
            return Response::create($html);
0 ignored issues
show
Deprecated Code introduced by
The function Symfony\Component\HttpFo...tion\Response::create() has been deprecated: since Symfony 5.1, use __construct() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

85
            return /** @scrutinizer ignore-deprecated */ Response::create($html);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
86
        }
87
        return XMLResponse::create($xml);
0 ignored issues
show
Deprecated Code introduced by
The function Symfony\Component\HttpFo...tion\Response::create() has been deprecated: since Symfony 5.1, use __construct() instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

87
        return /** @scrutinizer ignore-deprecated */ XMLResponse::create($xml);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
88
    }
89
90
    /**
91
     * Posts an authn request to the SA Gateway, adding two additional
92
     * parameters to the POST in addition to those found on a regular
93
     * authn request (AuthNRequest and RelayState)
94
     */
95
    public function adfsSsoAction(Request $request)
96
    {
97
        $nameId = $request->get('nameId');
98
        $loa = $request->get('loa');
99
        $entityId = $request->get('entityId');
100
101
        $authnRequest = new AuthnRequest();
102
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
103
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
104
        $issuerVo = new Issuer();
105
        $issuerVo->setValue($entityId);
0 ignored issues
show
Bug introduced by
It seems like $entityId can also be of type null; however, parameter $value of SAML2\XML\saml\NameIDType::setValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
        $issuerVo->setValue(/** @scrutinizer ignore-type */ $entityId);
Loading history...
106
        $authnRequest->setIssuer($issuerVo);
107
        $authnRequest->setDestination(ServiceProviderContext::SFO_ENDPOINT_URL);
108
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
109
110
        $nameIdVo = new NameID();
111
        $nameIdVo->setValue($nameId);
112
        $nameIdVo->setFormat(Constants::NAMEFORMAT_UNSPECIFIED);
113
114
        $authnRequest->setNameId($nameIdVo);
115
116
        $keyLoader = new PrivateKeyLoader();
117
        $privateKey = $keyLoader->loadPrivateKey(new PrivateKey('/var/www/ci/certificates/sp.key', 'default'));
118
        $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
119
        $key->loadKey($privateKey->getKeyAsString());
120
121
        $authnRequest->setSignatureKey($key);
122
        $authnRequest->setRequestedAuthnContext(
123
            ['AuthnContextClassRef' => [$loa]]
124
        );
125
126
        $context = '<EncryptedData></EncryptedData>';
127
        $authMethod = 'ADFS.SCSA';
128
        $arXml = $authnRequest->toSignedXML();
129
        $arBase64Encoded = base64_encode($arXml->ownerDocument->saveXml($arXml));
0 ignored issues
show
Bug introduced by
The method saveXml() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

129
        $arBase64Encoded = base64_encode($arXml->ownerDocument->/** @scrutinizer ignore-call */ saveXml($arXml));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
130
        $response = $this->twig->render(
131
            '@test_resources/adfs_login.html.twig',
132
            [
133
                'ssoUrl' => $authnRequest->getDestination(),
134
                'authNRequest' => $arBase64Encoded,
135
                'adfs' => AdfsResponse::fromValues($authMethod, $context)
136
            ]
137
        );
138
139
        return new Response($response);
140
    }
141
}
142