mirror of
https://github.com/koel/koel
synced 2024-11-23 20:53:05 +00:00
feat: support reverse proxy authentication
This commit is contained in:
parent
bd8ada1d10
commit
d80a19ba70
19 changed files with 288 additions and 52 deletions
12
.env.example
12
.env.example
|
@ -157,6 +157,18 @@ SSO_GOOGLE_CLIENT_SECRET=
|
|||
SSO_GOOGLE_HOSTED_DOMAIN=yourdomain.com
|
||||
|
||||
|
||||
# Koel can be configured to authenticate users via a reverse proxy.
|
||||
# To enable this feature, set PROXY_AUTH_ENABLED to true and provide the necessary configuration below.
|
||||
PROXY_AUTH_ENABLED=false
|
||||
# The header name that contains the unique identifer for the user
|
||||
PROXY_AUTH_USER_HEADER=remote-user
|
||||
# The header name that contains the user's preferred, humanly-readable name
|
||||
PROXY_AUTH_PREFERRED_NAME_HEADER=remote-preferred-name
|
||||
# A comma-separated list of allowed proxy IPs or CIDRs, for example 10.10.1.0/24 or 2001:0db8:/32
|
||||
# If empty, NO requests will be allowed (which means proxy authentication is disabled).
|
||||
PROXY_AUTH_ALLOW_LIST=
|
||||
|
||||
|
||||
# Sync logs can be found under storage/logs/. Valid options are:
|
||||
# all: Log everything (errored-, skipped-, and successfully processed file).
|
||||
# error: Log errors only. This is the default.
|
||||
|
|
25
app/Http/Controllers/IndexController.php
Normal file
25
app/Http/Controllers/IndexController.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Facades\License;
|
||||
use App\Services\AuthenticationService;
|
||||
use App\Services\ProxyAuthService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class IndexController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request, ProxyAuthService $proxyAuthService, AuthenticationService $auth)
|
||||
{
|
||||
$data = ['token' => null];
|
||||
|
||||
if (License::isPlus() && config('koel.proxy_auth.enabled')) {
|
||||
$data['token'] = optional(
|
||||
$proxyAuthService->tryGetProxyAuthenticatedUserFromRequest($request),
|
||||
static fn ($user) => $auth->logUserIn($user)->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
return view('index', $data);
|
||||
}
|
||||
}
|
|
@ -6,6 +6,7 @@ use App\Facades\License;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Services\AuthenticationService;
|
||||
use App\Services\UserService;
|
||||
use App\Values\SSOUser;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
class GoogleCallbackController extends Controller
|
||||
|
@ -15,7 +16,7 @@ class GoogleCallbackController extends Controller
|
|||
assert(License::isPlus());
|
||||
|
||||
$user = Socialite::driver('google')->user();
|
||||
$user = $userService->createOrUpdateUserFromSocialiteUser($user, 'Google');
|
||||
$user = $userService->createOrUpdateUserFromSSO(SSOUser::fromSocialite($user, 'Google'));
|
||||
|
||||
return view('sso-callback')->with('token', $auth->logUserIn($user)->toArray());
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
namespace App\Repositories;
|
||||
|
||||
use App\Models\User;
|
||||
use Laravel\Socialite\Contracts\User as SocialiteUser;
|
||||
use App\Values\SSOUser;
|
||||
|
||||
class UserRepository extends Repository
|
||||
{
|
||||
|
@ -19,12 +19,12 @@ class UserRepository extends Repository
|
|||
return User::query()->firstWhere('email', $email);
|
||||
}
|
||||
|
||||
public function findOneBySocialiteUser(SocialiteUser $socialiteUser, string $provider): ?User
|
||||
public function findOneBySSO(SSOUser $ssoUser): ?User
|
||||
{
|
||||
// we prioritize the SSO ID over the email address, but still resort to the latter
|
||||
return User::query()->firstWhere([
|
||||
'sso_id' => $socialiteUser->getId(),
|
||||
'sso_provider' => $provider,
|
||||
]) ?? $this->findOneByEmail($socialiteUser->getEmail());
|
||||
'sso_id' => $ssoUser->id,
|
||||
'sso_provider' => $ssoUser->provider,
|
||||
]) ?? $this->findOneByEmail($ssoUser->email);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,11 +53,6 @@ class AuthenticationService
|
|||
return $this->passwordBroker->sendResetLink(['email' => $email]) === Password::RESET_LINK_SENT;
|
||||
}
|
||||
|
||||
public function generatePasswordResetToken(User $user): string
|
||||
{
|
||||
return $this->passwordBroker->createToken($user);
|
||||
}
|
||||
|
||||
public function tryResetPasswordUsingBroker(string $email, string $password, string $token): bool
|
||||
{
|
||||
$credentials = [
|
||||
|
|
37
app/Services/ProxyAuthService.php
Normal file
37
app/Services/ProxyAuthService.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Values\SSOUser;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\IpUtils;
|
||||
use Throwable;
|
||||
|
||||
class ProxyAuthService
|
||||
{
|
||||
public function __construct(private UserService $userService)
|
||||
{
|
||||
}
|
||||
|
||||
public function tryGetProxyAuthenticatedUserFromRequest(Request $request): ?User
|
||||
{
|
||||
if (!self::validateProxyIp($request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return $this->userService->createOrUpdateUserFromSSO(SSOUser::fromProxyAuthRequest($request));
|
||||
} catch (Throwable $e) {
|
||||
Log::error($e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function validateProxyIp(Request $request): bool
|
||||
{
|
||||
return IpUtils::checkIp($request->ip(), config('koel.proxy_auth.allow_list'));
|
||||
}
|
||||
}
|
|
@ -6,11 +6,10 @@ use App\Exceptions\UserProspectUpdateDeniedException;
|
|||
use App\Facades\License;
|
||||
use App\Models\User;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\Values\SSOUser;
|
||||
use Illuminate\Contracts\Hashing\Hasher;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Contracts\User as SocialiteUser;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
class UserService
|
||||
{
|
||||
|
@ -33,7 +32,7 @@ class UserService
|
|||
): User {
|
||||
if ($ssoProvider) {
|
||||
License::requirePlus();
|
||||
Assert::oneOf($ssoProvider, ['Google']);
|
||||
SSOUser::assertValidProvider($ssoProvider);
|
||||
}
|
||||
|
||||
return User::query()->create([
|
||||
|
@ -47,31 +46,30 @@ class UserService
|
|||
]);
|
||||
}
|
||||
|
||||
public function createOrUpdateUserFromSocialiteUser(SocialiteUser $socialiteUser, string $provider): User
|
||||
public function createOrUpdateUserFromSSO(SSOUser $ssoUser): User
|
||||
{
|
||||
License::requirePlus();
|
||||
Assert::oneOf($provider, ['Google']);
|
||||
|
||||
$existingUser = $this->repository->findOneBySocialiteUser($socialiteUser, $provider);
|
||||
$existingUser = $this->repository->findOneBySSO($ssoUser);
|
||||
|
||||
if ($existingUser) {
|
||||
$existingUser->update([
|
||||
'avatar' => $existingUser->has_custom_avatar ? $existingUser->avatar : $socialiteUser->getAvatar(),
|
||||
'sso_id' => $socialiteUser->getId(),
|
||||
'sso_provider' => $provider,
|
||||
'avatar' => $existingUser->has_custom_avatar ? $existingUser->avatar : $ssoUser->avatar,
|
||||
'sso_id' => $ssoUser->id,
|
||||
'sso_provider' => $ssoUser->provider,
|
||||
]);
|
||||
|
||||
return $existingUser;
|
||||
}
|
||||
|
||||
return $this->createUser(
|
||||
name: $socialiteUser->getName(),
|
||||
email: $socialiteUser->getEmail(),
|
||||
name: $ssoUser->name,
|
||||
email: $ssoUser->email,
|
||||
plainTextPassword: '',
|
||||
isAdmin: false,
|
||||
avatar: $socialiteUser->getAvatar(),
|
||||
ssoId: $socialiteUser->getId(),
|
||||
ssoProvider: $provider
|
||||
avatar: $ssoUser->avatar,
|
||||
ssoId: $ssoUser->id,
|
||||
ssoProvider: $ssoUser->provider,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
49
app/Values/SSOUser.php
Normal file
49
app/Values/SSOUser.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Values;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Laravel\Socialite\Contracts\User as SocialiteUser;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class SSOUser
|
||||
{
|
||||
private function __construct(
|
||||
public string $provider,
|
||||
public string $id,
|
||||
public string $email,
|
||||
public string $name,
|
||||
public ?string $avatar,
|
||||
) {
|
||||
self::assertValidProvider($provider);
|
||||
}
|
||||
|
||||
public static function fromSocialite(SocialiteUser $socialiteUser, string $provider): self
|
||||
{
|
||||
return new self(
|
||||
provider: $provider,
|
||||
id: $socialiteUser->getId(),
|
||||
email: $socialiteUser->getEmail(),
|
||||
name: $socialiteUser->getName(),
|
||||
avatar: $socialiteUser->getAvatar(),
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromProxyAuthRequest(Request $request): self
|
||||
{
|
||||
$identifier = $request->header(config('koel.proxy_auth.user_header'));
|
||||
|
||||
return new self(
|
||||
provider: 'Reverse Proxy',
|
||||
id: $identifier,
|
||||
email: "$identifier@reverse-proxy",
|
||||
name: $request->header(config('koel.proxy_auth.preferred_name_header')) ?: $identifier,
|
||||
avatar: null,
|
||||
);
|
||||
}
|
||||
|
||||
public static function assertValidProvider(string $provider): void
|
||||
{
|
||||
Assert::oneOf($provider, ['Google', 'Reverse Proxy']);
|
||||
}
|
||||
}
|
|
@ -141,6 +141,13 @@ return [
|
|||
|
||||
'sync_log_level' => env('SYNC_LOG_LEVEL', 'error'),
|
||||
|
||||
'proxy_auth' => [
|
||||
'enabled' => env('PROXY_AUTH_ENABLED', false),
|
||||
'user_header' => env('PROXY_AUTH_USER_HEADER', 'remote-user'),
|
||||
'preferred_name_header' => env('PROXY_AUTH_PREFERRED_NAME_HEADER', 'remote-preferred-name'),
|
||||
'allow_list' => array_map(static fn ($entry) => trim($entry), explode(',', env('PROXY_AUTH_ALLOW_LIST', ''))),
|
||||
],
|
||||
|
||||
'misc' => [
|
||||
'home_url' => 'https://koel.dev',
|
||||
'docs_url' => 'https://docs.koel.dev',
|
||||
|
|
|
@ -85,6 +85,12 @@ const onUserLoggedIn = async () => {
|
|||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// If the user is authenticated via a proxy, we have the token in the window object.
|
||||
// Simply forward it to the authService and continue with the normal flow.
|
||||
if (window.AUTH_TOKEN) {
|
||||
authService.setTokensUsingCompositeToken(window.AUTH_TOKEN)
|
||||
}
|
||||
|
||||
// The app has just been initialized, check if we can get the user data with an already existing token
|
||||
if (authService.hasApiToken()) {
|
||||
await init()
|
||||
|
|
|
@ -34,9 +34,9 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { authService, CompositeToken } from '@/services'
|
||||
import { authService } from '@/services'
|
||||
import { logger } from '@/utils'
|
||||
import { useMessageToaster, useRouter } from '@/composables'
|
||||
import { useMessageToaster } from '@/composables'
|
||||
|
||||
import Btn from '@/components/ui/Btn.vue'
|
||||
import PasswordField from '@/components/ui/PasswordField.vue'
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<template>
|
||||
<form data-testid="update-profile-form" @submit.prevent="update">
|
||||
<AlertBox v-if="currentUser.sso_provider">
|
||||
You’re logging in via Single Sign On provided by <strong>{{ currentUser.sso_provider }}</strong>.
|
||||
<template v-if="currentUser.sso_provider === 'Reverse Proxy'">
|
||||
You’re authenticated by a reverse proxy.
|
||||
</template>
|
||||
<template v-else>
|
||||
You’re logging in via Single Sign On provided by <strong>{{ currentUser.sso_provider }}</strong>.
|
||||
</template>
|
||||
You can still update your name and avatar here.
|
||||
</AlertBox>
|
||||
<div class="profile form-row">
|
||||
|
|
|
@ -11,11 +11,6 @@ export interface UpdateCurrentProfileData {
|
|||
new_password?: string
|
||||
}
|
||||
|
||||
export interface CompositeToken {
|
||||
'audio-token': string
|
||||
'token': string
|
||||
}
|
||||
|
||||
const API_TOKEN_STORAGE_KEY = 'api-token'
|
||||
const AUDIO_TOKEN_STORAGE_KEY = 'audio-token'
|
||||
|
||||
|
|
10
resources/assets/js/types.d.ts
vendored
10
resources/assets/js/types.d.ts
vendored
|
@ -55,13 +55,19 @@ interface Constructable<T> {
|
|||
new (...args: any): T
|
||||
}
|
||||
|
||||
type SSOProvider = 'Google' | 'Facebook'
|
||||
interface CompositeToken {
|
||||
'audio-token': string
|
||||
token: string
|
||||
}
|
||||
|
||||
type SSOProvider = 'Google' | 'Reverse Proxy'
|
||||
|
||||
interface Window {
|
||||
BASE_URL: string
|
||||
MAILER_CONFIGURED: boolean
|
||||
IS_DEMO: boolean
|
||||
SSO_PROVIDERS: SSOProvider[] // not supporting Facebook yet, though
|
||||
SSO_PROVIDERS: SSOProvider[]
|
||||
AUTH_TOKEN: CompositeToken | null
|
||||
|
||||
readonly PUSHER_APP_KEY: string
|
||||
readonly PUSHER_APP_CLUSTER: string
|
||||
|
|
|
@ -33,11 +33,8 @@
|
|||
|
||||
<script>
|
||||
window.BASE_URL = @json(asset(''));
|
||||
window.MAILER_CONFIGURED = @json(mailer_configured());
|
||||
window.IS_DEMO = @json(config('koel.misc.demo'));
|
||||
|
||||
window.SSO_PROVIDERS = @json(collect_sso_providers());
|
||||
|
||||
window.PUSHER_APP_KEY = @json(config('broadcasting.connections.pusher.key'));
|
||||
window.PUSHER_APP_CLUSTER = @json(config('broadcasting.connections.pusher.options.cluster'));
|
||||
</script>
|
||||
|
|
|
@ -3,5 +3,10 @@
|
|||
@section('title', 'Koel')
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
window.MAILER_CONFIGURED = @json(mailer_configured());
|
||||
window.SSO_PROVIDERS = @json(collect_sso_providers());
|
||||
window.AUTH_TOKEN = @json($token);
|
||||
</script>
|
||||
@vite(['resources/assets/js/app.ts'])
|
||||
@endpush
|
||||
|
|
|
@ -7,6 +7,7 @@ use App\Http\Controllers\Download\DownloadArtistController;
|
|||
use App\Http\Controllers\Download\DownloadFavoritesController;
|
||||
use App\Http\Controllers\Download\DownloadPlaylistController;
|
||||
use App\Http\Controllers\Download\DownloadSongsController;
|
||||
use App\Http\Controllers\IndexController;
|
||||
use App\Http\Controllers\LastfmController;
|
||||
use App\Http\Controllers\PlayController;
|
||||
use App\Http\Controllers\SSO\GoogleCallbackController;
|
||||
|
@ -15,7 +16,7 @@ use Illuminate\Support\Facades\Route;
|
|||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
Route::middleware('web')->group(static function (): void {
|
||||
Route::get('/', static fn () => view('index'));
|
||||
Route::get('/', IndexController::class);
|
||||
|
||||
Route::get('remote', static fn () => view('remote'));
|
||||
|
||||
|
|
95
tests/Feature/KoelPlus/ProxyAuthTest.php
Normal file
95
tests/Feature/KoelPlus/ProxyAuthTest.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature\KoelPlus;
|
||||
|
||||
use App\Models\User;
|
||||
use Laravel\Sanctum\PersonalAccessToken;
|
||||
use Tests\PlusTestCase;
|
||||
|
||||
use function Tests\create_user;
|
||||
|
||||
class ProxyAuthTest extends PlusTestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
config([
|
||||
'koel.proxy_auth.enabled' => true,
|
||||
'koel.proxy_auth.allow_list' => ['192.168.1.0/24'],
|
||||
'koel.proxy_auth.user_header' => 'remote-user',
|
||||
'koel.proxy_auth.preferred_name_header' => 'remote-preferred-name',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
config([
|
||||
'koel.proxy_auth.enabled' => false,
|
||||
'koel.proxy_auth.allow_list' => [],
|
||||
'koel.proxy_auth.user_header' => 'remote-user',
|
||||
'koel.proxy_auth.preferred_name_header' => 'remote-preferred-name',
|
||||
]);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testProxyAuthenticateNewUser(): void
|
||||
{
|
||||
$response = $this->get('/', [
|
||||
'REMOTE_ADDR' => '192.168.1.127',
|
||||
'remote-user' => '123456',
|
||||
'remote-preferred-name' => 'Bruce Dickinson',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('token');
|
||||
|
||||
/** @var array $token */
|
||||
$token = $response->viewData('token');
|
||||
|
||||
self::assertNotNull(PersonalAccessToken::findToken($token['token']));
|
||||
|
||||
self::assertDatabaseHas(User::class, [
|
||||
'email' => '123456@reverse-proxy',
|
||||
'name' => 'Bruce Dickinson',
|
||||
'sso_id' => '123456',
|
||||
'sso_provider' => 'Reverse Proxy',
|
||||
]);
|
||||
}
|
||||
|
||||
public function testProxyAuthenticateExistingUser(): void
|
||||
{
|
||||
$user = create_user([
|
||||
'sso_id' => '123456',
|
||||
'sso_provider' => 'Reverse Proxy',
|
||||
]);
|
||||
|
||||
$response = $this->get('/', [
|
||||
'REMOTE_ADDR' => '192.168.1.127',
|
||||
'remote-user' => '123456',
|
||||
'remote-preferred-name' => 'Bruce Dickinson',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertViewHas('token');
|
||||
|
||||
/** @var array $token */
|
||||
$token = $response->viewData('token');
|
||||
|
||||
self::assertTrue($user->is(PersonalAccessToken::findToken($token['token'])->tokenable));
|
||||
}
|
||||
|
||||
public function testProxyAuthenticateWithDisallowedIp(): void
|
||||
{
|
||||
$response = $this->get('/', [
|
||||
'REMOTE_ADDR' => '255.168.1.127',
|
||||
'remote-user' => '123456',
|
||||
'remote-preferred-name' => 'Bruce Dickinson',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
self::assertNull($response->viewData('token'));
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace Tests\Integration\KoelPlus\Services;
|
|||
|
||||
use App\Models\User;
|
||||
use App\Services\UserService;
|
||||
use App\Values\SSOUser;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
use Mockery;
|
||||
|
@ -40,7 +41,7 @@ class UserServiceTest extends PlusTestCase
|
|||
self::assertSame('https://lh3.googleusercontent.com/a/vatar', $user->avatar);
|
||||
}
|
||||
|
||||
public function testCreateUserFromSocialiteUser(): void
|
||||
public function testCreateUserFromSSO(): void
|
||||
{
|
||||
self::assertDatabaseMissing(User::class, ['email' => 'bruce@iron.com']);
|
||||
|
||||
|
@ -51,7 +52,7 @@ class UserServiceTest extends PlusTestCase
|
|||
'getAvatar' => 'https://lh3.googleusercontent.com/a/vatar',
|
||||
]);
|
||||
|
||||
$user = $this->service->createOrUpdateUserFromSocialiteUser($socialiteUser, 'Google');
|
||||
$user = $this->service->createOrUpdateUserFromSSO(SSOUser::fromSocialite($socialiteUser, 'Google'));
|
||||
|
||||
self::assertModelExists($user);
|
||||
|
||||
|
@ -66,6 +67,7 @@ class UserServiceTest extends PlusTestCase
|
|||
{
|
||||
$user = create_user([
|
||||
'email' => 'bruce@iron.com',
|
||||
'name' => 'Bruce Dickinson',
|
||||
'sso_id' => '123',
|
||||
'sso_provider' => 'Google',
|
||||
]);
|
||||
|
@ -77,30 +79,34 @@ class UserServiceTest extends PlusTestCase
|
|||
'getAvatar' => 'https://lh3.googleusercontent.com/a/vatar',
|
||||
]);
|
||||
|
||||
$this->service->createOrUpdateUserFromSocialiteUser($socialiteUser, 'Google');
|
||||
$this->service->createOrUpdateUserFromSSO(SSOUser::fromSocialite($socialiteUser, 'Google'));
|
||||
$user->refresh();
|
||||
|
||||
self::assertSame('Steve Harris', $user->name);
|
||||
self::assertSame('Bruce Dickinson', $user->name); // Name should not be updated
|
||||
self::assertSame('https://lh3.googleusercontent.com/a/vatar', $user->avatar);
|
||||
self::assertSame('steve@iron.com', $user->email);
|
||||
self::assertSame('bruce@iron.com', $user->email); // Email should not be updated
|
||||
self::assertSame('Google', $user->sso_provider);
|
||||
}
|
||||
|
||||
public function testUpdateUserFromSSOEmail(): void
|
||||
{
|
||||
$user = create_user(['email' => 'bruce@iron.com']);
|
||||
$user = create_user([
|
||||
'email' => 'bruce@iron.com',
|
||||
'name' => 'Bruce Dickinson',
|
||||
]);
|
||||
|
||||
$socialiteUser = Mockery::mock(SocialiteUser::class, [
|
||||
'getId' => '123',
|
||||
'getEmail' => 'bruce@iron.com',
|
||||
'getName' => 'Bruce Dickinson',
|
||||
'getName' => 'Steve Harris',
|
||||
'getAvatar' => 'https://lh3.googleusercontent.com/a/vatar',
|
||||
|
||||
]);
|
||||
|
||||
$this->service->createOrUpdateUserFromSocialiteUser($socialiteUser, 'Google');
|
||||
$this->service->createOrUpdateUserFromSSO(SSOUser::fromSocialite($socialiteUser, 'Google'));
|
||||
$user->refresh();
|
||||
|
||||
self::assertSame('Bruce Dickinson', $user->name);
|
||||
self::assertSame('Bruce Dickinson', $user->name); // Name should not be updated
|
||||
self::assertSame('https://lh3.googleusercontent.com/a/vatar', $user->avatar);
|
||||
self::assertSame('Google', $user->sso_provider);
|
||||
}
|
||||
|
@ -110,7 +116,6 @@ class UserServiceTest extends PlusTestCase
|
|||
$user = create_user([
|
||||
'email' => 'bruce@iron.com',
|
||||
'name' => 'Bruce Dickinson',
|
||||
'avatar' => 'https://lh3.googleusercontent.com/a/vatar',
|
||||
'sso_provider' => 'Google',
|
||||
]);
|
||||
|
||||
|
@ -120,15 +125,12 @@ class UserServiceTest extends PlusTestCase
|
|||
email: 'steve@iron.com',
|
||||
password: 'TheTrooper',
|
||||
isAdmin: true,
|
||||
avatar: 'https://lh3.googleusercontent.com/a/vatar/2'
|
||||
);
|
||||
|
||||
$user->refresh();
|
||||
|
||||
self::assertSame('Bruce Dickinson', $user->name);
|
||||
self::assertSame('bruce@iron.com', $user->email);
|
||||
self::assertFalse(Hash::check('TheTrooper', $user->password));
|
||||
self::assertTrue($user->is_admin);
|
||||
self::assertSame('https://lh3.googleusercontent.com/a/vatar', $user->avatar);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue