Completed
Push — feature/implement-state-handli... ( bd5ae0 )
by Michiel
01:52
created

IdentityProviderController::getResponseAsXML()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace Surfnet\StepupGateway\Behat\Controller;
4
5
use RobRichards\XMLSecLibs\XMLSecurityKey;
6
use SAML2\Assertion;
7
use SAML2\Certificate\KeyLoader;
8
use SAML2\Certificate\PrivateKeyLoader;
9
use SAML2\Configuration\PrivateKey;
10
use SAML2\Constants;
11
use SAML2\Response;
12
use SAML2\Response as SAMLResponse;
13
use SAML2\XML\saml\NameID;
14
use SAML2\XML\saml\SubjectConfirmation;
15
use SAML2\XML\saml\SubjectConfirmationData;
16
use Surfnet\SamlBundle\Http\Exception\UnsignedRequestException;
17
use Surfnet\SamlBundle\Http\ReceivedAuthnRequestQueryString;
18
use Surfnet\SamlBundle\SAML2\ReceivedAuthnRequest;
19
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
20
use Symfony\Component\HttpFoundation\Request;
21
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
22
23
class IdentityProviderController extends Controller
24
{
25
    /**
26
     * Handles a SSO request
27
     * @param Request $request
28
     */
29 View Code Duplication
    public function ssoAction(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
30
    {
31
        // receives the AuthnRequest and sends a SAML response
32
        $authnRequest = $this->receiveSignedAuthnRequestFrom($request);
33
        // Todo: For some reason, the nameId is not transpored even tho it is set on the auhtnrequest.. Figure out whats going on here and fix this.
34
        // now the test will only work with one hard-coded user.
35
        $response = $this->createResponse(
36
            'https://gateway.stepup.example.com/authentication/consume-assertion',
37
            ['Value' => 'urn:collab:person:stepup.example.com:john_haack', 'Format' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified'],
38
            $authnRequest->getRequestId()
39
        );
40
        return $this->renderSamlResponse($response);
41
    }
42
43
    /**
44
     * Handles a GSSP SSO request
45
     * @param Request $request
46
     */
47 View Code Duplication
    public function gsspSsoAction(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
48
    {
49
        // receives the AuthnRequest and sends a SAML response
50
        $authnRequest = $this->receiveSignedAuthnRequestFrom($request);
51
        // Todo: For some reason, the nameId is not transpored even tho it is set on the auhtnrequest.. Figure out whats going on here and fix this.
52
        // now the test will only work with one hard-coded user.
53
        $response = $this->createResponse(
54
            $authnRequest->getAssertionConsumerServiceURL(),
55
            ['Value' => 'foobar', 'Format' => 'urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified'],
56
            $authnRequest->getRequestId()
57
        );
58
        return $this->renderSamlResponse($response);
59
    }
60
61
    /**
62
     * @param SAMLResponse $response
63
     * @return \Symfony\Component\HttpFoundation\Response
64
     */
65
    public function renderSamlResponse(SAMLResponse $response)
66
    {
67
        $parameters = [
68
            'acu' => $response->getDestination(),
69
            'response' => $this->getResponseAsXML($response),
70
            'relayState' => ''
71
        ];
72
73
        $response = parent::render(
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (render() instead of renderSamlResponse()). Are you sure this is correct? If so, you might want to change this to $this->render().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
74
            'SurfnetStepupGatewaySamlStepupProviderBundle:SamlProxy:consumeAssertion.html.twig',
75
            $parameters
76
        );
77
78
        return $response;
79
    }
80
81
    /**
82
     * @param string $destination
83
     * @param string $nameId
0 ignored issues
show
Documentation introduced by
Should the type for parameter $nameId not be array?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
84
     * @return Response
85
     */
86
    public function createResponse($destination, array $nameId, $requestId)
87
    {
88
        $newAssertion = new Assertion();
89
        $newAssertion->setNotBefore(time());
90
        $newAssertion->setNotOnOrAfter(time() + (60 * 5));//
91
        $newAssertion->setAttributes(['urn:mace:dir:attribute-def:eduPersonTargetedID' => [NameID::fromArray($nameId)]]);
0 ignored issues
show
Deprecated Code introduced by
The method SAML2\XML\saml\NameIDType::fromArray() has been deprecated.

This method has been deprecated.

Loading history...
92
        $newAssertion->setIssuer('https://idp.stepup.example.com/');
93
        $newAssertion->setIssueInstant(time());
94
95
        $this->signAssertion($newAssertion);
96
        $this->addSubjectConfirmationFor($newAssertion, $destination, $requestId);
97
        $newAssertion->setNameId($nameId);
98
        $response = new SAMLResponse();
99
        $response->setAssertions([$newAssertion]);
100
        $response->setIssuer('https://idp.stepup.example.com/');
101
        $response->setIssueInstant(time());
102
        $response->setDestination($destination);
103
        $response->setInResponseTo($requestId);
104
        return $response;
105
    }
106
107
    /**
108
     * @param SAMLResponse $response
109
     * @return string
110
     */
111
    private function getResponseAsXML(SAMLResponse $response)
112
    {
113
        return base64_encode($response->toUnsignedXML()->ownerDocument->saveXML());
114
    }
115
116
    /**
117
     * @param Request $request
118
     * @return string
119
     */
120
    private function getFullRequestUri(Request $request)
121
    {
122
        return $request->getSchemeAndHttpHost() . $request->getBasePath() . $request->getRequestUri();
123
    }
124
125
    /**
126
     * @param Request $request
127
     * @return ReceivedAuthnRequest
128
     */
129
    public function receiveSignedAuthnRequestFrom(Request $request)
130
    {
131
        if (!$request->isMethod(Request::METHOD_GET)) {
132
            throw new BadRequestHttpException(sprintf(
133
                'Could not receive AuthnRequest from HTTP Request: expected a GET method, got %s',
134
                $request->getMethod()
135
            ));
136
        }
137
138
        $requestUri = $request->getRequestUri();
139
        if (strpos($requestUri, '?') === false) {
140
            throw new BadRequestHttpException(
141
                'Could not receive AuthnRequest from HTTP Request: expected query parameters, none found'
142
            );
143
        }
144
145
        list(, $rawQueryString) = explode('?', $requestUri);
146
        $query = ReceivedAuthnRequestQueryString::parse($rawQueryString);
147
148
        if (!$query->isSigned()) {
149
            throw new UnsignedRequestException('The SAMLRequest is expected to be signed but it was not');
150
        }
151
152
        $authnRequest = ReceivedAuthnRequest::from($query->getDecodedSamlRequest());
153
154
        $currentUri = $this->getFullRequestUri($request);
155
        if (!$authnRequest->getDestination() === $currentUri) {
156
            throw new BadRequestHttpException(sprintf(
157
                'Actual Destination "%s" does not match the AuthnRequest Destination "%s"',
158
                $currentUri,
159
                $authnRequest->getDestination()
160
            ));
161
        }
162
163
        return $authnRequest;
164
    }
165
166
    /**
167
     * @param Assertion $assertion
168
     * @return Assertion
169
     */
170
    public function signAssertion(Assertion $assertion)
171
    {
172
        $assertion->setSignatureKey($this->loadPrivateKey());
173
        $assertion->setCertificates([$this->getPublicCertificate()]);
174
175
        return $assertion;
176
    }
177
178 View Code Duplication
    private function addSubjectConfirmationFor(Assertion $newAssertion, $destination, $requestId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
    {
180
        $confirmation = new SubjectConfirmation();
181
        $confirmation->Method = Constants::CM_BEARER;
182
183
        $confirmationData                      = new SubjectConfirmationData();
184
        $confirmationData->InResponseTo        = $requestId;
185
        $confirmationData->Recipient           = $destination;
186
        $confirmationData->NotOnOrAfter        = $newAssertion->getNotOnOrAfter();
187
188
        $confirmation->SubjectConfirmationData = $confirmationData;
189
190
        $newAssertion->setSubjectConfirmation([$confirmation]);
191
    }
192
193
    /**
194
     * @return XMLSecurityKey
195
     */
196 View Code Duplication
    private function loadPrivateKey()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
    {
198
        $key        = new PrivateKey('/var/www/ci/certificates/sp.pem', 'default');
199
        $keyLoader  = new PrivateKeyLoader();
200
        $privateKey = $keyLoader->loadPrivateKey($key);
201
202
        $xmlSecurityKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
203
        $xmlSecurityKey->loadKey($privateKey->getKeyAsString());
204
205
        return $xmlSecurityKey;
206
    }
207
208
    /**
209
     * @return string
210
     */
211
    private function getPublicCertificate()
212
    {
213
        $keyLoader = new KeyLoader();
214
        $keyLoader->loadCertificateFile('/var/www/ci/certificates/sp.crt');
215
        /** @var \SAML2\Certificate\X509 $publicKey */
216
        $publicKey = $keyLoader->getKeys()->getOnlyElement();
217
218
        return $publicKey->getCertificate();
219
    }
220
}
221