mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added the possibility of social provider extension via theme
Also started docs page
This commit is contained in:
parent
9d37af9453
commit
2ae89f2c32
@ -2,21 +2,21 @@
|
||||
|
||||
use BookStack\Actions\ActivityType;
|
||||
use BookStack\Auth\SocialAccount;
|
||||
use BookStack\Auth\UserRepo;
|
||||
use BookStack\Auth\User;
|
||||
use BookStack\Exceptions\SocialDriverNotConfigured;
|
||||
use BookStack\Exceptions\SocialSignInAccountNotUsed;
|
||||
use BookStack\Exceptions\UserRegistrationException;
|
||||
use BookStack\Facades\Activity;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Contracts\Factory as Socialite;
|
||||
use Laravel\Socialite\Contracts\Provider;
|
||||
use Laravel\Socialite\Contracts\User as SocialUser;
|
||||
use SocialiteProviders\Manager\SocialiteWasCalled;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
class SocialAuthService
|
||||
{
|
||||
|
||||
protected $userRepo;
|
||||
protected $socialite;
|
||||
protected $socialAccount;
|
||||
|
||||
@ -25,14 +25,11 @@ class SocialAuthService
|
||||
/**
|
||||
* SocialAuthService constructor.
|
||||
*/
|
||||
public function __construct(UserRepo $userRepo, Socialite $socialite, SocialAccount $socialAccount)
|
||||
public function __construct(Socialite $socialite)
|
||||
{
|
||||
$this->userRepo = $userRepo;
|
||||
$this->socialite = $socialite;
|
||||
$this->socialAccount = $socialAccount;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Start the social login path.
|
||||
* @throws SocialDriverNotConfigured
|
||||
@ -60,11 +57,11 @@ class SocialAuthService
|
||||
public function handleRegistrationCallback(string $socialDriver, SocialUser $socialUser): SocialUser
|
||||
{
|
||||
// Check social account has not already been used
|
||||
if ($this->socialAccount->where('driver_id', '=', $socialUser->getId())->exists()) {
|
||||
if (SocialAccount::query()->where('driver_id', '=', $socialUser->getId())->exists()) {
|
||||
throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver]), '/login');
|
||||
}
|
||||
|
||||
if ($this->userRepo->getByEmail($socialUser->getEmail())) {
|
||||
if (User::query()->where('email', '=', $socialUser->getEmail())->exists()) {
|
||||
$email = $socialUser->getEmail();
|
||||
throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $email]), '/login');
|
||||
}
|
||||
@ -91,7 +88,7 @@ class SocialAuthService
|
||||
$socialId = $socialUser->getId();
|
||||
|
||||
// Get any attached social accounts or users
|
||||
$socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first();
|
||||
$socialAccount = SocialAccount::query()->where('driver_id', '=', $socialId)->first();
|
||||
$isLoggedIn = auth()->check();
|
||||
$currentUser = user();
|
||||
$titleCaseDriver = Str::title($socialDriver);
|
||||
@ -107,8 +104,8 @@ class SocialAuthService
|
||||
// When a user is logged in but the social account does not exist,
|
||||
// Create the social account and attach it to the user & redirect to the profile page.
|
||||
if ($isLoggedIn && $socialAccount === null) {
|
||||
$this->fillSocialAccount($socialDriver, $socialUser);
|
||||
$currentUser->socialAccounts()->save($this->socialAccount);
|
||||
$account = $this->newSocialAccount($socialDriver, $socialUser);
|
||||
$currentUser->socialAccounts()->save($account);
|
||||
session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => $titleCaseDriver]));
|
||||
return redirect($currentUser->getEditUrl());
|
||||
}
|
||||
@ -207,19 +204,17 @@ class SocialAuthService
|
||||
/**
|
||||
* Fill and return a SocialAccount from the given driver name and SocialUser.
|
||||
*/
|
||||
public function fillSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount
|
||||
public function newSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount
|
||||
{
|
||||
$this->socialAccount->fill([
|
||||
return new SocialAccount([
|
||||
'driver' => $socialDriver,
|
||||
'driver_id' => $socialUser->getId(),
|
||||
'avatar' => $socialUser->getAvatar()
|
||||
]);
|
||||
return $this->socialAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach a social account from a user.
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function detachSocialAccount(string $socialDriver): void
|
||||
{
|
||||
@ -242,4 +237,20 @@ class SocialAuthService
|
||||
|
||||
return $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a custom socialite driver to be used.
|
||||
* Driver name should be lower_snake_case.
|
||||
* Config array should mirror the structure of a service
|
||||
* within the `Config/services.php` file.
|
||||
* Handler should be a Class@method handler to the SocialiteWasCalled event.
|
||||
*/
|
||||
public function addSocialDriver(string $driverName, array $config, string $socialiteHandler)
|
||||
{
|
||||
$this->validSocialDrivers[] = $driverName;
|
||||
config()->set('services.' . $driverName, $config);
|
||||
config()->set('services.' . $driverName . '.redirect', url('/login/service/' . $driverName . '/callback'));
|
||||
config()->set('services.' . $driverName . '.name', $config['name'] ?? $driverName);
|
||||
Event::listen(SocialiteWasCalled::class, $socialiteHandler);
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ class SocialController extends Controller
|
||||
protected function socialRegisterCallback(string $socialDriver, SocialUser $socialUser)
|
||||
{
|
||||
$socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver, $socialUser);
|
||||
$socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser);
|
||||
$socialAccount = $this->socialAuthService->newSocialAccount($socialDriver, $socialUser);
|
||||
$emailVerified = $this->socialAuthService->driverAutoConfirmEmailEnabled($socialDriver);
|
||||
|
||||
// Create an array of the user data to create a new user instance
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php namespace BookStack\Providers;
|
||||
|
||||
use Blade;
|
||||
use BookStack\Auth\Access\SocialAuthService;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\BreadcrumbsViewComposer;
|
||||
@ -12,6 +13,7 @@ use Illuminate\Contracts\Cache\Repository;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Laravel\Socialite\Contracts\Factory as SocialiteFactory;
|
||||
use Schema;
|
||||
use URL;
|
||||
|
||||
@ -62,5 +64,9 @@ class AppServiceProvider extends ServiceProvider
|
||||
$this->app->singleton(SettingService::class, function ($app) {
|
||||
return new SettingService($app->make(Setting::class), $app->make(Repository::class));
|
||||
});
|
||||
|
||||
$this->app->singleton(SocialAuthService::class, function($app) {
|
||||
return new SocialAuthService($app->make(SocialiteFactory::class));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php namespace BookStack\Theming;
|
||||
|
||||
use BookStack\Auth\Access\SocialAuthService;
|
||||
|
||||
class ThemeService
|
||||
{
|
||||
protected $listeners = [];
|
||||
@ -47,4 +49,13 @@ class ThemeService
|
||||
require $themeActionsFile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SocialAuthService::addSocialDriver
|
||||
*/
|
||||
public function addSocialDriver(string $driverName, array $config, string $socialiteHandler)
|
||||
{
|
||||
$socialAuthService = app()->make(SocialAuthService::class);
|
||||
$socialAuthService->addSocialDriver($driverName, $config, $socialiteHandler);
|
||||
}
|
||||
}
|
22
dev/docs/logic-theme-service.md
Normal file
22
dev/docs/logic-theme-service.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Logic Theme Service
|
||||
|
||||
|
||||
#### Custom Socialite Service Example
|
||||
|
||||
The below shows an example of adding a custom reddit socialite service to BookStack.
|
||||
BookStack exposes a helper function for this via `Theme::addSocialDriver` which sets the required config and event listeners in the platform.
|
||||
|
||||
The require statements reference composer installed dependencies within the theme folder. They are required manually since they are not auto-loaded like other app files due to being outside the main BookStack dependency list.
|
||||
|
||||
```php
|
||||
require "vendor/socialiteproviders/reddit/Provider.php";
|
||||
require "vendor/socialiteproviders/reddit/RedditExtendSocialite.php";
|
||||
|
||||
Theme::listen(ThemeEvents::APP_BOOT, function($app) {
|
||||
Theme::addSocialDriver('reddit', [
|
||||
'client_id' => 'abc123',
|
||||
'client_secret' => 'def456789',
|
||||
'name' => 'Reddit',
|
||||
], '\SocialiteProviders\Reddit\RedditExtendSocialite@handle');
|
||||
});
|
||||
```
|
@ -122,6 +122,34 @@ class ThemeTest extends TestCase
|
||||
$resp->assertStatus(443);
|
||||
}
|
||||
|
||||
public function test_add_social_driver()
|
||||
{
|
||||
Theme::addSocialDriver('catnet', [
|
||||
'client_id' => 'abc123',
|
||||
'client_secret' => 'def456'
|
||||
], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
|
||||
|
||||
$this->assertEquals('catnet', config('services.catnet.name'));
|
||||
$this->assertEquals('abc123', config('services.catnet.client_id'));
|
||||
$this->assertEquals(url('/login/service/catnet/callback'), config('services.catnet.redirect'));
|
||||
|
||||
$loginResp = $this->get('/login');
|
||||
$loginResp->assertSee('login/service/catnet');
|
||||
}
|
||||
|
||||
public function test_add_social_driver_uses_name_in_config_if_given()
|
||||
{
|
||||
Theme::addSocialDriver('catnet', [
|
||||
'client_id' => 'abc123',
|
||||
'client_secret' => 'def456',
|
||||
'name' => 'Super Cat Name',
|
||||
], 'SocialiteProviders\Discord\DiscordExtendSocialite@handleTesting');
|
||||
|
||||
$this->assertEquals('Super Cat Name', config('services.catnet.name'));
|
||||
$loginResp = $this->get('/login');
|
||||
$loginResp->assertSee('Super Cat Name');
|
||||
}
|
||||
|
||||
protected function usingThemeFolder(callable $callback)
|
||||
{
|
||||
// Create a folder and configure a theme
|
||||
|
Loading…
Reference in New Issue
Block a user