Checked over and aligned registration option behavior across all auth options
- Added tests to cover
This commit is contained in:
		
							parent
							
								
									e6c6de0848
								
							
						
					
					
						commit
						3991fbe726
					
				| 
						 | 
					@ -64,10 +64,8 @@ class ExternalAuthService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Sync the groups to the user roles for the current user
 | 
					     * Sync the groups to the user roles for the current user
 | 
				
			||||||
     * @param \BookStack\Auth\User $user
 | 
					 | 
				
			||||||
     * @param array $userGroups
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function syncWithGroups(User $user, array $userGroups)
 | 
					    public function syncWithGroups(User $user, array $userGroups): void
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // Get the ids for the roles from the names
 | 
					        // Get the ids for the roles from the names
 | 
				
			||||||
        $groupsAsRoles = $this->matchGroupsToSystemsRoles($userGroups);
 | 
					        $groupsAsRoles = $this->matchGroupsToSystemsRoles($userGroups);
 | 
				
			||||||
| 
						 | 
					@ -75,7 +73,7 @@ class ExternalAuthService
 | 
				
			||||||
        // Sync groups
 | 
					        // Sync groups
 | 
				
			||||||
        if ($this->config['remove_from_groups']) {
 | 
					        if ($this->config['remove_from_groups']) {
 | 
				
			||||||
            $user->roles()->sync($groupsAsRoles);
 | 
					            $user->roles()->sync($groupsAsRoles);
 | 
				
			||||||
            $this->userRepo->attachDefaultRole($user);
 | 
					            $user->attachDefaultRole();
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            $user->roles()->syncWithoutDetaching($groupsAsRoles);
 | 
					            $user->roles()->syncWithoutDetaching($groupsAsRoles);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,10 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace BookStack\Auth\Access\Guards;
 | 
					namespace BookStack\Auth\Access\Guards;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use BookStack\Auth\User;
 | 
					use BookStack\Auth\Access\RegistrationService;
 | 
				
			||||||
use BookStack\Auth\UserRepo;
 | 
					 | 
				
			||||||
use BookStack\Exceptions\LoginAttemptEmailNeededException;
 | 
					 | 
				
			||||||
use BookStack\Exceptions\LoginAttemptException;
 | 
					 | 
				
			||||||
use Illuminate\Auth\GuardHelpers;
 | 
					use Illuminate\Auth\GuardHelpers;
 | 
				
			||||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
 | 
					use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
 | 
				
			||||||
use Illuminate\Contracts\Auth\StatefulGuard;
 | 
					use Illuminate\Contracts\Auth\StatefulGuard;
 | 
				
			||||||
| 
						 | 
					@ -56,23 +53,23 @@ class ExternalBaseSessionGuard implements StatefulGuard
 | 
				
			||||||
    protected $loggedOut = false;
 | 
					    protected $loggedOut = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Repository to perform user-specific actions.
 | 
					     * Service to handle common registration actions.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @var UserRepo
 | 
					     * @var RegistrationService
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected $userRepo;
 | 
					    protected $registrationService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Create a new authentication guard.
 | 
					     * Create a new authentication guard.
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @return void
 | 
					     * @return void
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(string $name, UserProvider $provider, Session $session, UserRepo $userRepo)
 | 
					    public function __construct(string $name, UserProvider $provider, Session $session, RegistrationService $registrationService)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->name = $name;
 | 
					        $this->name = $name;
 | 
				
			||||||
        $this->session = $session;
 | 
					        $this->session = $session;
 | 
				
			||||||
        $this->provider = $provider;
 | 
					        $this->provider = $provider;
 | 
				
			||||||
        $this->userRepo = $userRepo;
 | 
					        $this->registrationService = $registrationService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,13 +3,17 @@
 | 
				
			||||||
namespace BookStack\Auth\Access\Guards;
 | 
					namespace BookStack\Auth\Access\Guards;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use BookStack\Auth\Access\LdapService;
 | 
					use BookStack\Auth\Access\LdapService;
 | 
				
			||||||
 | 
					use BookStack\Auth\Access\RegistrationService;
 | 
				
			||||||
use BookStack\Auth\User;
 | 
					use BookStack\Auth\User;
 | 
				
			||||||
use BookStack\Auth\UserRepo;
 | 
					use BookStack\Auth\UserRepo;
 | 
				
			||||||
use BookStack\Exceptions\LdapException;
 | 
					use BookStack\Exceptions\LdapException;
 | 
				
			||||||
use BookStack\Exceptions\LoginAttemptException;
 | 
					use BookStack\Exceptions\LoginAttemptException;
 | 
				
			||||||
use BookStack\Exceptions\LoginAttemptEmailNeededException;
 | 
					use BookStack\Exceptions\LoginAttemptEmailNeededException;
 | 
				
			||||||
 | 
					use BookStack\Exceptions\UserRegistrationException;
 | 
				
			||||||
use Illuminate\Contracts\Auth\UserProvider;
 | 
					use Illuminate\Contracts\Auth\UserProvider;
 | 
				
			||||||
use Illuminate\Contracts\Session\Session;
 | 
					use Illuminate\Contracts\Session\Session;
 | 
				
			||||||
 | 
					use Illuminate\Support\Facades\Hash;
 | 
				
			||||||
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class LdapSessionGuard extends ExternalBaseSessionGuard
 | 
					class LdapSessionGuard extends ExternalBaseSessionGuard
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
| 
						 | 
					@ -23,11 +27,11 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
 | 
				
			||||||
        UserProvider $provider,
 | 
					        UserProvider $provider,
 | 
				
			||||||
        Session $session,
 | 
					        Session $session,
 | 
				
			||||||
        LdapService $ldapService,
 | 
					        LdapService $ldapService,
 | 
				
			||||||
        UserRepo $userRepo
 | 
					        RegistrationService $registrationService
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->ldapService = $ldapService;
 | 
					        $this->ldapService = $ldapService;
 | 
				
			||||||
        parent::__construct($name, $provider, $session, $userRepo);
 | 
					        parent::__construct($name, $provider, $session, $registrationService);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -56,6 +60,7 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
 | 
				
			||||||
     * @throws LoginAttemptEmailNeededException
 | 
					     * @throws LoginAttemptEmailNeededException
 | 
				
			||||||
     * @throws LoginAttemptException
 | 
					     * @throws LoginAttemptException
 | 
				
			||||||
     * @throws LdapException
 | 
					     * @throws LdapException
 | 
				
			||||||
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function attempt(array $credentials = [], $remember = false)
 | 
					    public function attempt(array $credentials = [], $remember = false)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					@ -70,12 +75,9 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_null($user)) {
 | 
					        if (is_null($user)) {
 | 
				
			||||||
            $user = $this->freshUserInstanceFromLdapUserDetails($userDetails);
 | 
					            $user = $this->createNewFromLdapAndCreds($userDetails, $credentials);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->checkForUserEmail($user, $credentials['email'] ?? '');
 | 
					 | 
				
			||||||
        $this->saveIfNew($user);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Sync LDAP groups if required
 | 
					        // Sync LDAP groups if required
 | 
				
			||||||
        if ($this->ldapService->shouldSyncGroups()) {
 | 
					        if ($this->ldapService->shouldSyncGroups()) {
 | 
				
			||||||
            $this->ldapService->syncGroups($user, $username);
 | 
					            $this->ldapService->syncGroups($user, $username);
 | 
				
			||||||
| 
						 | 
					@ -86,58 +88,27 @@ class LdapSessionGuard extends ExternalBaseSessionGuard
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Save the give user if they don't yet existing in the system.
 | 
					     * Create a new user from the given ldap credentials and login credentials
 | 
				
			||||||
     * @throws LoginAttemptException
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected function saveIfNew(User $user)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if ($user->exists) {
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Check for existing users with same email
 | 
					 | 
				
			||||||
        $alreadyUser = $user->newQuery()->where('email', '=', $user->email)->count() > 0;
 | 
					 | 
				
			||||||
        if ($alreadyUser) {
 | 
					 | 
				
			||||||
            throw new LoginAttemptException(trans('errors.error_user_exists_different_creds', ['email' => $user->email]));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $user->save();
 | 
					 | 
				
			||||||
        $this->userRepo->attachDefaultRole($user);
 | 
					 | 
				
			||||||
        $this->userRepo->downloadAndAssignUserAvatar($user);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Ensure the given user has an email.
 | 
					 | 
				
			||||||
     * Takes the provided email in the request if a value is provided
 | 
					 | 
				
			||||||
     * and the user does not have an existing email.
 | 
					 | 
				
			||||||
     * @throws LoginAttemptEmailNeededException
 | 
					     * @throws LoginAttemptEmailNeededException
 | 
				
			||||||
 | 
					     * @throws LoginAttemptException
 | 
				
			||||||
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function checkForUserEmail(User $user, string $providedEmail)
 | 
					    protected function createNewFromLdapAndCreds(array $ldapUserDetails, array $credentials): User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        // Request email if missing from user and missing from request
 | 
					        $email = trim($ldapUserDetails['email'] ?: ($credentials['email'] ?? ''));
 | 
				
			||||||
        if (is_null($user->email) && !$providedEmail) {
 | 
					
 | 
				
			||||||
 | 
					        if (empty($email)) {
 | 
				
			||||||
            throw new LoginAttemptEmailNeededException();
 | 
					            throw new LoginAttemptEmailNeededException();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Add email to model if non-existing and email provided in request
 | 
					        $details = [
 | 
				
			||||||
        if (!$user->exists && is_null($user->email) && $providedEmail) {
 | 
					            'name' => $ldapUserDetails['name'],
 | 
				
			||||||
            $user->email = $providedEmail;
 | 
					            'email' => $ldapUserDetails['email'] ?: $credentials['email'],
 | 
				
			||||||
        }
 | 
					            'external_auth_id' => $ldapUserDetails['uid'],
 | 
				
			||||||
    }
 | 
					            'password' => Str::random(32),
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					        return $this->registrationService->registerUser($details, null, false);
 | 
				
			||||||
     * Create a fresh user instance from details provided by a LDAP lookup.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected function freshUserInstanceFromLdapUserDetails(array $ldapUserDetails): User
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $user = new User();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $user->name = $ldapUserDetails['name'];
 | 
					 | 
				
			||||||
        $user->external_auth_id = $ldapUserDetails['uid'];
 | 
					 | 
				
			||||||
        $user->email = $ldapUserDetails['email'];
 | 
					 | 
				
			||||||
        $user->email_confirmed = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return $user;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,8 @@
 | 
				
			||||||
<?php namespace BookStack\Auth\Access;
 | 
					<?php namespace BookStack\Auth\Access;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use BookStack\Auth\User;
 | 
					use BookStack\Auth\User;
 | 
				
			||||||
use BookStack\Auth\UserRepo;
 | 
					 | 
				
			||||||
use BookStack\Exceptions\LdapException;
 | 
					use BookStack\Exceptions\LdapException;
 | 
				
			||||||
use ErrorException;
 | 
					use ErrorException;
 | 
				
			||||||
use Illuminate\Contracts\Auth\Authenticatable;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Class LdapService
 | 
					 * Class LdapService
 | 
				
			||||||
| 
						 | 
					@ -16,17 +14,15 @@ class LdapService extends ExternalAuthService
 | 
				
			||||||
    protected $ldap;
 | 
					    protected $ldap;
 | 
				
			||||||
    protected $ldapConnection;
 | 
					    protected $ldapConnection;
 | 
				
			||||||
    protected $config;
 | 
					    protected $config;
 | 
				
			||||||
    protected $userRepo;
 | 
					 | 
				
			||||||
    protected $enabled;
 | 
					    protected $enabled;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * LdapService constructor.
 | 
					     * LdapService constructor.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(Ldap $ldap, UserRepo $userRepo)
 | 
					    public function __construct(Ldap $ldap)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->ldap = $ldap;
 | 
					        $this->ldap = $ldap;
 | 
				
			||||||
        $this->config = config('services.ldap');
 | 
					        $this->config = config('services.ldap');
 | 
				
			||||||
        $this->userRepo = $userRepo;
 | 
					 | 
				
			||||||
        $this->enabled = config('auth.method') === 'ldap';
 | 
					        $this->enabled = config('auth.method') === 'ldap';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
<?php namespace BookStack\Auth\Access;
 | 
					<?php namespace BookStack\Auth\Access;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use BookStack\Auth\SocialAccount;
 | 
					use BookStack\Auth\SocialAccount;
 | 
				
			||||||
 | 
					use BookStack\Auth\User;
 | 
				
			||||||
use BookStack\Auth\UserRepo;
 | 
					use BookStack\Auth\UserRepo;
 | 
				
			||||||
use BookStack\Exceptions\UserRegistrationException;
 | 
					use BookStack\Exceptions\UserRegistrationException;
 | 
				
			||||||
use Exception;
 | 
					use Exception;
 | 
				
			||||||
| 
						 | 
					@ -24,38 +25,51 @@ class RegistrationService
 | 
				
			||||||
     * Check whether or not registrations are allowed in the app settings.
 | 
					     * Check whether or not registrations are allowed in the app settings.
 | 
				
			||||||
     * @throws UserRegistrationException
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function checkRegistrationAllowed()
 | 
					    public function ensureRegistrationAllowed()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!$this->registrationAllowed()) {
 | 
				
			||||||
 | 
					            throw new UserRegistrationException(trans('auth.registrations_disabled'), '/login');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Check if standard BookStack User registrations are currently allowed.
 | 
				
			||||||
 | 
					     * Does not prevent external-auth based registration.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function registrationAllowed(): bool
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $authMethod = config('auth.method');
 | 
					        $authMethod = config('auth.method');
 | 
				
			||||||
        $authMethodsWithRegistration = ['standard'];
 | 
					        $authMethodsWithRegistration = ['standard'];
 | 
				
			||||||
        if (!setting('registration-enabled') || !in_array($authMethod, $authMethodsWithRegistration)) {
 | 
					        return in_array($authMethod, $authMethodsWithRegistration) && setting('registration-enabled');
 | 
				
			||||||
            throw new UserRegistrationException(trans('auth.registrations_disabled'), '/login');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * The registrations flow for all users.
 | 
					     * The registrations flow for all users.
 | 
				
			||||||
     * @throws UserRegistrationException
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailVerified = false)
 | 
					    public function registerUser(array $userData, ?SocialAccount $socialAccount = null, bool $emailConfirmed = false): User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $registrationRestrict = setting('registration-restrict');
 | 
					        $userEmail = $userData['email'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($registrationRestrict) {
 | 
					        // Email restriction
 | 
				
			||||||
            $restrictedEmailDomains = explode(',', str_replace(' ', '', $registrationRestrict));
 | 
					        $this->ensureEmailDomainAllowed($userEmail);
 | 
				
			||||||
            $userEmailDomain = $domain = mb_substr(mb_strrchr($userData['email'], "@"), 1);
 | 
					
 | 
				
			||||||
            if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
 | 
					        // Ensure user does not already exist
 | 
				
			||||||
                throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), '/register');
 | 
					        $alreadyUser = !is_null($this->userRepo->getByEmail($userEmail));
 | 
				
			||||||
            }
 | 
					        if ($alreadyUser) {
 | 
				
			||||||
 | 
					            throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $userEmail]));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $newUser = $this->userRepo->registerNew($userData, $emailVerified);
 | 
					        // Create the user
 | 
				
			||||||
 | 
					        $newUser = $this->userRepo->registerNew($userData, $emailConfirmed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Assign social account if given
 | 
				
			||||||
        if ($socialAccount) {
 | 
					        if ($socialAccount) {
 | 
				
			||||||
            $newUser->socialAccounts()->save($socialAccount);
 | 
					            $newUser->socialAccounts()->save($socialAccount);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->emailConfirmationService->confirmationRequired() && !$emailVerified) {
 | 
					        // Start email confirmation flow if required
 | 
				
			||||||
 | 
					        if ($this->emailConfirmationService->confirmationRequired() && !$emailConfirmed) {
 | 
				
			||||||
            $newUser->save();
 | 
					            $newUser->save();
 | 
				
			||||||
            $message = '';
 | 
					            $message = '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -68,7 +82,37 @@ class RegistrationService
 | 
				
			||||||
            throw new UserRegistrationException($message, '/register/confirm');
 | 
					            throw new UserRegistrationException($message, '/register/confirm');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        auth()->login($newUser);
 | 
					        return $newUser;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Ensure that the given email meets any active email domain registration restrictions.
 | 
				
			||||||
 | 
					     * Throws if restrictions are active and the email does not match an allowed domain.
 | 
				
			||||||
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function ensureEmailDomainAllowed(string $userEmail): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $registrationRestrict = setting('registration-restrict');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!$registrationRestrict) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $restrictedEmailDomains = explode(',', str_replace(' ', '', $registrationRestrict));
 | 
				
			||||||
 | 
					        $userEmailDomain = $domain = mb_substr(mb_strrchr($userEmail, "@"), 1);
 | 
				
			||||||
 | 
					        if (!in_array($userEmailDomain, $restrictedEmailDomains)) {
 | 
				
			||||||
 | 
					            $redirect = $this->registrationAllowed() ? '/register' : '/login';
 | 
				
			||||||
 | 
					            throw new UserRegistrationException(trans('auth.registration_email_domain_invalid'), $redirect);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Alias to the UserRepo method of the same name.
 | 
				
			||||||
 | 
					     * Attaches the default system role, if configured, to the given user.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function attachDefaultRole(User $user): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->userRepo->attachDefaultRole($user);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
<?php namespace BookStack\Auth\Access;
 | 
					<?php namespace BookStack\Auth\Access;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use BookStack\Auth\User;
 | 
					use BookStack\Auth\User;
 | 
				
			||||||
use BookStack\Auth\UserRepo;
 | 
					 | 
				
			||||||
use BookStack\Exceptions\JsonDebugException;
 | 
					use BookStack\Exceptions\JsonDebugException;
 | 
				
			||||||
use BookStack\Exceptions\SamlException;
 | 
					use BookStack\Exceptions\SamlException;
 | 
				
			||||||
 | 
					use BookStack\Exceptions\UserRegistrationException;
 | 
				
			||||||
use Exception;
 | 
					use Exception;
 | 
				
			||||||
use Illuminate\Support\Str;
 | 
					use Illuminate\Support\Str;
 | 
				
			||||||
use OneLogin\Saml2\Auth;
 | 
					use OneLogin\Saml2\Auth;
 | 
				
			||||||
| 
						 | 
					@ -18,16 +18,16 @@ use OneLogin\Saml2\ValidationError;
 | 
				
			||||||
class Saml2Service extends ExternalAuthService
 | 
					class Saml2Service extends ExternalAuthService
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    protected $config;
 | 
					    protected $config;
 | 
				
			||||||
    protected $userRepo;
 | 
					    protected $registrationService;
 | 
				
			||||||
    protected $user;
 | 
					    protected $user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Saml2Service constructor.
 | 
					     * Saml2Service constructor.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(UserRepo $userRepo, User $user)
 | 
					    public function __construct(RegistrationService $registrationService, User $user)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->config = config('saml2');
 | 
					        $this->config = config('saml2');
 | 
				
			||||||
        $this->userRepo = $userRepo;
 | 
					        $this->registrationService = $registrationService;
 | 
				
			||||||
        $this->user = $user;
 | 
					        $this->user = $user;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,6 +78,7 @@ class Saml2Service extends ExternalAuthService
 | 
				
			||||||
     * @throws SamlException
 | 
					     * @throws SamlException
 | 
				
			||||||
     * @throws ValidationError
 | 
					     * @throws ValidationError
 | 
				
			||||||
     * @throws JsonDebugException
 | 
					     * @throws JsonDebugException
 | 
				
			||||||
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function processAcsResponse(?string $requestId): ?User
 | 
					    public function processAcsResponse(?string $requestId): ?User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					@ -308,34 +309,10 @@ class Saml2Service extends ExternalAuthService
 | 
				
			||||||
        return $defaultValue;
 | 
					        return $defaultValue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     *  Register a user that is authenticated but not already registered.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected function registerUser(array $userDetails): User
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // Create an array of the user data to create a new user instance
 | 
					 | 
				
			||||||
        $userData = [
 | 
					 | 
				
			||||||
            'name' => $userDetails['name'],
 | 
					 | 
				
			||||||
            'email' => $userDetails['email'],
 | 
					 | 
				
			||||||
            'password' => Str::random(32),
 | 
					 | 
				
			||||||
            'external_auth_id' => $userDetails['external_id'],
 | 
					 | 
				
			||||||
            'email_confirmed' => true,
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $existingUser = $this->user->newQuery()->where('email', '=', $userDetails['email'])->first();
 | 
					 | 
				
			||||||
        if ($existingUser) {
 | 
					 | 
				
			||||||
            throw new SamlException(trans('errors.saml_email_exists', ['email' => $userDetails['email']]));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        $user = $this->user->newQuery()->forceCreate($userData);
 | 
					 | 
				
			||||||
        $this->userRepo->attachDefaultRole($user);
 | 
					 | 
				
			||||||
        $this->userRepo->downloadAndAssignUserAvatar($user);
 | 
					 | 
				
			||||||
        return $user;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get the user from the database for the specified details.
 | 
					     * Get the user from the database for the specified details.
 | 
				
			||||||
     * @throws SamlException
 | 
					     * @throws SamlException
 | 
				
			||||||
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function getOrRegisterUser(array $userDetails): ?User
 | 
					    protected function getOrRegisterUser(array $userDetails): ?User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					@ -344,7 +321,14 @@ class Saml2Service extends ExternalAuthService
 | 
				
			||||||
          ->first();
 | 
					          ->first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (is_null($user)) {
 | 
					        if (is_null($user)) {
 | 
				
			||||||
            $user = $this->registerUser($userDetails);
 | 
					            $userData = [
 | 
				
			||||||
 | 
					                'name' => $userDetails['name'],
 | 
				
			||||||
 | 
					                'email' => $userDetails['email'],
 | 
				
			||||||
 | 
					                'password' => Str::random(32),
 | 
				
			||||||
 | 
					                'external_auth_id' => $userDetails['external_id'],
 | 
				
			||||||
 | 
					            ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            $user = $this->registrationService->registerUser($userData, null, false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $user;
 | 
					        return $user;
 | 
				
			||||||
| 
						 | 
					@ -355,6 +339,7 @@ class Saml2Service extends ExternalAuthService
 | 
				
			||||||
     * they exist, optionally registering them automatically.
 | 
					     * they exist, optionally registering them automatically.
 | 
				
			||||||
     * @throws SamlException
 | 
					     * @throws SamlException
 | 
				
			||||||
     * @throws JsonDebugException
 | 
					     * @throws JsonDebugException
 | 
				
			||||||
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function processLoginCallback(string $samlID, array $samlAttributes): User
 | 
					    public function processLoginCallback(string $samlID, array $samlAttributes): User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ class SocialAuthService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($this->userRepo->getByEmail($socialUser->getEmail())) {
 | 
					        if ($this->userRepo->getByEmail($socialUser->getEmail())) {
 | 
				
			||||||
            $email = $socialUser->getEmail();
 | 
					            $email = $socialUser->getEmail();
 | 
				
			||||||
            throw new UserRegistrationException(trans('errors.social_account_in_use', ['socialAccount'=>$socialDriver, 'email' => $email]), '/login');
 | 
					            throw new UserRegistrationException(trans('errors.error_user_exists_different_creds', ['email' => $email]), '/login');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $socialUser;
 | 
					        return $socialUser;
 | 
				
			||||||
| 
						 | 
					@ -124,7 +124,7 @@ class SocialAuthService
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Otherwise let the user know this social account is not used by anyone.
 | 
					        // Otherwise let the user know this social account is not used by anyone.
 | 
				
			||||||
        $message = trans('errors.social_account_not_used', ['socialAccount' => $titleCaseDriver]);
 | 
					        $message = trans('errors.social_account_not_used', ['socialAccount' => $titleCaseDriver]);
 | 
				
			||||||
        if (setting('registration-enabled') && config('auth.method') !== 'ldap') {
 | 
					        if (setting('registration-enabled') && config('auth.method') !== 'ldap' && config('auth.method') !== 'saml2') {
 | 
				
			||||||
            $message .= trans('errors.social_account_register_instructions', ['socialAccount' => $titleCaseDriver]);
 | 
					            $message .= trans('errors.social_account_register_instructions', ['socialAccount' => $titleCaseDriver]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -116,6 +116,17 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
 | 
				
			||||||
        return $this->roles->pluck('system_name')->contains($role);
 | 
					        return $this->roles->pluck('system_name')->contains($role);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Attach the default system role to this user.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function attachDefaultRole(): void
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $roleId = setting('registration-role');
 | 
				
			||||||
 | 
					        if ($roleId && $this->roles()->where('id', '=', $roleId)->count() === 0) {
 | 
				
			||||||
 | 
					            $this->roles()->attach($roleId);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Get all permissions belonging to a the current user.
 | 
					     * Get all permissions belonging to a the current user.
 | 
				
			||||||
     * @param bool $cache
 | 
					     * @param bool $cache
 | 
				
			||||||
| 
						 | 
					@ -153,16 +164,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function attachRole(Role $role)
 | 
					    public function attachRole(Role $role)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->attachRoleId($role->id);
 | 
					        $this->roles()->attach($role->id);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Attach a role id to this user.
 | 
					 | 
				
			||||||
     * @param $id
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function attachRoleId($id)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $this->roles()->attach($id);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,10 +29,9 @@ class UserRepo
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param string $email
 | 
					     * Get a user by their email address.
 | 
				
			||||||
     * @return User|null
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getByEmail($email)
 | 
					    public function getByEmail(string $email): ?User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->user->where('email', '=', $email)->first();
 | 
					        return $this->user->where('email', '=', $email)->first();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -78,31 +77,16 @@ class UserRepo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
     /**
 | 
					     /**
 | 
				
			||||||
     * Creates a new user and attaches a role to them.
 | 
					     * Creates a new user and attaches a role to them.
 | 
				
			||||||
     * @param array $data
 | 
					 | 
				
			||||||
     * @param boolean $verifyEmail
 | 
					 | 
				
			||||||
     * @return User
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function registerNew(array $data, $verifyEmail = false)
 | 
					    public function registerNew(array $data, bool $emailConfirmed = false): User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $user = $this->create($data, $verifyEmail);
 | 
					        $user = $this->create($data, $emailConfirmed);
 | 
				
			||||||
        $this->attachDefaultRole($user);
 | 
					        $user->attachDefaultRole();
 | 
				
			||||||
        $this->downloadAndAssignUserAvatar($user);
 | 
					        $this->downloadAndAssignUserAvatar($user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $user;
 | 
					        return $user;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Give a user the default role. Used when creating a new user.
 | 
					 | 
				
			||||||
     * @param User $user
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public function attachDefaultRole(User $user)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        $roleId = setting('registration-role');
 | 
					 | 
				
			||||||
        if ($roleId !== false && $user->roles()->where('id', '=', $roleId)->count() === 0) {
 | 
					 | 
				
			||||||
            $user->attachRoleId($roleId);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Assign a user to a system-level role.
 | 
					     * Assign a user to a system-level role.
 | 
				
			||||||
     * @param User $user
 | 
					     * @param User $user
 | 
				
			||||||
| 
						 | 
					@ -172,17 +156,15 @@ class UserRepo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Create a new basic instance of user.
 | 
					     * Create a new basic instance of user.
 | 
				
			||||||
     * @param array $data
 | 
					 | 
				
			||||||
     * @param boolean $verifyEmail
 | 
					 | 
				
			||||||
     * @return User
 | 
					 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function create(array $data, $verifyEmail = false)
 | 
					    public function create(array $data, bool $emailConfirmed = false): User
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->user->forceCreate([
 | 
					        return $this->user->forceCreate([
 | 
				
			||||||
            'name'     => $data['name'],
 | 
					            'name'     => $data['name'],
 | 
				
			||||||
            'email'    => $data['email'],
 | 
					            'email'    => $data['email'],
 | 
				
			||||||
            'password' => bcrypt($data['password']),
 | 
					            'password' => bcrypt($data['password']),
 | 
				
			||||||
            'email_confirmed' => $verifyEmail
 | 
					            'email_confirmed' => $emailConfirmed,
 | 
				
			||||||
 | 
					            'external_auth_id' => $data['external_auth_id'] ?? '',
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,7 +57,10 @@ class Handler extends ExceptionHandler
 | 
				
			||||||
        // Handle notify exceptions which will redirect to the
 | 
					        // Handle notify exceptions which will redirect to the
 | 
				
			||||||
        // specified location then show a notification message.
 | 
					        // specified location then show a notification message.
 | 
				
			||||||
        if ($this->isExceptionType($e, NotifyException::class)) {
 | 
					        if ($this->isExceptionType($e, NotifyException::class)) {
 | 
				
			||||||
            session()->flash('error', $this->getOriginalMessage($e));
 | 
					            $message = $this->getOriginalMessage($e);
 | 
				
			||||||
 | 
					            if (!empty($message)) {
 | 
				
			||||||
 | 
					                session()->flash('error', $message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            return redirect($e->redirectLocation);
 | 
					            return redirect($e->redirectLocation);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers\Auth;
 | 
				
			||||||
use BookStack\Auth\Access\SocialAuthService;
 | 
					use BookStack\Auth\Access\SocialAuthService;
 | 
				
			||||||
use BookStack\Exceptions\LoginAttemptEmailNeededException;
 | 
					use BookStack\Exceptions\LoginAttemptEmailNeededException;
 | 
				
			||||||
use BookStack\Exceptions\LoginAttemptException;
 | 
					use BookStack\Exceptions\LoginAttemptException;
 | 
				
			||||||
 | 
					use BookStack\Exceptions\UserRegistrationException;
 | 
				
			||||||
use BookStack\Http\Controllers\Controller;
 | 
					use BookStack\Http\Controllers\Controller;
 | 
				
			||||||
use Illuminate\Foundation\Auth\AuthenticatesUsers;
 | 
					use Illuminate\Foundation\Auth\AuthenticatesUsers;
 | 
				
			||||||
use Illuminate\Http\Request;
 | 
					use Illuminate\Http\Request;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,7 +74,7 @@ class RegisterController extends Controller
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function getRegister()
 | 
					    public function getRegister()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->registrationService->checkRegistrationAllowed();
 | 
					        $this->registrationService->ensureRegistrationAllowed();
 | 
				
			||||||
        $socialDrivers = $this->socialAuthService->getActiveDrivers();
 | 
					        $socialDrivers = $this->socialAuthService->getActiveDrivers();
 | 
				
			||||||
        return view('auth.register', [
 | 
					        return view('auth.register', [
 | 
				
			||||||
            'socialDrivers' => $socialDrivers,
 | 
					            'socialDrivers' => $socialDrivers,
 | 
				
			||||||
| 
						 | 
					@ -87,12 +87,13 @@ class RegisterController extends Controller
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function postRegister(Request $request)
 | 
					    public function postRegister(Request $request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->registrationService->checkRegistrationAllowed();
 | 
					        $this->registrationService->ensureRegistrationAllowed();
 | 
				
			||||||
        $this->validator($request->all())->validate();
 | 
					        $this->validator($request->all())->validate();
 | 
				
			||||||
        $userData = $request->all();
 | 
					        $userData = $request->all();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            $this->registrationService->registerUser($userData);
 | 
					            $user = $this->registrationService->registerUser($userData);
 | 
				
			||||||
 | 
					            auth()->login($user);
 | 
				
			||||||
        } catch (UserRegistrationException $exception) {
 | 
					        } catch (UserRegistrationException $exception) {
 | 
				
			||||||
            if ($exception->getMessage()) {
 | 
					            if ($exception->getMessage()) {
 | 
				
			||||||
                $this->showErrorNotification($exception->getMessage());
 | 
					                $this->showErrorNotification($exception->getMessage());
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,7 +81,6 @@ class Saml2Controller extends Controller
 | 
				
			||||||
            return redirect('/login');
 | 
					            return redirect('/login');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        session()->put('last_login_type', 'saml2');
 | 
					 | 
				
			||||||
        return redirect()->intended();
 | 
					        return redirect()->intended();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,7 @@ class SocialController extends Controller
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function socialRegister(string $socialDriver)
 | 
					    public function socialRegister(string $socialDriver)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->registrationService->checkRegistrationAllowed();
 | 
					        $this->registrationService->ensureRegistrationAllowed();
 | 
				
			||||||
        session()->put('social-callback', 'register');
 | 
					        session()->put('social-callback', 'register');
 | 
				
			||||||
        return $this->socialAuthService->startRegister($socialDriver);
 | 
					        return $this->socialAuthService->startRegister($socialDriver);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -78,7 +78,7 @@ class SocialController extends Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Attempt login or fall-back to register if allowed.
 | 
					        // Attempt login or fall-back to register if allowed.
 | 
				
			||||||
        $socialUser = $this->socialAuthService->getSocialUser($socialDriver);
 | 
					        $socialUser = $this->socialAuthService->getSocialUser($socialDriver);
 | 
				
			||||||
        if ($action == 'login') {
 | 
					        if ($action === 'login') {
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                return $this->socialAuthService->handleLoginCallback($socialDriver, $socialUser);
 | 
					                return $this->socialAuthService->handleLoginCallback($socialDriver, $socialUser);
 | 
				
			||||||
            } catch (SocialSignInAccountNotUsed $exception) {
 | 
					            } catch (SocialSignInAccountNotUsed $exception) {
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ class SocialController extends Controller
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if ($action == 'register') {
 | 
					        if ($action === 'register') {
 | 
				
			||||||
            return $this->socialRegisterCallback($socialDriver, $socialUser);
 | 
					            return $this->socialRegisterCallback($socialDriver, $socialUser);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -108,7 +108,6 @@ class SocialController extends Controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Register a new user after a registration callback.
 | 
					     * Register a new user after a registration callback.
 | 
				
			||||||
     * @return RedirectResponse|Redirector
 | 
					 | 
				
			||||||
     * @throws UserRegistrationException
 | 
					     * @throws UserRegistrationException
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    protected function socialRegisterCallback(string $socialDriver, SocialUser $socialUser)
 | 
					    protected function socialRegisterCallback(string $socialDriver, SocialUser $socialUser)
 | 
				
			||||||
| 
						 | 
					@ -121,17 +120,11 @@ class SocialController extends Controller
 | 
				
			||||||
        $userData = [
 | 
					        $userData = [
 | 
				
			||||||
            'name' => $socialUser->getName(),
 | 
					            'name' => $socialUser->getName(),
 | 
				
			||||||
            'email' => $socialUser->getEmail(),
 | 
					            'email' => $socialUser->getEmail(),
 | 
				
			||||||
            'password' => Str::random(30)
 | 
					            'password' => Str::random(32)
 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        $user = $this->registrationService->registerUser($userData, $socialAccount, $emailVerified);
 | 
				
			||||||
            $this->registrationService->registerUser($userData, $socialAccount, $emailVerified);
 | 
					        auth()->login($user);
 | 
				
			||||||
        } catch (UserRegistrationException $exception) {
 | 
					 | 
				
			||||||
            if ($exception->getMessage()) {
 | 
					 | 
				
			||||||
                $this->showErrorNotification($exception->getMessage());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return redirect($exception->redirectLocation);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->showSuccessNotification(trans('auth.register_success'));
 | 
					        $this->showSuccessNotification(trans('auth.register_success'));
 | 
				
			||||||
        return redirect('/');
 | 
					        return redirect('/');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,6 +8,7 @@ use BookStack\Auth\Access\ExternalBaseUserProvider;
 | 
				
			||||||
use BookStack\Auth\Access\Guards\LdapSessionGuard;
 | 
					use BookStack\Auth\Access\Guards\LdapSessionGuard;
 | 
				
			||||||
use BookStack\Auth\Access\Guards\Saml2SessionGuard;
 | 
					use BookStack\Auth\Access\Guards\Saml2SessionGuard;
 | 
				
			||||||
use BookStack\Auth\Access\LdapService;
 | 
					use BookStack\Auth\Access\LdapService;
 | 
				
			||||||
 | 
					use BookStack\Auth\Access\RegistrationService;
 | 
				
			||||||
use BookStack\Auth\UserRepo;
 | 
					use BookStack\Auth\UserRepo;
 | 
				
			||||||
use Illuminate\Support\ServiceProvider;
 | 
					use Illuminate\Support\ServiceProvider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +32,7 @@ class AuthServiceProvider extends ServiceProvider
 | 
				
			||||||
                $provider,
 | 
					                $provider,
 | 
				
			||||||
                $this->app['session.store'],
 | 
					                $this->app['session.store'],
 | 
				
			||||||
                $app[LdapService::class],
 | 
					                $app[LdapService::class],
 | 
				
			||||||
                $app[UserRepo::class]
 | 
					                $app[RegistrationService::class]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,7 +42,7 @@ class AuthServiceProvider extends ServiceProvider
 | 
				
			||||||
                $name,
 | 
					                $name,
 | 
				
			||||||
                $provider,
 | 
					                $provider,
 | 
				
			||||||
                $this->app['session.store'],
 | 
					                $this->app['session.store'],
 | 
				
			||||||
                $app[UserRepo::class]
 | 
					                $app[RegistrationService::class]
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,6 @@ return [
 | 
				
			||||||
    'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
 | 
					    'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system',
 | 
				
			||||||
    '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_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',
 | 
					    'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization',
 | 
				
			||||||
    'saml_email_exists' => 'Registration unsuccessful since a user already exists with email address ":email"',
 | 
					 | 
				
			||||||
    'social_no_action_defined' => 'No action defined',
 | 
					    'social_no_action_defined' => 'No action defined',
 | 
				
			||||||
    'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
 | 
					    'social_login_bad_response' => "Error received during :socialAccount login: \n:error",
 | 
				
			||||||
    'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.',
 | 
					    'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ return [
 | 
				
			||||||
    'reg_enable_toggle' => 'Enable registration',
 | 
					    'reg_enable_toggle' => 'Enable registration',
 | 
				
			||||||
    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
 | 
					    'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.',
 | 
				
			||||||
    'reg_default_role' => 'Default user role after registration',
 | 
					    'reg_default_role' => 'Default user role after registration',
 | 
				
			||||||
    'reg_enable_ldap_warning' => 'The option above is not used while LDAP authentication is active. User accounts for non-existing members will be auto-created if authentication, against the LDAP system in use, is successful.',
 | 
					    'reg_enable_external_warning' => 'The option above is ignore while external LDAP or SAML authentication is active. User accounts for non-existing members will be auto-created if authentication, against the external system in use, is successful.',
 | 
				
			||||||
    'reg_email_confirmation' => 'Email Confirmation',
 | 
					    'reg_email_confirmation' => 'Email Confirmation',
 | 
				
			||||||
    'reg_email_confirmation_toggle' => 'Require email confirmation',
 | 
					    'reg_email_confirmation_toggle' => 'Require email confirmation',
 | 
				
			||||||
    'reg_confirm_email_desc' => 'If domain restriction is used then email confirmation will be required and this option will be ignored.',
 | 
					    'reg_confirm_email_desc' => 'If domain restriction is used then email confirmation will be required and this option will be ignored.',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -219,8 +219,8 @@
 | 
				
			||||||
                                'label' => trans('settings.reg_enable_toggle')
 | 
					                                'label' => trans('settings.reg_enable_toggle')
 | 
				
			||||||
                            ])
 | 
					                            ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            @if(config('auth.method') === 'ldap')
 | 
					                            @if(in_array(config('auth.method'), ['ldap', 'saml2']))
 | 
				
			||||||
                                <div class="text-warn text-small mb-l">{{ trans('settings.reg_enable_ldap_warning') }}</div>
 | 
					                                <div class="text-warn text-small mb-l">{{ trans('settings.reg_enable_external_warning') }}</div>
 | 
				
			||||||
                            @endif
 | 
					                            @endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            <label for="setting-registration-role">{{ trans('settings.reg_default_role') }}</label>
 | 
					                            <label for="setting-registration-role">{{ trans('settings.reg_default_role') }}</label>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -86,6 +86,38 @@ class LdapTest extends BrowserKitTest
 | 
				
			||||||
            ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name]);
 | 
					            ->seeInDatabase('users', ['email' => $this->mockUser->email, 'email_confirmed' => false, 'external_auth_id' => $this->mockUser->name]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function test_email_domain_restriction_active_on_new_ldap_login()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->setSettings([
 | 
				
			||||||
 | 
					            'registration-restrict' => 'testing.com'
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
 | 
				
			||||||
 | 
					        $this->mockLdap->shouldReceive('setVersion')->once();
 | 
				
			||||||
 | 
					        $this->mockLdap->shouldReceive('setOption')->times(2);
 | 
				
			||||||
 | 
					        $this->mockLdap->shouldReceive('searchAndGetEntries')->times(2)
 | 
				
			||||||
 | 
					            ->with($this->resourceId, config('services.ldap.base_dn'), \Mockery::type('string'), \Mockery::type('array'))
 | 
				
			||||||
 | 
					            ->andReturn(['count' => 1, 0 => [
 | 
				
			||||||
 | 
					                'uid' => [$this->mockUser->name],
 | 
				
			||||||
 | 
					                'cn' => [$this->mockUser->name],
 | 
				
			||||||
 | 
					                'dn' => ['dc=test' . config('services.ldap.base_dn')]
 | 
				
			||||||
 | 
					            ]]);
 | 
				
			||||||
 | 
					        $this->mockLdap->shouldReceive('bind')->times(4)->andReturn(true);
 | 
				
			||||||
 | 
					        $this->mockEscapes(2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->mockUserLogin()
 | 
				
			||||||
 | 
					            ->seePageIs('/login')
 | 
				
			||||||
 | 
					            ->see('Please enter an email to use for this account.');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $email = 'tester@invaliddomain.com';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->type($email, '#email')
 | 
				
			||||||
 | 
					            ->press('Log In')
 | 
				
			||||||
 | 
					            ->seePageIs('/login')
 | 
				
			||||||
 | 
					            ->see('That email domain does not have access to this application')
 | 
				
			||||||
 | 
					            ->dontSeeInDatabase('users', ['email' => $email]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public function test_login_works_when_no_uid_provided_by_ldap_server()
 | 
					    public function test_login_works_when_no_uid_provided_by_ldap_server()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
 | 
					        $this->mockLdap->shouldReceive('connect')->once()->andReturn($this->resourceId);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,7 @@ class Saml2Test extends TestCase
 | 
				
			||||||
            $this->assertDatabaseHas('users', [
 | 
					            $this->assertDatabaseHas('users', [
 | 
				
			||||||
                'email' => 'user@example.com',
 | 
					                'email' => 'user@example.com',
 | 
				
			||||||
                'external_auth_id' => 'user',
 | 
					                'external_auth_id' => 'user',
 | 
				
			||||||
                'email_confirmed' => true,
 | 
					                'email_confirmed' => false,
 | 
				
			||||||
                'name' => 'Barry Scott'
 | 
					                'name' => 'Barry Scott'
 | 
				
			||||||
            ]);
 | 
					            ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -209,7 +209,7 @@ class Saml2Test extends TestCase
 | 
				
			||||||
            $acsPost = $this->post('/saml2/acs');
 | 
					            $acsPost = $this->post('/saml2/acs');
 | 
				
			||||||
            $acsPost->assertRedirect('/');
 | 
					            $acsPost->assertRedirect('/');
 | 
				
			||||||
            $errorMessage = session()->get('error');
 | 
					            $errorMessage = session()->get('error');
 | 
				
			||||||
            $this->assertEquals('Registration unsuccessful since a user already exists with email address "user@example.com"', $errorMessage);
 | 
					            $this->assertEquals('A user with the email user@example.com already exists but with different credentials.', $errorMessage);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -271,6 +271,24 @@ class Saml2Test extends TestCase
 | 
				
			||||||
        $this->assertPermissionError($resp);
 | 
					        $this->assertPermissionError($resp);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public function test_email_domain_restriction_active_on_new_saml_login()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        $this->setSettings([
 | 
				
			||||||
 | 
					            'registration-restrict' => 'testing.com'
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        config()->set([
 | 
				
			||||||
 | 
					            'saml2.onelogin.strict' => false,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $this->withPost(['SAMLResponse' => $this->acsPostData], function () {
 | 
				
			||||||
 | 
					            $acsPost = $this->post('/saml2/acs');
 | 
				
			||||||
 | 
					            $acsPost->assertRedirect('/login');
 | 
				
			||||||
 | 
					            $errorMessage = session()->get('error');
 | 
				
			||||||
 | 
					            $this->assertStringContainsString('That email domain does not have access to this application', $errorMessage);
 | 
				
			||||||
 | 
					            $this->assertDatabaseMissing('users', ['email' => 'user@example.com']);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected function withGet(array $options, callable $callback)
 | 
					    protected function withGet(array $options, callable $callback)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        return $this->withGlobal($_GET, $options, $callback);
 | 
					        return $this->withGlobal($_GET, $options, $callback);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue