Completed
Pull Request — feature/create-sfo-behat-test (#192)
by
unknown
04:17 queued 02:30
created

IdentityProviderController::ssoAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
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
    public function ssoAction(Request $request)
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
     * @param SAMLResponse $response
45
     * @return \Symfony\Component\HttpFoundation\Response
46
     */
47
    public function renderSamlResponse(SAMLResponse $response)
48
    {
49
        $parameters = [
50
            'acu' => $response->getDestination(),
51
            'response' => $this->getResponseAsXML($response),
52
            'relayState' => ''
53
        ];
54
55
        $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...
56
            'SurfnetStepupGatewaySamlStepupProviderBundle:SamlProxy:consumeAssertion.html.twig',
57
            $parameters
58
        );
59
60
        return $response;
61
    }
62
63
    /**
64
     * @param string $destination
65
     * @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...
66
     * @return Response
67
     */
68
    public function createResponse($destination, array $nameId, $requestId)
69
    {
70
        $newAssertion = new Assertion();
71
        $newAssertion->setNotBefore(time());
72
        $newAssertion->setNotOnOrAfter(time() + (60 * 5));//
73
        $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...
74
        $newAssertion->setIssuer('https://idp.stepup.example.com/');
75
        $newAssertion->setIssueInstant(time());
76
77
        $this->signAssertion($newAssertion);
78
        $this->addSubjectConfirmationFor($newAssertion, $destination, $requestId);
79
        $newAssertion->setNameId($nameId);
80
        $response = new SAMLResponse();
81
        $response->setAssertions([$newAssertion]);
82
        $response->setIssuer('https://idp.stepup.example.com/');
83
        $response->setIssueInstant(time());
84
        $response->setDestination($destination);
85
        $response->setInResponseTo($requestId);
86
        return $response;
87
    }
88
89
    /**
90
     * @param SAMLResponse $response
91
     * @return string
92
     */
93
    private function getResponseAsXML(SAMLResponse $response)
94
    {
95
        return base64_encode($response->toUnsignedXML()->ownerDocument->saveXML());
96
    }
97
98
    /**
99
     * @param Request $request
100
     * @return string
101
     */
102
    private function getFullRequestUri(Request $request)
103
    {
104
        return $request->getSchemeAndHttpHost() . $request->getBasePath() . $request->getRequestUri();
105
    }
106
107
    /**
108
     * @param Request $request
109
     * @return ReceivedAuthnRequest
110
     */
111
    public function receiveSignedAuthnRequestFrom(Request $request)
112
    {
113
        if (!$request->isMethod(Request::METHOD_GET)) {
114
            throw new BadRequestHttpException(sprintf(
115
                'Could not receive AuthnRequest from HTTP Request: expected a GET method, got %s',
116
                $request->getMethod()
117
            ));
118
        }
119
120
        $requestUri = $request->getRequestUri();
121
        if (strpos($requestUri, '?') === false) {
122
            throw new BadRequestHttpException(
123
                'Could not receive AuthnRequest from HTTP Request: expected query parameters, none found'
124
            );
125
        }
126
127
        list(, $rawQueryString) = explode('?', $requestUri);
128
        $query = ReceivedAuthnRequestQueryString::parse($rawQueryString);
129
130
        if (!$query->isSigned()) {
131
            throw new UnsignedRequestException('The SAMLRequest is expected to be signed but it was not');
132
        }
133
134
        $authnRequest = ReceivedAuthnRequest::from($query->getDecodedSamlRequest());
135
136
        $currentUri = $this->getFullRequestUri($request);
137
        if (!$authnRequest->getDestination() === $currentUri) {
138
            throw new BadRequestHttpException(sprintf(
139
                'Actual Destination "%s" does not match the AuthnRequest Destination "%s"',
140
                $currentUri,
141
                $authnRequest->getDestination()
142
            ));
143
        }
144
145
        return $authnRequest;
146
    }
147
148
    /**
149
     * @param Assertion $assertion
150
     * @return Assertion
151
     */
152
    public function signAssertion(Assertion $assertion)
153
    {
154
        $assertion->setSignatureKey($this->loadPrivateKey());
155
        $assertion->setCertificates([$this->getPublicCertificate()]);
156
157
        return $assertion;
158
    }
159
160 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...
161
    {
162
        $confirmation = new SubjectConfirmation();
163
        $confirmation->Method = Constants::CM_BEARER;
164
165
        $confirmationData                      = new SubjectConfirmationData();
166
        $confirmationData->InResponseTo        = $requestId;
167
        $confirmationData->Recipient           = $destination;
168
        $confirmationData->NotOnOrAfter        = $newAssertion->getNotOnOrAfter();
169
170
        $confirmation->SubjectConfirmationData = $confirmationData;
171
172
        $newAssertion->setSubjectConfirmation([$confirmation]);
173
    }
174
175
    /**
176
     * @return XMLSecurityKey
177
     */
178 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...
179
    {
180
        $key        = new PrivateKey('/var/www/ci/certificates/sp.pem', 'default');
181
        $keyLoader  = new PrivateKeyLoader();
182
        $privateKey = $keyLoader->loadPrivateKey($key);
183
184
        $xmlSecurityKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
185
        $xmlSecurityKey->loadKey($privateKey->getKeyAsString());
186
187
        return $xmlSecurityKey;
188
    }
189
190
    /**
191
     * @return string
192
     */
193
    private function getPublicCertificate()
194
    {
195
        $keyLoader = new KeyLoader();
196
        $keyLoader->loadCertificateFile('/var/www/ci/certificates/sp.crt');
197
        /** @var \SAML2\Certificate\X509 $publicKey */
198
        $publicKey = $keyLoader->getKeys()->getOnlyElement();
199
200
        return $publicKey->getCertificate();
201
    }
202
}
203