Passed
Pull Request — main (#308)
by Paul
16:16 queued 09:07
created

SamlProvider::supportsClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types = 1);
4
5
/**
6
 * Copyright 2014 SURFnet bv
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 *     http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
Coding Style introduced by
Missing @link tag in file comment
Loading history...
20
21
namespace Surfnet\StepupSelfService\SelfServiceBundle\Security\Authentication\Provider;
22
23
use BadMethodCallException;
24
use Psr\Log\LoggerInterface;
25
use SAML2\Assertion;
26
use Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary;
27
use Surfnet\SamlBundle\SAML2\Response\AssertionAdapter;
28
use Surfnet\SamlBundle\Security\Authentication\Provider\SamlProviderInterface;
29
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\Identity;
30
use Surfnet\StepupMiddlewareClientBundle\Uuid\Uuid;
31
use Surfnet\StepupSelfService\SelfServiceBundle\Exception\MissingRequiredAttributeException;
32
use Surfnet\StepupSelfService\SelfServiceBundle\Locale\PreferredLocaleProvider;
33
use Surfnet\StepupSelfService\SelfServiceBundle\Security\Authentication\AuthenticatedIdentity;
34
use Surfnet\StepupSelfService\SelfServiceBundle\Security\Authentication\Token\SamlToken;
35
use Surfnet\StepupSelfService\SelfServiceBundle\Service\IdentityService;
36
use Symfony\Component\Security\Core\User\UserInterface;
37
use Symfony\Component\Security\Core\User\UserProviderInterface;
38
39
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
40
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) -- Hard to reduce due to different commands and queries used.
41
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
42
class SamlProvider implements SamlProviderInterface, UserProviderInterface
43
{
44
    public function __construct(
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
45
        private readonly IdentityService $identityService,
46
        private readonly AttributeDictionary $attributeDictionary,
47
        private readonly PreferredLocaleProvider $preferredLocaleProvider,
48
        private readonly LoggerInterface $logger
49
    ) {
50
    }
51
52
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $attribute should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $translatedAssertion should have a doc-comment as per coding-style.
Loading history...
53
     * @return string
54
     */
55
    private function getSingleStringValue(string $attribute, AssertionAdapter $translatedAssertion): string
0 ignored issues
show
Coding Style introduced by
Private method name "SamlProvider::getSingleStringValue" must be prefixed with an underscore
Loading history...
56
    {
57
        $values = $translatedAssertion->getAttributeValue($attribute);
58
59
        if (empty($values)) {
60
            throw new MissingRequiredAttributeException(
61
                sprintf('Missing value for required attribute "%s"', $attribute)
62
            );
63
        }
64
65
        // see https://www.pivotaltracker.com/story/show/121296389
66
        if (count($values) > 1) {
67
            $this->logger->warning(sprintf(
0 ignored issues
show
Coding Style introduced by
The opening parenthesis of a multi-line function call should be the last content on the line.
Loading history...
68
                'Found "%d" values for attribute "%s", using first value',
69
                count($values),
70
                $attribute
71
            ));
0 ignored issues
show
Coding Style introduced by
For multi-line function calls, the closing parenthesis should be on a new line.

If a function call spawns multiple lines, the coding standard suggests to move the closing parenthesis to a new line:

someFunctionCall(
    $firstArgument,
    $secondArgument,
    $thirdArgument
); // Closing parenthesis on a new line.
Loading history...
72
        }
73
74
        $value = reset($values);
75
76
        if (!is_string($value)) {
77
            $message = sprintf(
78
                'First value of attribute "%s" must be a string, "%s" given',
79
                $attribute,
80
                get_debug_type($value)
81
            );
82
83
            $this->logger->warning($message);
84
85
            throw new MissingRequiredAttributeException($message);
86
        }
87
88
        return $value;
89
    }
90
91
    public function getNameId(Assertion $assertion): string
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getNameId()
Loading history...
92
    {
93
        return $this->attributeDictionary->translate($assertion)->getNameID();
94
    }
95
96
    public function getUser(Assertion $assertion): UserInterface
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getUser()
Loading history...
97
    {
98
        $translatedAssertion = $this->attributeDictionary->translate($assertion);
99
100
        $nameId         = $translatedAssertion->getNameID();
101
        $institution    = $this->getSingleStringValue('schacHomeOrganization', $translatedAssertion);
102
        $email          = $this->getSingleStringValue('mail', $translatedAssertion);
103
        $commonName     = $this->getSingleStringValue('commonName', $translatedAssertion);
104
105
        $identity = $this->identityService->findByNameIdAndInstitution($nameId, $institution);
0 ignored issues
show
Bug introduced by
It seems like $nameId can also be of type null; however, parameter $nameId of Surfnet\StepupSelfServic...yNameIdAndInstitution() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

105
        $identity = $this->identityService->findByNameIdAndInstitution(/** @scrutinizer ignore-type */ $nameId, $institution);
Loading history...
106
107
        if (!$identity instanceof Identity) {
108
            $identity                  = new Identity();
109
            $identity->id              = Uuid::generate();
110
            $identity->nameId          = $nameId;
111
            $identity->institution     = $institution;
112
            $identity->email           = $email;
113
            $identity->commonName      = $commonName;
114
            $identity->preferredLocale = $this->preferredLocaleProvider->providePreferredLocale();
115
116
            $this->identityService->createIdentity($identity);
117
        } elseif ($identity->email !== $email || $identity->commonName !== $commonName) {
118
            $identity->email = $email;
119
            $identity->commonName = $commonName;
120
121
            $this->identityService->updateIdentity($identity);
122
        }
123
124
        $authenticatedIdentity = new AuthenticatedIdentity($identity);
125
        $authenticatedToken = new SamlToken(['ROLE_USER']);
126
127
        $authenticatedToken->setUser($authenticatedIdentity);
128
129
//        return $authenticatedToken;
0 ignored issues
show
Coding Style introduced by
Line indented incorrectly; expected at least 8 spaces, found 0
Loading history...
130
        return $authenticatedIdentity;
131
    }
132
133
134
    public function refreshUser(UserInterface $user): UserInterface
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function refreshUser()
Loading history...
135
    {
136
        // TODO: Implement refreshUser() method.
137
        return $user;
138
    }
139
140
    public function supportsClass(string $class): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function supportsClass()
Loading history...
141
    {
142
        return $class === AuthenticatedIdentity::class;
143
    }
144
145
    public function loadUserByIdentifier(string $identifier): UserInterface
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function loadUserByIdentifier()
Loading history...
146
    {
147
        throw new BadMethodCallException('Use `getUser` to load a user by username');
148
    }
149
}
150