Completed
Pull Request — develop (#110)
by Daan van
10:17 queued 07:23
created

SamlProvider::getCommonName()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 14

Duplication

Lines 27
Ratio 100 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 27
loc 27
rs 8.5806
cc 4
eloc 14
nc 4
nop 1
1
<?php
2
3
/**
4
 * Copyright 2014 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupSelfService\SelfServiceBundle\Security\Authentication\Provider;
20
21
use Psr\Log\LoggerInterface;
22
use Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary;
23
use Surfnet\SamlBundle\SAML2\Response\AssertionAdapter;
24
use Surfnet\StepupMiddlewareClientBundle\Identity\Dto\Identity;
25
use Surfnet\StepupMiddlewareClientBundle\Uuid\Uuid;
26
use Surfnet\StepupSelfService\SelfServiceBundle\Locale\PreferredLocaleProvider;
27
use Surfnet\StepupSelfService\SelfServiceBundle\Security\Authentication\Token\SamlToken;
28
use Surfnet\StepupSelfService\SelfServiceBundle\Service\IdentityService;
29
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
30
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
31
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
32
33
class SamlProvider implements AuthenticationProviderInterface
34
{
35
    /**
36
     * @var \Surfnet\StepupSelfService\SelfServiceBundle\Service\IdentityService
37
     */
38
    private $identityService;
39
40
    /**
41
     * @var \Surfnet\SamlBundle\SAML2\Attribute\AttributeDictionary
42
     */
43
    private $attributeDictionary;
44
45
    /**
46
     * @var \Surfnet\StepupSelfService\SelfServiceBundle\Locale\PreferredLocaleProvider
47
     */
48
    private $preferredLocaleProvider;
49
50
    /**
51
     * @var \Psr\Log\LoggerInterface
52
     */
53
    private $logger;
54
55
    public function __construct(
56
        IdentityService $identityService,
57
        AttributeDictionary $attributeDictionary,
58
        PreferredLocaleProvider $preferredLocaleProvider,
59
        LoggerInterface $logger
60
    ) {
61
        $this->identityService = $identityService;
62
        $this->attributeDictionary = $attributeDictionary;
63
        $this->preferredLocaleProvider = $preferredLocaleProvider;
64
        $this->logger = $logger;
65
    }
66
67
    /**
68
     * @param  SamlToken $token
0 ignored issues
show
Documentation introduced by
Should the type for parameter $token not be TokenInterface?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
69
     * @return TokenInterface|void
70
     */
71
    public function authenticate(TokenInterface $token)
72
    {
73
        $translatedAssertion = $this->attributeDictionary->translate($token->assertion);
0 ignored issues
show
Bug introduced by
Accessing assertion on the interface Symfony\Component\Securi...on\Token\TokenInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
74
75
        $nameId         = $translatedAssertion->getNameID();
76
        $institution    = $this->getSingleStringValue('schacHomeOrganization', $translatedAssertion);
77
        $email          = $this->getSingleStringValue('mail', $translatedAssertion);
78
        $commonName     = $this->getSingleStringValue('commonName', $translatedAssertion);
79
80
        $identity = $this->identityService->findByNameIdAndInstitution($nameId, $institution);
81
82
        if ($identity === null) {
83
            $identity                  = new Identity();
84
            $identity->id              = Uuid::generate();
85
            $identity->nameId          = $nameId;
86
            $identity->institution     = $institution;
87
            $identity->email           = $email;
88
            $identity->commonName      = $commonName;
89
            $identity->preferredLocale = $this->preferredLocaleProvider->providePreferredLocale();
90
91
            $this->identityService->createIdentity($identity);
92
        } elseif ($identity->email !== $email || $identity->commonName !== $commonName) {
93
            $identity->email = $email;
94
            $identity->commonName = $commonName;
95
96
            $this->identityService->updateIdentity($identity);
97
        }
98
99
        $authenticatedToken = new SamlToken(['ROLE_USER']);
100
101
        $authenticatedToken->setUser($identity);
102
103
        return $authenticatedToken;
104
    }
105
106
    public function supports(TokenInterface $token)
107
    {
108
        return $token instanceof SamlToken;
109
    }
110
111
    /**
112
     * @param string           $attribute
113
     * @param AssertionAdapter $translatedAssertion
114
     * @return string
115
     */
116
    private function getSingleStringValue($attribute, AssertionAdapter $translatedAssertion)
117
    {
118
        $values = $translatedAssertion->getAttributeValue($attribute);
119
120
        if (empty($values)) {
121
            throw new BadCredentialsException(sprintf('Missing value for required attribute "%s"', $attribute));
122
        }
123
124
        // see https://www.pivotaltracker.com/story/show/121296389
125
        if (count($values) > 1) {
126
            $this->logger->warning(sprintf(
127
                'Found "%d" values for attribute "%s", using first value',
128
                count($values),
129
                $attribute
130
            ));
131
        }
132
133
        $value = reset($values);
134
135
        if (!is_string($value)) {
136
            $message = sprintf(
137
                'First value of attribute "%s" must be a string, "%s" given',
138
                $attribute,
139
                is_object($value) ? get_class($value) : gettype($value)
140
            );
141
142
            $this->logger->warning($message);
143
144
            throw new BadCredentialsException($message);
145
        }
146
147
        return $value;
148
    }
149
}
150