feat: sync, webhook, tokens refresh, fillable, key config

This commit is contained in:
Maxime 2022-05-19 13:24:54 +02:00
parent 4156db2cdc
commit 830ebbcdd3
7 changed files with 158 additions and 52 deletions

View File

@ -19,13 +19,13 @@ class RefreshTokens extends Command
$has_fields = in_array(HasConnectTokens::class, class_uses($class));
if (!$has_fields) {
if (! $has_fields) {
throw new ConnectException("$class does not implement HasConnectTokens");
}
$class::query()->chunks(10, function ($models) use ($connect) {
$models->each(function ($model) use ($connect) {
if (!empty($model->connect_refresh_token) && $model->connect_expires_at <= now()->addHour()) {
if (! empty($model->connect_refresh_token) && $model->connect_expires_at <= now()->addHour()) {
try {
$tokens = $connect->getAccessTokenFromRefreshToken($model->connect_refresh_token);
$connect->updateUserConnectData($model, $tokens);

View File

@ -21,14 +21,14 @@ class Sync extends Command
$has_fields = in_array(HasConnectTokens::class, class_uses($class));
if (!$has_fields) {
if (! $has_fields) {
throw new ConnectException("$class does not implement HasConnectTokens");
}
$class::query()->chunks(10, function ($models) use ($connect) {
$models->each(function ($model) use ($connect) {
try {
if (!empty($model->connect_access_token)) {
if (! empty($model->connect_access_token)) {
$data = $connect->getUserData($model->connect_access_token);
$connect->updateUserData($model, $data);
$model->save();

View File

@ -3,8 +3,8 @@
namespace Bluesquare\Connect;
use Bluesquare\Connect\Traits\HasConnectData;
use Bluesquare\Connect\Traits\HasConnectSync;
use Bluesquare\Connect\Traits\HasConnectTokens;
use Bluesquare\Connect\Traits\HasConnectWebhook;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
@ -44,11 +44,11 @@ class Connect
]
];
if ($access_token !== null) {
if (! is_null($access_token)) {
$config['headers']['Authorization'] = 'Bearer ' . $access_token;
}
if (!is_null($data)) {
if (! is_null($data)) {
$config['form_params'] = $data;
}
@ -77,7 +77,7 @@ class Connect
$states = session()->get('connect_states');
if (!is_array($states))
if (! is_array($states))
$states = [];
$states[] = $state;
@ -99,15 +99,15 @@ class Connect
public function checkState(Request $request)
{
if (!session()->has('connect_states'))
if (! session()->has('connect_states'))
return false;
$states = session()->get('connect_states');
if (!is_array($states))
if (! is_array($states))
return false;
if (!$request->has('state') || !in_array($request->state, $states))
if (! $request->has('state') || ! in_array($request->state, $states))
return false;
unset($states[array_search($request->state, $states)]);
@ -119,22 +119,21 @@ class Connect
public function loginFromCallback(Request $request, $redirect_to = '/')
{
if (!$this->checkState($request) || !$request->has('code'))
if (! $this->checkState($request) || ! $request->has('code'))
return redirect('/');
// Access token
$expires_at = now();
$connect_data = $this->getAccessTokenFromAuthorizationCode($request->code);
$connect_data = $this->getAccessTokenFromAuthorizationCode($request->get('code'));
$connect_data['expires_at'] = $expires_at->addSeconds($connect_data['expires_in']);
// User data
$user_data = $this->getUserData($connect_data['access_token']);
$user = $this->resolveUser($user_data);
$user = $this->sync('create', $user_data);
$this->updateUserConnectData($user, $connect_data);
$this->updateUserData($user, $user_data);
$user->save();
// Login
@ -148,7 +147,7 @@ class Connect
public function getAccessTokenFromAuthorizationCode($code)
{
$data = $this->request('post', 'oauth/token', [
return $this->request('post', 'oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => config('bconnect.client_id'),
'client_secret' => config('bconnect.client_secret'),
@ -156,13 +155,11 @@ class Connect
'redirect_uri' => config('bconnect.redirect_url'),
'code' => $code
]);
return $data;
}
public function getAccessTokenFromRefreshToken($refresh_token)
{
$data = $this->request('post', 'oauth/token', [
return $this->request('post', 'oauth/token', [
'grant_type' => 'refresh_token',
'client_id' => config('bconnect.client_id'),
'client_secret' => config('bconnect.client_secret'),
@ -170,8 +167,6 @@ class Connect
'redirect_uri' => config('bconnect.redirect_url'),
'refresh_token' => $refresh_token
]);
return $data;
}
public function getUserData($access_token)
@ -184,7 +179,7 @@ class Connect
$class = get_class($model);
$has_fields = in_array(HasConnectTokens::class, class_uses($class));
if (!$has_fields) {
if (! $has_fields) {
throw new ConnectException("$class does not implement HasConnectTokens");
}
@ -199,17 +194,38 @@ class Connect
// Sync
public function handleWebhook(Request $request)
public function sync(string $event, array $data)
{
$data = $request->validate([
'event_type' => 'required|in:created,updated,deleted,restored',
'connect_data' => 'required|array',
'connect_data.id' => 'required'
]);
$class = config('bconnect.model');
//@TODO
if (in_array($event, ['update', 'delete'])) {
$model = $this->resolveUser($data);
return ['handled' => false];
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()) {
if ($hasSoftDeletes) {
$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();
}
return $model;
}
public function updateUserConnectData($user, $data)
@ -224,20 +240,32 @@ class Connect
$user->fillConnectData($data);
}
protected function resolveUser($data)
protected function resolveUser($data, $withTrashed = false)
{
$model = config('bconnect.model');
$class = config('bconnect.model');
$query = $class::query();
if (in_array(HasConnectData::class, class_uses($model))) {
$origin = is_array($model::$connectIdentifier) ? $model::$connectIdentifier[0] : $model::$connectIdentifier;
$target = is_array($model::$connectIdentifier) ? $model::$connectIdentifier[1] : $model::$connectIdentifier;
if ($withTrashed)
$query->withTrashed();
$user = $model::where($target, $data[$origin])->first() ?? new $model;
$model = new $class;
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];
} else {
$user = $model::where('email', $data['email'])->first() ?? new $model;
$model = $query->where('email', $data['email'])->first() ?? $model;
if (! $model->exists())
$model->email = $data['email'];
}
return $user;
return $model;
}
// Routing

View File

@ -10,7 +10,7 @@ class ConnectServiceProvider extends ServiceProvider
{
public function register()
{
$this->mergeConfigFrom($this->path('src/config/bconnect.php'), 'bconnect');
$this->mergeConfigFrom($this->path('config/bconnect.php'), 'bconnect');
$this->app->singleton(Connect::class, function ($app) {
return new Connect($app);
@ -19,7 +19,7 @@ class ConnectServiceProvider extends ServiceProvider
public function boot()
{
$config_path = $this->path('src/config/bconnect.php');
$config_path = $this->path('config/bconnect.php');
$views_path = $this->path('resources/views/connect');
$this->publishes([
@ -50,6 +50,6 @@ class ConnectServiceProvider extends ServiceProvider
private function path($path = '')
{
return __DIR__ . "/../../$path";
return __DIR__ . "/../$path";
}
}

View File

@ -20,6 +20,19 @@ class ConnectController extends Controller
public function webhook(Request $request, Connect $connect)
{
return $connect->handleWebhook($request);
$hash = sha1(config('bconnect.client_secret') . date('Y-m-d'));
if ($request->header('x-connect-hash') !== $hash)
abort(403);
$data = $request->validate([
'event_type' => 'required|in:create,update,delete',
'connect_data' => 'required|array',
'connect_data.*' => 'nullable',
'connect_data.id' => 'required',
'connect_data.email' => 'required_if:event_type,create|required_if:event_type,update',
]);
$connect->sync($data['event_type'], $data['connect_data']);
}
}

View File

@ -1,23 +1,47 @@
<?php
namespace Bluesquare\Connect\Traits;
use Illuminate\Support\Facades\Log;
trait HasConnectData
{
public static $connectIdentifier = 'connect_id';
protected $connectFillable = [];
public function getConnectIdentifier()
{
return $this->connectIdentifier ?? 'connect_id';
}
public function fillConnectData(array $data)
{
foreach ($this->connectFillable as $origin => $target) {
if (is_string($origin)) {
$this->$target = $data[$origin] ?? null;
} else {
$this->$target = $data[$target] ?? null;
$touched = [];
$fillable = $this->connectFillable ?? [];
foreach ($fillable as $origin => $targets) {
$value = is_string($origin) ? $data[$origin] : $data[$targets];
$targets = is_string($origin) && is_array($targets) ? $targets : [$targets];
foreach ($targets as $target) {
$parts = explode('|', $target);
$target = $parts[0];
$currentValue = $value ?? ($parts[1] ?? null);
$target_model = $this;
$parts = explode('.', $target);
foreach ($parts as $i => $property) {
if ($i < count($parts) - 1) {
$target_model = $target_model->$property;
continue;
}
if ($target_model !== $this)
$touched[] = $target_model;
$target_model->$property = $currentValue;
}
}
}
}
abstract public function fill(array $attributes);
foreach ($touched as $model)
$model->save();
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace Bluesquare\Connect\Traits;
trait HasConnectWebhook
{
public function onConnectCreate(array $data)
{
$this->onConnectUpdate($data);
}
public function onConnectUpdate(array $data)
{
if (in_array(HasConnectData::class, class_uses(self::class))) {
$this->fillConnectData($data);
} else {
$this->email = $data['email'];
}
$this->save();
}
public function onConnectDelete(array $data)
{
$this->onConnectUpdate($data);
if (in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses(self::class))) {
$this->delete();
} elseif (array_key_exists('remember_token', $this->attributes)) {
$this->remember_token = null;
$this->save();
}
}
public function onConnectRestore(array $data)
{
$this->restore();
$this->onConnectUpdate($data);
}
}