Issues (95)

Branch: main

tests/src/Controller/ServiceProviderController.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * Copyright 2020 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupGateway\Behat\Controller;
20
21
use Exception;
22
use Psr\Log\LoggerInterface;
23
use RobRichards\XMLSecLibs\XMLSecurityKey;
24
use RuntimeException;
25
use SAML2\AuthnRequest;
26
use SAML2\Certificate\PrivateKeyLoader;
27
use SAML2\Configuration\PrivateKey;
28
use SAML2\Constants;
29
use SAML2\HTTPPost;
30
use SAML2\HTTPRedirect;
31
use SAML2\Response as SAMLResponse;
32
use SAML2\XML\saml\Issuer;
33
use SAML2\XML\saml\NameID;
34
use Surfnet\SamlBundle\Http\XMLResponse;
35
use Surfnet\StepupGateway\Behat\Repository\SamlEntityRepository;
36
use Surfnet\StepupGateway\Behat\ServiceProviderContext;
37
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Adfs\ValueObject\Response as AdfsResponse;
38
use Symfony\Component\HttpFoundation\Request;
39
use Symfony\Component\HttpFoundation\Response;
40
use Twig\Environment;
41
42
class ServiceProviderController
43
{
44
    private $twig;
45
46
    private $logger;
47
48
    public function __construct(Environment $twig, LoggerInterface $logger)
49
    {
50
        $this->logger = $logger;
51
        $this->twig = $twig;
52
    }
53
54
    /**
55
     * Simply dumps the SAMLResponse XML
56
     */
57
    public function acsAction(Request $request)
58
    {
59
        $this->logger->notice('Getting ready to consume the assertion on the test SP');
60
        $isAdfs = false;
61
        if ($request->request->has('_SAMLResponse')) {
62
            $this->logger->notice('Handling a test ADFS assertion');
63
            // The ADFS saml response is hidden in the _SAMLResponse input, in order to get the
64
            $request->request->set('SAMLResponse', $request->request->get('_SAMLResponse'));
65
            $_POST['SAMLResponse'] = $request->request->get('_SAMLResponse');
66
            $isAdfs = true;
67
        }
68
        try {
69
            $this->logger->notice('Process the assertion on the test SP (try POST binding)');
70
            $httpPostBinding = new HTTPPost();
71
            $message = $httpPostBinding->receive();
72
        } catch (Exception $e1) {
73
            try {
74
                $this->logger->alert('Processing failed on the test SP');
75
                $httpRedirectBinding = new HTTPRedirect();
76
                $message = $httpRedirectBinding->receive();
77
            } catch (Exception) {
78
                throw new RuntimeException('Unable to retrieve SAML message?', 1, $e1);
79
            }
80
        }
81
82
        if (!$message instanceof SAMLResponse) {
83
            throw new RuntimeException(sprintf('Unrecognized message type received: "%s"', get_class($message)));
84
        }
85
86
        $xml = base64_decode($request->get('SAMLResponse'));
87
        $this->logger->notice(sprintf('Received SAMLResponse with status "%s"', implode($message->getStatus())));
88
        $this->logger->notice('The XML received', ['response-xml' => $xml]);
89
90
        if ($isAdfs) {
91
            $html = $this->twig->render(
92
                '@test_resources/adfs_acs.html.twig',
93
                [
94
                    'samlResponse' => $xml,
95
                    'context' => $request->request->get('Context'),
96
                    'authMethod' => $request->request->get('AuthMethod'),
97
                ]
98
            );
99
            return new Response($html);
100
        }
101
        return new XMLResponse($xml);
102
    }
103
104
    /**
105
     * Posts an authn request to the SA Gateway, adding two additional
106
     * parameters to the POST in addition to those found on a regular
107
     * authn request (AuthNRequest and RelayState)
108
     */
109
    public function adfsSsoAction(Request $request)
110
    {
111
        $nameId = $request->get('nameId');
112
        $loa = $request->get('loa');
113
        $entityId = $request->get('entityId');
114
115
        $authnRequest = new AuthnRequest();
116
        // In order to later assert if the response succeeded or failed, set our own dummy ACS location
117
        $authnRequest->setAssertionConsumerServiceURL(SamlEntityRepository::SP_ACS_LOCATION);
118
        $issuerVo = new Issuer();
119
        $issuerVo->setValue($entityId);
120
        $authnRequest->setIssuer($issuerVo);
121
        $authnRequest->setDestination(ServiceProviderContext::SFO_ENDPOINT_URL);
122
        $authnRequest->setProtocolBinding(Constants::BINDING_HTTP_REDIRECT);
123
124
        $nameIdVo = new NameID();
125
        $nameIdVo->setValue($nameId);
126
        $nameIdVo->setFormat(Constants::NAMEFORMAT_UNSPECIFIED);
127
128
        $authnRequest->setNameId($nameIdVo);
129
130
        $keyLoader = new PrivateKeyLoader();
131
        $privateKey = $keyLoader->loadPrivateKey(new PrivateKey('/config/ssp/sp.key', 'default'));
132
        $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
133
        $key->loadKey($privateKey->getKeyAsString());
134
135
        $authnRequest->setSignatureKey($key);
136
        $authnRequest->setRequestedAuthnContext(
137
            ['AuthnContextClassRef' => [$loa]]
138
        );
139
140
        $context = '<EncryptedData></EncryptedData>';
141
        $authMethod = 'ADFS.SCSA';
142
        $arXml = $authnRequest->toSignedXML();
143
        $arBase64Encoded = base64_encode($arXml->ownerDocument->saveXml($arXml));
0 ignored issues
show
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

143
        $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...
144
        $response = $this->twig->render(
145
            '@test_resources/adfs_login.html.twig',
146
            [
147
                'ssoUrl' => $authnRequest->getDestination(),
148
                'authNRequest' => $arBase64Encoded,
149
                'adfs' => AdfsResponse::fromValues($authMethod, $context)
150
            ]
151
        );
152
153
        return new Response($response);
154
    }
155
}
156