Failed Conditions
Pull Request — master (#26)
by
unknown
02:32
created

Authorization::formatScopes()   A

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 DI;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Psr\Http\Message\ResponseInterface;
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
36
     */
37
    private $container;
38
39
    /**
40
     * Create a new instance of the Authorization middleware.
41
     *
42
     * @param OAuth2\Server $server    The configured OAuth2 server.
43
     * @param DI\Container  $container A container object in which to store the token from the request.
44
     * @param array         $scopes    Scopes required for authorization. $scopes can be given as an array of arrays. OR
45
     *                                 logic will use with each grouping.  Example:
46
     *                                 Given ['superUser', ['basicUser', 'aPermission']], the request will be verified
47
     *                                 if the request token has 'superUser' scope OR 'basicUser' and 'aPermission' as
48
     *                                 its scope.
49
     */
50
    public function __construct(OAuth2\Server $server, DI\Container $container, array $scopes = [])
51
    {
52
        $this->server = $server;
53
        $this->container = $container;
0 ignored issues
show
Documentation Bug introduced by
It seems like $container of type object<DI\Container> is incompatible with the declared type object<ArrayAccess> of property $container.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
54
        $this->scopes = $this->formatScopes($scopes);
55
    }
56
57
    /**
58
     * Execute this middleware.
59
     *
60
     * @param  ServerRequestInterface $request  The PSR7 request.
61
     * @param  ResponseInterface      $response The PSR7 response.
62
     * @param  callable               $next     The Next middleware.
63
     *
64
     * @return ResponseInterface
65
     */
66
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
67
    {
68
        $oauth2Request = RequestBridge::toOAuth2($request);
69
        foreach ($this->scopes as $scope) {
70
            if ($this->server->verifyResourceRequest($oauth2Request, null, $scope)) {
71
                $this->container->set('token', $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...
Bug introduced by
It seems like you code against a concrete implementation and not the interface ArrayAccess as the method set() does only exist in the following implementations of said interface: Guzzle\Common\Collection, Guzzle\Http\QueryString, Guzzle\Service\Builder\ServiceBuilder, Guzzle\Service\Command\AbstractCommand, Guzzle\Service\Command\ClosureCommand, Guzzle\Service\Command\OperationCommand, Guzzle\Service\Resource\Model, Guzzle\Tests\Service\Mock\Command\IterableCommand, Guzzle\Tests\Service\Mock\Command\MockCommand, Guzzle\Tests\Service\Mock\Command\OtherCommand, Guzzle\Tests\Service\Mock\Command\Sub\Sub, HttpQueryString.

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...
72
                return $next($request, $response);
73
            }
74
        }
75
76
        $response = ResponseBridge::fromOAuth2($this->server->getResponse());
77
78
        if ($response->hasHeader('Content-Type')) {
79
            return $response;
80
        }
81
82
        return $response->withHeader('Content-Type', 'application/json');
83
    }
84
85
    /**
86
     * Returns a callable function to be used as a authorization middleware with a specified scope.
87
     *
88
     * @param array $scopes Scopes require for authorization.
89
     *
90
     * @return Authorization
91
     */
92
    public function withRequiredScope(array $scopes)
93
    {
94
        $clone = clone $this;
95
        $clone->scopes = $clone->formatScopes($scopes);
96
        return $clone;
97
    }
98
99
    /**
100
     * Helper method to ensure given scopes are formatted properly.
101
     *
102
     * @param array $scopes Scopes required for authorization.
103
     *
104
     * @return array The formatted scopes array.
105
     */
106
    private function formatScopes(array $scopes)
107
    {
108
        if (empty($scopes)) {
109
            return [null]; //use at least 1 null scope
110
        }
111
112
        array_walk(
113
            $scopes,
114
            function (&$scope) {
115
                if (is_array($scope)) {
116
                    $scope = implode(' ', $scope);
117
                }
118
            }
119
        );
120
121
        return $scopes;
122
    }
123
}
124