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 ( 6b08d5...ad0fa8 )
by Damien
05:38
created

ResponseAssertion::create()   B

Complexity

Conditions 7
Paths 24

Size

Total Lines 85
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 7.8802

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 7
eloc 41
c 5
b 0
f 0
nc 24
nop 6
dl 0
loc 85
ccs 31
cts 42
cp 0.7381
crap 7.8802
rs 8.3306

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