Compare commits
13 Commits
Author | SHA1 | Date |
---|---|---|
Maxime | 1201ef254e | |
Maxime | abce313193 | |
Maxime | 9d17dacae2 | |
Maxime | 0d1885bead | |
Maxime | 887d5239fd | |
Maxime | de0f263a46 | |
Maxime | 830ebbcdd3 | |
Maxime | 4156db2cdc | |
Maxime | 7b3a698daa | |
Maxime | 3b79a880ae | |
Maxime | 14b47b6f6e | |
Maxime | eda52c3d65 | |
Maxime Renou | 7e13ddcf67 |
309
README.md
309
README.md
|
@ -1,216 +1,217 @@
|
||||||
# laravel-connect
|
# laravel-connect
|
||||||
|
|
||||||
The Bluesquare Connect package allows you to use its OAuth server and sync its resources.
|
Ce package permet d'utiliser [Bluesquare Connect](https://connect.bluesquare.io) comme méthode d'authentification grâce au protocole OAuth 2.0.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Update your `composer.json`:
|
Mettre à jour les sources de `composer.json` avec :
|
||||||
|
|
||||||
```
|
```
|
||||||
"require": {
|
composer config repositories.connect vcs https://git.bluesquare.io/bluesquare/laravel-connect.git -n
|
||||||
"bluesquare/laravel-connect": "dev-master"
|
|
||||||
}
|
|
||||||
"repositories": [
|
|
||||||
{
|
|
||||||
"type": "vcs",
|
|
||||||
"url": "https://git.bluesquare.io/bluesquare/laravel-connect"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the package:
|
Puis installer le package :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
composer update bluesquare/laravel-connect
|
composer require bluesquare/laravel-connect "2.3"
|
||||||
```
|
```
|
||||||
|
|
||||||
Finally, update your `.env` with your client's credentials:
|
Mettre à jour le `.env` avec les identifiants du client OAuth généré sur [Bluesquare Connect](https://connect.bluesquare.io) :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
BCONNECT_CLIENT_ID=your_client_id
|
BCONNECT_CLIENT_ID=your_client_id
|
||||||
BCONNECT_CLIENT_SECRET=your_client_secret
|
BCONNECT_CLIENT_SECRET=your_client_secret
|
||||||
BCONNECT_REDIRECT=http://localhost:8000/connect/callback
|
BCONNECT_REDIRECT="${APP_URL}/connect/callback"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sign in with Bluesquare Connect
|
Et optionnellement :
|
||||||
|
|
||||||
Follow there instructions to add Bluesquare Connect's authentication to your app.
|
```bash
|
||||||
|
# URL du serveur OAuth
|
||||||
Update your `routes/web.php`:
|
BCONNECT_URL=https://connect.bluesquare.io
|
||||||
|
|
||||||
|
# Données demandées (scopes)
|
||||||
|
BCONNECT_USER_SCOPES=profile
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Il est possible de modifier d'autres comportements, à savoir :
|
||||||
|
- Model utilisateur utilisé (par défaut `App\Models\User`)
|
||||||
|
- URL de connexion via Bluesquare Connect (par défaut `/connect/authorize`)
|
||||||
|
- Remember me : garder la session active post-connexion (par défaut `true`)
|
||||||
|
|
||||||
|
Pour cela, il suffit de modifier le fichier `config/bconnect.php` généré via la commande `php artisan vendor:publish`.
|
||||||
|
|
||||||
|
### Configuration minimale de `User`
|
||||||
|
|
||||||
|
À moins d'utiliser le trait `HasConnectData` (voir ci-dessous), il faut s'assurer que toutes les colonnes autres que `email` sont `nullable` dans la table `users` (y compris `password`).
|
||||||
|
La colonne `email` est alors utilisée comme identifiant de référence par Bluesquare Connect.
|
||||||
|
|
||||||
|
### Connexion
|
||||||
|
|
||||||
|
Pour activer la connexion via Bluesquare Connect, il faut mettre à jour `web.php` en y ajoutant :
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Bluesquare\Connect\Facades\Connect;
|
||||||
|
|
||||||
Connect::routes();
|
Connect::routes();
|
||||||
```
|
```
|
||||||
|
|
||||||
Add the "Sign in with Bluesquare Connect" button in your blade login page:
|
Si Blade est utilisé, un bouton "Connexion via Bluesquare Connect" peut être ajouté ainsi :
|
||||||
|
|
||||||
```blade
|
```blade
|
||||||
<x-connect-button/>
|
<x-connect-button/>
|
||||||
```
|
```
|
||||||
|
|
||||||
Make sure that the `password` column of users table is nullable.
|
Sinon il suffit de rediriger vers `/connect/authorize` pour déclencher le flux.
|
||||||
|
|
||||||
#### Keep user tokens (optional)
|
### Configuration personnalisée de `User`
|
||||||
|
|
||||||
First, make sure that your model implements `HasConnectTokens` trait.
|
Le trait `HasConnectData` permet d'exploiter au maximum les données fournies par Bluesquare Connect. Notamment :
|
||||||
|
- Définir quelle colonne sert d'identifiant unique (par défaut `connect_id`) avec `$connectIdentifier` ;
|
||||||
|
- Définir quelles données sont à injectées dans le Model à la connexion, avec `$connectFillable`
|
||||||
|
|
||||||
Then, add the following columns to your table:
|
Pour utiliser l'identifiant par défaut, il convient d'ajouter cette colonne à `User` :
|
||||||
|
|
||||||
|
```php
|
||||||
|
$table->string('connect_id')->nullable();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Ou alors, et pour revenir à l'utilisation de `email` comme identifiant :
|
||||||
|
|
||||||
|
```php
|
||||||
|
use HasConnectData;
|
||||||
|
|
||||||
|
protected $connectIdentifier = 'email';
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensuite, pour injecter automatiquement les données fournies par Bluesquare Connect (liste indicative ci-dessous) :
|
||||||
|
|
||||||
|
```php
|
||||||
|
protected $connectFillable = [
|
||||||
|
'name',
|
||||||
|
'email'
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Il est possible de mapper les colonnes avec des noms différents et des valeurs par defaut, voire même d'injecter dans plusieurs colonnes, comme ceci :
|
||||||
|
|
||||||
|
```php
|
||||||
|
protected $connectIdentifier = ['connect_id' => 'oauth_id'];
|
||||||
|
|
||||||
|
protected $connectFillable = [
|
||||||
|
'name' => 'fullname|Anonymous',
|
||||||
|
'firstname' => ['firstname', 'nickname'],
|
||||||
|
'email' => 'email_address',
|
||||||
|
'group' => 'role|user'
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Un hook est disponible pour un post-traitement des données Connect (après création / mise à jour de l'utilisateur) ce qui peut être utile pour mettre à jour des relations :
|
||||||
|
|
||||||
|
```php
|
||||||
|
use HasConnectData;
|
||||||
|
|
||||||
|
public function postFillConnectData(array $data)
|
||||||
|
{
|
||||||
|
// Exemple
|
||||||
|
$this->roles()->sync(
|
||||||
|
Role::whereIn('name', $data['groups'])->pluck('id')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Synchronisation des données _(optionnel)_
|
||||||
|
|
||||||
|
Par défaut, les données des utilisateurs sont mises à jour à chaque nouvelle connexion.
|
||||||
|
Cependant, deux méthodes peuvent être utilisées pour conserver les données de tous les utilisateurs à jour sans reconnexion.
|
||||||
|
|
||||||
|
### 1. Synchronisation passive
|
||||||
|
|
||||||
|
Le trait `HasConnectTokens` permet de conserver les tokens OAuth pour une utilisation future (par exemple, pour synchroniser les utilisateurs périodiquement).
|
||||||
|
|
||||||
|
Avant tout, il faut ajouter ces colonnes dans la table `users` :
|
||||||
|
|
||||||
|
```php
|
||||||
$table->text('connect_access_token')->nullable();
|
$table->text('connect_access_token')->nullable();
|
||||||
$table->text('connect_refresh_token')->nullable();
|
$table->text('connect_refresh_token')->nullable();
|
||||||
$table->dateTime('connect_expires_at')->nullable();
|
$table->datetime('connect_expires_at')->nullable();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database syncing
|
Il est ensuite possible de mettre à jour les utilisateurs avec cette commande :
|
||||||
|
|
||||||
Follow these instructions to sync your database with Bluesquare Connect.
|
|
||||||
|
|
||||||
In your `AppServiceProvider`, specify in the `boot()` function which entities you want to sync:
|
|
||||||
|
|
||||||
```
|
|
||||||
$connect->setSynchronized([
|
|
||||||
Role::class,
|
|
||||||
Company::class,
|
|
||||||
Team::class,
|
|
||||||
User::class,
|
|
||||||
UserTeam::class
|
|
||||||
]);
|
|
||||||
```
|
|
||||||
|
|
||||||
Your models must use `HasConnectSync` trait. This trait allows you to customize the syncing behavior.
|
|
||||||
|
|
||||||
You also need to add this column to your synced tables:
|
|
||||||
|
|
||||||
```
|
|
||||||
$table->unsignedBigInteger('connect_resource_id')->unique();
|
|
||||||
```
|
|
||||||
|
|
||||||
_You can customize this column name and the syncing behavior in your model. Take a look at `HasConnectSync`._
|
|
||||||
|
|
||||||
Finally, use this command to sync everything:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
php artisan connect:sync
|
php artisan connect:sync
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Live updates (optional)
|
Mais aussi de seulement mettre à jour les tokens avant expiration avec cette commande :
|
||||||
|
|
||||||
First, configure a webhook on Bluesquare Connect :
|
```bash
|
||||||
|
php artisan connect:refresh
|
||||||
|
```
|
||||||
|
|
||||||
|
Il est conseillé d'appeler ces commandes périodiquement via une tâche CRON dans `Console/Kernel.php`.
|
||||||
|
|
||||||
|
### 2. Synchronisation active (webhook)
|
||||||
|
|
||||||
|
Avant tout, il est requis de créer une `App` liée au client utilisé sur Bluesquare Connect, et d'en activer le webhook.
|
||||||
|
|
||||||
|
Ensuite, pour pouvoir utiliser le webhook, il faut ajouter ceci à `routes/api.php`:
|
||||||
|
|
||||||
|
```
|
||||||
|
use Bluesquare\Connect\Facades\Connect;
|
||||||
|
|
||||||
|
Connect::apiRoutes();
|
||||||
|
```
|
||||||
|
|
||||||
|
Cela va permettre à Bluesquare Connect d'appeler cette URL à chaque modification d'un utilisateur :
|
||||||
|
|
||||||
```
|
```
|
||||||
https://your-app.com/api/connect/webhook
|
https://your-app.com/api/connect/webhook
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, update your `routes/api.php`:
|
Pour terminer, il suffit d'ajouter le trait `HasConnectWebhook` à `User`.
|
||||||
|
|
||||||
```
|
#### Configurer le comportement du webhook
|
||||||
Connect::apiRoutes();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced usage
|
Par défaut, le webhook se comporte ainsi :
|
||||||
|
- Lorsqu'un utilisateur est créé, il est inséré dans `users` comme à la première connexion ;
|
||||||
|
- Lorsqu'un utilisateur est mis à jour, il est modifié comme à chaque connexion / synchronisation ;
|
||||||
|
- Lorsqu'un utilisateur est supprimé, deux cas se présentent :
|
||||||
|
- si `User` implémente `SoftDeletes`, alors le webhook fait appel à `$user->delete()` ;
|
||||||
|
- si `SoftDeletes` est absent, alors la session utilisateur est simplement expirée en repassant `remember_token` à `null`.
|
||||||
|
- Si l'utilisateur est restauré, deux cas se présentent :
|
||||||
|
- si `User` implémente `SoftDeletes` et que l'utilisateur est bien présent en base de données, alors le webhook fait appel à `$user->restore()` ;
|
||||||
|
- si `SoftDeletes` est absent ou que l'utilisateur n'existe pas, alors on recréé l'utilisateur (s'il existe, il est simplement mis à jour).
|
||||||
|
|
||||||
### OAuth (sign in)
|
Il est recommandé de personnaliser le cas de la suppression en ajoutant la fonction suivante à `User.php` :
|
||||||
|
|
||||||
#### Authorization
|
```php
|
||||||
|
public function onConnectDelete(array $data)
|
||||||
Redirect to Bluesquare Connect authorization page:
|
|
||||||
|
|
||||||
```
|
|
||||||
public function authorize(Connect $connect)
|
|
||||||
{
|
{
|
||||||
return $connect->redirect($optional_custom_state);
|
// Optionnel : avec cette fonction on met à jour une dernière fois l'utilisateur
|
||||||
|
$this->onConnectUpdate($data);
|
||||||
|
|
||||||
|
//...supprimer l'utilisateur comme souhaité...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Authorization callback
|
Les autres cas peuvent être personnalisés avec ces différentes fonctions :
|
||||||
|
|
||||||
Auto: check state, login and redirect
|
```php
|
||||||
|
public function onConnectCreate(array $data);
|
||||||
```
|
public function onConnectUpdate(array $data);
|
||||||
public function callback(Request $request, Connect $connect)
|
public function onConnectRestore(array $data);
|
||||||
{
|
|
||||||
return $connect->loginFromCallback($request, $optional_redirect_to);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Manual: check state
|
## Données fournies par Bluesquare Connect
|
||||||
|
|
||||||
```
|
- `id` et `connect_id` : identifiant unique de l'utilisateur
|
||||||
public function callback(Request $request, Connect $connect)
|
- `email` : adresse e-mail
|
||||||
{
|
- `name` : nom complet
|
||||||
$valid = $connect->checkState($request);
|
- `firstname` et `nickname` : prénom
|
||||||
// ...
|
- `lastname` : nom de famille
|
||||||
}
|
- `avatar`, `picture` et `profile_picture` : URL de la photo de profil
|
||||||
```
|
- `company` : entreprise
|
||||||
|
- `job` : mêtier exercé dans l'entreprise
|
||||||
#### Tokens management
|
- `group` : rôle principal (les groupes sont personnalisées pour chaque App sur Bluesquare Connect)
|
||||||
|
- `groups` : tableau contenant tous les rôles associés à l'utilisateur
|
||||||
```
|
|
||||||
// Retrieve tokens from an authorization code
|
|
||||||
$connect_data = $connect->getAccessTokenFromAuthorizationCode($code);
|
|
||||||
|
|
||||||
// Retrieve tokens from a refresh token
|
|
||||||
$connect_data = $connect->getAccessTokenFromRefreshToken($connect_data['refresh_token']);
|
|
||||||
|
|
||||||
// With HasConnectTokens trait: get your local user tokens
|
|
||||||
$connect->getUserAccessToken($user);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### User data
|
|
||||||
|
|
||||||
```
|
|
||||||
// Retrieve user data from an access token
|
|
||||||
$user_data = $connect->getUserData($connect_data['access_token']);
|
|
||||||
|
|
||||||
// Example: find the corresponding user in your database
|
|
||||||
$user = User::where('email', $user_data['email'])->first();
|
|
||||||
```
|
|
||||||
|
|
||||||
### OAuth (client)
|
|
||||||
|
|
||||||
#### Token management
|
|
||||||
|
|
||||||
```
|
|
||||||
// Get an access token
|
|
||||||
$connect->getAccessToken();
|
|
||||||
|
|
||||||
// Delete the current access token from cache
|
|
||||||
$connect->deleteAccessToken();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### API resources
|
|
||||||
|
|
||||||
```
|
|
||||||
// Fetch all users
|
|
||||||
$connect->getAll('User');
|
|
||||||
|
|
||||||
// Fetch an user
|
|
||||||
$connect->get('User', 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Syncing
|
|
||||||
|
|
||||||
```
|
|
||||||
// Sync everything
|
|
||||||
$optional_resource_types = ['User', ...];
|
|
||||||
$connect->syncAll($optional_resource_types);
|
|
||||||
|
|
||||||
// Sync a specific resource
|
|
||||||
$connect->sync('User', 1);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Webhook
|
|
||||||
|
|
||||||
```
|
|
||||||
// Handle a webhook request
|
|
||||||
$connect->handleWebhook($request);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Publish our config file (`config/bconnect.php`) to customize the package configuration:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
php artisan vendor:publish
|
|
||||||
```
|
|
|
@ -35,8 +35,8 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"guzzlehttp/guzzle": "^6.5",
|
"guzzlehttp/guzzle": "^7.3",
|
||||||
"php": "^7.2"
|
"php": "^7.3|^8.0"
|
||||||
},
|
},
|
||||||
"prefer-stable": true
|
"prefer-stable": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,41 +4,47 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "98b09ff09c6392aadb0f56bb9dc51a96",
|
"content-hash": "9819197b9702d3ffb6483686d1005f4a",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/guzzle",
|
"name": "guzzlehttp/guzzle",
|
||||||
"version": "6.5.2",
|
"version": "7.2.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/guzzle.git",
|
"url": "https://github.com/guzzle/guzzle.git",
|
||||||
"reference": "43ece0e75098b7ecd8d13918293029e555a50f82"
|
"reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82",
|
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/0aa74dfb41ae110835923ef10a9d803a22d50e79",
|
||||||
"reference": "43ece0e75098b7ecd8d13918293029e555a50f82",
|
"reference": "0aa74dfb41ae110835923ef10a9d803a22d50e79",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"guzzlehttp/promises": "^1.0",
|
"guzzlehttp/promises": "^1.4",
|
||||||
"guzzlehttp/psr7": "^1.6.1",
|
"guzzlehttp/psr7": "^1.7",
|
||||||
"php": ">=5.5"
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"psr/http-client": "^1.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/http-client-implementation": "1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
|
"php-http/client-integration-tests": "^3.0",
|
||||||
|
"phpunit/phpunit": "^8.5.5 || ^9.3.5",
|
||||||
"psr/log": "^1.1"
|
"psr/log": "^1.1"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
|
"ext-curl": "Required for CURL handler support",
|
||||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||||
"psr/log": "Required for using the Log middleware"
|
"psr/log": "Required for using the Log middleware"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "6.5-dev"
|
"dev-master": "7.1-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -58,6 +64,11 @@
|
||||||
"name": "Michael Dowling",
|
"name": "Michael Dowling",
|
||||||
"email": "mtdowling@gmail.com",
|
"email": "mtdowling@gmail.com",
|
||||||
"homepage": "https://github.com/mtdowling"
|
"homepage": "https://github.com/mtdowling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Márk Sági-Kazár",
|
||||||
|
"email": "mark.sagikazar@gmail.com",
|
||||||
|
"homepage": "https://sagikazarmark.hu"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Guzzle is a PHP HTTP client library",
|
"description": "Guzzle is a PHP HTTP client library",
|
||||||
|
@ -68,30 +79,50 @@
|
||||||
"framework",
|
"framework",
|
||||||
"http",
|
"http",
|
||||||
"http client",
|
"http client",
|
||||||
|
"psr-18",
|
||||||
|
"psr-7",
|
||||||
"rest",
|
"rest",
|
||||||
"web service"
|
"web service"
|
||||||
],
|
],
|
||||||
"time": "2019-12-23T11:57:10+00:00"
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Nyholm",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/alexeyshockov",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/gmponos",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2020-10-10T11:47:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/promises",
|
"name": "guzzlehttp/promises",
|
||||||
"version": "v1.3.1",
|
"version": "1.4.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/promises.git",
|
"url": "https://github.com/guzzle/promises.git",
|
||||||
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
|
"reference": "60d379c243457e073cff02bc323a2a86cb355631"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
|
"url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631",
|
||||||
"reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
|
"reference": "60d379c243457e073cff02bc323a2a86cb355631",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.5.0"
|
"php": ">=5.5"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.0"
|
"symfony/phpunit-bridge": "^4.4 || ^5.1"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
|
@ -122,20 +153,20 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"promise"
|
"promise"
|
||||||
],
|
],
|
||||||
"time": "2016-12-20T10:07:11+00:00"
|
"time": "2020-09-30T07:37:28+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/psr7",
|
"name": "guzzlehttp/psr7",
|
||||||
"version": "1.6.1",
|
"version": "1.7.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/guzzle/psr7.git",
|
"url": "https://github.com/guzzle/psr7.git",
|
||||||
"reference": "239400de7a173fe9901b9ac7c06497751f00727a"
|
"reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a",
|
"url": "https://api.github.com/repos/guzzle/psr7/zipball/53330f47520498c0ae1f61f7e2c90f55690c06a3",
|
||||||
"reference": "239400de7a173fe9901b9ac7c06497751f00727a",
|
"reference": "53330f47520498c0ae1f61f7e2c90f55690c06a3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -148,15 +179,15 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ext-zlib": "*",
|
"ext-zlib": "*",
|
||||||
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
|
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"zendframework/zend-httphandlerrunner": "Emit PSR-7 responses"
|
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "1.6-dev"
|
"dev-master": "1.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -193,7 +224,56 @@
|
||||||
"uri",
|
"uri",
|
||||||
"url"
|
"url"
|
||||||
],
|
],
|
||||||
"time": "2019-07-01T23:21:34+00:00"
|
"time": "2020-09-30T07:37:11+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-client",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-client.git",
|
||||||
|
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||||
|
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.0 || ^8.0",
|
||||||
|
"psr/http-message": "^1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Client\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP clients",
|
||||||
|
"homepage": "https://github.com/php-fig/http-client",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-client",
|
||||||
|
"psr",
|
||||||
|
"psr-18"
|
||||||
|
],
|
||||||
|
"time": "2020-06-29T06:28:15+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/http-message",
|
"name": "psr/http-message",
|
||||||
|
@ -288,10 +368,13 @@
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "dev",
|
||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false,
|
"prefer-stable": true,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": {
|
||||||
"platform-dev": []
|
"php": "^7.2"
|
||||||
|
},
|
||||||
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "1.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ return [
|
||||||
/**
|
/**
|
||||||
* OAuth model
|
* OAuth model
|
||||||
*/
|
*/
|
||||||
'model' => \App\User::class,
|
'model' => \App\Models\User::class,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Route that redirects to Bluesquare Connect
|
* Route that redirects to Bluesquare Connect
|
||||||
|
@ -13,18 +13,19 @@ return [
|
||||||
'login_url' => '/connect/authorize',
|
'login_url' => '/connect/authorize',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Use post-login remember cookie
|
||||||
|
*/
|
||||||
|
'login_remember' => true,
|
||||||
|
|
||||||
|
/**
|
||||||
* OAuth callback URL
|
* OAuth callback URL
|
||||||
*/
|
*/
|
||||||
'redirect_url' => env('BCONNECT_REDIRECT', 'http://localhost:8000/connect/callback'),
|
'redirect_url' => env('BCONNECT_REDIRECT', 'http://localhost:8000/connect/callback'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OAuth client id
|
* OAuth client identifiers
|
||||||
*/
|
*/
|
||||||
'client_id' => env('BCONNECT_CLIENT_ID', null),
|
'client_id' => env('BCONNECT_CLIENT_ID', null),
|
||||||
|
|
||||||
/**
|
|
||||||
* OAuth client secret
|
|
||||||
*/
|
|
||||||
'client_secret' => env('BCONNECT_CLIENT_SECRET', null),
|
'client_secret' => env('BCONNECT_CLIENT_SECRET', null),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Connect\Commands;
|
||||||
|
|
||||||
|
use Bluesquare\Connect\Connect;
|
||||||
|
use Bluesquare\Connect\ConnectException;
|
||||||
|
use Bluesquare\Connect\Traits\HasConnectTokens;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class RefreshTokens extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'connect:refresh';
|
||||||
|
|
||||||
|
protected $description = 'Refresh Bluesquare Connect tokens';
|
||||||
|
|
||||||
|
public function handle(Connect $connect)
|
||||||
|
{
|
||||||
|
$class = config('bconnect.model');
|
||||||
|
|
||||||
|
$has_fields = in_array(HasConnectTokens::class, class_uses($class));
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
try {
|
||||||
|
$tokens = $connect->getAccessTokenFromRefreshToken($model->connect_refresh_token);
|
||||||
|
$connect->updateUserConnectData($model, $tokens);
|
||||||
|
$model->save();
|
||||||
|
}
|
||||||
|
catch (\Exception $exception) {
|
||||||
|
$this->warn("Failed to refresh model tokens", $model->toArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->info("Tokens refreshed");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,41 +3,50 @@
|
||||||
namespace Bluesquare\Connect\Commands;
|
namespace Bluesquare\Connect\Commands;
|
||||||
|
|
||||||
use Bluesquare\Connect\Connect;
|
use Bluesquare\Connect\Connect;
|
||||||
|
use Bluesquare\Connect\ConnectException;
|
||||||
|
use Bluesquare\Connect\Traits\HasConnectData;
|
||||||
|
use Bluesquare\Connect\Traits\HasConnectTokens;
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
class Sync extends Command
|
class Sync extends Command
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'connect:sync';
|
protected $signature = 'connect:sync';
|
||||||
|
|
||||||
/**
|
protected $description = 'Sync Bluesquare Connect users';
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Synchronize Bluesquare Connect resources';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new command instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
|
||||||
public function handle(Connect $connect)
|
public function handle(Connect $connect)
|
||||||
{
|
{
|
||||||
$connect->syncAll();
|
$this->call('connect:refresh');
|
||||||
|
|
||||||
|
$class = config('bconnect.model');
|
||||||
|
|
||||||
|
$has_fields = in_array(HasConnectTokens::class, class_uses($class));
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
$data = $connect->getUserData($model->connect_access_token);
|
||||||
|
$connect->updateUserData($model, $data);
|
||||||
|
$model->save();
|
||||||
|
|
||||||
|
if (in_array(HasConnectData::class, class_uses($model))) {
|
||||||
|
$model->postFillConnectData($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Exception $exception) {
|
||||||
|
$this->warn("Failed to sync model data", $model->toArray());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->info("Models synced");
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
379
src/Connect.php
379
src/Connect.php
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
namespace Bluesquare\Connect;
|
namespace Bluesquare\Connect;
|
||||||
|
|
||||||
use Bluesquare\Connect\Traits\HasConnectSync;
|
use Bluesquare\Connect\Traits\HasConnectData;
|
||||||
use Bluesquare\Connect\Traits\HasConnectTokens;
|
use Bluesquare\Connect\Traits\HasConnectTokens;
|
||||||
|
use Bluesquare\Connect\Traits\HasConnectWebhook;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Router;
|
use Illuminate\Routing\Router;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
@ -14,59 +16,13 @@ use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
class Connect
|
class Connect
|
||||||
{
|
{
|
||||||
protected static $resources = [
|
|
||||||
'Role',
|
|
||||||
'Company',
|
|
||||||
'Team',
|
|
||||||
'User',
|
|
||||||
'UserTeam'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected static $foreignKeys = [
|
|
||||||
'role_id' => 'Role',
|
|
||||||
'company_id' => 'Company',
|
|
||||||
'team_id' => 'Team',
|
|
||||||
'user_id' => 'User',
|
|
||||||
'user_teams_id' => 'UserTeam'
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $app;
|
protected $app;
|
||||||
protected $synchronized = [];
|
|
||||||
|
|
||||||
public function __construct($app)
|
public function __construct(Application $app)
|
||||||
{
|
{
|
||||||
$this->app = $app;
|
$this->app = $app;
|
||||||
}
|
}
|
||||||
|
|
||||||
// User config
|
|
||||||
|
|
||||||
public function setSynchronized($models)
|
|
||||||
{
|
|
||||||
$items = [];
|
|
||||||
|
|
||||||
foreach ($models as $model)
|
|
||||||
{
|
|
||||||
if (!in_array(HasConnectSync::class, class_uses($model)))
|
|
||||||
throw new ConnectException("$model does not implement HasConnectSync trait.");
|
|
||||||
|
|
||||||
$class = explode('\\', $model);
|
|
||||||
$resource = $model::$connectResource ?? end($class);
|
|
||||||
|
|
||||||
$items[$resource] = $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
$synchronized = [];
|
|
||||||
|
|
||||||
foreach (self::$resources as $resourceType) { // Re-ordering
|
|
||||||
foreach ($items as $resource => $model) {
|
|
||||||
if ($resource == $resourceType)
|
|
||||||
$synchronized[$resource] = $model;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->synchronized = $synchronized;
|
|
||||||
}
|
|
||||||
|
|
||||||
// API
|
// API
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,7 +32,7 @@ class Connect
|
||||||
* @return array
|
* @return array
|
||||||
* @throws ConnectException
|
* @throws ConnectException
|
||||||
*/
|
*/
|
||||||
public function request($method, $uri, $data = null, $auth = true): array
|
public function request($method, $uri, $data = null, $access_token = null): array
|
||||||
{
|
{
|
||||||
$url = $this->getUrl();
|
$url = $this->getUrl();
|
||||||
$url = $url . '/' . trim($uri, '/');
|
$url = $url . '/' . trim($uri, '/');
|
||||||
|
@ -89,14 +45,11 @@ class Connect
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($auth === true) {
|
if (! is_null($access_token)) {
|
||||||
$config['headers']['Authorization'] = 'Bearer ' . $this->getAccessToken();
|
$config['headers']['Authorization'] = 'Bearer ' . $access_token;
|
||||||
}
|
|
||||||
elseif ($auth !== false) {
|
|
||||||
$config['headers']['Authorization'] = 'Bearer ' . $auth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($data)) {
|
if (! is_null($data)) {
|
||||||
$config['form_params'] = $data;
|
$config['form_params'] = $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,12 +63,13 @@ class Connect
|
||||||
return json_decode($body, true);
|
return json_decode($body, true);
|
||||||
|
|
||||||
} catch(\Exception $e) {
|
} catch(\Exception $e) {
|
||||||
$this->deleteAccessToken();
|
$this->flushTokens();
|
||||||
|
|
||||||
throw new ConnectException($e->getMessage());
|
throw new ConnectException($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth (user)
|
// Authorization flow
|
||||||
|
|
||||||
public function redirect($state = null)
|
public function redirect($state = null)
|
||||||
{
|
{
|
||||||
|
@ -123,9 +77,12 @@ class Connect
|
||||||
$state = Str::random();
|
$state = Str::random();
|
||||||
|
|
||||||
$states = session()->get('connect_states');
|
$states = session()->get('connect_states');
|
||||||
if (!is_array($states))
|
|
||||||
|
if (! is_array($states))
|
||||||
$states = [];
|
$states = [];
|
||||||
|
|
||||||
$states[] = $state;
|
$states[] = $state;
|
||||||
|
|
||||||
session()->put('connect_states', $states);
|
session()->put('connect_states', $states);
|
||||||
|
|
||||||
$query = http_build_query([
|
$query = http_build_query([
|
||||||
|
@ -137,106 +94,80 @@ class Connect
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$url = $this->getUrl() . '/oauth/authorize?' . $query;
|
$url = $this->getUrl() . '/oauth/authorize?' . $query;
|
||||||
return redirect()->to($url);
|
|
||||||
|
return redirect($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkState(Request $request)
|
public function checkState(Request $request)
|
||||||
{
|
{
|
||||||
if (!session()->has('connect_states')) {
|
if (! session()->has('connect_states'))
|
||||||
Log::debug("Missing session states");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
$states = session()->get('connect_states');
|
$states = session()->get('connect_states');
|
||||||
|
|
||||||
if (!is_array($states)) {
|
if (! is_array($states))
|
||||||
Log::debug("Invalid session state");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (!$request->has('state') || !in_array($request->state, $states)) {
|
if (! $request->has('state') || ! in_array($request->state, $states))
|
||||||
Log::debug("Missing valid state in request");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
unset($states[array_search($request->state, $states)]);
|
unset($states[array_search($request->state, $states)]);
|
||||||
|
|
||||||
session()->put('connect_states', $states);
|
session()->put('connect_states', $states);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loginFromCallback(Request $request, $redirect_to = '/')
|
public function loginFromCallback(Request $request, $redirect_to = '/')
|
||||||
{
|
{
|
||||||
if (!$this->checkState($request))
|
if (! $this->checkState($request) || ! $request->has('code'))
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
|
|
||||||
// Code check
|
|
||||||
|
|
||||||
if (!$request->has('code')) {
|
|
||||||
Log::debug("Missing authorization code");
|
|
||||||
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']);
|
||||||
|
|
||||||
$model_data = $this->getUserData($connect_data['access_token']);
|
// User data
|
||||||
$model = config('bconnect.model');
|
|
||||||
|
|
||||||
if (in_array($model, $this->synchronized)) {
|
$user_data = $this->getUserData($connect_data['access_token']);
|
||||||
$user = $model::findConnectResource($model_data['id']) ?? new $model;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$user = $model::where('email', $model_data['email'])->first() ?? new $model;
|
|
||||||
}
|
|
||||||
|
|
||||||
$model_data = $this->convertForeignKeys($model_data);
|
|
||||||
|
|
||||||
$user->fill($model_data);
|
|
||||||
|
|
||||||
if (in_array($model, $this->synchronized))
|
|
||||||
$user->{$model::$connectColumnId} = $model_data['id'];
|
|
||||||
|
|
||||||
|
$user = $this->sync('create', $user_data);
|
||||||
|
$this->updateUserConnectData($user, $connect_data);
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
if (in_array($model, $this->synchronized))
|
// Login
|
||||||
$user = $model::findConnectResource($model_data['id']);
|
|
||||||
|
|
||||||
$this->updateUserConnectData($user, $connect_data);
|
auth()->login($user, config('bconnect.login_remember', true));
|
||||||
|
|
||||||
auth()->login($user, true);
|
|
||||||
|
|
||||||
return redirect($redirect_to);
|
return redirect($redirect_to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OAuth methods
|
||||||
|
|
||||||
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'),
|
||||||
'scope' => config('bconnect.user_scopes'),
|
'scope' => config('bconnect.user_scopes'),
|
||||||
'redirect_uri' => config('bconnect.redirect_url'),
|
'redirect_uri' => config('bconnect.redirect_url'),
|
||||||
'code' => $code
|
'code' => $code
|
||||||
], false);
|
]);
|
||||||
|
|
||||||
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'),
|
||||||
'scope' => config('bconnect.user_scopes'),
|
'scope' => config('bconnect.user_scopes'),
|
||||||
'redirect_uri' => config('bconnect.redirect_url'),
|
'redirect_uri' => config('bconnect.redirect_url'),
|
||||||
'refresh_token' => $refresh_token
|
'refresh_token' => $refresh_token
|
||||||
], false);
|
]);
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserData($access_token)
|
public function getUserData($access_token)
|
||||||
|
@ -244,164 +175,102 @@ class Connect
|
||||||
return $this->request('get', 'api/user', null, $access_token);
|
return $this->request('get', 'api/user', null, $access_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateUserConnectData($user, $data)
|
public function getUserAccessToken($model)
|
||||||
{
|
{
|
||||||
if (!in_array(HasConnectTokens::class, class_uses(get_class($user))))
|
$class = get_class($model);
|
||||||
return false;
|
$has_fields = in_array(HasConnectTokens::class, class_uses($class));
|
||||||
|
|
||||||
$user->connect_access_token = $data['access_token'];
|
if (! $has_fields) {
|
||||||
$user->connect_refresh_token = $data['refresh_token'];
|
throw new ConnectException("$class does not implement HasConnectTokens");
|
||||||
$user->connect_expires_at = $data['expires_at'];
|
|
||||||
|
|
||||||
return $user->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserAccessToken($user)
|
|
||||||
{
|
|
||||||
$has_fields = in_array(HasConnectTokens::class, class_uses(get_class($user)));
|
|
||||||
|
|
||||||
if (!$has_fields) {
|
|
||||||
throw new ConnectException("User class does not implement HasConnectTokens");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user->connect_expires_at <= now()) {
|
if ($model->connect_expires_at <= now()->addHour()) {
|
||||||
$connect_data = $this->getAccessTokenFromRefreshToken($user->connect_refresh_token);
|
$connect_data = $this->getAccessTokenFromRefreshToken($model->connect_refresh_token);
|
||||||
$this->updateUserConnectData($user, $connect_data);
|
$this->updateUserConnectData($model, $connect_data);
|
||||||
return $connect_data['access_token'];
|
return $connect_data['access_token'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $user->connect_access_token;
|
return $model->connect_access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth (client)
|
// Sync
|
||||||
|
|
||||||
public function getAccessToken()
|
public function sync(string $event, array $data)
|
||||||
{
|
{
|
||||||
$access_token = cache()->get('bconnect.access_token');
|
$class = config('bconnect.model');
|
||||||
$access_token_expiration = cache()->get('bconnect.access_token_expiration');
|
|
||||||
|
|
||||||
if ($access_token && $access_token_expiration > time() + 60) {
|
if (in_array($event, ['update', 'delete'])) {
|
||||||
return $access_token;
|
$model = $this->resolveUser($data);
|
||||||
|
|
||||||
|
if (! $model->exists() && $event === 'delete')
|
||||||
|
return $model;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
$hasSoftDeletes = in_array(\Illuminate\Database\Eloquent\SoftDeletes::class, class_uses($class));
|
||||||
|
$model = $this->resolveUser($data, $hasSoftDeletes);
|
||||||
|
|
||||||
$data = $this->request('post', '/oauth/token', [
|
if ($model->exists()) {
|
||||||
'grant_type' => 'client_credentials',
|
if ($hasSoftDeletes && $model->trashed()) {
|
||||||
'client_id' => config('bconnect.client_id'),
|
$event = 'restore';
|
||||||
'client_secret' => config('bconnect.client_secret'),
|
} else {
|
||||||
'scope' => config('bconnect.client_scopes')
|
$event = 'update';
|
||||||
], false);
|
}
|
||||||
|
|
||||||
cache()->set('bconnect.access_token', $data['access_token']);
|
|
||||||
cache()->set('bconnect.access_token_expiration', time() + $data['expires_in']);
|
|
||||||
|
|
||||||
return $data['access_token'];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteAccessToken()
|
|
||||||
{
|
|
||||||
cache()->delete('bconnect.access_token');
|
|
||||||
cache()->delete('bconnect.access_token_expiration');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Webhook handler
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Request $request
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
public function handleWebhook(Request $request)
|
|
||||||
{
|
|
||||||
$data = $request->validate([
|
|
||||||
'connectEventType' => 'required|in:created,updated,deleted',
|
|
||||||
'connectResourceType' => 'required',
|
|
||||||
'connectResourceTable' => 'required',
|
|
||||||
'connectResourceData' => 'required|array',
|
|
||||||
'connectResourceData.id' => 'required'
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!array_key_exists($data['connectResourceType'], $this->synchronized))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
$model = $this->synchronized[$data['connectResourceType']];
|
|
||||||
$method = $this->getEventMethod($data['connectEventType']);
|
|
||||||
|
|
||||||
$data = $data['connectResourceData'];
|
|
||||||
|
|
||||||
try {
|
|
||||||
$data = $this->get($data['connectResourceType'], $data['connectResourceData']['id']);
|
|
||||||
if ($data['connectEventType'] == 'deleted') {
|
|
||||||
abort(403, "This resource still exists.");
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
if ($data['connectEventType'] != 'deleted') {
|
|
||||||
abort(404, "Could not retrieve this resource.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $this->convertForeignKeys($data);
|
if (in_array(HasConnectWebhook::class, class_uses($class))) {
|
||||||
|
$method = 'onConnect' . ucfirst($event);
|
||||||
|
$model->$method($data);
|
||||||
|
} else {
|
||||||
|
$this->updateUserData($model, $data);
|
||||||
|
$model->save();
|
||||||
|
|
||||||
$model::$method($data['id'], $data);
|
if (in_array(HasConnectData::class, class_uses($model))) {
|
||||||
|
$model->postFillConnectData($data);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resources getters
|
|
||||||
|
|
||||||
public function getAll($resourceType)
|
|
||||||
{
|
|
||||||
return $this->request('get', "api/resources/$resourceType");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function get($resourceType, $resourceId)
|
|
||||||
{
|
|
||||||
return $this->request('get', "api/resources/$resourceType/$resourceId");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resources syncing
|
|
||||||
|
|
||||||
public function syncAll($resourceTypes = null)
|
|
||||||
{
|
|
||||||
$resourceTypes = $resourceTypes ?? $this->synchronized;
|
|
||||||
|
|
||||||
foreach ($resourceTypes as $resourceType)
|
|
||||||
{
|
|
||||||
$resourceType = $this->resolveResourceType($resourceType);
|
|
||||||
|
|
||||||
if (!array_key_exists($resourceType, $this->synchronized))
|
|
||||||
throw new ConnectException("Resource $resourceType not declared as synchronized.");
|
|
||||||
|
|
||||||
$resources = $this->getAll($resourceType);
|
|
||||||
$model = $this->synchronized[$resourceType];
|
|
||||||
$identifiers = [];
|
|
||||||
|
|
||||||
foreach ($resources as $data)
|
|
||||||
{
|
|
||||||
$identifiers[] = intval($data['id']);
|
|
||||||
$this->sync($resourceType, $data['id'], $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($model::all() as $item)
|
|
||||||
{
|
|
||||||
if (!in_array(intval($item->{$model::$connectColumnId}), $identifiers))
|
|
||||||
$model::onConnectResourceDoesNotExist($item);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $model;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sync($resourceType, $resourceId, $resourceData = null)
|
public function updateUserConnectData($user, $data)
|
||||||
{
|
{
|
||||||
$resourceType = $this->resolveResourceType($resourceType);
|
if (in_array(HasConnectTokens::class, class_uses(get_class($user))))
|
||||||
|
$user->fillConnectTokens($data);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_null($resourceData)) {
|
public function updateUserData($user, $data)
|
||||||
$resourceData = $this->get($resourceType, $resourceId);
|
{
|
||||||
|
if (in_array(HasConnectData::class, class_uses(get_class($user))))
|
||||||
|
$user->fillConnectData($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveUser($data, $withTrashed = false)
|
||||||
|
{
|
||||||
|
$class = config('bconnect.model');
|
||||||
|
$query = $class::query();
|
||||||
|
|
||||||
|
if ($withTrashed)
|
||||||
|
$query->withTrashed();
|
||||||
|
|
||||||
|
$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 {
|
||||||
|
$model = $query->where('email', $data['email'])->first() ?? $model;
|
||||||
|
|
||||||
|
if (! $model->exists())
|
||||||
|
$model->email = $data['email'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$model = $this->synchronized[$resourceType];
|
return $model;
|
||||||
$item = $model::findConnectResource($resourceId);
|
|
||||||
$method = $this->getEventMethod($item ? 'updated' : 'created');
|
|
||||||
|
|
||||||
$data = $this->convertForeignKeys($resourceData);
|
|
||||||
$model::$method($resourceId, $data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routing
|
// Routing
|
||||||
|
@ -425,44 +294,14 @@ class Connect
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
|
|
||||||
protected function resolveResourceType($class)
|
protected function flushTokens()
|
||||||
{
|
{
|
||||||
if (in_array($class, $this->synchronized))
|
session()->forget('bconnect.access_token');
|
||||||
return array_flip($this->synchronized)[$class];
|
session()->forget('bconnect.access_token_expiration');
|
||||||
|
|
||||||
return $class;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function resolveResourceModel($class)
|
|
||||||
{
|
|
||||||
if (array_key_exists($class, $this->synchronized))
|
|
||||||
return $this->synchronized[$class];
|
|
||||||
|
|
||||||
return $class;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getEventMethod($event)
|
|
||||||
{
|
|
||||||
return 'onConnectResource' . ucfirst($event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getUrl()
|
protected function getUrl()
|
||||||
{
|
{
|
||||||
return config('bconnect.url') ?? 'https://connect.bluesquare.io';
|
return config('bconnect.url') ?? 'https://connect.bluesquare.io';
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function convertForeignKeys($data)
|
|
||||||
{
|
|
||||||
foreach (self::$foreignKeys as $key => $resourceType)
|
|
||||||
{
|
|
||||||
if (!array_key_exists($key, $data)) continue;
|
|
||||||
if (!array_key_exists($resourceType, $this->synchronized)) continue;
|
|
||||||
|
|
||||||
$model = $this->resolveResourceModel($resourceType);
|
|
||||||
$record = $model::findConnectResource($data[$key]);
|
|
||||||
$data[$key] = $record ? $record->id : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,70 +2,54 @@
|
||||||
|
|
||||||
namespace Bluesquare\Connect;
|
namespace Bluesquare\Connect;
|
||||||
|
|
||||||
|
use Bluesquare\Connect\Commands\RefreshTokens;
|
||||||
use Bluesquare\Connect\Commands\Sync;
|
use Bluesquare\Connect\Commands\Sync;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class ConnectServiceProvider extends ServiceProvider
|
class ConnectServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Register any application services.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
// Config
|
$this->mergeConfigFrom($this->path('config/bconnect.php'), 'bconnect');
|
||||||
|
|
||||||
$this->mergeConfigFrom(
|
|
||||||
__DIR__ . '/../config/bconnect.php',
|
|
||||||
'bconnect'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Singletons
|
|
||||||
|
|
||||||
$this->app->singleton(Connect::class, function ($app) {
|
$this->app->singleton(Connect::class, function ($app) {
|
||||||
return new Connect($app);
|
return new Connect($app);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Bootstrap any application services.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
// Config
|
$config_path = $this->path('config/bconnect.php');
|
||||||
|
$views_path = $this->path('resources/views/connect');
|
||||||
|
|
||||||
$this->publishes([
|
$this->publishes([
|
||||||
__DIR__ . '/../config/bconnect.php' => config_path('bconnect.php')
|
$config_path => config_path('bconnect.php'),
|
||||||
|
$views_path => resource_path('views/vendor/connect'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Translations
|
$this->loadTranslationsFrom($this->path('resources/translations'), 'connect');
|
||||||
|
|
||||||
$this->loadTranslationsFrom(__DIR__.'/../resources/translations', 'connect');
|
$this->loadViewsFrom($this->path('resources/views/connect'), 'connect');
|
||||||
|
|
||||||
// Views
|
if ($this->app->runningInConsole()) {
|
||||||
|
$this->commands([
|
||||||
$this->loadViewsFrom(__DIR__.'/../resources/views/connect', 'connect');
|
RefreshTokens::class,
|
||||||
|
Sync::class,
|
||||||
$this->publishes([
|
]);
|
||||||
__DIR__.'/../resources/views/connect' => resource_path('views/vendor/connect'),
|
}
|
||||||
]);
|
|
||||||
|
|
||||||
|
// Laravel 7+
|
||||||
if (method_exists($this, 'loadViewComponentsAs')) {
|
if (method_exists($this, 'loadViewComponentsAs')) {
|
||||||
// Laravel 7+
|
|
||||||
$this->loadViewComponentsAs('connect', [
|
$this->loadViewComponentsAs('connect', [
|
||||||
\Bluesquare\Connect\View\Components\Button::class
|
\Bluesquare\Connect\View\Components\Button::class
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Commands
|
// Misc
|
||||||
|
|
||||||
if ($this->app->runningInConsole()) {
|
private function path($path = '')
|
||||||
$this->commands([
|
{
|
||||||
Sync::class
|
return __DIR__ . "/../$path";
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Bluesquare\Connect\Traits;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
trait HasConnectData
|
||||||
|
{
|
||||||
|
public function getConnectIdentifier()
|
||||||
|
{
|
||||||
|
return $this->connectIdentifier ?? 'connect_id';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fillConnectData(array $data)
|
||||||
|
{
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($touched as $model)
|
||||||
|
$model->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postFillConnectData(array $data)
|
||||||
|
{
|
||||||
|
// Intended for post-processing / relationship handling
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Bluesquare\Connect\Traits;
|
|
||||||
|
|
||||||
trait HasConnectSync
|
|
||||||
{
|
|
||||||
abstract function fill(array $attributes);
|
|
||||||
abstract function save();
|
|
||||||
abstract function delete();
|
|
||||||
|
|
||||||
public static $connectResource;
|
|
||||||
|
|
||||||
public static $connectColumnId = 'connect_resource_id';
|
|
||||||
|
|
||||||
public static function findConnectResource($id)
|
|
||||||
{
|
|
||||||
return self::query()->where(self::$connectColumnId, $id)->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function onConnectResourceCreated($id, $data)
|
|
||||||
{
|
|
||||||
$record = self::findConnectResource($id) ?? new self;
|
|
||||||
$record->fill($data); // TODO
|
|
||||||
$record->{self::$connectColumnId} = $id;
|
|
||||||
return $record->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function onConnectResourceUpdated($id, $data)
|
|
||||||
{
|
|
||||||
return self::onConnectResourceCreated($id, $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function onConnectResourceDeleted($id, $data = null)
|
|
||||||
{
|
|
||||||
$record = self::findConnectResource($id);
|
|
||||||
return $record ? $record->forceDelete() : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function onConnectResourceDoesNotExist($record)
|
|
||||||
{
|
|
||||||
return $record->forceDelete();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,5 +4,10 @@ namespace Bluesquare\Connect\Traits;
|
||||||
|
|
||||||
trait HasConnectTokens
|
trait HasConnectTokens
|
||||||
{
|
{
|
||||||
//
|
public function fillConnectTokens(array $data)
|
||||||
|
{
|
||||||
|
$this->connect_access_token = $data['access_token'];
|
||||||
|
$this->connect_refresh_token = $data['refresh_token'];
|
||||||
|
$this->connect_expires_at = $data['expires_at'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?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();
|
||||||
|
|
||||||
|
if (in_array(HasConnectData::class, class_uses(self::class))) {
|
||||||
|
$this->postFillConnectData($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue