diff --git a/app/Auth/Access/Guards/LdapSessionGuard.php b/app/Auth/Access/Guards/LdapSessionGuard.php index d761a30e6..cabbfbbcb 100644 --- a/app/Auth/Access/Guards/LdapSessionGuard.php +++ b/app/Auth/Access/Guards/LdapSessionGuard.php @@ -5,14 +5,12 @@ namespace BookStack\Auth\Access\Guards; use BookStack\Auth\Access\LdapService; use BookStack\Auth\Access\RegistrationService; use BookStack\Auth\User; -use BookStack\Auth\UserRepo; use BookStack\Exceptions\LdapException; use BookStack\Exceptions\LoginAttemptException; use BookStack\Exceptions\LoginAttemptEmailNeededException; use BookStack\Exceptions\UserRegistrationException; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Session\Session; -use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; class LdapSessionGuard extends ExternalBaseSessionGuard diff --git a/app/Auth/Access/RegistrationService.php b/app/Auth/Access/RegistrationService.php index 9070d1241..68b17771d 100644 --- a/app/Auth/Access/RegistrationService.php +++ b/app/Auth/Access/RegistrationService.php @@ -6,6 +6,8 @@ use BookStack\Auth\User; use BookStack\Auth\UserRepo; use BookStack\Exceptions\UserRegistrationException; use BookStack\Facades\Activity; +use BookStack\Facades\Theme; +use BookStack\Theming\ThemeEvents; use Exception; class RegistrationService @@ -71,6 +73,7 @@ class RegistrationService } Activity::add(ActivityType::AUTH_REGISTER, $socialAccount ?? $newUser); + Theme::dispatch(ThemeEvents::AUTH_REGISTER, $socialAccount ? $socialAccount->driver : auth()->getDefaultDriver(), $newUser); // Start email confirmation flow if required if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) { diff --git a/app/Auth/Access/Saml2Service.php b/app/Auth/Access/Saml2Service.php index 0316ff976..105853997 100644 --- a/app/Auth/Access/Saml2Service.php +++ b/app/Auth/Access/Saml2Service.php @@ -6,6 +6,8 @@ use BookStack\Exceptions\JsonDebugException; use BookStack\Exceptions\SamlException; use BookStack\Exceptions\UserRegistrationException; use BookStack\Facades\Activity; +use BookStack\Facades\Theme; +use BookStack\Theming\ThemeEvents; use Exception; use Illuminate\Support\Str; use OneLogin\Saml2\Auth; @@ -375,6 +377,7 @@ class Saml2Service extends ExternalAuthService auth()->login($user); Activity::add(ActivityType::AUTH_LOGIN, "saml2; {$user->logDescriptor()}"); + Theme::dispatch(ThemeEvents::AUTH_LOGIN, 'saml2', $user); return $user; } } diff --git a/app/Auth/Access/SocialAuthService.php b/app/Auth/Access/SocialAuthService.php index f7a166d0e..7c8b66ea5 100644 --- a/app/Auth/Access/SocialAuthService.php +++ b/app/Auth/Access/SocialAuthService.php @@ -7,6 +7,8 @@ use BookStack\Exceptions\SocialDriverNotConfigured; use BookStack\Exceptions\SocialSignInAccountNotUsed; use BookStack\Exceptions\UserRegistrationException; use BookStack\Facades\Activity; +use BookStack\Facades\Theme; +use BookStack\Theming\ThemeEvents; use Illuminate\Support\Facades\Event; use Illuminate\Support\Str; use Laravel\Socialite\Contracts\Factory as Socialite; @@ -58,7 +60,7 @@ class SocialAuthService { // Check social account has not already been used if (SocialAccount::query()->where('driver_id', '=', $socialUser->getId())->exists()) { - throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver]), '/login'); + throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount' => $socialDriver]), '/login'); } if (User::query()->where('email', '=', $socialUser->getEmail())->exists()) { @@ -98,6 +100,7 @@ class SocialAuthService if (!$isLoggedIn && $socialAccount !== null) { auth()->login($socialAccount->user); Activity::add(ActivityType::AUTH_LOGIN, $socialAccount); + Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $socialAccount->user); return redirect()->intended('/'); } @@ -127,7 +130,7 @@ class SocialAuthService if (setting('registration-enabled') && config('auth.method') !== 'ldap' && config('auth.method') !== 'saml2') { $message .= trans('errors.social_account_register_instructions', ['socialAccount' => $titleCaseDriver]); } - + throw new SocialSignInAccountNotUsed($message, '/login'); } @@ -207,9 +210,9 @@ class SocialAuthService public function newSocialAccount(string $socialDriver, SocialUser $socialUser): SocialAccount { return new SocialAccount([ - 'driver' => $socialDriver, + 'driver' => $socialDriver, 'driver_id' => $socialUser->getId(), - 'avatar' => $socialUser->getAvatar() + 'avatar' => $socialUser->getAvatar() ]); } diff --git a/app/Http/Controllers/Auth/ConfirmEmailController.php b/app/Http/Controllers/Auth/ConfirmEmailController.php index bffeb5f61..6e6a0e779 100644 --- a/app/Http/Controllers/Auth/ConfirmEmailController.php +++ b/app/Http/Controllers/Auth/ConfirmEmailController.php @@ -2,12 +2,15 @@ namespace BookStack\Http\Controllers\Auth; +use BookStack\Actions\ActivityType; use BookStack\Auth\Access\EmailConfirmationService; use BookStack\Auth\UserRepo; use BookStack\Exceptions\ConfirmationEmailException; use BookStack\Exceptions\UserTokenExpiredException; use BookStack\Exceptions\UserTokenNotFoundException; +use BookStack\Facades\Theme; use BookStack\Http\Controllers\Controller; +use BookStack\Theming\ThemeEvents; use Exception; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -80,6 +83,8 @@ class ConfirmEmailController extends Controller $user->save(); auth()->login($user); + Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user); + $this->logActivity(ActivityType::AUTH_LOGIN, $user); $this->showSuccessNotification(trans('auth.email_confirm_success')); $this->emailConfirmationService->deleteByUser($user); diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index d5685644e..01255f466 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -7,7 +7,9 @@ use BookStack\Actions\ActivityType; use BookStack\Auth\Access\SocialAuthService; use BookStack\Exceptions\LoginAttemptEmailNeededException; use BookStack\Exceptions\LoginAttemptException; +use BookStack\Facades\Theme; use BookStack\Http\Controllers\Controller; +use BookStack\Theming\ThemeEvents; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; @@ -150,6 +152,7 @@ class LoginController extends Controller } } + Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user); $this->logActivity(ActivityType::AUTH_LOGIN, $user); return redirect()->intended($this->redirectPath()); } diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index cbec1f03f..7d7d8732b 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -2,11 +2,14 @@ namespace BookStack\Http\Controllers\Auth; +use BookStack\Actions\ActivityType; use BookStack\Auth\Access\RegistrationService; use BookStack\Auth\Access\SocialAuthService; use BookStack\Auth\User; use BookStack\Exceptions\UserRegistrationException; +use BookStack\Facades\Theme; use BookStack\Http\Controllers\Controller; +use BookStack\Theming\ThemeEvents; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; @@ -93,6 +96,8 @@ class RegisterController extends Controller try { $user = $this->registrationService->registerUser($userData); auth()->login($user); + Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user); + $this->logActivity(ActivityType::AUTH_LOGIN, $user); } catch (UserRegistrationException $exception) { if ($exception->getMessage()) { $this->showErrorNotification($exception->getMessage()); diff --git a/app/Http/Controllers/Auth/SocialController.php b/app/Http/Controllers/Auth/SocialController.php index 428194e07..447f0afc9 100644 --- a/app/Http/Controllers/Auth/SocialController.php +++ b/app/Http/Controllers/Auth/SocialController.php @@ -2,13 +2,16 @@ namespace BookStack\Http\Controllers\Auth; +use BookStack\Actions\ActivityType; use BookStack\Auth\Access\RegistrationService; use BookStack\Auth\Access\SocialAuthService; use BookStack\Exceptions\SocialDriverNotConfigured; use BookStack\Exceptions\SocialSignInAccountNotUsed; use BookStack\Exceptions\SocialSignInException; use BookStack\Exceptions\UserRegistrationException; +use BookStack\Facades\Theme; use BookStack\Http\Controllers\Controller; +use BookStack\Theming\ThemeEvents; use Illuminate\Http\Request; use Illuminate\Support\Str; use Laravel\Socialite\Contracts\User as SocialUser; @@ -127,6 +130,8 @@ class SocialController extends Controller $user = $this->registrationService->registerUser($userData, $socialAccount, $emailVerified); auth()->login($user); + Theme::dispatch(ThemeEvents::AUTH_LOGIN, $socialDriver, $user); + $this->logActivity(ActivityType::AUTH_LOGIN, $user); $this->showSuccessNotification(trans('auth.register_success')); return redirect('/'); diff --git a/app/Http/Controllers/Auth/UserInviteController.php b/app/Http/Controllers/Auth/UserInviteController.php index 926458fa6..ab7452248 100644 --- a/app/Http/Controllers/Auth/UserInviteController.php +++ b/app/Http/Controllers/Auth/UserInviteController.php @@ -2,11 +2,14 @@ namespace BookStack\Http\Controllers\Auth; +use BookStack\Actions\ActivityType; use BookStack\Auth\Access\UserInviteService; use BookStack\Auth\UserRepo; use BookStack\Exceptions\UserTokenExpiredException; use BookStack\Exceptions\UserTokenNotFoundException; +use BookStack\Facades\Theme; use BookStack\Http\Controllers\Controller; +use BookStack\Theming\ThemeEvents; use Exception; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; @@ -68,6 +71,8 @@ class UserInviteController extends Controller $user->save(); auth()->login($user); + Theme::dispatch(ThemeEvents::AUTH_LOGIN, auth()->getDefaultDriver(), $user); + $this->logActivity(ActivityType::AUTH_LOGIN, $user); $this->showSuccessNotification(trans('auth.user_invite_success', ['appName' => setting('app-name')])); $this->inviteService->deleteByUser($user); diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php index a81179ed7..56e1fba1c 100644 --- a/app/Theming/ThemeEvents.php +++ b/app/Theming/ThemeEvents.php @@ -41,6 +41,26 @@ class ThemeEvents */ const WEB_MIDDLEWARE_AFTER = 'web_middleware_after'; + /** + * Auth login event. + * Runs right after a user is logged-in to the application by any authentication + * system as a standard app user. This includes a user becoming logged in + * after registration. This is not emitted upon API usage. + * @param string $authSystem + * @param \BookStack\Auth\User $user + */ + const AUTH_LOGIN = 'auth_login'; + + /** + * Auth register event. + * Runs right after a user is newly registered to the application by any authentication + * system as a standard app user. This includes auto-registration systems used + * by LDAP, SAML and social systems. It only includes self-registrations. + * @param string $authSystem + * @param \BookStack\Auth\User $user + */ + const AUTH_REGISTER = 'auth_register'; + /** * Commonmark environment configure. * Provides the commonmark library environment for customization diff --git a/tests/Auth/SocialAuthTest.php b/tests/Auth/SocialAuthTest.php index d448b567e..4369d8b7a 100644 --- a/tests/Auth/SocialAuthTest.php +++ b/tests/Auth/SocialAuthTest.php @@ -17,8 +17,7 @@ class SocialAuthTest extends TestCase $this->setSettings(['registration-enabled' => 'true']); config(['GOOGLE_APP_ID' => 'abc123', 'GOOGLE_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']); - $mockSocialite = Mockery::mock(Factory::class); - $this->app[Factory::class] = $mockSocialite; + $mockSocialite = $this->mock(Factory::class); $mockSocialDriver = Mockery::mock(Provider::class); $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class); @@ -46,8 +45,7 @@ class SocialAuthTest extends TestCase 'APP_URL' => 'http://localhost' ]); - $mockSocialite = Mockery::mock(Factory::class); - $this->app[Factory::class] = $mockSocialite; + $mockSocialite = $this->mock(Factory::class); $mockSocialDriver = Mockery::mock(Provider::class); $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class); @@ -93,8 +91,7 @@ class SocialAuthTest extends TestCase ]); $user = factory(User::class)->make(); - $mockSocialite = Mockery::mock(Factory::class); - $this->app[Factory::class] = $mockSocialite; + $mockSocialite = $this->mock(Factory::class); $mockSocialDriver = Mockery::mock(Provider::class); $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class); @@ -132,8 +129,7 @@ class SocialAuthTest extends TestCase ]); $user = factory(User::class)->make(); - $mockSocialite = Mockery::mock(Factory::class); - $this->app[Factory::class] = $mockSocialite; + $mockSocialite = $this->mock(Factory::class); $mockSocialDriver = Mockery::mock(Provider::class); $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class); @@ -169,8 +165,7 @@ class SocialAuthTest extends TestCase $this->setSettings(['registration-enabled' => 'true']); config(['GITHUB_APP_ID' => 'abc123', 'GITHUB_APP_SECRET' => '123abc', 'APP_URL' => 'http://localhost']); - $mockSocialite = Mockery::mock(Factory::class); - $this->app[Factory::class] = $mockSocialite; + $mockSocialite = $this->mock(Factory::class); $mockSocialDriver = Mockery::mock(Provider::class); $mockSocialUser = Mockery::mock(\Laravel\Socialite\Contracts\User::class); diff --git a/tests/ThemeTest.php b/tests/ThemeTest.php index 198c2b0aa..7a0cd49cb 100644 --- a/tests/ThemeTest.php +++ b/tests/ThemeTest.php @@ -1,5 +1,7 @@ assertStatus(443); } + public function test_event_auth_login_standard() + { + $args = []; + $callback = function (...$eventArgs) use (&$args) { + $args = $eventArgs; + }; + + Theme::listen(ThemeEvents::AUTH_LOGIN, $callback); + $this->post('/login', ['email' => 'admin@admin.com', 'password' => 'password']); + + $this->assertCount(2, $args); + $this->assertEquals('standard', $args[0]); + $this->assertInstanceOf(User::class, $args[1]); + } + + public function test_event_auth_register_standard() + { + $args = []; + $callback = function (...$eventArgs) use (&$args) { + $args = $eventArgs; + }; + Theme::listen(ThemeEvents::AUTH_REGISTER, $callback); + $this->setSettings(['registration-enabled' => 'true']); + + $user = factory(User::class)->make(); + $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']); + + $this->assertCount(2, $args); + $this->assertEquals('standard', $args[0]); + $this->assertInstanceOf(User::class, $args[1]); + } + public function test_add_social_driver() { Theme::addSocialDriver('catnet', [