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.

ResponseAssertion::createAuthnStatement()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 13
cts 13
cp 1
rs 9.28
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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
                $serviceProvider->getEntityId(),
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->setCertificates(
78
                [
79
                    $identityProvider->keychain->getDecryptedCertificate(),
80
                ]
81
            );
82
            $assertion->setSignatureKey(
83
                $identityProvider->keychainPrivateXmlSecurityKey()
84
            );
85
        }
86
87
88
        // Encrypt Assertions
89 2
        if ($serviceProvider->encryptAssertions) {
90
            $unencrypted = $assertion;
91
92
            if (is_null($serviceProvider->encryptionKey())) {
93
                throw new \Exception('No encryption key found for the service provider.');
94
            }
95
            $unencrypted->setEncryptionKey(
96
                $serviceProvider->encryptionKey()
97
            );
98
99
            $assertion = new EncryptedAssertion();
100
            $assertion->setAssertion(
101
                $unencrypted,
102
                $serviceProvider->encryptionKey()
103
            );
104
        }
105
106 2
        $response->setAssertions(
107
            [
108 2
                $assertion,
109
            ]
110
        );
111
112 2
        return $assertion;
113
    }
114
115
    /**
116
     * @param AbstractProvider $serviceProvider
117
     * @param User $user
118
     * @param Settings $settings
119
     * @param SamlAuthnRequest|null $authnRequest
120
     * @return SubjectConfirmation
121
     * @throws \Throwable
122
     * @throws \yii\base\Exception
123
     */
124 2
    protected function createSubjectConfirmation(
125
        AbstractProvider $serviceProvider,
126
        User $user,
127
        Settings $settings,
128
        SamlAuthnRequest $authnRequest = null
129
    ) {
130
        /**
131
         * Subject Confirmation
132
         * Reference: https://stackoverflow.com/a/29546696/1590910
133
         *
134
         * The times in the <SubjectConfirmationData> signals for how long time assertion can be tied to the subject.
135
         * In Web SSO where the subject confirmation method "bearer" is usually used, it means that within this time
136
         * we can trust that the assertion applies to the one providing the assertion. The assertion might be valid
137
         * for a longer time, but we must create a session within this time frame. This is described in the Web SSO
138
         * Profile section 4.1.4.3. The times in <SubjectConfirmationData> must fall within the interval of
139
         * those in <Conditions>.
140
         */
141
142 2
        $subjectConfirmation = new SubjectConfirmation();
143
144 2
        $subjectConfirmation->setMethod(
145 2
            Constants::CM_BEARER
146
        );
147
148
149
        // Add Subject Confirmation Data
150 2
        $subjectConfirmation->setSubjectConfirmationData(
151 2
            $subjectConfirmationData = new SubjectConfirmationData()
152
        );
153
154 2
        $subjectConfirmationData->setNotOnOrAfter(
155 2
            (new \DateTime(
156 2
                $settings->messageNotOnOrAfter
157 2
            ))->getTimestamp()
158
        );
159
160 2
        if ($authnRequest) {
161 2
            $subjectConfirmationData->setInResponseTo($authnRequest->getId());
162 2
            $subjectConfirmationData->setRecipient(
163 2
                $authnRequest->getAssertionConsumerServiceURL()
164
            );
165
        }
166
167 2
        $subjectConfirmation->setNameID(
168 2
            $nameId = new NameID()
169
        );
170
171 2
        $nameId->setFormat(Constants::NAMEID_UNSPECIFIED);
172 2
        $nameId->setNameQualifier(
173 2
            $settings->getEntityId()
174
        );
175 2
        $nameId->setValue(
176 2
            $serviceProvider->assignNameId($user)
177
        );
178
179 2
        return $subjectConfirmation;
180
    }
181
182
    /**
183
     * @param Assertion $assertion
184
     * @throws \Exception
185
     */
186 2
    protected function createConditions(
187
        Assertion $assertion,
188
        Settings $settings
189
    ) {
190
        /**
191
         * Conditions
192
         * Reference: https://stackoverflow.com/a/29546696/1590910
193
         *
194
         * The times in <Conditions> is the validity of the entire assertion.
195
         * It should not be consumed after this time. There is nothing preventing a user
196
         * session on an SP to extend beyond this point in time though.
197
         */
198
199 2
        $assertion->setNotBefore(
200 2
            (new \DateTime(
201 2
                $settings->messageNotBefore
202 2
            ))->getTimestamp()
203
        );
204
205 2
        $assertion->setNotOnOrAfter(
206 2
            (new \DateTime(
207 2
                $settings->messageNotOnOrAfter
208 2
            ))->getTimestamp()
209
        );
210 2
    }
211
212
    /**
213
     * @param Assertion $assertion
214
     * @throws \yii\base\Exception
215
     * @throws \yii\base\InvalidConfigException
216
     */
217 2
    protected function createAuthnStatement(Assertion $assertion)
218
    {
219
        /**
220
         * Reference: https://stackoverflow.com/a/29546696/1590910
221
         *
222
         * SessionNotOnOrAfter is something completely different that is not directly related to the lifetime of
223
         * the assertion or the subject. It is a parameter the idp can use to control how long an SP session may be.
224
         * Please note that this parameter is defined that it SHOULD be handled by an SP according to the SAML2Core
225
         * spec, but far from all SP implementations do. An example of an implementation that does is as usual
226
         * Shibboleth, that always will respect the occurence of this parameter. When using Single Logout, this
227
         * parameter is more critical, as it synchronizes the timeout of the session on both the SP and the
228
         * Idp, to ensure that an SP does not issue a logout request for a session no longer known to the Idp.
229
         */
230 2
        $sessionEnd = (new \DateTime())->setTimestamp(
231 2
            ConfigHelper::durationInSeconds(
232
                /**
233
                * Use crafts user session duration
234
                */
235 2
                \Craft::$app->config->getGeneral()->userSessionDuration
236
            )
237
            + // Math!
238 2
            (new \DateTime())->getTimestamp()
239
        );
240
241
        /**
242
         * Add AuthnStatement attributes and AuthnContext
243
         */
244 2
        $assertion->setAuthnInstant((new \DateTime())->getTimestamp());
245 2
        $assertion->setSessionNotOnOrAfter(
246 2
            $sessionEnd->getTimestamp()
247
        );
248
249 2
        $assertion->setSessionIndex(
250 2
            Saml::getInstance()->getSession()->getId()
251
        );
252
253 2
        $assertion->setAuthnContextClassRef(
254 2
            Constants::AC_PASSWORD_PROTECTED_TRANSPORT
255
        );
256 2
    }
257
258 2
    public function setAssertionAttributes(
259
        User $user,
260
        Assertion $assertion,
261
        ProviderRecord $serviceProvider,
262
        Settings $settings
263
    ) {
264
265
        // set on the assertion and the subject confirmations
266 2
        $assertion->setNameID(
267 2
            $nameId = new NameID()
268
        );
269
270 2
        $nameId->setFormat(Constants::NAMEID_UNSPECIFIED);
271 2
        $nameId->setValue(
272 2
            $serviceProvider->assignNameId($user)
273
        );
274
275
276
        // Check the provider first
277
        $attributeMap =
278 2
            $serviceProvider->hasMapping() ? $serviceProvider->getMapping() : $settings->responseAttributeMap;
279
280 2
        $attributes = [];
281 2
        foreach ($attributeMap as $map) {
282 2
            $map = new AttributeMap($map);
283 2
            $attributes[$map->attributeName] = $this->assignProperty(
284 2
                $user,
285 2
                $map
286
            );
287
        }
288
289
        // Add groups if configured
290 2
        if ($serviceProvider->syncGroups &&
0 ignored issues
show
Documentation introduced by
The property syncGroups does not exist on object<flipbox\saml\idp\records\ProviderRecord>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
291 2
            ($groupAttribute = $this->groupsToAttributes($user, $serviceProvider))
292
        ) {
293
            $attributes[$serviceProvider->groupsAttributeName] = $groupAttribute;
0 ignored issues
show
Documentation introduced by
The property groupsAttributeName does not exist on object<flipbox\saml\idp\records\ProviderRecord>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
294
        }
295
296 2
        Saml::debug(json_encode($attributes));
297 2
        $assertion->setAttributes($attributes);
298 2
    }
299
300
    /**
301
     * @param User $user
302
     * @param AbstractProvider $serviceProvider
303
     * @return array|bool
304
     */
305
    protected function groupsToAttributes(User $user, AbstractProvider $serviceProvider)
306
    {
307
        if (count($user->getGroups()) === 0) {
308
            return false;
309
        }
310
        $attribute = [];
311
        foreach ($user->getGroups() as $group) {
312
            $options = $serviceProvider->getGroupOptions();
313
            if (! $options->shouldSync($group->id)) {
314
                continue;
315
            }
316
317
            $attribute[] = $group->handle;
318
        }
319
320
        return $attribute;
321
    }
322
323
    /**
324
     * Utilities
325
     */
326
327
    /**
328
     * @param User $user
329
     * @param $attributeName
330
     * @param $craftProperty
331
     * @return array
332
     */
333 2
    protected function assignProperty(
334
        User $user,
335
        AttributeMap $map
336
    ) {
337 2
        $value = $map->renderValue($user);
338
339 2
        if ($value instanceof \DateTime) {
340
            $value = $value->format(\DateTime::ISO8601);
341
        }
342
343
        return [
344 2
            $map->attributeName => $value,
345
        ];
346
    }
347
348
    /**
349
     * @return User|false|\yii\web\IdentityInterface|null
350
     */
351
    protected function getUser()
352
    {
353
        return \Craft::$app->getUser()->getIdentity();
0 ignored issues
show
Bug introduced by
The method getUser does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
354
    }
355
}
356