From 411c331a628a5db7e23686cf0d181c6f9e37fec6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 6 Sep 2015 12:14:32 +0100 Subject: [PATCH] Got registration process working with social accounts --- app/Http/Controllers/Auth/AuthController.php | 101 +++++++++++++------ app/Http/Controllers/SettingController.php | 1 - app/Http/Controllers/UserController.php | 11 +- app/Http/routes.php | 1 + app/Providers/SocialiteServiceProvider.php | 36 +++++++ app/Repos/UserRepo.php | 68 ++++++++++++- app/Services/EmailConfirmationService.php | 4 +- app/Services/SocialAuthService.php | 65 ++++++++++-- resources/views/auth/register.blade.php | 2 +- 9 files changed, 239 insertions(+), 50 deletions(-) create mode 100644 app/Providers/SocialiteServiceProvider.php diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php index 0d4f6b844..717c4fc4d 100644 --- a/app/Http/Controllers/Auth/AuthController.php +++ b/app/Http/Controllers/Auth/AuthController.php @@ -7,9 +7,8 @@ use Oxbow\Exceptions\SocialSignInException; use Oxbow\Exceptions\UserRegistrationException; use Oxbow\Repos\UserRepo; use Oxbow\Services\EmailConfirmationService; -use Oxbow\Services\Facades\Setting; use Oxbow\Services\SocialAuthService; -use Oxbow\User; +use Oxbow\SocialAccount; use Validator; use Oxbow\Http\Controllers\Controller; use Illuminate\Foundation\Auth\ThrottlesLogins; @@ -46,7 +45,7 @@ class AuthController extends Controller */ public function __construct(SocialAuthService $socialAuthService, EmailConfirmationService $emailConfirmationService, UserRepo $userRepo) { - $this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister']]); + $this->middleware('guest', ['only' => ['getLogin', 'postLogin', 'getRegister', 'postRegister']]); $this->socialAuthService = $socialAuthService; $this->emailConfirmationService = $emailConfirmationService; $this->userRepo = $userRepo; @@ -55,7 +54,6 @@ class AuthController extends Controller /** * Get a validator for an incoming registration request. - * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ @@ -68,31 +66,15 @@ class AuthController extends Controller ]); } - /** - * Create a new user instance after a valid registration. - * - * @param array $data - * @return User - */ - protected function create(array $data) - { - return User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => bcrypt($data['password']), - ]); - } - protected function checkRegistrationAllowed() { - if(!\Setting::get('registration-enabled')) { + if (!\Setting::get('registration-enabled')) { throw new UserRegistrationException('Registrations are currently disabled.', '/login'); } } /** * Show the application registration form. - * * @return \Illuminate\Http\Response */ public function getRegister() @@ -104,7 +86,6 @@ class AuthController extends Controller /** * Handle a registration request for the application. - * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response * @throws UserRegistrationException @@ -120,18 +101,54 @@ class AuthController extends Controller ); } - if(\Setting::get('registration-restrict')) { + $userData = $request->all(); + return $this->registerUser($userData); + } + + /** + * Register a new user after a registration callback. + * @param $socialDriver + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws UserRegistrationException + */ + protected function socialRegisterCallback($socialDriver) + { + $socialUser = $this->socialAuthService->handleRegistrationCallback($socialDriver); + $socialAccount = $this->socialAuthService->fillSocialAccount($socialDriver, $socialUser); + + // Create an array of the user data to create a new user instance + $userData = [ + 'name' => $socialUser->getName(), + 'email' => $socialUser->getEmail(), + 'password' => str_random(30) + ]; + return $this->registerUser($userData, $socialAccount); + } + + /** + * The registrations flow for all users. + * @param array $userData + * @param bool|false|SocialAccount $socialAccount + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws UserRegistrationException + * @throws \Oxbow\Exceptions\ConfirmationEmailException + */ + protected function registerUser(array $userData, $socialAccount = false) + { + if (\Setting::get('registration-restrict')) { $restrictedEmailDomains = explode(',', str_replace(' ', '', \Setting::get('registration-restrict'))); - $userEmailDomain = $domain = substr(strrchr($request->get('email'), "@"), 1); - if(!in_array($userEmailDomain, $restrictedEmailDomains)) { + $userEmailDomain = $domain = substr(strrchr($userData['email'], "@"), 1); + if (!in_array($userEmailDomain, $restrictedEmailDomains)) { throw new UserRegistrationException('That email domain does not have access to this application', '/register'); } } - $newUser = $this->create($request->all()); - $newUser->attachRoleId(\Setting::get('registration-role'), 1); + $newUser = $this->userRepo->registerNew($userData); + if ($socialAccount) { + $newUser->socialAccounts()->save($socialAccount); + } - if(\Setting::get('registration-confirmation') || \Setting::get('registration-restrict')) { + if (\Setting::get('registration-confirmation') || \Setting::get('registration-restrict')) { $newUser->email_confirmed = false; $newUser->save(); $this->emailConfirmationService->sendConfirmation($newUser); @@ -139,6 +156,7 @@ class AuthController extends Controller } auth()->login($newUser); + session()->flash('success', 'Thanks for signing up! You are now registered and signed in.'); return redirect($this->redirectPath()); } @@ -197,7 +215,6 @@ class AuthController extends Controller /** * Show the application login form. - * * @return \Illuminate\Http\Response */ public function getLogin() @@ -218,19 +235,41 @@ class AuthController extends Controller */ public function getSocialLogin($socialDriver) { + session()->put('social-callback', 'login'); return $this->socialAuthService->startLogIn($socialDriver); } + /** + * Redirect to the social site for authentication initended to register. + * @param $socialDriver + * @return mixed + */ + public function socialRegister($socialDriver) + { + $this->checkRegistrationAllowed(); + session()->put('social-callback', 'register'); + return $this->socialAuthService->startRegister($socialDriver); + } + /** * The callback for social login services. - * * @param $socialDriver * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws SocialSignInException */ public function socialCallback($socialDriver) { - return $this->socialAuthService->handleCallback($socialDriver); + if (session()->has('social-callback')) { + $action = session()->pull('social-callback'); + if ($action == 'login') { + return $this->socialAuthService->handleLoginCallback($socialDriver); + } elseif ($action == 'register') { + return $this->socialRegisterCallback($socialDriver); + } + } else { + throw new SocialSignInException('No action defined', '/login'); + } + return redirect()->back(); } /** diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php index 5193a9d3b..822ee662d 100644 --- a/app/Http/Controllers/SettingController.php +++ b/app/Http/Controllers/SettingController.php @@ -22,7 +22,6 @@ class SettingController extends Controller } - /** * Update the specified settings in storage. * diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 9923e9d9c..65263cd12 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -6,6 +6,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Oxbow\Http\Requests; +use Oxbow\Repos\UserRepo; use Oxbow\Services\SocialAuthService; use Oxbow\User; @@ -13,14 +14,16 @@ class UserController extends Controller { protected $user; + protected $userRepo; /** * UserController constructor. * @param $user */ - public function __construct(User $user) + public function __construct(User $user, UserRepo $userRepo) { $this->user = $user; + $this->userRepo = $userRepo; parent::__construct(); } @@ -150,8 +153,12 @@ class UserController extends Controller $this->checkPermissionOr('user-delete', function () use ($id) { return $this->currentUser->id == $id; }); - $user = $this->user->findOrFail($id); + $user = $this->userRepo->getById($id); // Delete social accounts + if($this->userRepo->isOnlyAdmin($user)) { + session()->flash('error', 'You cannot delete the only admin'); + return redirect($user->getEditUrl()); + } $user->socialAccounts()->delete(); $user->delete(); return redirect('/users'); diff --git a/app/Http/routes.php b/app/Http/routes.php index df2860ea5..07da97ae7 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -92,6 +92,7 @@ Route::get('/register/confirm', 'Auth\AuthController@getRegisterConfirmation'); Route::get('/register/confirm/awaiting', 'Auth\AuthController@showAwaitingConfirmation'); Route::post('/register/confirm/resend', 'Auth\AuthController@resendConfirmation'); Route::get('/register/confirm/{token}', 'Auth\AuthController@confirmEmail'); +Route::get('/register/service/{socialDriver}', 'Auth\AuthController@socialRegister'); Route::post('/register', 'Auth\AuthController@postRegister'); // Password reset link request routes... diff --git a/app/Providers/SocialiteServiceProvider.php b/app/Providers/SocialiteServiceProvider.php new file mode 100644 index 000000000..ce94c4201 --- /dev/null +++ b/app/Providers/SocialiteServiceProvider.php @@ -0,0 +1,36 @@ +app->bindShared('Laravel\Socialite\Contracts\Factory', function ($app) { + return new SocialiteManager($app); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return ['Laravel\Socialite\Contracts\Factory']; + } +} \ No newline at end of file diff --git a/app/Repos/UserRepo.php b/app/Repos/UserRepo.php index 43b3cf4ca..732b092f3 100644 --- a/app/Repos/UserRepo.php +++ b/app/Repos/UserRepo.php @@ -1,29 +1,91 @@ user = $user; + $this->role = $role; } - - public function getByEmail($email) { + /** + * @param string $email + * @return User|null + */ + public function getByEmail($email) + { return $this->user->where('email', '=', $email)->first(); } + /** + * @param int $id + * @return User + */ public function getById($id) { return $this->user->findOrFail($id); } + + /** + * Creates a new user and attaches a role to them. + * @param array $data + * @return User + */ + public function registerNew(array $data) + { + $user = $this->create($data); + $roleId = \Setting::get('registration-role'); + + if ($roleId === false) { + $roleId = $this->role->getDefault()->id; + } + + $user->attachRoleId($roleId); + return $user; + } + + /** + * Checks if the give user is the only admin. + * @param User $user + * @return bool + */ + public function isOnlyAdmin(User $user) + { + if ($user->role->name != 'admin') { + return false; + } + + $adminRole = $this->role->where('name', '=', 'admin')->first(); + if (count($adminRole->users) > 1) { + return false; + } + + return true; + } + + /** + * Create a new basic instance of user. + * @param array $data + * @return User + */ + public function create(array $data) + { + return $this->user->create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => bcrypt($data['password']) + ]); + } } \ No newline at end of file diff --git a/app/Services/EmailConfirmationService.php b/app/Services/EmailConfirmationService.php index dd85ad834..31fe1da08 100644 --- a/app/Services/EmailConfirmationService.php +++ b/app/Services/EmailConfirmationService.php @@ -35,7 +35,7 @@ class EmailConfirmationService */ public function sendConfirmation(User $user) { - if($user->email_confirmed) { + if ($user->email_confirmed) { throw new ConfirmationEmailException('Email has already been confirmed, Try logging in.', '/login'); } $this->deleteConfirmationsByUser($user); @@ -66,7 +66,7 @@ class EmailConfirmationService } // If more than a day old - if(Carbon::now()->subDay()->gt($emailConfirmation->created_at)) { + if (Carbon::now()->subDay()->gt($emailConfirmation->created_at)) { $this->sendConfirmation($emailConfirmation->user); throw new UserRegistrationException('The confirmation token has expired, A new confirmation email has been sent.', '/register/confirm'); } diff --git a/app/Services/SocialAuthService.php b/app/Services/SocialAuthService.php index f76a339b4..b39a82d9f 100644 --- a/app/Services/SocialAuthService.php +++ b/app/Services/SocialAuthService.php @@ -1,8 +1,11 @@ socialAccount = $socialAccount; } + /** * Start the social login path. - * @param $socialDriver + * @param string $socialDriver * @return \Symfony\Component\HttpFoundation\RedirectResponse * @throws SocialDriverNotConfigured */ @@ -42,14 +46,52 @@ class SocialAuthService } /** - * Get a user from socialite after a oAuth callback. - * + * 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. * @param $socialDriver - * @return User + * @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'); + } + + if($this->userRepo->getByEmail($socialUser->getEmail())) { + $email = $socialUser->getEmail(); + 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'); + } + + return $socialUser; + } + + /** + * Handle the login process on a oAuth callback. + * @param $socialDriver + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws SocialDriverNotConfigured * @throws SocialSignInException */ - public function handleCallback($socialDriver) + public function handleLoginCallback($socialDriver) { $driver = $this->validateDriver($socialDriver); @@ -93,12 +135,13 @@ class SocialAuthService // 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'; - if(\Setting::get('registration-enabled')) { - $message .= 'or, If you do not yet have an account, You can register an account using the ' . $socialDriver . ' option'; + if (\Setting::get('registration-enabled')) { + $message .= ' or, If you do not yet have an account, You can register an account using the ' . $socialDriver . ' option'; } throw new SocialSignInException($message . '.', '/login'); } + private function logUserIn($user) { auth()->login($user); @@ -150,16 +193,18 @@ class SocialAuthService } /** - * @param $socialDriver - * @param $socialUser + * @param string $socialDriver + * @param \Laravel\Socialite\Contracts\User $socialUser + * @return SocialAccount */ - private function fillSocialAccount($socialDriver, $socialUser) + public function fillSocialAccount($socialDriver, $socialUser) { $this->socialAccount->fill([ 'driver' => $socialDriver, 'driver_id' => $socialUser->getId(), 'avatar' => $socialUser->getAvatar() ]); + return $this->socialAccount; } /** diff --git a/resources/views/auth/register.blade.php b/resources/views/auth/register.blade.php index f9f5d0149..f85440e7f 100644 --- a/resources/views/auth/register.blade.php +++ b/resources/views/auth/register.blade.php @@ -25,7 +25,7 @@
- @include('form/password', ['name' => 'password']) + @include('form/password', ['name' => 'password', 'placeholder' => 'Must be over 5 characters'])