laravel-connect/src/Connect.php

308 lines
8.4 KiB
PHP
Raw Normal View History

2020-05-11 16:26:34 +02:00
<?php
namespace Bluesquare\Connect;
2022-05-18 17:40:18 +02:00
use Bluesquare\Connect\Traits\HasConnectData;
2020-05-12 15:57:23 +02:00
use Bluesquare\Connect\Traits\HasConnectTokens;
use Bluesquare\Connect\Traits\HasConnectWebhook;
2020-05-11 16:26:34 +02:00
use GuzzleHttp\Client;
2024-04-12 12:13:50 +02:00
use Illuminate\Contracts\Foundation\Application;
2020-05-11 16:26:34 +02:00
use Illuminate\Http\Request;
2020-05-12 12:47:10 +02:00
use Illuminate\Routing\Router;
2020-05-11 17:54:18 +02:00
use Illuminate\Support\Facades\Log;
2020-05-12 13:00:14 +02:00
use Illuminate\Support\Facades\Route;
2020-05-11 16:26:34 +02:00
use Illuminate\Support\Str;
use Psr\Http\Message\StreamInterface;
class Connect
{
protected $app;
2024-04-12 12:13:50 +02:00
public function __construct(Application $app)
2020-05-11 16:26:34 +02:00
{
$this->app = $app;
}
// API
/**
* @param $method
* @param $uri
* @param null $data
2020-05-11 17:54:18 +02:00
* @return array
2020-05-11 16:26:34 +02:00
* @throws ConnectException
*/
2022-05-18 17:40:18 +02:00
public function request($method, $uri, $data = null, $access_token = null): array
2020-05-11 16:26:34 +02:00
{
2020-05-11 17:13:58 +02:00
$url = $this->getUrl();
2020-05-11 16:26:34 +02:00
$url = $url . '/' . trim($uri, '/');
$client = new Client();
$config = [
'headers' => [
'Accept' => 'application/json'
]
];
if (! is_null($access_token)) {
2022-05-18 17:40:18 +02:00
$config['headers']['Authorization'] = 'Bearer ' . $access_token;
2020-05-11 16:26:34 +02:00
}
if (! is_null($data)) {
2020-05-11 16:26:34 +02:00
$config['form_params'] = $data;
}
try {
2020-05-12 13:37:30 +02:00
$response = $client->request($method, $url, $config);
$body = (string) $response->getBody();
if ($response->getStatusCode() !== 200)
throw new ConnectException($body);
return json_decode($body, true);
2020-05-11 16:26:34 +02:00
} catch(\Exception $e) {
2022-05-18 17:40:18 +02:00
$this->flushTokens();
2020-05-11 16:26:34 +02:00
throw new ConnectException($e->getMessage());
}
}
2022-05-18 17:40:18 +02:00
// Authorization flow
2020-05-11 16:26:34 +02:00
public function redirect($state = null)
{
if (is_null($state))
$state = Str::random();
$states = session()->get('connect_states');
2022-05-18 17:40:18 +02:00
if (! is_array($states))
2020-05-11 16:26:34 +02:00
$states = [];
2022-05-18 17:40:18 +02:00
2020-05-11 16:26:34 +02:00
$states[] = $state;
2022-05-18 17:40:18 +02:00
2020-05-11 16:26:34 +02:00
session()->put('connect_states', $states);
$query = http_build_query([
'client_id' => config('bconnect.client_id'),
'scope' => config('bconnect.scopes'),
2020-05-11 16:49:01 +02:00
'redirect_uri' => config('bconnect.redirect_url'),
2020-05-11 16:26:34 +02:00
'response_type' => 'code',
'state' => $state
]);
2020-05-11 17:13:58 +02:00
$url = $this->getUrl() . '/oauth/authorize?' . $query;
2022-05-18 17:40:18 +02:00
2024-04-12 12:13:50 +02:00
return redirect($url);
2020-05-11 16:26:34 +02:00
}
2020-05-12 16:56:50 +02:00
public function checkState(Request $request)
2020-05-11 16:26:34 +02:00
{
if (! session()->has('connect_states'))
2020-05-12 16:56:50 +02:00
return false;
2020-05-11 16:26:34 +02:00
$states = session()->get('connect_states');
if (! is_array($states))
2020-05-12 16:56:50 +02:00
return false;
2020-05-11 16:26:34 +02:00
if (! $request->has('state') || ! in_array($request->state, $states))
2020-05-12 16:56:50 +02:00
return false;
2020-05-11 16:26:34 +02:00
unset($states[array_search($request->state, $states)]);
session()->put('connect_states', $states);
2022-05-18 17:40:18 +02:00
2020-05-12 16:56:50 +02:00
return true;
}
public function loginFromCallback(Request $request, $redirect_to = '/')
{
if (! $this->checkState($request) || ! $request->has('code'))
2020-05-12 16:56:50 +02:00
return redirect('/');
2020-05-11 16:26:34 +02:00
// Access token
2022-05-18 17:40:18 +02:00
2020-05-11 17:54:18 +02:00
$expires_at = now();
$connect_data = $this->getAccessTokenFromAuthorizationCode($request->get('code'));
2020-05-11 17:54:18 +02:00
$connect_data['expires_at'] = $expires_at->addSeconds($connect_data['expires_in']);
2020-05-11 16:26:34 +02:00
2022-05-18 17:40:18 +02:00
// User data
2020-05-11 17:54:18 +02:00
2022-05-18 17:40:18 +02:00
$user_data = $this->getUserData($connect_data['access_token']);
2020-05-11 18:28:00 +02:00
$user = $this->sync('create', $user_data);
2022-05-18 17:40:18 +02:00
$this->updateUserConnectData($user, $connect_data);
2020-05-11 18:28:00 +02:00
$user->save();
2020-05-11 17:54:18 +02:00
2022-05-18 17:40:18 +02:00
// Login
2020-05-12 18:49:09 +02:00
2022-05-18 17:40:18 +02:00
auth()->login($user, config('bconnect.login_remember', true));
2020-05-11 18:22:06 +02:00
2020-05-12 16:56:50 +02:00
return redirect($redirect_to);
2020-05-11 16:26:34 +02:00
}
2022-05-18 17:40:18 +02:00
// OAuth methods
2020-05-12 15:57:23 +02:00
public function getAccessTokenFromAuthorizationCode($code)
{
return $this->request('post', 'oauth/token', [
2020-05-12 15:57:23 +02:00
'grant_type' => 'authorization_code',
'client_id' => config('bconnect.client_id'),
'client_secret' => config('bconnect.client_secret'),
'scope' => config('bconnect.user_scopes'),
'redirect_uri' => config('bconnect.redirect_url'),
'code' => $code
2022-05-18 17:40:18 +02:00
]);
2020-05-12 15:57:23 +02:00
}
public function getAccessTokenFromRefreshToken($refresh_token)
{
return $this->request('post', 'oauth/token', [
2020-05-12 15:57:23 +02:00
'grant_type' => 'refresh_token',
'client_id' => config('bconnect.client_id'),
'client_secret' => config('bconnect.client_secret'),
'scope' => config('bconnect.user_scopes'),
'redirect_uri' => config('bconnect.redirect_url'),
'refresh_token' => $refresh_token
2022-05-18 17:40:18 +02:00
]);
2020-05-12 15:57:23 +02:00
}
public function getUserData($access_token)
{
return $this->request('get', 'api/user', null, $access_token);
}
2022-05-18 17:40:18 +02:00
public function getUserAccessToken($model)
2020-05-12 15:57:23 +02:00
{
2022-05-18 17:40:18 +02:00
$class = get_class($model);
$has_fields = in_array(HasConnectTokens::class, class_uses($class));
2020-05-12 15:57:23 +02:00
if (! $has_fields) {
2022-05-18 17:40:18 +02:00
throw new ConnectException("$class does not implement HasConnectTokens");
2020-05-12 15:57:23 +02:00
}
2022-05-18 17:40:18 +02:00
if ($model->connect_expires_at <= now()->addHour()) {
$connect_data = $this->getAccessTokenFromRefreshToken($model->connect_refresh_token);
$this->updateUserConnectData($model, $connect_data);
2020-05-12 15:57:23 +02:00
return $connect_data['access_token'];
}
2022-05-18 17:40:18 +02:00
return $model->connect_access_token;
2020-05-12 15:57:23 +02:00
}
2022-05-18 17:40:18 +02:00
// Sync
2020-05-11 16:26:34 +02:00
public function sync(string $event, array $data)
2020-05-11 16:26:34 +02:00
{
$class = config('bconnect.model');
2020-05-11 16:26:34 +02:00
if (in_array($event, ['update', 'delete'])) {
$model = $this->resolveUser($data);
2020-05-11 16:26:34 +02:00
if (! $model->exists() && $event === 'delete')
return $model;
}
else {
$hasSoftDeletes = in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses($class));
$model = $this->resolveUser($data, $hasSoftDeletes);
if ($model->exists()) {
2022-06-02 10:12:13 +02:00
if ($hasSoftDeletes && $model->trashed()) {
$event = 'restore';
} else {
$event = 'update';
}
}
}
if (in_array(HasConnectWebhook::class, class_uses($class))) {
$method = 'onConnect' . ucfirst($event);
$model->$method($data);
} else {
$this->updateUserData($model, $data);
$model->save();
2024-04-17 13:06:48 +02:00
if (in_array(HasConnectData::class, class_uses($model))) {
$model->postFillConnectData($data);
}
}
return $model;
2020-05-11 16:26:34 +02:00
}
2022-05-18 17:40:18 +02:00
public function updateUserConnectData($user, $data)
2020-05-11 16:26:34 +02:00
{
2022-05-18 17:40:18 +02:00
if (in_array(HasConnectTokens::class, class_uses(get_class($user))))
$user->fillConnectTokens($data);
2020-05-11 16:26:34 +02:00
}
2022-05-18 17:40:18 +02:00
public function updateUserData($user, $data)
2020-05-11 16:26:34 +02:00
{
2022-05-18 17:40:18 +02:00
if (in_array(HasConnectData::class, class_uses(get_class($user))))
$user->fillConnectData($data);
2020-05-11 16:26:34 +02:00
}
protected function resolveUser($data, $withTrashed = false)
2020-05-11 16:26:34 +02:00
{
$class = config('bconnect.model');
$query = $class::query();
if ($withTrashed)
$query->withTrashed();
2020-05-11 16:26:34 +02:00
$model = new $class;
2020-05-11 17:54:18 +02:00
if (in_array(HasConnectData::class, class_uses($class))) {
$id = $model->getConnectIdentifier();
$origin = is_array($id) ? $id[0] : $id;
$target = is_array($id) ? $id[1] : $id;
$model = $query->where($target, $data[$origin])->first() ?? $model;
$model->$target = $data[$origin];
2022-05-18 17:40:18 +02:00
} else {
$model = $query->where('email', $data['email'])->first() ?? $model;
if (! $model->exists())
$model->email = $data['email'];
2020-05-11 16:26:34 +02:00
}
return $model;
2020-05-11 16:26:34 +02:00
}
2020-05-12 15:57:23 +02:00
// Routing
public function routes()
{
Route::namespace('\Bluesquare\Connect\Controllers')
->group(function () {
Route::get('connect/authorize', 'ConnectController@authorize');
Route::get('connect/callback', 'ConnectController@callback');
});
}
public function apiRoutes()
{
Route::namespace('\Bluesquare\Connect\Controllers')
->group(function () {
Route::post('connect/webhook', 'ConnectController@webhook');
});
}
// Misc
2020-05-11 16:26:34 +02:00
2022-05-18 17:40:18 +02:00
protected function flushTokens()
2020-05-11 17:54:18 +02:00
{
2022-05-18 17:40:18 +02:00
session()->forget('bconnect.access_token');
session()->forget('bconnect.access_token_expiration');
2020-05-11 16:26:34 +02:00
}
2020-05-11 17:13:58 +02:00
protected function getUrl()
{
return config('bconnect.url') ?? 'https://connect.bluesquare.io';
}
2020-05-11 16:26:34 +02:00
}