Completed
Push — develop ( 784583...9a84aa )
by Michiel
02:05 queued 10s
created

AllowedToSwitchInstitutionVoter::vote()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 9.1768
c 0
b 0
f 0
cc 5
nc 5
nop 3
1
<?php
2
3
/**
4
 * Copyright 2018 SURFnet B.V.
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\StepupRa\RaBundle\Security\Authorization\Voter;
20
21
use InvalidArgumentException;
22
use Surfnet\StepupRa\RaBundle\Service\RaListingService;
23
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
24
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
25
use Symfony\Component\Security\Core\Role\Role;
26
27
/**
28
 * Votes whether or not a RAA user is allowed to see the institution switcher
29
 *
30
 * The ROLE_RAA is allowed to switch institutions when (s)he is:
31
 *  - is RAA
32
 *  - for more than one institution
33
 */
34
class AllowedToSwitchInstitutionVoter implements VoterInterface
35
{
36
    const RAA_SWITCHING = 'raa_switching';
37
38
    /**
39
     * @var RaListingService
40
     */
41
    private $service;
42
43
    public function __construct(RaListingService $service)
44
    {
45
        $this->service = $service;
46
    }
47
48
    /**
49
     * @param TokenInterface $token A TokenInterface instance
50
     * @param $subject not used
51
     * @param array $attributes contains the subject (triggered from twig is_granted function)
52
     * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED
53
     */
54
    public function vote(TokenInterface $token, $subject, array $attributes)
55
    {
56
        // Check if the class of this object is supported by this voter
57
        if (!$this->supportsAttribute(reset($attributes))) {
58
            return VoterInterface::ACCESS_ABSTAIN;
59
        }
60
61
        // This voter allows one attribute to vote on.
62
        if (count($attributes) > 1) {
63
            throw new InvalidArgumentException('Only one attribute is allowed');
64
        }
65
66
        $actorRoles = $token->getRoles();
67
68
        // Does the actor have one of the required roles?
69
        if (!$this->authorizedByRole($actorRoles)) {
70
            return VoterInterface::ACCESS_DENIED;
71
        }
72
73
        $raListing = $this->service->searchBy($token->getUser()->id, $token->getIdentityInstitution());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Securi...on\Token\TokenInterface as the method getIdentityInstitution() does only exist in the following implementations of said interface: Surfnet\StepupRa\RaBundl...ication\Token\SamlToken.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
74
75
        if ($raListing->getTotalItems() >= 1) {
76
            return VoterInterface::ACCESS_GRANTED;
77
        }
78
79
        return VoterInterface::ACCESS_DENIED;
80
    }
81
82
    private function supportsAttribute($attribute)
83
    {
84
        return in_array($attribute, [self::RAA_SWITCHING]);
85
    }
86
87 View Code Duplication
    private function authorizedByRole(array $roles)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
88
    {
89
        $allowedRoles = ['ROLE_SRAA', 'ROLE_RAA'];
90
91
        // Convert the Role[] to an array of strings representing the role names.
92
        $roles = array_map(
93
            function (Role $role) {
94
                return $role->getRole();
95
            },
96
            $roles
97
        );
98
99
        // And test if there is an intersection (is one or more of the token roles also in the allowed roles)
100
        return count(array_intersect($roles, $allowedRoles)) > 0;
101
    }
102
}
103