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)); $has_fields = in_array(HasConnectTokens::class, class_uses($class));
if (!$has_fields) { if (! $has_fields) {
throw new ConnectException("$class does not implement HasConnectTokens"); throw new ConnectException("$class does not implement HasConnectTokens");
} }
$class::query()->chunks(10, function ($models) use ($connect) { $class::query()->chunks(10, function ($models) use ($connect) {
$models->each(function ($model) 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 { try {
$tokens = $connect->getAccessTokenFromRefreshToken($model->connect_refresh_token); $tokens = $connect->getAccessTokenFromRefreshToken($model->connect_refresh_token);
$connect->updateUserConnectData($model, $tokens); $connect->updateUserConnectData($model, $tokens);

View File

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

View File

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

View File

@ -10,7 +10,7 @@ class ConnectServiceProvider extends ServiceProvider
{ {
public function register() 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) { $this->app->singleton(Connect::class, function ($app) {
return new Connect($app); return new Connect($app);
@ -19,7 +19,7 @@ class ConnectServiceProvider extends ServiceProvider
public function boot() 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'); $views_path = $this->path('resources/views/connect');
$this->publishes([ $this->publishes([
@ -50,6 +50,6 @@ class ConnectServiceProvider extends ServiceProvider
private function path($path = '') 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) 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 <?php
namespace Bluesquare\Connect\Traits; namespace Bluesquare\Connect\Traits;
use Illuminate\Support\Facades\Log;
trait HasConnectData trait HasConnectData
{ {
public static $connectIdentifier = 'connect_id'; public function getConnectIdentifier()
{
protected $connectFillable = []; return $this->connectIdentifier ?? 'connect_id';
}
public function fillConnectData(array $data) public function fillConnectData(array $data)
{ {
foreach ($this->connectFillable as $origin => $target) { $touched = [];
if (is_string($origin)) {
$this->$target = $data[$origin] ?? null; $fillable = $this->connectFillable ?? [];
} else {
$this->$target = $data[$target] ?? null; 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);
}
}