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.
Passed
Push — master ( ad0fa8...0fb162 )
by Damien
03:23
created

ResponseAssertion::create()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 90
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 8.2643

Importance

Changes 6
Bugs 0 Features 0
Metric Value
cc 7
eloc 43
c 6
b 0
f 0
nc 24
nop 6
dl 0
loc 90
ccs 31
cts 44
cp 0.7045
crap 8.2643
rs 8.2986

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