Skip to content

Commit

Permalink
поддержка долгоживущего токена
Browse files Browse the repository at this point in the history
поддержка кнопки на сайт для kommo.com
обновление deprecated метода для parseDisposableToken
  • Loading branch information
bessudnov committed Mar 10, 2024
1 parent 09251f3 commit 78d14de
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 23 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
## Оглавление
- [Установка](#установка)
- [Начало работы](#начало-работы-и-авторизация)
- [Авторизация с долгоживущим токеном](#авторизация-с-долгоживущим-токеном)
- [Подход к работе с библиотекой](#подход-к-работе-с-библиотекой)
- [Поддерживаемые методы и сервисы](#поддерживаемые-методы-и-сервисы)
- [Обработка ошибок](#обработка-ошибок)
Expand Down Expand Up @@ -85,6 +86,7 @@ $apiClient->getOAuthClient()->getOAuthButton(
]
);
```
Для аккаунтов kommo.com - добавьте параметр ```is_kommo => true,```.

2. Отправив пользователя на страницу авторизации
```php
Expand Down Expand Up @@ -122,6 +124,30 @@ $apiClient = $apiClient->setUserAgnet('App Name');
```


## Авторизация с долгоживущим токеном
Не так давно в amoCRM появилась возможность создавать долгоживущие токены. Их можно легко использовать с этой библиотекой.

Для начала использования вам необходимо создать объект библиотеки:
```php
$apiClient = new \AmoCRM\Client\AmoCRMApiClient();
```

После этого нужно создать объект ```AmoCRM\Client\LongLivedAccessToken```, который будет использоваться с запросами в API.

```php
$longLivedAccessToken = new LongLivedAccessToken($accessToken);
```

Затем нужно установить токен и адресс аккаунта в объект библиотеки:
```php
$apiClient->setAccessToken($longLivedAccessToken)
->setAccountBaseDomain('example.amocrm.ru');
```

После этих простых шагов, вы сможете делать запросы в amoCRM до тех пор, пока токен не истечет или его не отзовут.
В случае отзыва или истечения токена - при выполнении запроса - упадет ошибка с http кодом 401.


## Подход к работе с библиотекой

В библиотеке используется сервисный подход. Для каждой сущности имеется сервис.
Expand Down
33 changes: 33 additions & 0 deletions examples/long_lived_token.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

use AmoCRM\Client\AmoCRMApiClient;
use AmoCRM\Client\LongLivedAccessToken;
use AmoCRM\Exceptions\AmoCRMApiException;

include_once __DIR__ . '/../vendor/autoload.php';
include_once __DIR__ . '/error_printer.php';

$accessToken = 'XXX';
$accountUrl = 'example.amocrm.ru';

$apiClient = new AmoCRMApiClient();
try {
$longLivedAccessToken = new LongLivedAccessToken($accessToken);
} catch (\AmoCRM\Exceptions\InvalidArgumentException $e) {
printError($e);
die;
}

$apiClient->setAccessToken($longLivedAccessToken)
->setAccountBaseDomain($accountUrl);

//Получим информацию об аккаунте
try {
$account = $apiClient->account()->getCurrent();
} catch (AmoCRMApiException $e) {
var_dump($e->getTraceAsString());
printError($e);
die;
}

echo $account->getName();
7 changes: 4 additions & 3 deletions src/AmoCRM/Client/AmoCRMApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ class AmoCRMApiClient

/**
* AmoCRMApiClient constructor.
* @param string $clientId
* @param string $clientSecret
*
* @param string|null $clientId
* @param string|null $clientSecret
* @param null|string $redirectUri
*/
public function __construct(string $clientId, string $clientSecret, ?string $redirectUri)
public function __construct(?string $clientId = null, ?string $clientSecret = null, ?string $redirectUri = null)
{
$this->oAuthClient = new AmoCRMOAuth($clientId, $clientSecret, $redirectUri);
}
Expand Down
33 changes: 21 additions & 12 deletions src/AmoCRM/Client/AmoCRMApiRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class AmoCRMApiRequest
public const CONNECT_TIMEOUT = 5;
public const REQUEST_TIMEOUT = 20;
//TODO Do not forget to change this on each release
public const LIBRARY_VERSION = '1.5.1';
public const LIBRARY_VERSION = '1.6.0';
public const USER_AGENT = 'amoCRM-API-Library/' . self::LIBRARY_VERSION;

public const SUCCESS_STATUSES = [
Expand Down Expand Up @@ -132,8 +132,12 @@ public function __construct(
* Обновляем в библиотеке oAuth access токен по refresh
* @throws AmoCRMoAuthApiException
*/
private function refreshAccessToken()
private function refreshAccessToken(): void
{
if ($this->accessToken instanceof LongLivedAccessToken) {
throw new AmoCRMoAuthApiException('Can not update LongLivedAccessToken');
}

$newAccessToken = $this->oAuthClient->getAccessTokenByRefreshToken($this->accessToken);
$this->accessToken = $newAccessToken;
}
Expand Down Expand Up @@ -162,7 +166,7 @@ public function post(
bool $needToRefresh = false,
bool $isFullPath = false
): array {
if ($this->accessToken->hasExpired()) {
if ($this->isAccessTokenNeedToBeRefreshed()) {
$needToRefresh = true;
}

Expand Down Expand Up @@ -216,7 +220,7 @@ public function post(
try {
$response = $this->parseResponse($response);
} catch (AmoCRMoAuthApiException $e) {
if ($needToRefresh) {
if ($needToRefresh || $this->accessToken instanceof LongLivedAccessToken) {
throw $e;
}

Expand Down Expand Up @@ -250,7 +254,7 @@ public function put(
bool $needToRefresh = false,
bool $isFullPath = false
): array {
if ($this->accessToken->hasExpired()) {
if ($this->isAccessTokenNeedToBeRefreshed()) {
$needToRefresh = true;
}

Expand Down Expand Up @@ -304,7 +308,7 @@ public function put(
try {
$response = $this->parseResponse($response);
} catch (AmoCRMoAuthApiException $e) {
if ($needToRefresh) {
if ($needToRefresh || $this->accessToken instanceof LongLivedAccessToken) {
throw $e;
}

Expand Down Expand Up @@ -332,7 +336,7 @@ public function patch(
array $headers = [],
bool $needToRefresh = false
): array {
if ($this->accessToken->hasExpired()) {
if ($this->isAccessTokenNeedToBeRefreshed()) {
$needToRefresh = true;
}

Expand Down Expand Up @@ -379,7 +383,7 @@ public function patch(
try {
$response = $this->parseResponse($response);
} catch (AmoCRMoAuthApiException $e) {
if ($needToRefresh) {
if ($needToRefresh || $this->accessToken instanceof LongLivedAccessToken) {
throw $e;
}

Expand All @@ -406,7 +410,7 @@ public function delete(
array $headers = [],
bool $needToRefresh = false
): array {
if ($this->accessToken->hasExpired()) {
if ($this->isAccessTokenNeedToBeRefreshed()) {
$needToRefresh = true;
}

Expand Down Expand Up @@ -453,7 +457,7 @@ public function delete(
try {
$response = $this->parseResponse($response);
} catch (AmoCRMoAuthApiException $e) {
if ($needToRefresh) {
if ($needToRefresh || $this->accessToken instanceof LongLivedAccessToken) {
throw $e;
}

Expand All @@ -480,7 +484,7 @@ public function get(
array $headers = [],
bool $needToRefresh = false
): array {
if ($this->accessToken->hasExpired()) {
if ($this->isAccessTokenNeedToBeRefreshed()) {
$needToRefresh = true;
}

Expand Down Expand Up @@ -532,7 +536,7 @@ public function get(
try {
$response = $this->parseResponse($response);
} catch (AmoCRMoAuthApiException $e) {
if ($needToRefresh) {
if ($needToRefresh || $this->accessToken instanceof LongLivedAccessToken) {
throw $e;
}

Expand Down Expand Up @@ -740,4 +744,9 @@ public function getUserAgent(): string
? sprintf('%s (%s)', $this->userAgent, self::USER_AGENT)
: self::USER_AGENT;
}

private function isAccessTokenNeedToBeRefreshed(): bool
{
return !$this->accessToken instanceof LongLivedAccessToken && $this->accessToken->hasExpired();
}
}
48 changes: 48 additions & 0 deletions src/AmoCRM/Client/LongLivedAccessToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=0);

namespace AmoCRM\Client;

use AmoCRM\Exceptions\InvalidArgumentException;
use DateTimeImmutable;
use Lcobucci\JWT\Configuration;
use League\OAuth2\Client\Token\AccessToken;
use Throwable;

/**
* Class LongLivedAccessToken
*
* @package AmoCRM\Client
*/
class LongLivedAccessToken extends AccessToken
{
/**
* @throws InvalidArgumentException
*/
public function __construct(string $accessToken)
{
try {
$parsedAccessToken = Configuration::forUnsecuredSigner()->parser()->parse($accessToken);
} catch (Throwable $e) {
throw new InvalidArgumentException(
'Error parsing given access token. Prev error: ' . $e->getMessage(),
0,
[],
'Check access token.'
);
}

$claims = $parsedAccessToken->claims();

/** @var DateTimeImmutable $expiresAt */
$expiresAt = $claims->get('exp');

$options = [
'expires' => $expiresAt->getTimestamp(),
'access_token' => $accessToken,
];

parent::__construct($options);
}
}
2 changes: 2 additions & 0 deletions src/AmoCRM/Exceptions/InvalidArgumentException.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

declare(strict_types=1);

namespace AmoCRM\Exceptions;

class InvalidArgumentException extends AmoCRMApiException
Expand Down
23 changes: 15 additions & 8 deletions src/AmoCRM/OAuth/AmoCRMOAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class AmoCRMOAuth
/**
* @var null|callable
*/
private $accessTokenRefreshCallback = null;
private $accessTokenRefreshCallback;

/**
* @var string
Expand All @@ -89,11 +89,12 @@ class AmoCRMOAuth

/**
* AmoCRMOAuth constructor.
* @param string $clientId
* @param string $clientSecret
*
* @param string|null $clientId
* @param string|null $clientSecret
* @param null|string $redirectUri
*/
public function __construct(string $clientId, string $clientSecret, ?string $redirectUri)
public function __construct(?string $clientId, ?string $clientSecret, ?string $redirectUri)
{
$this->oauthProvider = new AmoCRM(
[
Expand Down Expand Up @@ -313,15 +314,21 @@ public function getOAuthButton(array $options = []): string
$mode = isset($options['mode']) && in_array($options['mode'], ['popup', 'post_message'])
? $options['mode']
: 'post_message';

try {
$state = $options['state'] ?? bin2hex(random_bytes(10));
} catch (Exception $exception) {
$state = rand(1, 100);
}

$mainClassName = isset($options['is_kommo']) && $options['is_kommo'] ? 'kommo_oauth' : 'amocrm_oauth';
$scriptPath = isset($options['is_kommo']) && $options['is_kommo']
? 'https://www.kommo.com/auth/button.min.js'
: 'https://www.amocrm.ru/auth/button.min.js';

return '<div>
<script
class="amocrm_oauth"
class="' . $mainClassName . '"
charset="utf-8"
data-client-id="' . $this->oauthProvider->getClientId() . '"
data-title="' . $title . '"
Expand All @@ -331,7 +338,7 @@ class="amocrm_oauth"
data-state="' . $state . '"
data-error-callback="' . $errorCallback . '"
data-mode="' . $mode . '"
src="https://www.amocrm.ru/auth/button.min.js"
src="' . $scriptPath . '"
></script>
</div>';
}
Expand Down Expand Up @@ -464,8 +471,8 @@ public function parseDisposableToken(string $token): DisposableTokenModel
new SignedWith($signer, $key),
// Проверим наш ли адресат
new PermittedFor($clientBaseUri),
// Проверка жизни токена, с 4.2 deprecated use LooseValidAt
new ValidAt(FrozenClock::fromUTC()),
// Проверка жизни токена
new Constraint\LooseValidAt(FrozenClock::fromUTC()),
];

$configuration = Configuration::forSymmetricSigner($signer, $key);
Expand Down

0 comments on commit 78d14de

Please sign in to comment.