GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 3c123c...1dd30d )
by Damien
03:16
created

ResponseAssertion::create()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 84
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 7.9463

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 7
eloc 40
c 4
b 0
f 0
nc 24
nop 6
dl 0
loc 84
ccs 30
cts 41
cp 0.7317
crap 7.9463
rs 8.3466

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace flipbox\saml\idp\services\messages;
4
5
use craft\base\Component;
6
use craft\elements\User;
7
use craft\helpers\ConfigHelper;
8
use flipbox\saml\core\models\AttributeMap;
9
use flipbox\saml\core\records\AbstractProvider;
10
use flipbox\saml\idp\models\Settings;
11
use flipbox\saml\idp\records\ProviderRecord;
12
use flipbox\saml\idp\Saml;
13
use SAML2\Assertion;
14
use SAML2\AuthnRequest as SamlAuthnRequest;
15
use SAML2\Constants;
16
use SAML2\EncryptedAssertion;
17
use SAML2\Response as ResponseMessage;
18
use SAML2\XML\saml\NameID;
19
use SAML2\XML\saml\SubjectConfirmation;
20
use SAML2\XML\saml\SubjectConfirmationData;
21
22
class ResponseAssertion extends Component
23
{
24 2
    public function create(
25
        User $user,
26
        ResponseMessage $response,
27
        ProviderRecord $identityProvider,
28
        ProviderRecord $serviceProvider,
29
        Settings $settings,
30
        SamlAuthnRequest $authnRequest = null
31
    ) {
32 2
        $assertion = new Assertion();
33
34 2
        $issuer = $response->getIssuer();
35 2
        if (! is_null($issuer)) {
36 2
            $assertion->setIssuer(
37 2
                $issuer
38
            );
39
        }
40
41
42 2
        $assertion->setSubjectConfirmation([
43 2
            $this->createSubjectConfirmation(
44 2
                $serviceProvider,
45 2
                $user,
46 2
                $settings,
47 2
                $authnRequest
48
            ),
49
        ]);
50
51 2
        $urlParts = parse_url(
52 2
            $response->getDestination()
53
        );
54
55 2
        if (isset($urlParts['scheme']) && isset($urlParts['host'])) {
56
            // allow all
57 2
            $assertion->setValidAudiences([
58 2
                $urlParts['scheme'] . '://' .$urlParts['host']
59
            ]);
60
        }
61
        
62 2
        $this->createConditions($assertion, $settings);
63
64 2
        $this->createAuthnStatement($assertion);
65
66 2
        $this->setAssertionAttributes(
67 2
            $user,
68 2
            $assertion,
69 2
            $serviceProvider,
70 2
            $settings
71
        );
72
73 2
        $firstDescriptor = $serviceProvider->spSsoDescriptors()[0];
74
75
        // Sign Assertions
76 2
        if ($firstDescriptor->wantAssertionsSigned()) {
77
            $assertion->setSignatureKey(
78
                $identityProvider->keychainPrivateXmlSecurityKey()
79
            );
80
        }
81
82
83
        // Encrypt Assertions
84 2
        if ($serviceProvider->encryptAssertions) {
85
            $unencrypted = $assertion;
86
87
            if (is_null($serviceProvider->encryptionKey())) {
88
                throw new \Exception('No encryption key found for the service provider.');
89
            }
90
            $unencrypted->setEncryptionKey(
91
                $serviceProvider->encryptionKey()
92
            );
93
94
            $assertion = new EncryptedAssertion();
95
            $assertion->setAssertion(
96
                $unencrypted,
97
                $serviceProvider->encryptionKey()
98
            );
99
        }
100
101 2
        $response->setAssertions(
102
            [
103 2
                $assertion,
104
            ]
105
        );
106
107 2
        return $assertion;
108
    }
109
110
    /**
111
     * @param AbstractProvider $serviceProvider
112
     * @param User $user
113
     * @param Settings $settings
114
     * @param SamlAuthnRequest|null $authnRequest
115
     * @return SubjectConfirmation
116
     * @throws \Throwable
117
     * @throws \yii\base\Exception
118
     */
119 2
    protected function createSubjectConfirmation(
120
        AbstractProvider $serviceProvider,
121
        User $user,
122
        Settings $settings,
123
        SamlAuthnRequest $authnRequest = null
124
    ) {
125
        /**
126
         * Subject Confirmation
127
         * Reference: https://stackoverflow.com/a/29546696/1590910
128
         *
129
         * The times in the <SubjectConfirmationData> signals for how long time assertion can be tied to the subject.
130
         * In Web SSO where the subject confirmation method "bearer" is usually used, it means that within this time
131
         * we can trust that the assertion applies to the one providing the assertion. The assertion might be valid
132
         * for a longer time, but we must create a session within this time frame. This is described in the Web SSO
133
         * Profile section 4.1.4.3. The times in <SubjectConfirmationData> must fall within the interval of
134
         * those in <Conditions>.
135
         */
136
137 2
        $subjectConfirmation = new SubjectConfirmation();
138
139 2
        $subjectConfirmation->setMethod(
140 2
            Constants::CM_BEARER
141
        );
142
143
144
        // Add Subject Confirmation Data
145 2
        $subjectConfirmation->setSubjectConfirmationData(
146 2
            $subjectConfirmationData = new SubjectConfirmationData()
147
        );
148
149 2
        $subjectConfirmationData->setNotOnOrAfter(
150 2
            (new \DateTime(
151 2
                $settings->messageNotOnOrAfter
152 2
            ))->getTimestamp()
153
        );
154
155 2
        if ($authnRequest) {
156 2
            $subjectConfirmationData->setInResponseTo($authnRequest->getId());
157 2
            $subjectConfirmationData->setRecipient(
158 2
                $authnRequest->getAssertionConsumerServiceURL()
159
            );
160
        }
161
162 2
        $subjectConfirmation->setNameID(
163 2
            $nameId = new NameID()
164
        );
165
166 2
        $nameId->setFormat(Constants::NAMEID_UNSPECIFIED);
167 2
        $nameId->setValue(
168 2
            $serviceProvider->assignNameId($user)
169
        );
170
171 2
        return $subjectConfirmation;
172
    }
173
174
    /**
175
     * @param Assertion $assertion
176
     * @throws \Exception
177
     */
178 2
    protected function createConditions(
179
        Assertion $assertion,
180
        Settings $settings
181
    ) {
182
        /**
183
         * Conditions
184
         * Reference: https://stackoverflow.com/a/29546696/1590910
185
         *
186
         * The times in <Conditions> is the validity of the entire assertion.
187
         * It should not be consumed after this time. There is nothing preventing a user
188
         * session on an SP to extend beyond this point in time though.
189
         */
190
191 2
        $assertion->setNotBefore(
192 2
            (new \DateTime(
193 2
                $settings->messageNotBefore
194 2
            ))->getTimestamp()
195
        );
196
197 2
        $assertion->setNotOnOrAfter(
198 2
            (new \DateTime(
199 2
                $settings->messageNotOnOrAfter
200 2
            ))->getTimestamp()
201
        );
202 2
    }
203
204
    /**
205
     * @param Assertion $assertion
206
     * @throws \yii\base\Exception
207
     * @throws \yii\base\InvalidConfigException
208
     */
209 2
    protected function createAuthnStatement(Assertion $assertion)
210
    {
211
        /**
212
         * Reference: https://stackoverflow.com/a/29546696/1590910
213
         *
214
         * SessionNotOnOrAfter is something completely different that is not directly related to the lifetime of
215
         * the assertion or the subject. It is a parameter the idp can use to control how long an SP session may be.
216
         * Please note that this parameter is defined that it SHOULD be handled by an SP according to the SAML2Core
217
         * spec, but far from all SP implementations do. An example of an implementation that does is as usual
218
         * Shibboleth, that always will respect the occurence of this parameter. When using Single Logout, this
219
         * parameter is more critical, as it synchronizes the timeout of the session on both the SP and the
220
         * Idp, to ensure that an SP does not issue a logout request for a session no longer known to the Idp.
221
         */
222 2
        $sessionEnd = (new \DateTime())->setTimestamp(
223 2
            ConfigHelper::durationInSeconds(
224
                /**
225
                * Use crafts user session duration
226
                */
227 2
                \Craft::$app->config->getGeneral()->userSessionDuration
228
            )
229
            + // Math!
230 2
            (new \DateTime())->getTimestamp()
231
        );
232
233
        /**
234
         * Add AuthnStatement attributes and AuthnContext
235
         */
236 2
        $assertion->setAuthnInstant((new \DateTime())->getTimestamp());
237 2
        $assertion->setSessionNotOnOrAfter(
238 2
            $sessionEnd->getTimestamp()
239
        );
240
241 2
        $assertion->setSessionIndex(
242 2
            Saml::getInstance()->getSession()->getId()
243
        );
244
245 2
        $assertion->setAuthnContextClassRef(
246 2
            Constants::AC_PASSWORD
247
        );
248 2
    }
249
250 2
    public function setAssertionAttributes(
251
        User $user,
252
        Assertion $assertion,
253
        ProviderRecord $serviceProvider,
254
        Settings $settings
255
    ) {
256
257
        // set on the assertion and the subject confirmations
258 2
        $assertion->setNameID(
259 2
            $nameId = new NameID()
260
        );
261
262 2
        $nameId->setFormat(Constants::NAMEID_UNSPECIFIED);
263 2
        $nameId->setValue(
264 2
            $serviceProvider->assignNameId($user)
265
        );
266
267
268
        // Check the provider first
269
        $attributeMap =
270 2
            $serviceProvider->hasMapping() ? $serviceProvider->getMapping() : $settings->responseAttributeMap;
271
272 2
        $attributes = [];
273 2
        foreach ($attributeMap as $map) {
274 2
            $map = new AttributeMap($map);
275 2
            $attributes[$map->attributeName] = $this->assignProperty(
276 2
                $user,
277 2
                $map
278
            );
279
        }
280
281
        // Add groups if configured
282 2
        if ($serviceProvider->syncGroups &&
283 2
            ($groupAttribute = $this->groupsToAttributes($user, $serviceProvider))
284
        ) {
285
            $attributes[$serviceProvider->groupsAttributeName] = $groupAttribute;
286
        }
287
288 2
        Saml::debug(json_encode($attributes));
289 2
        $assertion->setAttributes($attributes);
290 2
    }
291
292
    /**
293
     * @param User $user
294
     * @param AbstractProvider $serviceProvider
295
     * @return array|bool
296
     */
297
    protected function groupsToAttributes(User $user, AbstractProvider $serviceProvider)
298
    {
299
        if (count($user->getGroups()) === 0) {
300
            return false;
301
        }
302
        $attribute = [];
303
        foreach ($user->getGroups() as $group) {
304
            $options = $serviceProvider->getGroupOptions();
305
            if (! $options->shouldSync($group->id)) {
306
                continue;
307
            }
308
309
            $attribute[] = $group->handle;
310
        }
311
312
        return $attribute;
313
    }
314
315
    /**
316
     * Utilities
317
     */
318
319
    /**
320
     * @param User $user
321
     * @param $attributeName
322
     * @param $craftProperty
323
     * @return array
324
     */
325 2
    protected function assignProperty(
326
        User $user,
327
        AttributeMap $map
328
    ) {
329 2
        $value = $map->renderValue($user);
330
331 2
        if ($value instanceof \DateTime) {
332
            $value = $value->format(\DateTime::ISO8601);
333
        }
334
335
        return [
336 2
            $map->attributeName => $value,
337
        ];
338
    }
339
340
    /**
341
     * @return User|false|\yii\web\IdentityInterface|null
342
     */
343
    protected function getUser()
344
    {
345
        return \Craft::$app->getUser()->getIdentity();
346
    }
347
}
348