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