Included tests to cover.
Manually tested on standard and social (GitHub) auth.
For #4833
class RegistrationService
{
- protected $userRepo;
- protected $emailConfirmationService;
-
- /**
- * RegistrationService constructor.
- */
- public function __construct(UserRepo $userRepo, EmailConfirmationService $emailConfirmationService)
- {
- $this->userRepo = $userRepo;
- $this->emailConfirmationService = $emailConfirmationService;
+ public function __construct(
+ protected UserRepo $userRepo,
+ protected EmailConfirmationService $emailConfirmationService,
+ ) {
}
/**
- * Check whether or not registrations are allowed in the app settings.
+ * Check if registrations are allowed in the app settings.
*
* @throws UserRegistrationException
*/
public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailConfirmed = false): User
{
$userEmail = $userData['email'];
+ $authSystem = $socialAccount ? $socialAccount->driver : auth()->getDefaultDriver();
// Email restriction
$this->ensureEmailDomainAllowed($userEmail);
throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $userEmail]), '/login');
}
+ /** @var ?bool $shouldRegister */
+ $shouldRegister = Theme::dispatch(ThemeEvents::AUTH_PRE_REGISTER, $authSystem, $userData);
+ if ($shouldRegister === false) {
+ throw new UserRegistrationException(trans('errors.auth_pre_register_theme_prevention'), '/login');
+ }
+
// Create the user
$newUser = $this->userRepo->createWithoutActivity($userData, $emailConfirmed);
$newUser->attachDefaultRole();
}
Activity::add(ActivityType::AUTH_REGISTER, $socialAccount ?? $newUser);
- Theme::dispatch(ThemeEvents::AUTH_REGISTER, $socialAccount ? $socialAccount->driver : auth()->getDefaultDriver(), $newUser);
+ Theme::dispatch(ThemeEvents::AUTH_REGISTER, $authSystem, $newUser);
// Start email confirmation flow if required
if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) {
}
$restrictedEmailDomains = explode(',', str_replace(' ', '', $registrationRestrict));
- $userEmailDomain = $domain = mb_substr(mb_strrchr($userEmail, '@'), 1);
+ $userEmailDomain = mb_substr(mb_strrchr($userEmail, '@'), 1);
if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
$redirect = $this->registrationAllowed() ? '/register' : '/login';
* The provided $detail can be a string or a loggable type of model. You should check
* the type before making use of this parameter.
*
- * @param string $type
+ * @param string $type
* @param string|\BookStack\Activity\Models\Loggable $detail
*/
const ACTIVITY_LOGGED = 'activity_logged';
* 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 string $authSystem
* @param \BookStack\Users\Models\User $user
*/
const AUTH_LOGIN = 'auth_login';
+ /**
+ * Auth pre-register event.
+ * Runs right before a new user account is registered in the system by any authentication
+ * system as a standard app user including auto-registration systems used by LDAP,
+ * SAML, OIDC and social systems. It only includes self-registrations,
+ * not accounts created by others in the UI or via the REST API.
+ * It runs after any other normal validation steps.
+ * Any account/email confirmation occurs post-registration.
+ * The provided $userData contains the main details that would be used to create
+ * the account, and may depend on authentication method.
+ * If false is returned from the event, registration will be prevented and the user
+ * will be returned to the login page.
+ *
+ * @param string $authSystem
+ * @param array $userData
+ * @returns bool|null
+ */
+ const AUTH_PRE_REGISTER = 'auth_pre_register';
+
/**
* 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.
+ * by LDAP, SAML, OIDC and social systems. It only includes self-registrations.
*
- * @param string $authSystem
+ * @param string $authSystem
* @param \BookStack\Users\Models\User $user
*/
const AUTH_REGISTER = 'auth_register';
*
* @param string $tagReference
* @param string $replacementHTML
- * @param \BookStack\Entities\Models\Page $currentPage
- * @param ?\BookStack\Entities\Models\Page $referencedPage
+ * @param \BookStack\Entities\Models\Page $currentPage
+ * @param ?\BookStack\Entities\Models\Page $referencedPage
*/
const PAGE_INCLUDE_PARSE = 'page_include_parse';
* Provides both the original request and the currently resolved response.
* Return values, if provided, will be used as a new response to use.
*
- * @param \Illuminate\Http\Request $request
+ * @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse $response
* @returns \Illuminate\Http\Response|null
*/
* If the listener returns a non-null value, that will be used as the POST data instead
* of the system default.
*
- * @param string $event
- * @param \BookStack\Activity\Models\Webhook $webhook
+ * @param string $event
+ * @param \BookStack\Activity\Models\Webhook $webhook
* @param string|\BookStack\Activity\Models\Loggable $detail
- * @param \BookStack\Users\Models\User $initiator
- * @param int $initiatedTime
+ * @param \BookStack\Users\Models\User $initiator
+ * @param int $initiatedTime
* @returns array|null
*/
const WEBHOOK_CALL_BEFORE = 'webhook_call_before';
// Auth
'error_user_exists_different_creds' => 'A user with the email :email already exists but with different credentials.',
+ 'auth_pre_register_theme_prevention' => 'User account could not be registered for the provided details',
'email_already_confirmed' => 'Email has already been confirmed, Try logging in.',
'email_confirmation_invalid' => 'This confirmation token is not valid or has already been used, Please try registering again.',
'email_confirmation_expired' => 'The confirmation token has expired, A new confirmation email has been sent.',
'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.',
'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
'oidc_already_logged_in' => 'Already logged in',
- 'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled',
'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
'social_no_action_defined' => 'No action defined',
$this->assertInstanceOf(User::class, $args[1]);
}
+ public function test_event_auth_pre_register()
+ {
+ $args = [];
+ $callback = function (...$eventArgs) use (&$args) {
+ $args = $eventArgs;
+ };
+ Theme::listen(ThemeEvents::AUTH_PRE_REGISTER, $callback);
+ $this->setSettings(['registration-enabled' => 'true']);
+
+ $user = User::factory()->make();
+ $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
+
+ $this->assertCount(2, $args);
+ $this->assertEquals('standard', $args[0]);
+ $this->assertEquals([
+ 'email' => $user->email,
+ 'name' => $user->name,
+ 'password' => 'password',
+ ], $args[1]);
+ $this->assertDatabaseHas('users', ['email' => $user->email]);
+ }
+
+ public function test_event_auth_pre_register_with_false_return_blocks_registration()
+ {
+ $callback = function () {
+ return false;
+ };
+ Theme::listen(ThemeEvents::AUTH_PRE_REGISTER, $callback);
+ $this->setSettings(['registration-enabled' => 'true']);
+
+ $user = User::factory()->make();
+ $resp = $this->post('/register', ['email' => $user->email, 'name' => $user->name, 'password' => 'password']);
+ $resp->assertRedirect('/login');
+ $this->assertSessionError('User account could not be registered for the provided details');
+ $this->assertDatabaseMissing('users', ['email' => $user->email]);
+ }
+
public function test_event_webhook_call_before()
{
$args = [];