From 2ae9f3b17e8478d3d8ad4909fe8ca4bcbff4fe87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez=20Crespo?= <jaime.perez@uninett.no> Date: Mon, 8 Oct 2018 14:14:04 +0200 Subject: [PATCH] Initial version of routing, requests and responses. This introduces the following: - The use of Request objects to handle request data to controllers. - The use of Response objects to model responses that should be sent to the browser. - The use of "controllers" that are responsible for translating a request into a response. - The possibility to define your own URLs on each module by specifying them, together with their controllers, in a "routes.yaml" file in the root of a module. - The new UI is completely separated from the old, so "usenewui" must be set to "true" in the configuration. - Twigified templates are not used unless we're using the new UI, or the twig template is part of a theme. --- lib/SimpleSAML/Module.php | 227 ++++++++++++++++++++ lib/SimpleSAML/ModuleControllerResolver.php | 154 +++++++++++++ lib/SimpleSAML/Router.php | 81 +++++++ lib/SimpleSAML/XHTML/Template.php | 68 +++++- modules/core/lib/Controller.php | 139 ++++++++++++ modules/core/routes.yaml | 7 + modules/core/templates/login.twig | 6 +- modules/core/www/frontpage_auth.php | 2 +- www/index.php | 2 +- www/module.php | 167 +------------- 10 files changed, 675 insertions(+), 178 deletions(-) create mode 100644 lib/SimpleSAML/ModuleControllerResolver.php create mode 100644 lib/SimpleSAML/Router.php create mode 100644 modules/core/lib/Controller.php create mode 100644 modules/core/routes.yaml diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php index e3e018c7b..744dea83f 100644 --- a/lib/SimpleSAML/Module.php +++ b/lib/SimpleSAML/Module.php @@ -2,6 +2,12 @@ namespace SimpleSAML; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; + /** * Helper class for accessing information about modules. * @@ -12,6 +18,44 @@ namespace SimpleSAML; */ class Module { + + /** + * Index pages: file names to attempt when accessing directories. + * + * @var array + */ + public static $indexFiles = ['index.php', 'index.html', 'index.htm', 'index.txt']; + + /** + * MIME Types + * + * The key is the file extension and the value the corresponding MIME type. + * + * @var array + */ + public static $mimeTypes = [ + 'bmp' => 'image/x-ms-bmp', + 'css' => 'text/css', + 'gif' => 'image/gif', + 'htm' => 'text/html', + 'html' => 'text/html', + 'shtml' => 'text/html', + 'ico' => 'image/vnd.microsoft.icon', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'text/javascript', + 'pdf' => 'application/pdf', + 'png' => 'image/png', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swf' => 'application/x-shockwave-flash', + 'swfl' => 'application/x-shockwave-flash', + 'txt' => 'text/plain', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + ]; + /** * A list containing the modules currently installed. * @@ -63,6 +107,172 @@ class Module } + /** + * Handler for module requests. + * + * This controller receives requests for pages hosted by modules, and processes accordingly. Depending on the + * configuration and the actual request, it will run a PHP script and exit, or return a Response produced either + * by another controller or by a static file. + * + * @param Request|null $request The request to process. Defaults to the current one. + * + * @return Response|BinaryFileResponse Returns a Response object that can be sent to the browser. + * @throws Error\BadRequest In case the request URI is malformed. + * @throws Error\NotFound In case the request URI is invalid or the resource it points to cannot be found. + */ + public static function process(Request $request = null) + { + if ($request === null) { + $request = Request::createFromGlobals(); + } + + if ($request->getPathInfo() === '/') { + throw new Error\NotFound('No PATH_INFO to module.php'); + } + + $url = $request->getPathInfo(); + assert(substr($url, 0, 1) === '/'); + + /* clear the PATH_INFO option, so that a script can detect whether it is called with anything following the + *'.php'-ending. + */ + unset($_SERVER['PATH_INFO']); + + $modEnd = strpos($url, '/', 1); + if ($modEnd === false) { + // the path must always be on the form /module/ + throw new Error\NotFound('The URL must at least contain a module name followed by a slash.'); + } + + $module = substr($url, 1, $modEnd - 1); + $url = substr($url, $modEnd + 1); + if ($url === false) { + $url = ''; + } + + if (!self::isModuleEnabled($module)) { + throw new Error\NotFound('The module \''.$module.'\' was either not found, or wasn\'t enabled.'); + } + + /* Make sure that the request isn't suspicious (contains references to current directory or parent directory or + * anything like that. Searching for './' in the URL will detect both '../' and './'. Searching for '\' will + * detect attempts to use Windows-style paths. + */ + if (strpos($url, '\\') !== false) { + throw new Error\BadRequest('Requested URL contained a backslash.'); + } elseif (strpos($url, './') !== false) { + throw new Error\BadRequest('Requested URL contained \'./\'.'); + } + + $config = Configuration::getInstance(); + if ($config->getBoolean('usenewui', false) === true) { + $router = new Router($module); + try { + return $router->process(); + } catch (\Symfony\Component\Config\Exception\FileLocatorFileNotFoundException $e) { + // no routes configured for this module, fall back to the old system + } catch (\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e) { + // this module has been migrated, but the route wasn't found + } + } + + $moduleDir = self::getModuleDir($module).'/www/'; + + // check for '.php/' in the path, the presence of which indicates that another php-script should handle the + // request + for ($phpPos = strpos($url, '.php/'); $phpPos !== false; $phpPos = strpos($url, '.php/', $phpPos + 1)) { + $newURL = substr($url, 0, $phpPos + 4); + $param = substr($url, $phpPos + 4); + + if (is_file($moduleDir.$newURL)) { + /* $newPath points to a normal file. Point execution to that file, and save the remainder of the path + * in PATH_INFO. + */ + $url = $newURL; + $request->server->set('PATH_INFO', $param); + $_SERVER['PATH_INFO'] = $param; + break; + } + } + + $path = $moduleDir.$url; + + if ($path[strlen($path) - 1] === '/') { + // path ends with a slash - directory reference. Attempt to find index file in directory + foreach (self::$indexFiles as $if) { + if (file_exists($path.$if)) { + $path .= $if; + break; + } + } + } + + if (is_dir($path)) { + /* Path is a directory - maybe no index file was found in the previous step, or maybe the path didn't end + * with a slash. Either way, we don't do directory listings. + */ + throw new Error\NotFound('Directory listing not available.'); + } + + if (!file_exists($path)) { + // file not found + Logger::info('Could not find file \''.$path.'\'.'); + throw new Error\NotFound('The URL wasn\'t found in the module.'); + } + + if (preg_match('#\.php$#D', $path)) { + // PHP file - attempt to run it + + /* In some environments, $_SERVER['SCRIPT_NAME'] is already set with $_SERVER['PATH_INFO']. Check for that + * case, and append script name only if necessary. + * + * Contributed by Travis Hegner. + */ + $script = "/$module/$url"; + if (stripos($request->getScriptName(), $script) === false) { + $request->server->set('SCRIPT_NAME', $request->getScriptName().'/'.$module.'/'.$url); + } + + require($path); + exit(); + } + + // some other file type - attempt to serve it + + // find MIME type for file, based on extension + $contentType = null; + if (preg_match('#\.([^/\.]+)$#D', $path, $type)) { + $type = strtolower($type[1]); + if (array_key_exists($type, self::$mimeTypes)) { + $contentType = self::$mimeTypes[$type]; + } + } + + if ($contentType === null) { + /* We were unable to determine the MIME type from the file extension. Fall back to mime_content_type (if it + * exists). + */ + if (function_exists('mime_content_type')) { + $contentType = mime_content_type($path); + } else { + // mime_content_type doesn't exist. Return a default MIME type + Logger::warning('Unable to determine mime content type of file: '.$path); + $contentType = 'application/octet-stream'; + } + } + + $response = new BinaryFileResponse($path); + $response->setCache(['public' => true, 'max_age' => 86400]); + $response->setExpires(new \DateTime(gmdate('D, j M Y H:i:s \G\M\T', time() + 10 * 60))); + $response->setLastModified(new \DateTime(gmdate('D, j M Y H:i:s \G\M\T', filemtime($path)))); + $response->headers->set('Content-Type', $contentType); + $response->headers->set('Content-Length', sprintf('%u', filesize($path))); // force file size to an unsigned + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_INLINE); + $response->prepare($request); + return $response; + } + + private static function isModuleEnabledWithConf($module, $mod_config) { if (isset(self::$module_info[$module]['enabled'])) { @@ -310,4 +520,21 @@ class Module $fn($data); } } + + + /** + * Handle a valid request that ends with a trailing slash. + * + * This method removes the trailing slash and redirects to the resulting URL. + * + * @param Request $request The request to process by this controller method. + * + * @return RedirectResponse A redirection to the URI specified in the request, but without the trailing slash. + */ + public static function removeTrailingSlash(Request $request) + { + $pathInfo = $request->getPathInfo(); + $url = str_replace($pathInfo, rtrim($pathInfo, ' /'), $request->getRequestUri()); + return new RedirectResponse($url, 308); + } } diff --git a/lib/SimpleSAML/ModuleControllerResolver.php b/lib/SimpleSAML/ModuleControllerResolver.php new file mode 100644 index 000000000..97ebc9798 --- /dev/null +++ b/lib/SimpleSAML/ModuleControllerResolver.php @@ -0,0 +1,154 @@ +<?php + +namespace SimpleSAML; + +use SimpleSAML\Error\Exception; +use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface; +use Symfony\Component\HttpKernel\Controller\ControllerResolver; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; +use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadataFactory; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Loader\YamlFileLoader; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * A class to resolve module controllers based on a given request. + * + * This class allows us to find a controller (a callable) that's configured for a given URL. + * + * @package SimpleSAML + */ +class ModuleControllerResolver extends ControllerResolver implements ArgumentResolverInterface +{ + + /** @var ArgumentMetadataFactory */ + protected $argFactory; + + /** @var string */ + protected $module; + + /** @var array */ + protected $params; + + /** @var RouteCollection */ + protected $routes; + + + /** + * Build a module controller resolver. + * + * @param string $module The name of the module. + */ + public function __construct($module) + { + parent::__construct(); + $this->module = $module; + + $loader = new YamlFileLoader( + new FileLocator(Module::getModuleDir($this->module)) + ); + + $this->argFactory = new ArgumentMetadataFactory(); + + try { + $this->routes = $loader->load('routes.yaml'); + $redirect = new Route( + '/{url}', + ['_controller' => '\SimpleSAML\Module::removeTrailingSlash'], + ['url' => '.*/$'] + ); + $this->routes->add('trailing-slash', $redirect); + $this->routes->addPrefix('/'.$this->module); + } catch (FileLocatorFileNotFoundException $e) { + } + } + + + /** + * Get the controller associated with a given URL, based on a request. + * + * This method searches for a 'routes.yaml' file in the root of the module, defining valid routes for the module + * and mapping them given controllers. It's input is a Request object with the request that we want to serve. + * + * @param Request $request The request we need to find a controller for. + * + * @return callable|false A controller (as a callable) that can handle the request, or false if we cannot find + * one suitable for the given request. + */ + public function getController(Request $request) + { + if ($this->routes === null) { + return false; + } + $ctxt = new RequestContext(); + $ctxt->fromRequest($request); + + try { + $matcher = new UrlMatcher($this->routes, $ctxt); + $this->params = $matcher->match($ctxt->getPathInfo()); + return self::createController($this->params['_controller']); + } catch (ResourceNotFoundException $e) { + // no route defined matching this request + } + return false; + } + + + /** + * Get the arguments that should be passed to a controller from a given request. + * + * When the signature of the controller includes arguments with type Request, the given request will be passed to + * those. Otherwise, they'll be matched by name. If no value is available for a given argument, the method will + * try to set a default value or null, if possible. + * + * @param Request $request The request that holds all the information needed by the controller. + * @param callable $controller A controller for the given request. + * + * @return array An array of arguments that should be passed to the controller, in order. + * + * @throws \SimpleSAML\Error\Exception If we don't find anything suitable for an argument in the controller's + * signature. + */ + public function getArguments(Request $request, $controller) + { + $args = []; + $metadata = $this->argFactory->createArgumentMetadata($controller); + + /** @var ArgumentMetadata $argMeta */ + foreach ($metadata as $argMeta) { + if ($argMeta->getType() === 'Symfony\Component\HttpFoundation\Request') { + // add request argument + $args[] = $request; + continue; + } + + $argName = $argMeta->getName(); + if (array_key_exists($argName, $this->params)) { + // add argument by name + $args[] = $this->params[$argName]; + continue; + } + + // URL does not contain value for this argument + if ($argMeta->hasDefaultValue()) { + // it has a default value + $args[] = $argMeta->getDefaultValue(); + } + + // no default value + if ($argMeta->isNullable()) { + $args[] = null; + } + + throw new Exception('Missing value for argument '.$argName.'. This is probably a bug.'); + } + + return $args; + } +} diff --git a/lib/SimpleSAML/Router.php b/lib/SimpleSAML/Router.php new file mode 100644 index 000000000..fa9346896 --- /dev/null +++ b/lib/SimpleSAML/Router.php @@ -0,0 +1,81 @@ +<?php + +namespace SimpleSAML; + +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ArgumentResolver; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\Routing\RequestContext; + +/** + * Class that routes requests to responses. + * + * @package SimpleSAML + */ +class Router +{ + + protected $arguments; + + /** @var RequestContext */ + protected $context; + + /** @var ModuleControllerResolver */ + protected $resolver; + protected $dispatcher; + protected $request; + + protected $stack; + + + /** + * Router constructor. + * + * @param string $module + */ + public function __construct($module) + { + $this->arguments = new ArgumentResolver(); + $this->context = new RequestContext(); + $this->resolver = new ModuleControllerResolver($module); + $this->dispatcher = new EventDispatcher(); + } + + + /** + * Process a given request. + * + * @param Request|null $request The request to process. Defaults to the current one. + * + * @return Response A response suitable for the given request. + * + * @throws \Exception If an error occurs. + */ + public function process(Request $request = null) + { + $this->request = $request; + if ($request === null) { + $this->request = Request::createFromGlobals(); + } + $stack = new RequestStack(); + $stack->push($this->request); + $this->context->fromRequest($this->request); + $kernel = new HttpKernel($this->dispatcher, $this->resolver, $stack, $this->resolver); + return $kernel->handle($this->request); + } + + + /** + * Send a given response to the browser. + * + * @param Response $response The response to send. + */ + public function send(Response $response) + { + $response->prepare($this->request); + $response->send(); + } +} diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php index b20cac571..9d9fe35eb 100644 --- a/lib/SimpleSAML/XHTML/Template.php +++ b/lib/SimpleSAML/XHTML/Template.php @@ -11,8 +11,9 @@ namespace SimpleSAML\XHTML; use JaimePerez\TwigConfigurableI18n\Twig\Environment as Twig_Environment; use JaimePerez\TwigConfigurableI18n\Twig\Extensions\Extension\I18n as Twig_Extensions_Extension_I18n; +use Symfony\Component\HttpFoundation\Response; -class Template +class Template extends Response { /** * The data associated with this template, accessible within the template itself. @@ -68,6 +69,14 @@ class Template */ private $module; + /** + * Whether to use the new user interface or not. + * + * @var string + */ + private $useNewUI; + + /** * A template controller, if any. * @@ -84,10 +93,10 @@ class Template * Whether we are using a non-default theme or not. * * If we are using a theme, this variable holds an array with two keys: "module" and "name", those being the name - * of the module and the name of the theme, respectively. If we are using the default theme, the variable defaults - * to false. + * of the module and the name of the theme, respectively. If we are using the default theme, the variable has + * the 'default' string in the "name" key, and 'null' in the "module" key. * - * @var bool|array + * @var array */ private $theme; @@ -117,6 +126,9 @@ class Template $this->translator = new \SimpleSAML\Locale\Translate($configuration, $defaultDictionary); $this->localization = new \SimpleSAML\Locale\Localization($configuration); + // check if we are supposed to use the new UI + $this->useNewUI = $this->configuration->getBoolean('usenewui', false); + // check if we need to attach a theme controller $controller = $this->configuration->getString('theme.controller', false); if ($controller && class_exists($controller) && @@ -126,6 +138,7 @@ class Template } $this->twig = $this->setupTwig(); + parent::__construct(); } @@ -148,7 +161,11 @@ class Template if ($tplpos) { $templateName = substr($templateName, 0, $tplpos); } - return $templateName.'.twig'; + + if ($this->useNewUI || $this->theme['module'] !== null) { + return $templateName.'.twig'; + } + return $templateName; } @@ -407,20 +424,51 @@ class Template /** - * Show the template to the user. + * Get the contents produced by this template. + * + * @return string The HTML rendered by this template, as a string. + * @throws \Exception if the template cannot be found. */ - public function show() + protected function getContents() { if ($this->twig !== false) { $this->twigDefaultContext(); if ($this->controller) { $this->controller->display($this->data); } - echo $this->twig->render($this->twig_template, $this->data); + $content = $this->twig->render($this->twig_template, $this->data); } else { - $filename = $this->findTemplatePath($this->template); - require($filename); + $content = require($this->findTemplatePath($this->template)); } + return $content; + } + + + /** + * Send this template as a response. + * + * @return Response This response. + * @throws \Exception if the template cannot be found. + */ + public function send() + { + $this->content = $this->getContents(); + return parent::send(); + } + + + /** + * Show the template to the user. + * + * This method is a remnant of the old templating system, where templates where shown manually instead of + * returning a response. + * + * @deprecated Do not use this method, use send() instead. + */ + public function show() + { + $this->send(); + echo $this->getContents(); } diff --git a/modules/core/lib/Controller.php b/modules/core/lib/Controller.php new file mode 100644 index 000000000..afdd0b5d3 --- /dev/null +++ b/modules/core/lib/Controller.php @@ -0,0 +1,139 @@ +<?php + +namespace SimpleSAML\Module\core; + +use SimpleSAML\Error\Exception; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; + +/** + * Controller class for the core module. + * + * This class serves the different views available in the module. + * + * @package SimpleSAML\Module\core + */ +class Controller +{ + + /** @var \SimpleSAML\Configuration */ + protected $config; + + /** @var array */ + protected $sources; + + + public function __construct() + { + $this->config = \SimpleSAML\Configuration::getInstance(); + $this->sources = \SimpleSAML\Configuration::getOptionalConfig('authsources.php')->toArray(); + } + + + /** + * 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 \SimpleSAML\XHTML\Template|RedirectResponse An HTML template or a redirect response. + * + * @throws Exception + * @throws \SimpleSAML\Error\CriticalConfigurationError + */ + public function login(Request $request, $as = null) + { + //delete admin + if (isset($this->sources['admin'])) { + unset($this->sources['admin']); + } + + if (count($this->sources) === 1) { // we only have one source available + $as = key($this->sources); + } + + if ($as === null) { // no authentication source specified + $t = new \SimpleSAML\XHTML\Template($this->config, 'core:login.twig'); + $t->data['loginurl'] = \SimpleSAML\Utils\Auth::getAdminLoginURL(); + $t->data['sources'] = $this->sources; + return $t; + } + + // auth source defined, check if valid + if (!array_key_exists($as, $this->sources)) { + throw new Exception('Invalid authentication source'); + } + + // at this point, we have a valid auth source selected, start auth + $auth = new \SimpleSAML\Auth\Simple($as); + $as = urlencode($as); + + if ($request->get('logout', false) !== false) { + $auth->logout($this->config->getBasePath().'logout.php'); + } + + if ($request->get(\SimpleSAML\Auth\State::EXCEPTION_PARAM, false) !== false) { + // This is just a simple example of an error + + $state = \SimpleSAML\Auth\State::loadExceptionState(); + assert(array_key_exists(\SimpleSAML\Auth\State::EXCEPTION_DATA, $state)); + $e = $state[\SimpleSAML\Auth\State::EXCEPTION_DATA]; + + throw $e; + } + + if ($auth->isAuthenticated()) { + return new RedirectResponse(\SimpleSAML\Module::getModuleURL('core/account/'.$as)); + } + + // we're not logged in, start auth + $url = \SimpleSAML\Module::getModuleURL('core/login/'.$as); + $params = array( + 'ErrorURL' => $url, + 'ReturnTo' => $url, + ); + $auth->login($params); + } + + + /** + * Show account information for a given authentication source. + * + * @param string $as The identifier of the authentication source. + * + * @return \SimpleSAML\XHTML\Template|RedirectResponse An HTML template or a redirection if we are not + * authenticated. + * + * @throws Exception An exception in case the auth source specified is invalid. + */ + public function account($as) + { + if (!array_key_exists($as, $this->sources)) { + throw new Exception('Invalid authentication source'); + } + + $auth = new \SimpleSAML\Auth\Simple($as); + if (!$auth->isAuthenticated()) { + // not authenticated, start auth with specified source + return new RedirectResponse(\SimpleSAML\Module::getModuleURL('core/login/'.urlencode($as))); + } + + $attributes = $auth->getAttributes(); + $session = \SimpleSAML\Session::getSessionFromRequest(); + + $t = new \SimpleSAML\XHTML\Template($this->config, 'auth_status.php', 'attributes'); + $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['logouturl'] = \SimpleSAML\Module::getModuleURL('core/logout/'.urlencode($as)); + $t->data['remaining'] = $session->getAuthData($as, 'Expire') - time(); + $t->setStatusCode(200); + + return $t; + } +} diff --git a/modules/core/routes.yaml b/modules/core/routes.yaml new file mode 100644 index 000000000..2eea68a23 --- /dev/null +++ b/modules/core/routes.yaml @@ -0,0 +1,7 @@ +core-login: + path: /login/{as} + defaults: { _controller: '\SimpleSAML\Module\core\Controller::login', as: null } +core-account: + path: /account/{as} + defaults: { _controller: '\SimpleSAML\Module\core\Controller::account' } + diff --git a/modules/core/templates/login.twig b/modules/core/templates/login.twig index e1b342cc0..03297b971 100644 --- a/modules/core/templates/login.twig +++ b/modules/core/templates/login.twig @@ -12,7 +12,11 @@ <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="?as={{ id|url_encode }}" class="pure-menu-link">{{ config.name|translateFromArray|default(id) }}</a></li> + <li class="pure-menu-item"> + <a href="{{ baseurlpath }}/module.php/core/login/{{ id|url_encode }}" class="pure-menu-link"> + {{ config.name|translateFromArray|default(id) }} + </a> + </li> {% endfor -%} </ul> </div> diff --git a/modules/core/www/frontpage_auth.php b/modules/core/www/frontpage_auth.php index 804f3cd6f..0b36c9231 100644 --- a/modules/core/www/frontpage_auth.php +++ b/modules/core/www/frontpage_auth.php @@ -19,7 +19,7 @@ $links_auth = array(); $links_federation = array(); $links_auth[] = array( - 'href' => 'login.php', + 'href' => 'authenticate.php', 'text' => '{core:frontpage:authtest}', ); diff --git a/www/index.php b/www/index.php index b5b456c7a..d080b8af9 100644 --- a/www/index.php +++ b/www/index.php @@ -5,7 +5,7 @@ require_once('_include.php'); $config = \SimpleSAML\Configuration::getInstance(); if ($config->getBoolean('usenewui', false)) { - \SimpleSAML\Utils\HTTP::redirectTrustedURL(SimpleSAML\Module::getModuleURL('core/login.php')); + \SimpleSAML\Utils\HTTP::redirectTrustedURL(SimpleSAML\Module::getModuleURL('core/login')); } \SimpleSAML\Utils\HTTP::redirectTrustedURL(SimpleSAML\Module::getModuleURL('core/frontpage_welcome.php')); diff --git a/www/module.php b/www/module.php index d8a72013d..83ee0ff38 100644 --- a/www/module.php +++ b/www/module.php @@ -1,172 +1,9 @@ <?php /** - * Handler for module requests. - * * This web page receives requests for web-pages hosted by modules, and directs them to - * the RequestHandler in the module. - * - * @author Olav Morken, UNINETT AS. - * @package SimpleSAMLphp + * the process() handler in the Module class. */ - require_once('_include.php'); -// index pages - file names to attempt when accessing directories -$indexFiles = array('index.php', 'index.html', 'index.htm', 'index.txt'); - -// MIME types - key is file extension, value is MIME type -$mimeTypes = array( - 'bmp' => 'image/x-ms-bmp', - 'css' => 'text/css', - 'gif' => 'image/gif', - 'htm' => 'text/html', - 'html' => 'text/html', - 'shtml' => 'text/html', - 'ico' => 'image/vnd.microsoft.icon', - 'jpe' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'jpg' => 'image/jpeg', - 'js' => 'text/javascript', - 'pdf' => 'application/pdf', - 'png' => 'image/png', - 'svg' => 'image/svg+xml', - 'svgz' => 'image/svg+xml', - 'swf' => 'application/x-shockwave-flash', - 'swfl' => 'application/x-shockwave-flash', - 'txt' => 'text/plain', - 'xht' => 'application/xhtml+xml', - 'xhtml' => 'application/xhtml+xml', -); - -if (empty($_SERVER['PATH_INFO'])) { - throw new \SimpleSAML\Error\NotFound('No PATH_INFO to module.php'); -} - -$url = $_SERVER['PATH_INFO']; -assert(substr($url, 0, 1) === '/'); - -/* clear the PATH_INFO option, so that a script can detect whether it is called with anything following the - *'.php'-ending. - */ -unset($_SERVER['PATH_INFO']); - -$modEnd = strpos($url, '/', 1); -if ($modEnd === false) { - // the path must always be on the form /module/ - throw new \SimpleSAML\Error\NotFound('The URL must at least contain a module name followed by a slash.'); -} - -$module = substr($url, 1, $modEnd - 1); -$url = substr($url, $modEnd + 1); -if ($url === false) { - $url = ''; -} - -if (!SimpleSAML\Module::isModuleEnabled($module)) { - throw new \SimpleSAML\Error\NotFound('The module \''.$module.'\' was either not found, or wasn\'t enabled.'); -} - -/* Make sure that the request isn't suspicious (contains references to current directory or parent directory or - * anything like that. Searching for './' in the URL will detect both '../' and './'. Searching for '\' will detect - * attempts to use Windows-style paths. - */ -if (strpos($url, '\\') !== false) { - throw new SimpleSAML\Error\BadRequest('Requested URL contained a backslash.'); -} elseif (strpos($url, './') !== false) { - throw new \SimpleSAML\Error\BadRequest('Requested URL contained \'./\'.'); -} - -$moduleDir = SimpleSAML\Module::getModuleDir($module).'/www/'; - -// check for '.php/' in the path, the presence of which indicates that another php-script should handle the request -for ($phpPos = strpos($url, '.php/'); $phpPos !== false; $phpPos = strpos($url, '.php/', $phpPos + 1)) { - - $newURL = substr($url, 0, $phpPos + 4); - $param = substr($url, $phpPos + 4); - - if (is_file($moduleDir.$newURL)) { - /* $newPath points to a normal file. Point execution to that file, and - * save the remainder of the path in PATH_INFO. - */ - $url = $newURL; - $_SERVER['PATH_INFO'] = $param; - break; - } -} - -$path = $moduleDir.$url; - -if ($path[strlen($path) - 1] === '/') { - // path ends with a slash - directory reference. Attempt to find index file in directory - foreach ($indexFiles as $if) { - if (file_exists($path.$if)) { - $path .= $if; - break; - } - } -} - -if (is_dir($path)) { - /* Path is a directory - maybe no index file was found in the previous step, or maybe the path didn't end with - * a slash. Either way, we don't do directory listings. - */ - throw new \SimpleSAML\Error\NotFound('Directory listing not available.'); -} - -if (!file_exists($path)) { - // file not found - SimpleSAML\Logger::info('Could not find file \''.$path.'\'.'); - throw new \SimpleSAML\Error\NotFound('The URL wasn\'t found in the module.'); -} - -if (preg_match('#\.php$#D', $path)) { - // PHP file - attempt to run it - - /* In some environments, $_SERVER['SCRIPT_NAME'] is already set with $_SERVER['PATH_INFO']. Check for that case, - * and append script name only if necessary. - * - * Contributed by Travis Hegner. - */ - $script = "/$module/$url"; - if (stripos($_SERVER['SCRIPT_NAME'], $script) === false) { - $_SERVER['SCRIPT_NAME'] .= '/'.$module.'/'.$url; - } - - require($path); - exit(); -} - -// some other file type - attempt to serve it - -// find MIME type for file, based on extension -$contentType = null; -if (preg_match('#\.([^/\.]+)$#D', $path, $type)) { - $type = strtolower($type[1]); - if (array_key_exists($type, $mimeTypes)) { - $contentType = $mimeTypes[$type]; - } -} - -if ($contentType === null) { - /* We were unable to determine the MIME type from the file extension. Fall back to mime_content_type (if it - * exists). - */ - if (function_exists('mime_content_type')) { - $contentType = mime_content_type($path); - } else { - // mime_content_type doesn't exist. Return a default MIME type - SimpleSAML\Logger::warning('Unable to determine mime content type of file: '.$path); - $contentType = 'application/octet-stream'; - } -} - -$contentLength = sprintf('%u', filesize($path)); // force filesize to an unsigned number - -header('Content-Type: '.$contentType); -header('Content-Length: '.$contentLength); -header('Cache-Control: public,max-age=86400'); -header('Expires: '.gmdate('D, j M Y H:i:s \G\M\T', time() + 10 * 60)); -header('Last-Modified: '.gmdate('D, j M Y H:i:s \G\M\T', filemtime($path))); - -readfile($path); +\SimpleSAML\Module::process()->send(); -- GitLab