Completed
Push — feature/EVO-6305_worker_authen... ( 9fae6b )
by
unknown
10:18
created

SecurityAuthenticator::supportsToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 2
eloc 2
nc 2
nop 2
crap 2
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
        return new SecurityToken(
105 10
            'anon.',
106 5
            $apiKey,
107 5
            $providerKey,
108 10
            $this->extractionStrategy->getRoles()
109 5
        );
110
    }
111
112
    /**
113
     * Tries to authenticate the provided token
114
     *
115
     * @param TokenInterface        $token        token to authenticate
116
     * @param UserProviderInterface $userProvider provider to auth against
117
     * @param string                $providerKey  key to auth with
118
     *
119
     * @return SecurityToken
120
     */
121 4
    public function authenticateToken(
0 ignored issues
show
Coding Style introduced by
authenticateToken uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
122
        TokenInterface $token,
123
        UserProviderInterface $userProvider,
124
        $providerKey
125
    ) {
126 4
        $username = $token->getCredentials();
127 4
        $securityUser = false;
128
129
        // If no username in Strategy, check if required.
130 4
        if ($this->securityRequired && !$username) {
131
            $this->logger->warning('Authentication key is required.');
132
            throw new AuthenticationException(
133
                sprintf('Authentication key is required.')
134
            );
135
        }
136
137
        /** @var SecurityUser $securityUser */
138 4
        if ($user = $this->userProvider->loadUserByUsername($username)) {
139 2
            $securityUser = new SecurityUser($user, [SecurityUser::ROLE_USER, SecurityUser::ROLE_CONSULTANT]);
140 3
        } elseif ($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...
141
            $this->logger->info('Authentication, loading graviton subnet user IP address: '. $_SERVER['REMOTE_ADDR']);
142
            $securityUser = new SecurityUser(new SubnetUser($username), [SecurityUser::ROLE_SUBNET]);
143 2
        } elseif ($this->securityTestUsername) {
144
            $this->logger->info('Authentication, loading test user: '.$this->securityTestUsername);
145
            if ($user = $this->userProvider->loadUserByUsername($this->securityTestUsername)) {
146
                $securityUser = new SecurityUser($user, [SecurityUser::ROLE_USER]);
147
            }
148
        }
149
150
        // Check if allow Anonymous
151 4
        if (!$securityUser) {
152 2
            if ($this->allowAnonymous) {
153
                $this->logger->info('Authentication, loading anonymous user.');
154
                $securityUser = new SecurityUser(new AnonymousUser(), [SecurityUser::ROLE_ANONYMOUS]);
155
            } else {
156 2
                $this->logger->warning(sprintf('Authentication key "%s" could not be resolved.', $username));
157 2
                throw new AuthenticationException(
158 2
                    sprintf('Authentication key "%s" could not be resolved.', $username)
159 1
                );
160
            }
161
        }
162
163 2
        return new SecurityToken(
164 1
            $securityUser,
165 1
            $username,
166 1
            $providerKey,
167 2
            $securityUser->getRoles()
168 1
        );
169
    }
170
171
    /**
172
     * @param TokenInterface $token       token to check
173
     * @param string         $providerKey provider to check against
174
     *
175
     * @return bool
176
     */
177 2
    public function supportsToken(TokenInterface $token, $providerKey)
178
    {
179 2
        return $token instanceof SecurityToken && $token->getProviderKey() === $providerKey;
180
    }
181
182
    /**
183
     * This is called when an interactive authentication attempt fails. This is
184
     * called by authentication listeners inheriting from
185
     * AbstractAuthenticationListener.
186
     *
187
     * @param Request                 $request   original request
188
     * @param AuthenticationException $exception exception from auth attempt
189
     *
190
     * @return Response|null
191
     */
192 2
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
193
    {
194 2
        return new Response(
195 2
            $exception->getMessageKey(),
196 1
            Response::HTTP_NETWORK_AUTHENTICATION_REQUIRED
197 1
        );
198
    }
199
}
200