From b884d63c1377c4256613141c6ef970069dbf36d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miloslav=20Ko=C5=A1t=C3=AD=C5=99?= Date: Mon, 16 Dec 2024 20:55:21 +0100 Subject: [PATCH] [5.x] OAuth: option not to create or update user during authentication (#10853) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Koštíř Miloslav Co-authored-by: Jason Varga --- config/oauth.php | 38 +++++++++++ resources/views/auth/unauthorized.blade.php | 6 +- src/Http/Controllers/OAuthController.php | 47 +++++++++++-- src/OAuth/Provider.php | 20 +++++- tests/OAuth/ProviderTest.php | 75 ++++++++++++++++++++- 5 files changed, 177 insertions(+), 9 deletions(-) diff --git a/config/oauth.php b/config/oauth.php index 2b5b931b1a..d7e3fd2488 100644 --- a/config/oauth.php +++ b/config/oauth.php @@ -15,6 +15,44 @@ 'callback' => 'oauth/{provider}/callback', ], + /* + |-------------------------------------------------------------------------- + | Create User + |-------------------------------------------------------------------------- + | + | Whether or not a user account should be created upon authentication + | with an OAuth provider. If disabled, a user account will be need + | to be explicitly created ahead of time. + | + */ + + 'create_user' => true, + + /* + |-------------------------------------------------------------------------- + | Merge User Data + |-------------------------------------------------------------------------- + | + | When authenticating with an OAuth provider, the user data returned + | such as their name will be merged with the existing user account. + | + */ + + 'merge_user_data' => true, + + /* + |-------------------------------------------------------------------------- + | Unauthorized Redirect + |-------------------------------------------------------------------------- + | + | This controls where the user is taken after authenticating with + | an OAuth provider but their account is unauthorized. This may + | happen when the create_user option has been set to false. + | + */ + + 'unauthorized_redirect' => null, + /* |-------------------------------------------------------------------------- | Remember Me diff --git a/resources/views/auth/unauthorized.blade.php b/resources/views/auth/unauthorized.blade.php index 3502faa78b..d781cefc79 100644 --- a/resources/views/auth/unauthorized.blade.php +++ b/resources/views/auth/unauthorized.blade.php @@ -10,7 +10,11 @@
{{ __('Unauthorized') }}
- {{ __('Log out') }} + @auth + {{ __('Log out') }} + @else + {{ __('Log in') }} + @endauth
diff --git a/src/Http/Controllers/OAuthController.php b/src/Http/Controllers/OAuthController.php index 880a6ea922..ac98813dfa 100644 --- a/src/Http/Controllers/OAuthController.php +++ b/src/Http/Controllers/OAuthController.php @@ -36,14 +36,24 @@ public function handleProviderCallback(Request $request, string $provider) return $this->redirectToProvider($request, $provider); } - $user = $oauth->findOrCreateUser($providerUser); + if ($user = $oauth->findUser($providerUser)) { + if (config('statamic.oauth.merge_user_data', true)) { + $user = $oauth->mergeUser($user, $providerUser); + } + } elseif (config('statamic.oauth.create_user', true)) { + $user = $oauth->createUser($providerUser); + } + + if ($user) { + session()->put('oauth-provider', $provider); - session()->put('oauth-provider', $provider); + Auth::guard($request->session()->get('statamic.oauth.guard')) + ->login($user, config('statamic.oauth.remember_me', true)); - Auth::guard($request->session()->get('statamic.oauth.guard')) - ->login($user, config('statamic.oauth.remember_me', true)); + return redirect()->to($this->successRedirectUrl()); + } - return redirect()->to($this->successRedirectUrl()); + return redirect()->to($this->unauthorizedRedirectUrl()); } protected function successRedirectUrl() @@ -60,4 +70,31 @@ protected function successRedirectUrl() return Arr::get($query, 'redirect', $default); } + + protected function unauthorizedRedirectUrl() + { + // If a URL has been explicitly defined, use that. + if ($url = config('statamic.oauth.unauthorized_redirect')) { + return $url; + } + + // We'll check the redirect to see if they were intending on + // accessing the CP. If they were, we'll redirect them to + // the unauthorized page in the CP. Otherwise, to home. + + $default = '/'; + $previous = session('_previous.url'); + + if (! $query = Arr::get(parse_url($previous), 'query')) { + return $default; + } + + parse_str($query, $query); + + if (! $redirect = Arr::get($query, 'redirect')) { + return $default; + } + + return $redirect === '/'.config('statamic.cp.route') ? cp_route('unauthorized') : $default; + } } diff --git a/src/OAuth/Provider.php b/src/OAuth/Provider.php index b200052228..0aeafdf8fe 100644 --- a/src/OAuth/Provider.php +++ b/src/OAuth/Provider.php @@ -45,15 +45,31 @@ public function getUserId(string $id): ?string } public function findOrCreateUser($socialite): StatamicUser + { + if ($user = $this->findUser($socialite)) { + return config('statamic.oauth.merge_user_data', true) + ? $this->mergeUser($user, $socialite) + : $user; + } + + return $this->createUser($socialite); + } + + /** + * Find a Statamic user by a Socialite user. + * + * @param SocialiteUser $socialite + */ + public function findUser($socialite): ?StatamicUser { if ( ($user = User::findByOAuthId($this, $socialite->getId())) || ($user = User::findByEmail($socialite->getEmail())) ) { - return $this->mergeUser($user, $socialite); + return $user; } - return $this->createUser($socialite); + return null; } /** diff --git a/tests/OAuth/ProviderTest.php b/tests/OAuth/ProviderTest.php index e5d2edecd9..917aba151e 100644 --- a/tests/OAuth/ProviderTest.php +++ b/tests/OAuth/ProviderTest.php @@ -74,6 +74,8 @@ public function it_merges_data() $user = $this->user()->save(); + $this->assertEquals(['name' => 'foo', 'extra' => 'bar'], $user->data()->all()); + $provider->mergeUser($user, $this->socialite()); $this->assertEquals(['name' => 'Foo Bar', 'extra' => 'bar'], $user->data()->all()); @@ -122,20 +124,91 @@ public function it_creates_a_user() } #[Test] - public function it_finds_an_existing_user_by_email() + public function it_finds_an_existing_user_via_find_user_method() + { + $provider = $this->provider(); + + $savedUser = $this->user()->save(); + + $this->assertCount(1, UserFacade::all()); + $this->assertEquals([$savedUser], UserFacade::all()->all()); + + $foundUser = $provider->findUser($this->socialite()); + + $this->assertCount(1, UserFacade::all()); + $this->assertEquals([$savedUser], UserFacade::all()->all()); + $this->assertEquals($savedUser, $foundUser); + } + + #[Test] + public function it_does_not_find_or_create_a_user_via_find_user_method() + { + $this->assertCount(0, UserFacade::all()); + + $provider = $this->provider(); + $foundUser = $provider->findUser($this->socialite()); + + $this->assertNull($foundUser); + + $this->assertCount(0, UserFacade::all()); + $user = UserFacade::all()->get(0); + $this->assertNull($user); + } + + #[Test] + public function it_finds_an_existing_user_via_find_or_create_user_method() + { + $provider = $this->provider(); + + $savedUser = $this->user()->save(); + + $this->assertCount(1, UserFacade::all()); + $this->assertEquals([$savedUser], UserFacade::all()->all()); + $this->assertEquals('foo', $savedUser->name); + + $foundUser = $provider->findOrCreateUser($this->socialite()); + + $this->assertCount(1, UserFacade::all()); + $this->assertEquals([$savedUser], UserFacade::all()->all()); + $this->assertEquals($savedUser, $foundUser); + $this->assertEquals('Foo Bar', $savedUser->name); + } + + #[Test] + public function it_finds_an_existing_user_via_find_or_create_user_method_but_doesnt_merge_data() { + config(['statamic.oauth.merge_user_data' => false]); + $provider = $this->provider(); $savedUser = $this->user()->save(); $this->assertCount(1, UserFacade::all()); $this->assertEquals([$savedUser], UserFacade::all()->all()); + $this->assertEquals('foo', $savedUser->name); $foundUser = $provider->findOrCreateUser($this->socialite()); $this->assertCount(1, UserFacade::all()); $this->assertEquals([$savedUser], UserFacade::all()->all()); $this->assertEquals($savedUser, $foundUser); + $this->assertEquals('foo', $savedUser->name); + } + + #[Test] + public function it_creates_a_user_via_find_or_create_user_method() + { + $this->assertCount(0, UserFacade::all()); + + $provider = $this->provider(); + $provider->findOrCreateUser($this->socialite()); + + $this->assertCount(1, UserFacade::all()); + $user = UserFacade::all()->get(0); + $this->assertNotNull($user); + $this->assertEquals('foo@bar.com', $user->email()); + $this->assertEquals('Foo Bar', $user->name()); + $this->assertEquals($user->id(), $provider->getUserId('foo-bar')); } #[Test]