Skip to content
Snippets Groups Projects
Commit decc81cb authored by Andrew's avatar Andrew :milky_way:
Browse files

init & alpha version

parents
No related branches found
No related tags found
No related merge requests found
## git
!.gitkeep
## editors
/.idea
/.vscode
.editorconfig
.styleci.yml
## composer
/vendor
composer.lock
## cache
*.cache
## node
/node_modules
package-lock.json
npm-debug.log
yarn.lock
yarn-error.log
## storage
/storage/db
/storage/*.key
## laravel
Homestead.json
Homestead.yaml
## enviroment
.env
## tests
.phpunit.result.cache
index.php
## Status
copy from orchid
[![Latest Stable Version](https://poser.pugx.org/shadoll/orchid/v/stable)](https://packagist.org/packages/shadoll/orchid)
[![pipeline status](https://gitlab.com/fabrika-klientov/libraries/orchid/badges/master/pipeline.svg)](https://gitlab.com/fabrika-klientov/libraries/orchid/commits/master)
[![License](https://poser.pugx.org/shadoll/orchid/license)](https://packagist.org/packages/shadoll/orchid)
---
## Клиент авторизации OAuth2
Дает возможность:
- получить токен доступа клиента `password`,
- переиздать новый токен доступа клиента `password`,
- запрашивать необходимые scopes,
- получить токен доступа типа credentials (полезно для сервер-сервер соединений),
- сохраняет и управляет сохранностью (кеширования токенов доступа и обновления)
Вашему приложению необходимо
- подключить сервис в клиенте запросов,
- после получению кода ошибки `401` - переиздать токен `refresh()` или выписать новый `credentials()`,
и повторить запрос с новым токеном.
## Usage
`composer require shadoll/`
##### Переменные окружения
`LUPINUS_SERVER`= - Сервер авторизации `https://server.com`
`LUPINUS_CLIENT_ID`= - ID клиента OAuth2
`LUPINUS_CLIENT_SECRET`= - SECRET клиента OAuth2
`LUPINUS_SCOPE`= - Роли клиента необходимые для работы приложения
`LUPINUS_CACHE_DRIVER`= - тип драйвера хранения токенов доступа и токенов обновления
(доступно (2020-02-15) только Json) в будущем (Jstorage) и т.п.
ВСЕ переменные опциональны и могут быть переопределены в конструкторе сервиса
(кроме `LUPINUS_CACHE_DRIVER` - не переопределяется, если его не будет кешироваться токены не будут)
```php
$authService = new \Lupinus\AuthService();
// или с параметрами
$authService = new \Lupinus\AuthService(
'https://server.com',
'my_app',
'secret...',
'access_token...',
'refresh_token...',
'scope1 scope2'
);
/** Авторизация
* @var array $token
*/
$token = $authService->auth('jarvis@google.com', 'jarvis_password'); // LUPINUS_SCOPE
// запросить scope
$token = $authService->auth('jarvis@google.com', 'jarvis_password', 'scope3');
/** Обновления токена доступа по refresh_token
* @var array $token
*/
$token = $authService->refresh(); // используется последний токен refresh_token или с кеша (если включен)
// или
$token = $authService->refresh($refresh_token); // принудительный $refresh_token
/** токен credentials
* @var array $token
*/
$token = $authService->credentials(); // LUPINUS_SCOPE
// или с scope
$token = $authService->credentials('admin');
```
{
"name": "shadoll/lupinus",
"description": "OAuth2 library",
"type": "library",
"license": "MIT",
"homepage": "https://fabrika-klientov.com",
"authors": [
{
"name": "sHa",
"email": "sha@shadoll.dev",
"homepage": "https://shadoll.dev",
"role": "Lead Developer"
},
{
"name": "Andrew",
"email": "3oosor@gmail.com",
"homepage": "https://github.com/oosor",
"role": "Developer"
}
],
"require": {
"php": ">=7.2",
"guzzlehttp/guzzle": "^6.3"
},
"require-dev": {
"phpunit/phpunit": "^8",
"phpstan/phpstan": "^0.12",
"phpstan/phpstan-strict-rules": "^0.12",
"phpstan/phpstan-phpunit": "^0.12",
"sebastian/phpcpd": "^4.1"
},
"autoload": {
"psr-4": {
"Lupinus\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"test": "@php ./vendor/bin/phpunit --no-coverage",
"phpstan": "@php ./vendor/bin/phpstan analyze --ansi --level=8 ./src",
"phpcpd": "@php ./vendor/bin/phpcpd src/ --min-lines=10"
}
}
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Lupinus\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/instantiator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-common" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/reflection-docblock" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpdocumentor/type-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpspec/prophecy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpstan" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpstan-phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpstan/phpstan-strict-rules" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-token-stream" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/finder-facade" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/phpcpd" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/resource-operations" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/console" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php73" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/fdomdocument" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/webmozart/assert" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
<?php
/**
* Created by IntelliJ IDEA.
* User: jarvis
* Date: 14.02.20
* Time: 13:23
*/
namespace Lupinus;
use GuzzleHttp\Client;
use Lupinus\Cache\CacheService;
use Lupinus\Exceptions\LupinusException;
class AuthService
{
private $client;
private $cacheService;
private static $path = '/token';
private $access_token;
private $refresh_token;
private $client_id;
private $client_secret;
private $scope;
/**
* @param string $url
* @param string $client_id
* @param string $client_secret
* @param string $access_token
* @param string $refresh_token
* @param string $scope
* @return void
* @throws LupinusException
* */
public function __construct(
string $url = null,
string $client_id = null,
string $client_secret = null,
string $access_token = null,
string $refresh_token = null,
$scope = null
) {
$this->client_id = $client_id ?? getenv('LUPINUS_CLIENT_ID');
$this->client_secret = $client_secret ?? getenv('LUPINUS_CLIENT_SECRET');
$this->scope = $scope ?? getenv('LUPINUS_SCOPE');
if (empty($this->client_id) || empty($this->client_secret)) {
throw new LupinusException('AuthService: [client_id] and [client_secret] is required in params or/and in .env file');
}
$this->cacheService = new CacheService();
$this->initTokens($access_token, $refresh_token);
$this->client = new Client([
'base_uri' => $url ?? getenv('LUPINUS_SERVER'),
'verify' => false, // delete
'headers' => [
'Content-Type' => 'application/x-www-form-urlencoded',
'Accept' => 'application/json',
],
]);
}
/**
* @param string $username
* @param string $password
* @param string $scope
* @return array|null
* @throws LupinusException
* */
public function auth(string $username, string $password, $scope = null)
{
try {
$result = $this->client->post(static::$path, [
'form_params' => [
'grant_type' => 'password',
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'username' => $username,
'password' => $password,
'scope' => $scope ?? $this->scope ?? '',
],
]);
$result = json_decode($result->getBody()->getContents(), true);
if (!empty($result)) {
$this->access_token = $result['access_token'];
$this->refresh_token = $result['refresh_token'];
if ($driver = $this->cacheService->getDriver()) {
$driver->write($result);
}
return $result;
}
} catch (\Exception $exception) {
throw new LupinusException($exception->getMessage());
}
return null;
}
/**
* @param string $refresh_token
* @return array|null
* @throws LupinusException
* */
public function refresh(string $refresh_token = null)
{
try {
$result = $this->client->post(static::$path, [
'form_params' => [
'grant_type' => 'refresh_token',
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'refresh_token' => $refresh_token ?? $this->refresh_token,
],
]);
$result = json_decode($result->getBody()->getContents(), true);
if (!empty($result)) {
$this->access_token = $result['access_token'];
$this->refresh_token = $result['refresh_token'];
if ($driver = $this->cacheService->getDriver()) {
$driver->write($result);
}
return $result;
}
} catch (\Exception $exception) {
throw new LupinusException($exception->getMessage());
}
return null;
}
public function credentials($scope = null)
{
try {
$result = $this->client->post(static::$path, [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'scope' => $scope ?? $this->scope ?? '',
],
]);
$result = json_decode($result->getBody()->getContents(), true);
if (!empty($result)) {
$this->access_token = $result['access_token'];
if ($driver = $this->cacheService->getDriver()) {
$driver->write($result);
}
return $result;
}
} catch (\Exception $exception) {
throw new LupinusException($exception->getMessage());
}
return null;
}
protected function initTokens(string $access_token = null, string $refresh_token = null)
{
if ($driver = $this->cacheService->getDriver()) {
$data = $driver->read();
if (!empty($data)) {
$this->access_token = $data['access_token'] ?? null;
$this->refresh_token = $data['refresh_token'] ?? null;
}
}
$this->access_token = $access_token ?? $this->access_token;
$this->refresh_token = $refresh_token ?? $this->refresh_token;
}
}
\ No newline at end of file
<?php
/**
* Created by IntelliJ IDEA.
* User: jarvis
* Date: 15.02.20
* Time: 11:28
*/
namespace Lupinus\Cache;
use Lupinus\Cache\Drivers\BeDriver;
class CacheService
{
private $driver;
public function __construct()
{
$typeDriver = getenv('LUPINUS_CACHE_DRIVER') ?? null;
if (isset($typeDriver)) {
$class = 'Lupinus\\Cache\\Drivers\\' . $typeDriver . 'Driver';
if (class_exists($class) && is_subclass_of($class, BeDriver::class)) {
$this->driver = new $class();
}
}
}
public function getDriver(): ?BeDriver
{
return $this->driver ?? null;
}
}
\ No newline at end of file
/storage
\ No newline at end of file
<?php
/**
* Created by IntelliJ IDEA.
* User: jarvis
* Date: 15.02.20
* Time: 11:53
*/
namespace Lupinus\Cache\Drivers;
interface BeDriver
{
public function read();
public function write(array $data);
public function clear();
}
\ No newline at end of file
<?php
/**
* Created by IntelliJ IDEA.
* User: jarvis
* Date: 15.02.20
* Time: 11:53
*/
namespace Lupinus\Cache\Drivers;
class JsonDriver implements BeDriver
{
private static $cachePath;
public function __construct()
{
self::$cachePath = self::appPath() . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . 'lupinus';
$this->storeCacheDir();
}
public function read()
{
$file = $this->getFile();
if (file_exists($file)) {
$data = json_decode((string)file_get_contents($file), true);
return !empty($data) ? $data : null;
}
return null;
}
public function write(array $data)
{
$file = $this->getFile();
if (file_exists($file) && !is_writable($file)) {
throw new \RuntimeException('FIle [' . $file . '] is not writable. Permission denied.');
}
$fp = fopen($file, "w");
if ($fp) {
$status = fwrite($fp, (string)json_encode($data));
fclose($fp);
}
return (bool)($status ?? false);
}
public function clear()
{
return $this->write([]);
}
protected function getFile()
{
return static::$cachePath . DIRECTORY_SEPARATOR . 'auth.json';
}
/** path for cache
* @return void
* @throws \RuntimeException
* */
protected function storeCacheDir()
{
if (!file_exists(static::$cachePath)) {
if (!mkdir(static::$cachePath, 0775, true)) {
throw new \RuntimeException('Did\'t stored path for cache. Permission denied.');
}
}
}
protected static function appPath()
{
$listPath = explode('/vendor', __DIR__);
if (count($listPath) > 1) {
array_pop($listPath);
return join('/vendor', $listPath);
}
return array_shift($listPath);
}
}
\ No newline at end of file
<?php
/**
* Created by IntelliJ IDEA.
* User: jarvis
* @package Lupinus
* @category Exceptions
* @author Andrew <3oosor@gmail.com>
* @copyright 2020 Fabrika-Klientov
* @version GIT: 20.02.15
* @link https://fabrika-klientov.ua
*/
namespace Lupinus\Exceptions;
class LupinusException extends \Exception
{
public function __construct(string $message = "", int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->setMessage($message);
}
/**
* @param mixed $message
*/
public function setMessage($message): void
{
$this->message = 'LUPINUS::CORE: ' . $message;
}
}
\ No newline at end of file
<?php
/**
* Created by IntelliJ IDEA.
* User: jarvis
* Date: 15.02.20
* Time: 12:43
*/
namespace Tests;
use Lupinus\AuthService;
use PHPUnit\Framework\TestCase;
class AuthServiceTest extends TestCase
{
public function test__construct()
{
}
public function testAuth()
{
}
public function testRefresh()
{
}
public function testCredentials()
{
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment