diff --git a/.env.example.complete b/.env.example.complete
index a3b0702d5..71fe66bca 100644
--- a/.env.example.complete
+++ b/.env.example.complete
@@ -222,12 +222,7 @@ SAML2_IDP_x509=null
SAML2_ONELOGIN_OVERRIDES=null
SAML2_DUMP_USER_DETAILS=false
SAML2_AUTOLOAD_METADATA=false
-
-# SAML Authentication context.
-# Set to false and no AuthContext will be sent in the AuthNRequest,
-# Set true and you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'
-# Set an array with the possible auth context values: array ('urn:oasis:names:tc:SAML:2.0:ac:classes:Password', 'urn:oasis:names:tc:SAML:2.0:ac:classes:X509'),
-SAML2_IDP_AUTHNCONTEXT=false
+SAML2_IDP_AUTHNCONTEXT=true
# SAML group sync configuration
# Refer to https://www.bookstackapp.com/docs/admin/saml2-auth/
diff --git a/app/Config/saml2.php b/app/Config/saml2.php
index 0e186c269..8ba969549 100644
--- a/app/Config/saml2.php
+++ b/app/Config/saml2.php
@@ -1,5 +1,7 @@
[
- // Specifies Authentication context
- // false means that IDP choose authentication method
- // null force Form based authentication or is possible set via array supported methods. See to onelogin/php-sampl/advance_settings
- 'requestedAuthnContext' => env('SAML2_IDP_AUTHNCONTEXT',false),
+ // SAML2 Authn context
+ // When set to false no AuthContext will be sent in the AuthNRequest,
+ // When set to true (Default) you will get an AuthContext 'exact' 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport'.
+ // Multiple forced values can be passed via a space separated array, For example:
+ // SAML2_IDP_AUTHNCONTEXT="urn:federation:authentication:windows urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
+ 'requestedAuthnContext' => is_string($SAML2_IDP_AUTHNCONTEXT) ? explode(' ', $SAML2_IDP_AUTHNCONTEXT) : $SAML2_IDP_AUTHNCONTEXT,
],
],
diff --git a/tests/Auth/Saml2Test.php b/tests/Auth/Saml2Test.php
index 58c02b471..b6b02e2f7 100644
--- a/tests/Auth/Saml2Test.php
+++ b/tests/Auth/Saml2Test.php
@@ -28,6 +28,7 @@ class Saml2Test extends TestCase
'saml2.autoload_from_metadata' => false,
'saml2.onelogin.idp.x509cert' => $this->testCert,
'saml2.onelogin.debug' => false,
+ 'saml2.onelogin.security.requestedAuthnContext' => true,
]);
}
@@ -328,6 +329,40 @@ class Saml2Test extends TestCase
});
}
+ public function test_login_request_contains_expected_default_authncontext()
+ {
+ $authReq = $this->getAuthnRequest();
+ $this->assertStringContainsString('samlp:RequestedAuthnContext Comparison="exact"', $authReq);
+ $this->assertStringContainsString('urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport', $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('', $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('urn:federation:authentication:windows', $authReq);
+ $this->assertStringContainsString('urn:federation:authentication:linux', $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);
diff --git a/tests/Unit/ConfigTest.php b/tests/Unit/ConfigTest.php
index 1d4decc2b..bb475c354 100644
--- a/tests/Unit/ConfigTest.php
+++ b/tests/Unit/ConfigTest.php
@@ -67,12 +67,22 @@ class ConfigTest extends TestCase
$this->checkEnvConfigResult('APP_URL', '', 'session.path', '/');
}
+ public function test_saml2_idp_authn_context_string_parsed_as_space_separated_array()
+ {
+ $this->checkEnvConfigResult(
+ 'SAML2_IDP_AUTHNCONTEXT',
+ 'urn:federation:authentication:windows urn:federation:authentication:linux',
+ 'saml2.onelogin.security.requestedAuthnContext',
+ ['urn:federation:authentication:windows', 'urn:federation:authentication:linux']
+ );
+ }
+
/**
* Set an environment variable of the given name and value
* then check the given config key to see if it matches the given result.
* Providing a null $envVal clears the variable.
*/
- protected function checkEnvConfigResult(string $envName, ?string $envVal, string $configKey, string $expectedResult)
+ protected function checkEnvConfigResult(string $envName, ?string $envVal, string $configKey, mixed $expectedResult)
{
$this->runWithEnv($envName, $envVal, function() use ($configKey, $expectedResult) {
$this->assertEquals($expectedResult, config($configKey));