+ $resp = $this->get('/register/invite/abc123');
+ $this->assertPermissionError($resp);
+
+ $resp = $this->post('/register/invite/abc123');
+ $this->assertPermissionError($resp);
+ }
+
+ public function test_user_register_routes_inaccessible()
+ {
+ $resp = $this->get('/register');
+ $this->assertPermissionError($resp);
+
+ $resp = $this->post('/register');
+ $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,
+ ]);
+
+ $acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
+ $acsPost->assertSeeText('That email domain does not have access to this application');
+ $this->assertFalse(auth()->check());
+ }
+
+ public function test_group_sync_functions_when_email_confirmation_required()
+ {
+ setting()->put('registration-confirmation', 'true');
+ config()->set([
+ 'saml2.onelogin.strict' => false,
+ 'saml2.user_to_groups' => true,
+ 'saml2.remove_from_groups' => false,
+ ]);
+
+ $memberRole = Role::factory()->create(['external_auth_id' => 'member']);
+ $adminRole = Role::getSystemRole('admin');
+
+ $acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
+
+ $this->assertEquals('http://localhost/register/confirm', url()->current());
+ $acsPost->assertSee('Please check your email and click the confirmation button to access BookStack.');
+ /** @var User $user */
+ $user = User::query()->where('external_auth_id', '=', 'user')->first();
+
+ $userRoleIds = $user->roles()->pluck('id');
+ $this->assertContains($memberRole->id, $userRoleIds, 'User was assigned to member role');
+ $this->assertContains($adminRole->id, $userRoleIds, 'User was assigned to admin role');
+ $this->assertFalse(boolval($user->email_confirmed), 'User email remains unconfirmed');
+
+ $this->assertNull(auth()->user());
+ $homeGet = $this->get('/');
+ $homeGet->assertRedirect('/login');
+ }
+
+ public function test_login_where_existing_non_saml_user_shows_warning()
+ {
+ $this->post('/saml2/login');
+ config()->set(['saml2.onelogin.strict' => false]);
+
+ // Make the user pre-existing in DB with different auth_id
+ User::query()->forceCreate([
+ 'external_auth_id' => 'old_system_user_id',
+ 'email_confirmed' => false,
+ 'name' => 'Barry Scott',
+ ]);
+
+ $acsPost = $this->followingRedirects()->post('/saml2/acs', ['SAMLResponse' => $this->acsPostData]);
+ $this->assertFalse($this->isAuthenticated());
+ $this->assertDatabaseHas('users', [
+ 'external_auth_id' => 'old_system_user_id',
+ ]);
+
+ $acsPost->assertSee('A user with the email
[email protected] already exists but with different credentials');
+ }
+
+ public function test_login_request_contains_expected_default_authncontext()
+ {
+ $authReq = $this->getAuthnRequest();
+ $this->assertStringContainsString('samlp:RequestedAuthnContext Comparison="exact"', $authReq);
+ $this->assertStringContainsString('<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>', $authReq);
+ }
+
+ public function test_false_idp_authncontext_option_does_not_pass_authncontext_in_saml_request()
+ {
+ config()->set(['saml2.onelogin.security.requestedAuthnContext' => false]);
+ $authReq = $this->getAuthnRequest();
+ $this->assertStringNotContainsString('samlp:RequestedAuthnContext', $authReq);
+ $this->assertStringNotContainsString('<saml:AuthnContextClassRef>', $authReq);
+ }
+
+ public function test_array_idp_authncontext_option_passes_value_as_authncontextclassref_in_request()
+ {
+ config()->set(['saml2.onelogin.security.requestedAuthnContext' => ['urn:federation:authentication:windows', 'urn:federation:authentication:linux']]);
+ $authReq = $this->getAuthnRequest();
+ $this->assertStringContainsString('samlp:RequestedAuthnContext', $authReq);
+ $this->assertStringContainsString('<saml:AuthnContextClassRef>urn:federation:authentication:windows</saml:AuthnContextClassRef>', $authReq);
+ $this->assertStringContainsString('<saml:AuthnContextClassRef>urn:federation:authentication:linux</saml:AuthnContextClassRef>', $authReq);
+ }
+
+ protected function getAuthnRequest(): string
+ {
+ $req = $this->post('/saml2/login');
+ $location = $req->headers->get('Location');
+ $query = explode('?', $location)[1];
+ $params = [];
+ parse_str($query, $params);
+
+ return gzinflate(base64_decode($params['SAMLRequest']));
+ }
+
+ protected function withGet(array $options, callable $callback)
+ {
+ return $this->withGlobal($_GET, $options, $callback);