2015-09-10 14:31:09 -04:00
< ? php namespace BookStack\Services ;
2015-09-04 12:50:52 -04:00
use Laravel\Socialite\Contracts\Factory as Socialite ;
2015-09-10 14:31:09 -04:00
use BookStack\Exceptions\SocialDriverNotConfigured ;
use BookStack\Exceptions\SocialSignInException ;
use BookStack\Exceptions\UserRegistrationException ;
use BookStack\Repos\UserRepo ;
use BookStack\SocialAccount ;
2015-09-04 12:50:52 -04:00
class SocialAuthService
{
protected $userRepo ;
protected $socialite ;
2015-09-04 15:40:36 -04:00
protected $socialAccount ;
2015-09-04 12:50:52 -04:00
protected $validSocialDrivers = [ 'google' , 'github' ];
/**
* SocialAuthService constructor .
2015-09-04 15:40:36 -04:00
* @ param UserRepo $userRepo
* @ param Socialite $socialite
* @ param SocialAccount $socialAccount
2015-09-04 12:50:52 -04:00
*/
2015-09-04 15:40:36 -04:00
public function __construct ( UserRepo $userRepo , Socialite $socialite , SocialAccount $socialAccount )
2015-09-04 12:50:52 -04:00
{
$this -> userRepo = $userRepo ;
$this -> socialite = $socialite ;
2015-09-04 15:40:36 -04:00
$this -> socialAccount = $socialAccount ;
2015-09-04 12:50:52 -04:00
}
2015-09-06 07:14:32 -04:00
2015-09-04 15:40:36 -04:00
/**
* Start the social login path .
2015-09-06 07:14:32 -04:00
* @ param string $socialDriver
2015-09-04 15:40:36 -04:00
* @ return \Symfony\Component\HttpFoundation\RedirectResponse
* @ throws SocialDriverNotConfigured
*/
public function startLogIn ( $socialDriver )
2015-09-04 12:50:52 -04:00
{
$driver = $this -> validateDriver ( $socialDriver );
return $this -> socialite -> driver ( $driver ) -> redirect ();
}
/**
2015-09-06 07:14:32 -04:00
* Start the social registration process
* @ param string $socialDriver
* @ return \Symfony\Component\HttpFoundation\RedirectResponse
* @ throws SocialDriverNotConfigured
*/
public function startRegister ( $socialDriver )
{
$driver = $this -> validateDriver ( $socialDriver );
return $this -> socialite -> driver ( $driver ) -> redirect ();
}
/**
* Handle the social registration process on callback .
2015-09-04 12:50:52 -04:00
* @ param $socialDriver
2015-09-06 07:14:32 -04:00
* @ return \Laravel\Socialite\Contracts\User
* @ throws SocialDriverNotConfigured
* @ throws UserRegistrationException
*/
public function handleRegistrationCallback ( $socialDriver )
{
$driver = $this -> validateDriver ( $socialDriver );
// Get user details from social driver
$socialUser = $this -> socialite -> driver ( $driver ) -> user ();
// Check social account has not already been used
if ( $this -> socialAccount -> where ( 'driver_id' , '=' , $socialUser -> getId ()) -> exists ()) {
throw new UserRegistrationException ( 'This ' . $socialDriver . ' account is already in use, Try logging in via the ' . $socialDriver . ' option.' , '/login' );
}
2016-01-09 14:23:35 -05:00
if ( $this -> userRepo -> getByEmail ( $socialUser -> getEmail ())) {
2015-09-06 07:14:32 -04:00
$email = $socialUser -> getEmail ();
2016-01-09 14:23:35 -05:00
throw new UserRegistrationException ( 'The email ' . $email . ' is already in use. If you already have an account you can connect your ' . $socialDriver . ' account from your profile settings.' , '/login' );
2015-09-06 07:14:32 -04:00
}
return $socialUser ;
}
/**
* Handle the login process on a oAuth callback .
* @ param $socialDriver
* @ return \Illuminate\Http\RedirectResponse | \Illuminate\Routing\Redirector
2015-09-04 12:50:52 -04:00
* @ throws SocialDriverNotConfigured
2015-09-04 15:40:36 -04:00
* @ throws SocialSignInException
2015-09-04 12:50:52 -04:00
*/
2015-09-06 07:14:32 -04:00
public function handleLoginCallback ( $socialDriver )
2015-09-04 12:50:52 -04:00
{
$driver = $this -> validateDriver ( $socialDriver );
2015-09-04 15:40:36 -04:00
2015-09-04 12:50:52 -04:00
// Get user details from social driver
$socialUser = $this -> socialite -> driver ( $driver ) -> user ();
2015-09-04 15:40:36 -04:00
$socialId = $socialUser -> getId ();
// Get any attached social accounts or users
$socialAccount = $this -> socialAccount -> where ( 'driver_id' , '=' , $socialId ) -> first ();
2015-09-04 12:50:52 -04:00
$user = $this -> userRepo -> getByEmail ( $socialUser -> getEmail ());
2015-09-05 07:29:47 -04:00
$isLoggedIn = auth () -> check ();
$currentUser = auth () -> user ();
2015-09-04 15:40:36 -04:00
2015-09-05 12:42:05 -04:00
// When a user is not logged in and a matching SocialAccount exists,
// Simply log the user into the application.
2015-09-04 15:40:36 -04:00
if ( ! $isLoggedIn && $socialAccount !== null ) {
return $this -> logUserIn ( $socialAccount -> user );
}
// 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 );
2016-08-14 07:29:35 -04:00
session () -> flash ( 'success' , title_case ( $socialDriver ) . ' account was successfully attached to your profile.' );
2015-09-04 15:40:36 -04:00
return redirect ( $currentUser -> getEditUrl ());
}
// When a user is logged in and the social account exists and is already linked to the current user.
if ( $isLoggedIn && $socialAccount !== null && $socialAccount -> user -> id === $currentUser -> id ) {
2016-08-14 07:29:35 -04:00
session () -> flash ( 'error' , 'This ' . title_case ( $socialDriver ) . ' account is already attached to your profile.' );
2015-09-04 15:40:36 -04:00
return redirect ( $currentUser -> getEditUrl ());
}
// When a user is logged in, A social account exists but the users do not match.
// Change the user that the social account is assigned to.
if ( $isLoggedIn && $socialAccount !== null && $socialAccount -> user -> id != $currentUser -> id ) {
2016-08-14 07:29:35 -04:00
session () -> flash ( 'success' , 'This ' . title_case ( $socialDriver ) . ' account is already used by another user.' );
2015-09-05 12:42:05 -04:00
return redirect ( $currentUser -> getEditUrl ());
2015-09-04 15:40:36 -04:00
}
2015-09-04 12:50:52 -04:00
2015-09-05 12:42:05 -04:00
// Otherwise let the user know this social account is not used by anyone.
$message = 'This ' . $socialDriver . ' account is not linked to any users. Please attach it in your profile settings' ;
2016-03-06 07:55:08 -05:00
if ( setting ( 'registration-enabled' )) {
2015-09-06 07:14:32 -04:00
$message .= ' or, If you do not yet have an account, You can register an account using the ' . $socialDriver . ' option' ;
2015-09-04 12:50:52 -04:00
}
2016-08-14 07:29:35 -04:00
2015-09-05 12:42:05 -04:00
throw new SocialSignInException ( $message . '.' , '/login' );
2015-09-04 15:40:36 -04:00
}
2015-09-04 12:50:52 -04:00
2015-09-06 07:14:32 -04:00
2015-09-04 15:40:36 -04:00
private function logUserIn ( $user )
{
2015-09-05 07:29:47 -04:00
auth () -> login ( $user );
2015-09-04 15:40:36 -04:00
return redirect ( '/' );
2015-09-04 12:50:52 -04:00
}
/**
* Ensure the social driver is correct and supported .
*
* @ param $socialDriver
* @ return string
* @ throws SocialDriverNotConfigured
*/
private function validateDriver ( $socialDriver )
{
$driver = trim ( strtolower ( $socialDriver ));
if ( ! in_array ( $driver , $this -> validSocialDrivers )) abort ( 404 , 'Social Driver Not Found' );
2015-09-04 15:40:36 -04:00
if ( ! $this -> checkDriverConfigured ( $driver )) throw new SocialDriverNotConfigured ;
2015-09-04 12:50:52 -04:00
return $driver ;
}
/**
* Check a social driver has been configured correctly .
* @ param $driver
* @ return bool
*/
2015-09-04 15:40:36 -04:00
private function checkDriverConfigured ( $driver )
2015-09-04 12:50:52 -04:00
{
2016-01-09 14:23:35 -05:00
$lowerName = strtolower ( $driver );
$configPrefix = 'services.' . $lowerName . '.' ;
$config = [ config ( $configPrefix . 'client_id' ), config ( $configPrefix . 'client_secret' ), config ( 'services.callback_url' )];
return ! in_array ( false , $config ) && ! in_array ( null , $config );
2015-09-04 12:50:52 -04:00
}
/**
* Gets the names of the active social drivers .
* @ return array
*/
public function getActiveDrivers ()
{
$activeDrivers = [];
foreach ( $this -> validSocialDrivers as $driverName ) {
2015-09-04 15:40:36 -04:00
if ( $this -> checkDriverConfigured ( $driverName )) {
2015-09-04 12:50:52 -04:00
$activeDrivers [ $driverName ] = true ;
}
}
return $activeDrivers ;
}
2015-09-04 15:40:36 -04:00
/**
2016-01-09 14:23:35 -05:00
* @ param string $socialDriver
2015-09-06 07:14:32 -04:00
* @ param \Laravel\Socialite\Contracts\User $socialUser
* @ return SocialAccount
2015-09-04 15:40:36 -04:00
*/
2015-09-06 07:14:32 -04:00
public function fillSocialAccount ( $socialDriver , $socialUser )
2015-09-04 15:40:36 -04:00
{
$this -> socialAccount -> fill ([
'driver' => $socialDriver ,
'driver_id' => $socialUser -> getId (),
'avatar' => $socialUser -> getAvatar ()
]);
2015-09-06 07:14:32 -04:00
return $this -> socialAccount ;
2015-09-04 15:40:36 -04:00
}
/**
* Detach a social account from a user .
* @ param $socialDriver
* @ return \Illuminate\Http\RedirectResponse | \Illuminate\Routing\Redirector
*/
public function detachSocialAccount ( $socialDriver )
{
2015-09-05 07:29:47 -04:00
session ();
auth () -> user () -> socialAccounts () -> where ( 'driver' , '=' , $socialDriver ) -> delete ();
2015-09-04 15:40:36 -04:00
\Session :: flash ( 'success' , $socialDriver . ' account successfully detached' );
2015-09-05 07:29:47 -04:00
return redirect ( auth () -> user () -> getEditUrl ());
2015-09-04 15:40:36 -04:00
}
2015-09-04 12:50:52 -04:00
}