UserinfoEndPoint::name()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Parroauth2\Client\OpenID\EndPoint\Userinfo;
4
5
use BadMethodCallException;
6
use InvalidArgumentException;
7
use Parroauth2\Client\ClientInterface;
8
use Parroauth2\Client\EndPoint\CallableEndPointInterface;
9
use Parroauth2\Client\EndPoint\EndPointParametersTrait;
10
use Parroauth2\Client\EndPoint\EndPointResponseListenerTrait;
11
use Parroauth2\Client\EndPoint\EndPointTransformerInterface;
12
use Psr\Http\Message\RequestInterface;
13
14
/**
15
 * Endpoint for get information about the user of the access token
16
 *
17
 * @see https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
18
 *
19
 * @implements CallableEndPointInterface<UserinfoResponse>
20
 */
21
class UserinfoEndPoint implements CallableEndPointInterface
22
{
23
    use EndPointParametersTrait;
24
    /** @use EndPointResponseListenerTrait<UserinfoResponse> */
25
    use EndPointResponseListenerTrait;
26
27
    public const NAME = 'userinfo';
28
29
    public const AUTH_METHOD_HEADER = 'header';
30
    public const AUTH_METHOD_BODY = 'body';
31
    public const AUTH_METHOD_QUERY = 'query';
32
33
    /**
34
     * @var ClientInterface
35
     * @readonly
36
     */
37
    private $client;
38
39
    /**
40
     * The current access token
41
     *
42
     * @var string|null
43
     * @readonly
44
     */
45
    private $accessToken = null;
46
47
    /**
48
     * The authentication method to use
49
     * The value must be one of the AUTH_METHOD_* constants
50
     *
51
     * @var string
52
     * @readonly
53
     */
54
    private $method = self::AUTH_METHOD_HEADER;
55
56
57
    /**
58
     * UserinfoEndPoint constructor.
59
     *
60
     * @param ClientInterface $client
61
     */
62 117
    public function __construct(ClientInterface $client)
63
    {
64 117
        $this->client = $client;
65 117
    }
66
67
    /**
68
     * {@inheritdoc}
69
     *
70
     * @psalm-mutation-free
71
     */
72 117
    public function name(): string
73
    {
74 117
        return self::NAME;
75
    }
76
77
    /**
78
     * {@inheritdoc}
79
     */
80 4
    public function apply(EndPointTransformerInterface $transformer)
81
    {
82 4
        return $transformer->onUserinfo($this);
83
    }
84
85
    /**
86
     * Set the access token
87
     *
88
     * @param string $token
89
     *
90
     * @return static
91
     *
92
     * @psalm-mutation-free
93
     */
94 16
    public function token(string $token): self
95
    {
96 16
        $endpoint = clone $this;
97
98 16
        $endpoint->accessToken = $token;
99
100 16
        return $endpoint;
101
    }
102
103
    /**
104
     * Define the authentication method for the bearer token
105
     *
106
     * @param UserinfoEndPoint::AUTH_METHOD_* $method One of the UserinfoEndPoint::AUTH_METHOD_* constant
107
     *
108
     * @return static
109
     *
110
     * @psalm-mutation-free
111
     */
112 8
    public function authenticationMethod(string $method): self
113
    {
114 8
        $endpoint = clone $this;
115
116 8
        $endpoint->method = $method;
117
118 8
        return $endpoint;
119
    }
120
121
    /**
122
     * Provide the token into the authorization header (recommended)
123
     *
124
     * @return static
125
     *
126
     * @psalm-mutation-free
127
     */
128 2
    public function inHeader(): self
129
    {
130 2
        return $this->authenticationMethod(self::AUTH_METHOD_HEADER);
131
    }
132
133
    /**
134
     * Provide the token as a body parameter
135
     *
136
     * @return static
137
     *
138
     * @psalm-mutation-free
139
     */
140 2
    public function inBody(): self
141
    {
142 2
        return $this->authenticationMethod(self::AUTH_METHOD_BODY);
143
    }
144
145
    /**
146
     * Provide the token as a query parameter (not recommended)
147
     *
148
     * @return static
149
     *
150
     * @psalm-mutation-free
151
     */
152 2
    public function inQuery(): self
153
    {
154 2
        return $this->authenticationMethod(self::AUTH_METHOD_QUERY);
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     *
160
     * @todo handle the JWT response
161
     */
162 17
    public function call(): UserinfoResponse
163
    {
164 17
        $response = $this->client->provider()->sendRequest($this->request());
165 12
        $contentType = strtolower(trim(explode(';', $response->getHeaderLine('Content-Type'))[0]));
166
167 12
        switch ($contentType) {
168 12
            case 'application/json':
169 11
                $response = new UserinfoResponse(json_decode((string) $response->getBody(), true));
170 11
                break;
171
172
            default:
173 1
                throw new BadMethodCallException('The Content-Type ' . $contentType . ' is not supported');
174
        }
175
176 11
        $this->callResponseListeners($response);
177 11
        return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type Parroauth2\Client\OpenID...erinfo\UserinfoResponse which is incompatible with the return type mandated by Parroauth2\Client\EndPoi...dPointInterface::call() of Parroauth2\Client\EndPoint\T.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
178
    }
179
180
    /**
181
     * Get the request for the userinfo endpoint
182
     * Define the token parameter following the Bearer token usage
183
     *
184
     * @return RequestInterface
185
     * @throws \Parroauth2\Client\Exception\UnsupportedServerOperation
186
     */
187 17
    private function request(): RequestInterface
188
    {
189 17
        if (!$this->accessToken) {
190 1
            throw new BadMethodCallException('No access token has been provided');
191
        }
192
193 16
        switch ($this->method) {
194
            // @see https://tools.ietf.org/html/rfc6750#section-2.1
195 16
            case self::AUTH_METHOD_HEADER:
196 10
                return $this->client->endPoints()
197 10
                    ->request('GET', $this)
198 10
                    ->withHeader('Authorization', 'Bearer ' . $this->accessToken)
199
                ;
200
201
            // @see https://tools.ietf.org/html/rfc6750#section-2.2
202 6
            case self::AUTH_METHOD_BODY:
203 2
                return $this->client->endPoints()->request('POST', $this->set('access_token', $this->accessToken));
204
205
            // @see https://tools.ietf.org/html/rfc6750#section-2.3
206 4
            case self::AUTH_METHOD_QUERY:
207 3
                return $this->client->endPoints()
208 3
                    ->request('GET', $this->set('access_token', $this->accessToken))
209 3
                    ->withHeader('Cache-Control', 'no-store')
210
                ;
211
212
            default:
213 1
                throw new InvalidArgumentException('Unsupported authorization method ' . $this->method);
214
        }
215
    }
216
}
217