]> BookStack Code Mirror - bookstack/blob - app/Services/SocialAuthService.php
Fixes typo causing the message not to be displayed
[bookstack] / app / Services / SocialAuthService.php
1 <?php namespace BookStack\Services;
2
3 use Laravel\Socialite\Contracts\Factory as Socialite;
4 use BookStack\Exceptions\SocialDriverNotConfigured;
5 use BookStack\Exceptions\SocialSignInException;
6 use BookStack\Exceptions\UserRegistrationException;
7 use BookStack\Repos\UserRepo;
8 use BookStack\SocialAccount;
9
10 class SocialAuthService
11 {
12
13     protected $userRepo;
14     protected $socialite;
15     protected $socialAccount;
16
17     protected $validSocialDrivers = ['google', 'github'];
18
19     /**
20      * SocialAuthService constructor.
21      * @param UserRepo      $userRepo
22      * @param Socialite     $socialite
23      * @param SocialAccount $socialAccount
24      */
25     public function __construct(UserRepo $userRepo, Socialite $socialite, SocialAccount $socialAccount)
26     {
27         $this->userRepo = $userRepo;
28         $this->socialite = $socialite;
29         $this->socialAccount = $socialAccount;
30     }
31
32
33     /**
34      * Start the social login path.
35      * @param string $socialDriver
36      * @return \Symfony\Component\HttpFoundation\RedirectResponse
37      * @throws SocialDriverNotConfigured
38      */
39     public function startLogIn($socialDriver)
40     {
41         $driver = $this->validateDriver($socialDriver);
42         return $this->socialite->driver($driver)->redirect();
43     }
44
45     /**
46      * Start the social registration process
47      * @param string $socialDriver
48      * @return \Symfony\Component\HttpFoundation\RedirectResponse
49      * @throws SocialDriverNotConfigured
50      */
51     public function startRegister($socialDriver)
52     {
53         $driver = $this->validateDriver($socialDriver);
54         return $this->socialite->driver($driver)->redirect();
55     }
56
57     /**
58      * Handle the social registration process on callback.
59      * @param $socialDriver
60      * @return \Laravel\Socialite\Contracts\User
61      * @throws SocialDriverNotConfigured
62      * @throws UserRegistrationException
63      */
64     public function handleRegistrationCallback($socialDriver)
65     {
66         $driver = $this->validateDriver($socialDriver);
67
68         // Get user details from social driver
69         $socialUser = $this->socialite->driver($driver)->user();
70
71         // Check social account has not already been used
72         if ($this->socialAccount->where('driver_id', '=', $socialUser->getId())->exists()) {
73             throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver]), '/login');
74         }
75
76         if ($this->userRepo->getByEmail($socialUser->getEmail())) {
77             $email = $socialUser->getEmail();
78             throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver, 'email' => $email]), '/login');
79         }
80
81         return $socialUser;
82     }
83
84     /**
85      * Handle the login process on a oAuth callback.
86      * @param $socialDriver
87      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
88      * @throws SocialDriverNotConfigured
89      * @throws SocialSignInException
90      */
91     public function handleLoginCallback($socialDriver)
92     {
93         $driver = $this->validateDriver($socialDriver);
94
95         // Get user details from social driver
96         $socialUser = $this->socialite->driver($driver)->user();
97         $socialId = $socialUser->getId();
98
99         // Get any attached social accounts or users
100         $socialAccount = $this->socialAccount->where('driver_id', '=', $socialId)->first();
101         $isLoggedIn = auth()->check();
102         $currentUser = user();
103
104         // When a user is not logged in and a matching SocialAccount exists,
105         // Simply log the user into the application.
106         if (!$isLoggedIn && $socialAccount !== null) {
107             return $this->logUserIn($socialAccount->user);
108         }
109
110         // When a user is logged in but the social account does not exist,
111         // Create the social account and attach it to the user & redirect to the profile page.
112         if ($isLoggedIn && $socialAccount === null) {
113             $this->fillSocialAccount($socialDriver, $socialUser);
114             $currentUser->socialAccounts()->save($this->socialAccount);
115             session()->flash('success', trans('settings.users_social_connected', ['socialAccount' => title_case($socialDriver)]));
116             return redirect($currentUser->getEditUrl());
117         }
118
119         // When a user is logged in and the social account exists and is already linked to the current user.
120         if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id === $currentUser->id) {
121             session()->flash('error', trans('errors.social_account_existing', ['socialAccount' => title_case($socialDriver)]));
122             return redirect($currentUser->getEditUrl());
123         }
124
125         // When a user is logged in, A social account exists but the users do not match.
126         if ($isLoggedIn && $socialAccount !== null && $socialAccount->user->id != $currentUser->id) {
127             session()->flash('error', trans('errors.social_account_already_used_existing', ['socialAccount' => title_case($socialDriver)]));
128             return redirect($currentUser->getEditUrl());
129         }
130
131         // Otherwise let the user know this social account is not used by anyone.
132         $message = trans('errors.social_account_not_used', ['socialAccount' => title_case($socialDriver)]);
133         if (setting('registration-enabled')) {
134             $message .= trans('errors.social_account_register_instructions', ['socialAccount' => title_case($socialDriver)]);
135         }
136         
137         throw new SocialSignInException($message . '.', '/login');
138     }
139
140
141     private function logUserIn($user)
142     {
143         auth()->login($user);
144         return redirect('/');
145     }
146
147     /**
148      * Ensure the social driver is correct and supported.
149      *
150      * @param $socialDriver
151      * @return string
152      * @throws SocialDriverNotConfigured
153      */
154     private function validateDriver($socialDriver)
155     {
156         $driver = trim(strtolower($socialDriver));
157
158         if (!in_array($driver, $this->validSocialDrivers)) abort(404, trans('errors.social_driver_not_found'));
159         if (!$this->checkDriverConfigured($driver)) throw new SocialDriverNotConfigured(trans('errors.social_driver_not_configured', ['socialAccount' => title_case($socialDriver)]));
160
161         return $driver;
162     }
163
164     /**
165      * Check a social driver has been configured correctly.
166      * @param $driver
167      * @return bool
168      */
169     private function checkDriverConfigured($driver)
170     {
171         $lowerName = strtolower($driver);
172         $configPrefix = 'services.' . $lowerName . '.';
173         $config = [config($configPrefix . 'client_id'), config($configPrefix . 'client_secret'), config('services.callback_url')];
174         return !in_array(false, $config) && !in_array(null, $config);
175     }
176
177     /**
178      * Gets the names of the active social drivers.
179      * @return array
180      */
181     public function getActiveDrivers()
182     {
183         $activeDrivers = [];
184         foreach ($this->validSocialDrivers as $driverName) {
185             if ($this->checkDriverConfigured($driverName)) {
186                 $activeDrivers[$driverName] = true;
187             }
188         }
189         return $activeDrivers;
190     }
191
192     /**
193      * @param string                            $socialDriver
194      * @param \Laravel\Socialite\Contracts\User $socialUser
195      * @return SocialAccount
196      */
197     public function fillSocialAccount($socialDriver, $socialUser)
198     {
199         $this->socialAccount->fill([
200             'driver'    => $socialDriver,
201             'driver_id' => $socialUser->getId(),
202             'avatar'    => $socialUser->getAvatar()
203         ]);
204         return $this->socialAccount;
205     }
206
207     /**
208      * Detach a social account from a user.
209      * @param $socialDriver
210      * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
211      */
212     public function detachSocialAccount($socialDriver)
213     {
214         session();
215         user()->socialAccounts()->where('driver', '=', $socialDriver)->delete();
216         session()->flash('success', trans('settings.users_social_disconnected', ['socialAccount' => title_case($socialDriver)]));
217         return redirect(user()->getEditUrl());
218     }
219
220 }