Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.x] OAuth: option not to create or update user during authentication #10853

Merged
merged 8 commits into from
Dec 16, 2024
38 changes: 38 additions & 0 deletions config/oauth.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion resources/views/auth/unauthorized.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
<div class="outside-shadow absolute inset-0"></div>
<div class="card auth-card">
<div class="mb-6">{{ __('Unauthorized') }}</div>
<a class="btn-primary" href="{{ cp_route('logout') }}?redirect={{ $redirect }}">{{ __('Log out') }}</a>
@auth
<a class="btn-primary" href="{{ cp_route('logout') }}?redirect={{ $redirect }}">{{ __('Log out') }}</a>
@else
<a class="btn-primary" href="{{ cp_route('login') }}">{{ __('Log in') }}</a>
@endauth
</div>
</div>

Expand Down
47 changes: 42 additions & 5 deletions src/Http/Controllers/OAuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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;
}
}
20 changes: 18 additions & 2 deletions src/OAuth/Provider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
75 changes: 74 additions & 1 deletion tests/OAuth/ProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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('[email protected]', $user->email());
$this->assertEquals('Foo Bar', $user->name());
$this->assertEquals($user->id(), $provider->getUserId('foo-bar'));
}

#[Test]
Expand Down
Loading