SamlProvider   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 99
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 43
c 3
b 0
f 0
dl 0
loc 99
rs 10
wmc 13

7 Methods

Rating   Name   Duplication   Size   Complexity  
A loadUserByIdentifier() 0 3 1
A getSingleStringValue() 0 34 4
A refreshUser() 0 3 1
A getNameId() 0 3 1
A __construct() 0 6 1
A supportsClass() 0 3 1
A getUser() 0 29 4
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\Service\IdentityService;
35
use Symfony\Component\Security\Core\User\UserInterface;
36
use Symfony\Component\Security\Core\User\UserProviderInterface;
37
38
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
39
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects) -- Hard to reduce due to different commands and queries used.
40
 */
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...
41
class SamlProvider implements SamlProviderInterface, UserProviderInterface
42
{
43
    public function __construct(
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function __construct()
Loading history...
44
        private readonly IdentityService $identityService,
45
        private readonly AttributeDictionary $attributeDictionary,
46
        private readonly PreferredLocaleProvider $preferredLocaleProvider,
47
        private readonly LoggerInterface $logger
48
    ) {
49
    }
50
51
    /**
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...
52
     * @return string
53
     */
54
    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...
55
    {
56
        $values = $translatedAssertion->getAttributeValue($attribute);
57
58
        if (empty($values)) {
59
            throw new MissingRequiredAttributeException(
60
                sprintf('Missing value for required attribute "%s"', $attribute)
61
            );
62
        }
63
64
        // see https://www.pivotaltracker.com/story/show/121296389
65
        if (count($values) > 1) {
66
            $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...
67
                'Found "%d" values for attribute "%s", using first value',
68
                count($values),
69
                $attribute
70
            ));
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...
71
        }
72
73
        $value = reset($values);
74
75
        if (!is_string($value)) {
76
            $message = sprintf(
77
                'First value of attribute "%s" must be a string, "%s" given',
78
                $attribute,
79
                get_debug_type($value)
80
            );
81
82
            $this->logger->warning($message);
83
84
            throw new MissingRequiredAttributeException($message);
85
        }
86
87
        return $value;
88
    }
89
90
    public function getNameId(Assertion $assertion): string
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getNameId()
Loading history...
91
    {
92
        return $this->attributeDictionary->translate($assertion)->getNameID();
93
    }
94
95
    public function getUser(Assertion $assertion): UserInterface
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getUser()
Loading history...
96
    {
97
        $translatedAssertion = $this->attributeDictionary->translate($assertion);
98
99
        $nameId         = $translatedAssertion->getNameID();
100
        $institution    = $this->getSingleStringValue('schacHomeOrganization', $translatedAssertion);
101
        $email          = $this->getSingleStringValue('mail', $translatedAssertion);
102
        $commonName     = $this->getSingleStringValue('commonName', $translatedAssertion);
103
104
        $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

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