Completed
Push — feature/EVO-6305_worker_authen... ( 91cef1...6f533e )
by Bastian
09:27
created

SecurityAuthenticator::createToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
ccs 10
cts 10
cp 1
rs 9.4285
cc 1
eloc 9
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * auth interface for authing against an airlock key of some sorts
4
 */
5
6
namespace Graviton\SecurityBundle\Authentication;
7
8
use Graviton\SecurityBundle\Authentication\Strategies\StrategyInterface;
9
use Graviton\SecurityBundle\Authentication\Provider\AuthenticationProvider;
10
use Graviton\SecurityBundle\Authentication\Token\SecurityToken;
11
use Graviton\SecurityBundle\Entities\AnonymousUser;
12
use Graviton\SecurityBundle\Entities\SecurityUser;
13
use Graviton\SecurityBundle\Entities\SubnetUser;
14
use Psr\Log\LoggerInterface as Logger;
15
use Symfony\Component\HttpFoundation\Request;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface;
18
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19
use Symfony\Component\Security\Core\Exception\AuthenticationException;
20
use Symfony\Component\Security\Core\User\UserProviderInterface;
21
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
22
23
/**
24
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
25
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
26
 * @link     http://swisscom.ch
27
 */
28
final class SecurityAuthenticator implements
29
    SimplePreAuthenticatorInterface,
0 ignored issues
show
Deprecated Code introduced by
The interface Symfony\Component\Securi...eAuthenticatorInterface has been deprecated with message: Since version 2.8, to be removed in 3.0. Use the same interface from Security\Http\Authentication instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
30
    AuthenticationFailureHandlerInterface
31
{
32
33
    /**
34
     * Authentication can be required to use any service
35
     * @var bool,
36
     */
37
    protected $securityRequired;
38
39
    /**
40
     * Authentication can use a test user if no user found
41
     * @var bool,
42
     */
43
    protected $securityTestUsername;
44
45
    /**
46
     * Authentication can allow not identified users to get information
47
     * @var bool,
48
     */
49
    protected $allowAnonymous;
50
51
    /**
52
     * @var AuthenticationProvider
53
     */
54
    protected $userProvider;
55
56
    /**
57
     * @var StrategyInterface
58
     */
59
    protected $extractionStrategy;
60
61
    /**
62
     * @var Logger
63
     */
64
    protected $logger;
65
66
67
    /**
68
     * @param boolean                $securityRequired     user provider to use
69
     * @param string                 $securityTestUsername user for testing
70
     * @param boolean                $allowAnonymous       user provider to use
71
     * @param AuthenticationProvider $userProvider         user provider to use
72
     * @param StrategyInterface      $extractionStrategy   auth strategy to use
73
     * @param Logger                 $logger               logger to user for logging errors
74
     */
75 20
    public function __construct(
76
        $securityRequired,
77
        $securityTestUsername,
78
        $allowAnonymous,
79
        AuthenticationProvider $userProvider,
80
        StrategyInterface $extractionStrategy,
81
        Logger $logger
82
    ) {
83
84 20
        $this->securityRequired     = $securityRequired;
85 20
        $this->securityTestUsername = $securityTestUsername;
86 20
        $this->allowAnonymous       = $allowAnonymous;
87 20
        $this->userProvider         = $userProvider;
88 20
        $this->extractionStrategy   = $extractionStrategy;
89
90 20
        $this->logger = $logger;
91 20
    }
92
93
    /**
94
     * @param Request $request     request to authenticate
95
     * @param string  $providerKey provider key to auth with
96
     *
97
     * @return SecurityToken
98
     */
99 10
    public function createToken(Request $request, $providerKey)
100
    {
101
        // look for an apikey query parameter
102 10
        $apiKey = $this->extractionStrategy->apply($request);
103
104 10
        $token = new SecurityToken(
105 10
            'anon.',
106 5
            $apiKey,
107 5
            $providerKey,
108 10
            $this->extractionStrategy->getRoles()
109 5
        );
110
111 10
        $token->setAttribute('ipAddress', $request->getClientIp());
112
113 10
        return $token;
114
    }
115
116
    /**
117
     * Tries to authenticate the provided token
118
     *
119
     * @param TokenInterface        $token        token to authenticate
120
     * @param UserProviderInterface $userProvider provider to auth against
121
     * @param string                $providerKey  key to auth with
122
     *
123
     * @return SecurityToken
124
     */
125 4
    public function authenticateToken(
126
        TokenInterface $token,
127
        UserProviderInterface $userProvider,
128
        $providerKey
129
    ) {
130 4
        $username = $token->getCredentials();
131 4
        $securityUser = false;
132
133
        // If no username in Strategy, check if required.
134 4
        if ($this->securityRequired && !$username) {
135
            $this->logger->warning('Authentication key is required.');
136
            throw new AuthenticationException(
137
                sprintf('Authentication key is required.')
138
            );
139
        }
140
141
        /** @var SecurityUser $securityUser */
142 4
        if ($token->hasRole(SecurityUser::ROLE_SUBNET)) {
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 hasRole() does only exist in the following implementations of said interface: Graviton\SecurityBundle\...ion\Token\SecurityToken.

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...
143
            $this->logger->info('Authentication, loading graviton subnet user IP address: '. $token->getAttribute('ipAddress'));
144
            $securityUser = new SecurityUser(new SubnetUser($username), [SecurityUser::ROLE_SUBNET]);
145 4 View Code Duplication
        } elseif ($user = $this->userProvider->loadUserByUsername($username)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
146 2
            $securityUser = new SecurityUser($user, [SecurityUser::ROLE_USER, SecurityUser::ROLE_CONSULTANT]);
147 3
        } elseif ($this->securityTestUsername) {
148
            $this->logger->info('Authentication, loading test user: '.$this->securityTestUsername);
149 View Code Duplication
            if ($user = $this->userProvider->loadUserByUsername($this->securityTestUsername)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
150
                $securityUser = new SecurityUser($user, [SecurityUser::ROLE_USER]);
151
            }
152
        }
153
154
        // Check if allow Anonymous
155 4
        if (!$securityUser) {
156 2
            if ($this->allowAnonymous) {
157
                $this->logger->info('Authentication, loading anonymous user.');
158
                $securityUser = new SecurityUser(new AnonymousUser(), [SecurityUser::ROLE_ANONYMOUS]);
159
            } else {
160 2
                $this->logger->warning(sprintf('Authentication key "%s" could not be resolved.', $username));
161 2
                throw new AuthenticationException(
162 2
                    sprintf('Authentication key "%s" could not be resolved.', $username)
163 1
                );
164
            }
165
        }
166
167 2
        return new SecurityToken(
168 1
            $securityUser,
169 1
            $username,
170 1
            $providerKey,
171 2
            $securityUser->getRoles()
172 1
        );
173
    }
174
175
    /**
176
     * @param TokenInterface $token       token to check
177
     * @param string         $providerKey provider to check against
178
     *
179
     * @return bool
180
     */
181 2
    public function supportsToken(TokenInterface $token, $providerKey)
182
    {
183 2
        return $token instanceof SecurityToken && $token->getProviderKey() === $providerKey;
184
    }
185
186
    /**
187
     * This is called when an interactive authentication attempt fails. This is
188
     * called by authentication listeners inheriting from
189
     * AbstractAuthenticationListener.
190
     *
191
     * @param Request                 $request   original request
192
     * @param AuthenticationException $exception exception from auth attempt
193
     *
194
     * @return Response|null
195
     */
196 2
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
197
    {
198 2
        return new Response(
199 2
            $exception->getMessageKey(),
200 1
            Response::HTTP_NETWORK_AUTHENTICATION_REQUIRED
201 1
        );
202
    }
203
}
204