Skip to content

Commit d1b1c0e

Browse files
authored
Merge pull request #11 from kduma-forks/master
Implement „Device flow” authentication
2 parents 8d88190 + 87336af commit d1b1c0e

10 files changed

+304
-7
lines changed

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,54 @@ If you use IDE with typehinting such as PHPStorm, you'll easily
109109
figure it out. If not, please
110110
[take a look in Resource directory](src/Resource)
111111

112+
## Device Flow
113+
114+
```php
115+
use Imper86\PhpAllegroApi\AllegroApi;
116+
use Imper86\PhpAllegroApi\Model\Credentials;
117+
use Imper86\PhpAllegroApi\Oauth\FileTokenRepository;
118+
use Imper86\PhpAllegroApi\Plugin\AuthenticationPlugin;
119+
120+
// first, create Credentials object
121+
$credentials = new Credentials(
122+
'your-client-id',
123+
'your-client-secret',
124+
'your-redirect-uri',
125+
true //is sandbox
126+
);
127+
128+
// create api client
129+
$api = new AllegroApi($credentials);
130+
131+
// Create authorization session
132+
$session = $api->oauth()->getDeviceCode();
133+
134+
// Provide device code and/or url to user
135+
echo 'Please visit: ' . $session->getVerificationUriComplete();
136+
137+
// Poll for authorization result
138+
$interval = $session->getInterval();
139+
$token = false;
140+
do {
141+
sleep($interval);
142+
$device_code = $session->getDeviceCode();
143+
try{
144+
$token = $api->oauth()->fetchTokenWithDeviceCode($device_code);
145+
} catch (AuthorizationPendingException) {
146+
continue;
147+
} catch (SlowDownException) {
148+
$interval++;
149+
continue;
150+
}
151+
} while ($token == false);
152+
153+
// create TokenRepository object
154+
$tokenRepository = new FileTokenRepository(
155+
$token->getUserId(),
156+
__DIR__ . '/tokens'
157+
);
158+
$tokenRepository->save($token);
159+
```
160+
112161
## Contributing
113162
Any help will be very appreciated :)

src/Enum/GrantType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ final class GrantType
99
public const AUTHORIZATION_CODE = 'authorization_code';
1010
public const REFRESH_TOKEN = 'refresh_token';
1111
public const CLIENT_CREDENTIALS = 'client_credentials';
12-
public const DEVICE_CODE = 'device_code';
13-
}
12+
public const DEVICE_CODE = 'urn:ietf:params:oauth:grant-type:device_code';
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Imper86\PhpAllegroApi\Exceptions;
4+
5+
class AccessDeniedException extends BaseDeviceFlowException
6+
{
7+
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Imper86\PhpAllegroApi\Exceptions;
4+
5+
class AuthorizationPendingException extends BaseDeviceFlowException
6+
{
7+
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Imper86\PhpAllegroApi\Exceptions;
4+
5+
abstract class BaseDeviceFlowException extends \Exception
6+
{
7+
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Imper86\PhpAllegroApi\Exceptions;
4+
5+
class SlowDownException extends BaseDeviceFlowException
6+
{
7+
8+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace Imper86\PhpAllegroApi\Model;
4+
5+
class DeviceFlowAuthSession implements DeviceFlowAuthSessionInterface
6+
{
7+
/**
8+
* @var string
9+
*/
10+
private $deviceCode;
11+
12+
/**
13+
* @var string
14+
*/
15+
private $userCode;
16+
17+
/**
18+
* @var string
19+
*/
20+
private $verificationUri;
21+
22+
/**
23+
* @var int
24+
*/
25+
private $expiresIn;
26+
27+
/**
28+
* @var int
29+
*/
30+
private $interval;
31+
32+
/**
33+
* @var string
34+
*/
35+
private $verificationUriComplete;
36+
37+
/**
38+
* @param string $deviceCode
39+
* @param int $expiresIn
40+
* @param string $userCode
41+
* @param int $interval
42+
* @param string $verificationUri
43+
* @param string $verificationUriComplete
44+
*/
45+
public function __construct(
46+
string $deviceCode,
47+
int $expiresIn,
48+
string $userCode,
49+
int $interval,
50+
string $verificationUri,
51+
string $verificationUriComplete)
52+
{
53+
$this->deviceCode = $deviceCode;
54+
$this->userCode = $userCode;
55+
$this->verificationUri = $verificationUri;
56+
$this->expiresIn = $expiresIn;
57+
$this->interval = $interval;
58+
$this->verificationUriComplete = $verificationUriComplete;
59+
}
60+
61+
/**
62+
* @return string
63+
*/
64+
public function getDeviceCode(): string
65+
{
66+
return $this->deviceCode;
67+
}
68+
69+
/**
70+
* @return string
71+
*/
72+
public function getUserCode(): string
73+
{
74+
return $this->userCode;
75+
}
76+
77+
/**
78+
* @return string
79+
*/
80+
public function getVerificationUri(): string
81+
{
82+
return $this->verificationUri;
83+
}
84+
85+
/**
86+
* @return int
87+
*/
88+
public function getExpiresIn(): int
89+
{
90+
return $this->expiresIn;
91+
}
92+
93+
/**
94+
* @return int
95+
*/
96+
public function getInterval(): int
97+
{
98+
return $this->interval;
99+
}
100+
101+
/**
102+
* @return string
103+
*/
104+
public function getVerificationUriComplete(): string
105+
{
106+
return $this->verificationUriComplete;
107+
}
108+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
namespace Imper86\PhpAllegroApi\Model;
4+
5+
interface DeviceFlowAuthSessionInterface
6+
{
7+
/**
8+
* @return string
9+
*/
10+
public function getVerificationUri(): string;
11+
12+
/**
13+
* @return string
14+
*/
15+
public function getUserCode(): string;
16+
17+
/**
18+
* @return int
19+
*/
20+
public function getInterval(): int;
21+
22+
/**
23+
* @return int
24+
*/
25+
public function getExpiresIn(): int;
26+
27+
/**
28+
* @return string
29+
*/
30+
public function getDeviceCode(): string;
31+
32+
/**
33+
* @return string
34+
*/
35+
public function getVerificationUriComplete(): string;
36+
}

src/Oauth/OauthClient.php

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@
88

99
namespace Imper86\PhpAllegroApi\Oauth;
1010

11+
use Http\Client\Common\Exception\ClientErrorException;
1112
use Http\Client\Common\Plugin;
1213
use Http\Client\Common\Plugin\ErrorPlugin;
1314
use Imper86\HttpClientBuilder\Builder;
1415
use Imper86\HttpClientBuilder\BuilderInterface;
1516
use Imper86\PhpAllegroApi\Enum\ContentType;
1617
use Imper86\PhpAllegroApi\Enum\EndpointHost;
1718
use Imper86\PhpAllegroApi\Enum\GrantType;
19+
use Imper86\PhpAllegroApi\Exceptions\AccessDeniedException;
20+
use Imper86\PhpAllegroApi\Exceptions\AuthorizationPendingException;
21+
use Imper86\PhpAllegroApi\Exceptions\SlowDownException;
1822
use Imper86\PhpAllegroApi\Model\CredentialsInterface;
23+
use Imper86\PhpAllegroApi\Model\DeviceFlowAuthSession;
1924
use Imper86\PhpAllegroApi\Model\TokenInterface;
2025
use Imper86\PhpAllegroApi\Plugin\OauthUriPlugin;
2126
use Imper86\PhpAllegroApi\Plugin\SandboxUriPlugin;
27+
use Psr\Http\Client\ClientExceptionInterface;
2228
use Psr\Http\Message\RequestInterface;
2329
use Psr\Http\Message\UriInterface;
2430

@@ -84,6 +90,50 @@ public function getAuthorizationUri(
8490
return $uri;
8591
}
8692

93+
public function getDeviceCode(
94+
?array $scope = null
95+
): DeviceFlowAuthSession {
96+
$query = [
97+
'client_id' => $this->credentials->getClientId(),
98+
];
99+
100+
if ($scope) {
101+
$query['scope'] = implode(' ', $scope);
102+
}
103+
104+
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query, '/auth/oauth/device'));
105+
$body = json_decode($response->getBody()->__toString(), true);
106+
107+
return new DeviceFlowAuthSession($body['device_code'], $body['expires_in'], $body['user_code'], $body['interval'], $body['verification_uri'], $body['verification_uri_complete']);
108+
}
109+
110+
public function fetchTokenWithDeviceCode(string $code): TokenInterface
111+
{
112+
$query = [
113+
'grant_type' => GrantType::DEVICE_CODE,
114+
'device_code' => $code,
115+
];
116+
117+
try {
118+
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query, '/auth/oauth/token'));
119+
} catch (ClientErrorException $clientErrorException) {
120+
$response = $clientErrorException->getResponse();
121+
$body = json_decode($response->getBody()->__toString(), true);
122+
123+
if ($body['error'] == 'slow_down') {
124+
throw new SlowDownException($body['error_description'], 0, $clientErrorException);
125+
} elseif ($body['error'] == 'authorization_pending') {
126+
throw new AuthorizationPendingException($body['error_description'], 0, $clientErrorException);
127+
} elseif ($body['error'] == 'access_denied') {
128+
throw new AccessDeniedException($body['error_description'], 0, $clientErrorException);
129+
} else {
130+
throw $clientErrorException;
131+
}
132+
}
133+
134+
return $this->tokenFactory->createFromResponse($response, GrantType::DEVICE_CODE);
135+
}
136+
87137
public function fetchTokenWithCode(string $code): TokenInterface
88138
{
89139
$query = [
@@ -92,7 +142,7 @@ public function fetchTokenWithCode(string $code): TokenInterface
92142
'redirect_uri' => $this->credentials->getRedirectUri(),
93143
];
94144

95-
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query));
145+
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query, '/auth/oauth/token'));
96146

97147
return $this->tokenFactory->createFromResponse($response, GrantType::AUTHORIZATION_CODE);
98148
}
@@ -105,15 +155,15 @@ public function fetchTokenWithRefreshToken(string $refreshToken): TokenInterface
105155
'redirect_uri' => $this->credentials->getRedirectUri(),
106156
];
107157

108-
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query));
158+
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query, '/auth/oauth/token'));
109159

110160
return $this->tokenFactory->createFromResponse($response, GrantType::REFRESH_TOKEN);
111161
}
112162

113163
public function fetchTokenWithClientCredentials(): TokenInterface
114164
{
115165
$query = ['grant_type' => GrantType::CLIENT_CREDENTIALS];
116-
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query));
166+
$response = $this->builder->getHttpClient()->sendRequest($this->generateRequest($query, '/auth/oauth/token'));
117167

118168
return $this->tokenFactory->createFromResponse($response, GrantType::CLIENT_CREDENTIALS);
119169
}
@@ -130,13 +180,14 @@ public function removePlugin(string $fqcn): void
130180

131181
/**
132182
* @param string[] $query
183+
* @param string $path
133184
* @return RequestInterface
134185
*/
135-
private function generateRequest(array $query): RequestInterface
186+
private function generateRequest(array $query, string $path): RequestInterface
136187
{
137188
$uri = $this->builder
138189
->getUriFactory()
139-
->createUri('/auth/oauth/token')
190+
->createUri($path)
140191
->withQuery(http_build_query($query));
141192
$auth = base64_encode(
142193
sprintf(

0 commit comments

Comments
 (0)