diff --git a/config-templates/config.php b/config-templates/config.php index f76724ef981d8cfa945a599417a2aeaf318b74c3..3d246d1c4354f60f0a65f2858ac1deee951f1b69 100644 --- a/config-templates/config.php +++ b/config-templates/config.php @@ -143,10 +143,8 @@ $config = [ 'auth.adminpassword' => '123', /* - * Set this options to true if you want to require administrator password to access the web interface - * or the metadata pages, respectively. + * Set this option to true if you want to require administrator password to access the metadata. */ - 'admin.protectindexpage' => false, 'admin.protectmetadata' => false, /* @@ -860,6 +858,12 @@ $config = [ ], ], + /** + * Set to a full URL if you want to rediret users that land on SimpleSAMLphp's + * front page to somewhere more useful. If left unset, a basic welcome message + * is shown. + */ + //'frontpage.redirect' => 'https://example.com/', /********************* | DISCOVERY SERVICE | diff --git a/docs/simplesamlphp-upgrade-notes-2.0.md b/docs/simplesamlphp-upgrade-notes-2.0.md index 45a8e8d7ebe0d3de12337d9bd3c0c6b9bad975e9..d7af8dfafe5e3717659c334865d483a91bf7491f 100644 --- a/docs/simplesamlphp-upgrade-notes-2.0.md +++ b/docs/simplesamlphp-upgrade-notes-2.0.md @@ -39,6 +39,7 @@ config file= manualy. Configuration options that have been removed: - languages[priorities] - attributes.extradictionaries. Add an attributes.po to your configured theme instead. + - admin.protectindexpage. Replaced by the admin module which always requires login. Changes relevant for (module) developers ---------------------------------------- diff --git a/lib/SimpleSAML/Utils/Auth.php b/lib/SimpleSAML/Utils/Auth.php index 827d01ac72c1fa7861695452cf761abcd02704d9..7df2b9dfba9c8f90491783c5e5e3cccd11ec0c44 100644 --- a/lib/SimpleSAML/Utils/Auth.php +++ b/lib/SimpleSAML/Utils/Auth.php @@ -16,24 +16,6 @@ use SimpleSAML\Session; */ class Auth { - /** - * Retrieve an admin login URL. - * - * @param string|NULL $returnTo The URL the user should arrive on after admin authentication. Defaults to null. - * - * @return string A URL which can be used for admin authentication. - * @throws \InvalidArgumentException If $returnTo is neither a string nor null. - */ - public function getAdminLoginURL(?string $returnTo = null): string - { - $httpUtils = new HTTP(); - if ($returnTo === null) { - $returnTo = $httpUtils->getSelfURL(); - } - - return Module::getModuleURL('core/login-admin.php', ['ReturnTo' => $returnTo]); - } - /** * Retrieve an admin logout URL. diff --git a/modules/core/lib/Controller/Login.php b/modules/core/lib/Controller/Login.php index 84adca0d2a0f53c1cf9156e1710883a13d1adb9d..0cda6e748c1c1d0a4752918b2e025f9736873481 100644 --- a/modules/core/lib/Controller/Login.php +++ b/modules/core/lib/Controller/Login.php @@ -6,12 +6,10 @@ namespace SimpleSAML\Module\core\Controller; use SimpleSAML\Assert\Assert; use SimpleSAML\Auth; -use SimpleSAML\Auth\AuthenticationFactory; use SimpleSAML\Configuration; use SimpleSAML\Error; use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Module; -use SimpleSAML\Session; use SimpleSAML\Utils; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -30,170 +28,26 @@ class Login /** @var \SimpleSAML\Configuration */ protected Configuration $config; - /** @var \SimpleSAML\Auth\AuthenticationFactory */ - protected AuthenticationFactory $factory; - - /** @var \SimpleSAML\Session */ - protected Session $session; - - /** @var array */ - protected array $sources; - - /** * Controller constructor. * - * It initializes the global configuration and auth source configuration for the controllers implemented here. + * It initializes the global configuration for the controllers implemented here. * * @param \SimpleSAML\Configuration $config The configuration to use by the controllers. - * @param \SimpleSAML\Session $session The session to use by the controllers. - * @param \SimpleSAML\Auth\AuthenticationFactory $factory A factory to instantiate \SimpleSAML\Auth\Simple objects. * * @throws \Exception */ public function __construct( - Configuration $config, - Session $session, - AuthenticationFactory $factory + Configuration $config ) { $this->config = $config; - $this->factory = $factory; - $this->sources = $config::getOptionalConfig('authsources.php')->toArray(); - $this->session = $session; } - - /** - * Show account information for a given authentication source. - * - * @param string $as The identifier of the authentication source. - * - * @return \SimpleSAML\XHTML\Template|\Symfony\Component\HttpFoundation\RedirectResponse - * An HTML template or a redirection if we are not authenticated. - * - * @throws \SimpleSAML\Error\Exception An exception in case the auth source specified is invalid. - */ - public function account(string $as): Response - { - if (!array_key_exists($as, $this->sources)) { - throw new Error\Exception('Invalid authentication source'); - } - - $auth = $this->factory->create($as); - if (!$auth->isAuthenticated()) { - // not authenticated, start auth with specified source - return new RedirectResponse(Module::getModuleURL('core/login/' . urlencode($as))); - } - - $attributes = $auth->getAttributes(); - - $session = Session::getSessionFromRequest(); - - $t = new Template($this->config, 'auth_status.twig'); - $l = $t->getLocalization(); - $l->addAttributeDomains(); - $t->data['header'] = '{status:header_saml20_sp}'; - $t->data['attributes'] = $attributes; - $t->data['nameid'] = !is_null($auth->getAuthData('saml:sp:NameID')) - ? $auth->getAuthData('saml:sp:NameID') - : false; - $t->data['authData'] = $auth->getAuthDataArray(); - $t->data['trackid'] = $session->getTrackID(); - $t->data['logouturl'] = Module::getModuleURL('core/logout/' . urlencode($as)); - $t->data['remaining'] = $this->session->getAuthData($as, 'Expire') - time(); - $t->setStatusCode(200); - - return $t; - } - - - /** - * Perform a login operation. - * - * This controller will either start a login operation (if that was requested, or if only one authentication - * source is available), or show a template allowing the user to choose which auth source to use. - * - * @param Request $request The request that lead to this login operation. - * @param string|null $as The name of the authentication source to use, if any. Optional. - * - * @return \Symfony\Component\HttpFoundation\Response - * An HTML template, a redirect or a "runnable" response. - * - * @throws \SimpleSAML\Error\Exception - */ - public function login(Request $request, string $as = null): Response + public function welcome(): Response { - // delete admin - if (isset($this->sources['admin'])) { - unset($this->sources['admin']); - } - - if (count($this->sources) === 1 && $as === null) { // we only have one source available - $as = key($this->sources); - } - - $default = false; - if (array_key_exists('default', $this->sources) && is_array($this->sources['default'])) { - $default = $this->sources['default']; - } - - if ($as === null) { // no authentication source specified - if (!$default) { - $authUtils = new Utils\Auth(); - $t = new Template($this->config, 'core:login.twig'); - $t->data['loginurl'] = $authUtils->getAdminLoginURL(); - $t->data['sources'] = $this->sources; - return $t; - } - - // we have a default, use that one - $as = 'default'; - foreach ($this->sources as $id => $source) { - if ($id === 'default') { - continue; - } - if ($source === $this->sources['default']) { - $as = $id; - break; - } - } - } - - // auth source defined, check if valid - if (!array_key_exists($as, $this->sources)) { - throw new Error\Exception('Invalid authentication source'); - } - - // at this point, we have a valid auth source selected, start auth - $auth = $this->factory->create($as); - $as = urlencode($as); - - if ($request->request->get(Auth\State::EXCEPTION_PARAM, false) !== false) { - // This is just a simple example of an error - - /** @var array $state */ - $state = Auth\State::loadExceptionState(); - - Assert::keyExists($state, Auth\State::EXCEPTION_DATA); - $e = $state[Auth\State::EXCEPTION_DATA]; - - throw $e; - } - - if ($auth->isAuthenticated()) { - return new RedirectResponse(Module::getModuleURL('core/account/' . $as)); - } - - // we're not logged in, start auth - $url = Module::getModuleURL('core/login/' . $as); - $params = [ - 'ErrorURL' => $url, - 'ReturnTo' => $url, - ]; - return new RunnableResponse([$auth, 'login'], [$params]); + return new Template($this->config, 'core:welcome.twig'); } - /** * Log the user out of a given authentication source. * diff --git a/modules/core/routing/routes/routes.yml b/modules/core/routing/routes/routes.yml index 83e5e6ff300047432b9979050d13cb05d20f6afa..898fcae9a8869420b50314093d808c78055edfb3 100644 --- a/modules/core/routing/routes/routes.yml +++ b/modules/core/routing/routes/routes.yml @@ -1,12 +1,9 @@ -core-account: - path: /account/{as} - defaults: { _controller: 'SimpleSAML\Module\core\Controller\Login:account' } +core-welcome: + path: /welcome + defaults: { _controller: 'SimpleSAML\Module\core\Controller\Login:welcome' } core-account-disco-clearchoices: path: /account/disco/clearchoices defaults: { _controller: 'SimpleSAML\Module\core\Controller\Login:cleardiscochoices' } -core-login: - path: /login/{as} - defaults: { _controller: 'SimpleSAML\Module\core\Controller\Login:login', as: null } core-logout: path: /logout/{as} defaults: { _controller: 'SimpleSAML\Module\core\Controller\Login:logout' } diff --git a/modules/core/templates/login.twig b/modules/core/templates/login.twig deleted file mode 100644 index 70b2ad5623620060b03ad8ad9ee2a8594ca6c499..0000000000000000000000000000000000000000 --- a/modules/core/templates/login.twig +++ /dev/null @@ -1,28 +0,0 @@ -{% set pagetitle = 'Authenticate'|trans %} -{% extends "@core/base.twig" %} - -{% block content %} - <h1>{{ pagetitle|trans }}</h1> - <p>Please choose one of the following authentication methods: </p> - -{% if sources is empty -%} - <p>Please check your SimpleSAML configuration.<br> - Follow the link and log in with administrator credentials. <a href="{{ loginurl }}">Admin login.</a></p> -{% else %} - <div class="pure-menu custom-restricted-width"> - <ul class="pure-menu-list auth_methods"> - {% for id, config in sources -%} - <li class="pure-menu-item"> - <a href="{{ moduleURL('core/login/' ~ id|url_encode) }}" class="pure-menu-link"> - {% if config.name is defined %} - {{ config.name|translateFromArray|default(id) }} - {% else %} - {{ id }} - {% endif %} - </a> - </li> - {% endfor -%} - </ul> - </div> -{% endif %} -{% endblock %} diff --git a/modules/core/templates/welcome.twig b/modules/core/templates/welcome.twig new file mode 100644 index 0000000000000000000000000000000000000000..562a11646ab9be19660d7efd91b0f6ab2cfd4e84 --- /dev/null +++ b/modules/core/templates/welcome.twig @@ -0,0 +1,12 @@ +{% set pagetitle = 'Welcome'|trans %} + +{% extends "@core/base.twig" %} + +{% block content %} + <h1>{{ 'Welcome'|trans }}</h1> + + <p>{{ 'This is the front page of the SimpleSAMLphp authentication software. There\'s not much to see here.' }}</p> + + <p>{{ 'If you are the administrator of this installation, please refer to the <a href="https://simplesamlphp.org/docs/">SimpleSAMLphp documentation</a> for how to configure and interact with the software.' }}</p> + +{% endblock %} diff --git a/modules/core/www/login-admin.php b/modules/core/www/login-admin.php deleted file mode 100644 index a430c9c6a22002fbc9e88177fc014c5d9f19b13a..0000000000000000000000000000000000000000 --- a/modules/core/www/login-admin.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -/* - * Helper page for starting an admin login. Can be used as a target for links. - */ - -if (!array_key_exists('ReturnTo', $_REQUEST)) { - throw new \SimpleSAML\Error\BadRequest('Missing ReturnTo parameter.'); -} - -$authUtils = new \SimpleSAML\Utils\Auth(); -$authUtils->requireAdmin(); - -$httpUtils = new \SimpleSAML\Utils\HTTP(); -$httpUtils->redirectUntrustedURL($_REQUEST['ReturnTo']); diff --git a/tests/modules/core/lib/Controller/LoginTest.php b/tests/modules/core/lib/Controller/LoginTest.php index f204086633fd3c5ec69847fd519a6d246c741431..b84226d6188aef191eec0ff2f8b2186fad18d6b8 100644 --- a/tests/modules/core/lib/Controller/LoginTest.php +++ b/tests/modules/core/lib/Controller/LoginTest.php @@ -5,14 +5,11 @@ declare(strict_types=1); namespace SimpleSAML\Test\Module\core\Controller; use ReflectionClass; -use SimpleSAML\Auth\Simple; -use SimpleSAML\Auth\AuthenticationFactory; use SimpleSAML\Configuration; use SimpleSAML\Error\Exception; use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Locale\Localization; use SimpleSAML\Module\core\Controller; -use SimpleSAML\Session; use SimpleSAML\TestUtils\ClearStateTestCase; use SimpleSAML\XHTML\Template; use Symfony\Component\HttpFoundation\RedirectResponse; @@ -29,9 +26,6 @@ use Symfony\Component\HttpFoundation\Request; */ class LoginTest extends ClearStateTestCase { - /** @var array */ - protected array $authSources; - /** @var \SimpleSAML\Configuration */ protected Configuration $config; @@ -44,17 +38,6 @@ class LoginTest extends ClearStateTestCase protected function setUp(): void { parent::setUp(); - $this->authSources = [ - 'admin' => [ - 'core:adminPassword' - ], - 'example-userpass' => [ - 'exampleauth:UserPass', - 'username:password' => [ - 'uid' => ['test'] - ] - ] - ]; $this->config = Configuration::loadFromArray( [ 'baseurlpath' => 'https://example.org/simplesaml', @@ -66,189 +49,15 @@ class LoginTest extends ClearStateTestCase Configuration::setPreLoadedConfig($this->config, 'config.php'); } - - /** - * Test that authentication is started immediately if we hit the login endpoint and there's only one non-admin - * source configured. - */ - public function testAutomaticLoginWhenOnlyOneSource(): void - { - $asConfig = Configuration::loadFromArray($this->authSources); - Configuration::setPreLoadedConfig($asConfig, 'authsources.php'); - - $request = new Request(); - $session = Session::getSessionFromRequest(); - $factory = new AuthenticationFactory($this->config, $session); - - $c = new Controller\Login($this->config, $session, $factory); - /** @var \SimpleSAML\HTTP\RunnableResponse $response */ - $response = $c->login($request); - - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertIsCallable($response->getCallable()); - - $arguments = $response->getArguments(); - $this->assertArrayHasKey('ErrorURL', $arguments[0]); - $this->assertArrayHasKey('ReturnTo', $arguments[0]); - } - - - /** - * Test that the user can choose what auth source to use when there are multiple defined (admin excluded). - */ - public function testMultipleAuthSources(): void - { - $_SERVER['REQUEST_URI'] = '/'; - $asConfig = Configuration::loadFromArray( - array_merge( - $this->authSources, - [ - 'example-static' => [ - 'exampleauth:StaticSource', - 'uid' => ['test'] - ] - ] - ) - ); - - Configuration::setPreLoadedConfig($asConfig, 'authsources.php'); - $request = new Request(); - $session = Session::getSessionFromRequest(); - $factory = new AuthenticationFactory($this->config, $session); - - $c = new Controller\Login($this->config, $session, $factory); - /** @var \SimpleSAML\XHTML\Template $response */ - $response = $c->login($request); - - $this->assertInstanceOf(Template::class, $response); - $this->assertEquals('core:login.twig', $response->getTemplateName()); - $this->assertArrayHasKey('sources', $response->data); - $this->assertArrayHasKey('example-userpass', $response->data['sources']); - $this->assertArrayHasKey('example-static', $response->data['sources']); - } - - - /** - * Test that specifying an invalid auth source while trying to login raises an exception. - */ - public function testLoginWithInvalidAuthSource(): void - { - $asConfig = Configuration::loadFromArray($this->authSources); - Configuration::setPreLoadedConfig($asConfig, 'authsources.php'); - $request = new Request(); - $session = Session::getSessionFromRequest(); - $factory = new AuthenticationFactory($this->config, $session); - $c = new Controller\Login($this->config, $session, $factory); - $this->expectException(Exception::class); - $c->login($request, 'invalid-auth-source'); - } - - - /** - * Test that we get redirected to /account/authsource when accessing the login endpoint while being already - * authenticated. - */ - public function testLoginWhenAlreadyAuthenticated(): void - { - $asConfig = Configuration::loadFromArray($this->authSources); - Configuration::setPreLoadedConfig($asConfig, 'authsources.php'); - - $session = Session::getSessionFromRequest(); - $session->setConfiguration($this->config); - $class = new ReflectionClass($session); - - $authData = $class->getProperty('authData'); - $authData->setAccessible(true); - $authData->setValue($session, [ - 'example-userpass' => [ - 'exampleauth:UserPass', - 'Attributes' => ['uid' => ['test']], - 'Authority' => 'example-userpass', - 'AuthnInstant' => time(), - 'Expire' => time() + 8 * 60 * 60 - ] - ]); - - $factory = new AuthenticationFactory($this->config, $session); - - $request = new Request(); - $c = new Controller\Login($this->config, $session, $factory); - /** @var \Symfony\Component\HttpFoundation\RedirectResponse $response */ - $response = $c->login($request); - $this->assertInstanceOf(RedirectResponse::class, $response); - $this->assertEquals( - 'https://example.org/simplesaml/module.php/core/account/example-userpass', - $response->getTargetUrl() - ); - } - - - /** - * Test that triggering the logout controller actually proceeds to log out from the specified source. - */ - public function testLogout(): void - { - $asConfig = Configuration::loadFromArray($this->authSources); - Configuration::setPreLoadedConfig($asConfig, 'authsources.php'); - - $session = Session::getSessionFromRequest(); - $factory = new AuthenticationFactory($this->config, $session); - - $c = new Controller\Login($this->config, $session, $factory); - $response = $c->logout('example-userpass'); - - $this->assertInstanceOf(RunnableResponse::class, $response); - $this->assertIsCallable($response->getCallable()); - $this->assertEquals('/simplesaml/', $response->getArguments()[0]); - } - - - /** - * Test that accessing the "account" endpoint without being authenticated gets you redirected to the "login" - * endpoint. - */ - public function testNotAuthenticated(): void - { - $asConfig = Configuration::loadFromArray($this->authSources); - Configuration::setPreLoadedConfig($asConfig, 'authsources.php'); - $session = Session::getSessionFromRequest(); - $factory = new AuthenticationFactory($this->config, $session); - $c = new Controller\Login($this->config, $session, $factory); - /** @var RedirectResponse $response */ - $response = $c->account('example-userpass'); - $this->assertInstanceOf(RedirectResponse::class, $response); - $this->assertEquals( - 'https://example.org/simplesaml/module.php/core/login/example-userpass', - $response->getTargetUrl() - ); - } - - /** - * Test that we are presented with a regular page if we are authenticated and try to access the "account" endpoint. + * Test that we are presented with a regular page if we go to the landing page. */ - public function testAuthenticated(): void + public function testWelcome(): void { - $asConfig = Configuration::loadFromArray($this->authSources); - Configuration::setPreLoadedConfig($asConfig, 'authsources.php'); - $session = Session::getSessionFromRequest(); - $class = new ReflectionClass($session); - $authData = $class->getProperty('authData'); - $authData->setAccessible(true); - $authData->setValue($session, [ - 'example-userpass' => [ - 'exampleauth:UserPass', - 'Attributes' => ['uid' => ['test']], - 'Authority' => 'example-userpass', - 'AuthnInstant' => time(), - 'Expire' => time() + 8 * 60 * 60 - ] - ]); - $factory = new AuthenticationFactory($this->config, $session); - $c = new Controller\Login($this->config, $session, $factory); + $c = new Controller\Login($this->config); /** @var \SimpleSAML\XHTML\Template $response */ - $response = $c->account('example-userpass'); + $response = $c->welcome(); $this->assertInstanceOf(Template::class, $response); - $this->assertEquals('auth_status.twig', $response->getTemplateName()); + $this->assertEquals('core:welcome.twig', $response->getTemplateName()); } } diff --git a/www/index.php b/www/index.php index b5933a4f1b3baa81cf24ed4536bd5017ed6da490..2e366c1d2c491de59e7939593c84be965103d022 100644 --- a/www/index.php +++ b/www/index.php @@ -5,4 +5,5 @@ require_once('_include.php'); $config = \SimpleSAML\Configuration::getInstance(); $httpUtils = new \SimpleSAML\Utils\HTTP(); -$httpUtils->redirectTrustedURL(SimpleSAML\Module::getModuleURL('core/login')); +$redirect = $config->getString('frontpage.redirect', SimpleSAML\Module::getModuleURL('core/welcome')); +$httpUtils->redirectTrustedURL($redirect);