Authorization::formatScopes()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 2
nop 1
1
<?php
2
namespace Chadicus\Slim\OAuth2\Middleware;
3
4
use ArrayAccess;
5
use Chadicus\Slim\OAuth2\Http\RequestBridge;
6
use Chadicus\Slim\OAuth2\Http\ResponseBridge;
7
use Chadicus\Psr\Middleware\MiddlewareInterface;
8
use Psr\Http\Message\ServerRequestInterface;
9
use Psr\Http\Message\ResponseInterface;
10
use Interop\Container\ContainerInterface;
11
use OAuth2;
12
13
/**
14
 * Slim Middleware to handle OAuth2 Authorization.
15
 */
16
class Authorization implements MiddlewareInterface
17
{
18
    /**
19
     * OAuth2 Server
20
     *
21
     * @var OAuth2\Server
22
     */
23
    private $server;
24
25
    /**
26
     * Array of scopes required for authorization.
27
     *
28
     * @var array
29
     */
30
    private $scopes;
31
32
    /**
33
     * Container for token.
34
     *
35
     * @var ArrayAccess|ContainerInterface
36
     */
37
    private $container;
38
39
    /**
40
     * Create a new instance of the Authroization middleware.
41
     *
42
     * @param OAuth2\Server                  $server    The configured OAuth2 server.
43
     * @param ArrayAccess|ContainerInterface $container A container object in which to store the token from the
44
     *                                                  request.
45
     * @param array                          $scopes    Scopes required for authorization. $scopes can be given as an
46
     *                                                  array of arrays. OR logic will use with each grouping.
47
     *                                                  Example:
48
     *                                                  Given ['superUser', ['basicUser', 'aPermission']], the request
49
     *                                                  will be verified if the request token has 'superUser' scope
50
     *                                                  OR 'basicUser' and 'aPermission' as its scope.
51
     *
52
     * @throws \InvalidArgumentException Thrown if $container is not an instance of ArrayAccess or ContainerInterface.
53
     */
54
    public function __construct(OAuth2\Server $server, $container, array $scopes = [])
55
    {
56
        $this->server = $server;
57
        if (!is_a($container, '\\ArrayAccess') && !is_a($container, '\\Interop\\Container\\ContainerInterface')) {
58
            throw new \InvalidArgumentException(
59
                '$container does not implement \\ArrayAccess or \\Interop\\Container\\ContainerInterface'
60
            );
61
        }
62
63
        $this->container = $container;
64
        $this->scopes = $this->formatScopes($scopes);
65
    }
66
67
    /**
68
     * Execute this middleware.
69
     *
70
     * @param  ServerRequestInterface $request  The PSR7 request.
71
     * @param  ResponseInterface      $response The PSR7 response.
72
     * @param  callable               $next     The Next middleware.
73
     *
74
     * @return ResponseInterface
75
     */
76
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
77
    {
78
        $oauth2Request = RequestBridge::toOAuth2($request);
79
        foreach ($this->scopes as $scope) {
80
            if ($this->server->verifyResourceRequest($oauth2Request, null, $scope)) {
81
                $this->setToken($this->server->getResourceController()->getToken());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface OAuth2\Controller\ResourceControllerInterface as the method getToken() does only exist in the following implementations of said interface: OAuth2\Controller\ResourceController, OAuth2\OpenID\Controller\UserInfoController.

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...
82
                return $next($request, $response);
83
            }
84
        }
85
86
        $response = ResponseBridge::fromOauth2($this->server->getResponse());
0 ignored issues
show
Compatibility introduced by
$this->server->getResponse() of type object<OAuth2\ResponseInterface> is not a sub-type of object<OAuth2\Response>. It seems like you assume a concrete implementation of the interface OAuth2\ResponseInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
87
88
        if ($response->hasHeader('Content-Type')) {
89
            return $response;
90
        }
91
92
        return $response->withHeader('Content-Type', 'application/json');
93
    }
94
95
    /**
96
     * Helper method to set the token value in the container instance.
97
     *
98
     * @param array $token The token from the incoming request.
99
     *
100
     * @return void
101
     */
102
    private function setToken(array $token)
103
    {
104
        if (is_a($this->container, '\\ArrayAccess')) {
105
            $this->container['token'] = $token;
106
            return;
107
        }
108
109
        $this->container->set('token', $token);
110
    }
111
112
    /**
113
     * Returns a callable function to be used as a authorization middleware with a specified scope.
114
     *
115
     * @param array $scopes Scopes require for authorization.
116
     *
117
     * @return Authorization
118
     */
119
    public function withRequiredScope(array $scopes)
120
    {
121
        $clone = clone $this;
122
        $clone->scopes = $clone->formatScopes($scopes);
123
        return $clone;
124
    }
125
126
    /**
127
     * Helper method to ensure given scopes are formatted properly.
128
     *
129
     * @param array $scopes Scopes required for authorization.
130
     *
131
     * @return array The formatted scopes array.
132
     */
133
    private function formatScopes(array $scopes)
134
    {
135
        if (empty($scopes)) {
136
            return [null]; //use at least 1 null scope
137
        }
138
139
        array_walk(
140
            $scopes,
141
            function (&$scope) {
142
                if (is_array($scope)) {
143
                    $scope = implode(' ', $scope);
144
                }
145
            }
146
        );
147
148
        return $scopes;
149
    }
150
}
151