diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d16eec4696968af1e18c93acdf4a5227584b1667..7b7ef32c30a4cea023d719fbceed3df5c43c8826 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,10 +8,14 @@ These guidelines briefly explain how to contribute to SimpleSAMLphp effectively ## Team members Currently, the core team members are: -* Jaime PĂ©rez Crespo, *main developer and release manager*, UNINETT <jaime.perez@uninett.no> -* Olav Morken, *main developer*, UNINETT <olav.morken@uninett.no> +* Jaime PĂ©rez Crespo, *maintainer and main developer*, UNINETT <jaime.perez@uninett.no> +* Tim van Dijen, *main developer* +* Thijs Kinkhorst, *main developer* + +Two other persons are listed here for historical reasons, even though they are no longer involved in the project: + * Andreas Ă…kre Solberg, *architect and original developer*, UNINETT <andreas.solberg@uninett.no> -* Hanne Moa, *developer*, UNINETT <hanne.moa@uninett.no> +* Olav Morken, *architect and main developer*, UNINETT <olav.morken@uninett.no> We have been lucky enough to have so many people help us through the years. SimpleSAMLphp wouldn't have reached so far without them. We want to thank them from here, but unfortunately they are so many it is nearly impossible to mention all of them. [Here is a Github page that summarizes everyone's contributions](https://github.com/simplesamlphp/simplesamlphp/graphs/contributors?from=2007-09-09&to=2015-09-06&type=c). diff --git a/config-templates/config.php b/config-templates/config.php index f6de5cba9ef599b0a417640d3dda6d748a74177b..9d267be49d2f608b3e9b3d17d279ae3146b183f2 100644 --- a/config-templates/config.php +++ b/config-templates/config.php @@ -861,6 +861,31 @@ $config = [ */ 'production' => true, + /* + * SimpleSAMLphp modules can host static resources which are served through PHP. + * The serving of the resources can be configured through these settings. + */ + 'assets' => [ + /* + * These settings adjust the caching headers that are sent + * when serving static resources. + */ + 'caching' => [ + /* + * Amount of seconds before the resource should be fetched again + */ + 'max_age' => 86400, + /* + * Calculate a checksum of every file and send it to the browser + * This allows the browser to avoid downloading assets again in situations + * where the Last-Modified header cannot be trusted, + * for example in cluster setups + * + * Defaults false + */ + 'etag' => false, + ], + ], /********************* diff --git a/lib/SimpleSAML/Auth/Simple.php b/lib/SimpleSAML/Auth/Simple.php index 769c0ab55230ed6eadf7637aec97ae10ef03e1ef..2bfa424ec821ddfec48306b82f69867b0c8b4f67 100644 --- a/lib/SimpleSAML/Auth/Simple.php +++ b/lib/SimpleSAML/Auth/Simple.php @@ -377,7 +377,7 @@ class Simple $scheme = parse_url($url, PHP_URL_SCHEME); $host = parse_url($url, PHP_URL_HOST) ? : Utils\HTTP::getSelfHost(); $port = parse_url($url, PHP_URL_PORT) ? : ( - $scheme ? '' : trim(Utils\HTTP::getServerPort(), ':') + $scheme ? '' : ltrim(Utils\HTTP::getServerPort(), ':') ); $scheme = $scheme ? : (Utils\HTTP::getServerHTTPS() ? 'https' : 'http'); $path = parse_url($url, PHP_URL_PATH) ? : '/'; @@ -394,7 +394,7 @@ class Simple return $scheme.'://'.$host.$port.$path.($query ? '?'.$query : '').($fragment ? '#'.$fragment : ''); } - $base = trim($this->app_config->getString( + $base = rtrim($this->app_config->getString( 'baseURL', $scheme.'://'.$host.$port ), '/'); diff --git a/lib/SimpleSAML/Locale/Localization.php b/lib/SimpleSAML/Locale/Localization.php index 8a9346aacc307f9542633e38a4012b381d42d740..3731b1e4ddd3572f05323ec5236f319223661026 100644 --- a/lib/SimpleSAML/Locale/Localization.php +++ b/lib/SimpleSAML/Locale/Localization.php @@ -240,7 +240,7 @@ class Localization $langPath = $this->getLangPath($domain); } catch (\Exception $e) { $error = "Something went wrong when trying to get path to language file, cannot load domain '$domain'."; - Logger::error($_SERVER['PHP_SELF'].' - '.$error); + Logger::debug($_SERVER['PHP_SELF'].' - '.$error); if ($catchException) { // bail out! return; @@ -255,7 +255,7 @@ class Localization $this->translator->loadTranslations($translations); } else { $error = "Localization file '$poFile' not found in '$langPath', falling back to default"; - Logger::error($_SERVER['PHP_SELF'].' - '.$error); + Logger::debug($_SERVER['PHP_SELF'].' - '.$error); } } diff --git a/lib/SimpleSAML/Logger.php b/lib/SimpleSAML/Logger.php index 0c1d542e3895c5427afecddb8bb858c3b1bf828d..1bdc018740896e44faa3a0d77d9b14cfa9d087a1 100644 --- a/lib/SimpleSAML/Logger.php +++ b/lib/SimpleSAML/Logger.php @@ -321,7 +321,6 @@ class Logger $s = Session::getSessionFromRequest(); } catch (\Exception $e) { // loading session failed. We don't care why, at this point we have a transient session, so we use that - self::error('Cannot load or create session: '.$e->getMessage()); $s = Session::getSessionFromRequest(); } self::$trackid = $s->getTrackID(); diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php index 6c9f7fed0c26830fe611cad431a910ac1f865fe2..53cc9ffa9acf8d8f157d2e4a0d4a0d4629c958b8 100644 --- a/lib/SimpleSAML/Metadata/SAMLParser.php +++ b/lib/SimpleSAML/Metadata/SAMLParser.php @@ -1211,7 +1211,7 @@ class SAMLParser $contactPerson['givenName'] = $element->getGivenName(); } if ($element->getSurName() !== null) { - $contactPerson['surName'] = $element->SurName; + $contactPerson['surName'] = $element->getSurName(); } if ($element->getEmailAddress() !== []) { $contactPerson['emailAddress'] = $element->getEmailAddress(); diff --git a/lib/SimpleSAML/Module.php b/lib/SimpleSAML/Module.php index e0bf063a5c4f59b7da97ed64927f250333936786..2fd6426ea79d9a70e873ccb7f9237cd373666dd9 100644 --- a/lib/SimpleSAML/Module.php +++ b/lib/SimpleSAML/Module.php @@ -133,7 +133,7 @@ class Module throw new Error\NotFound('No PATH_INFO to module.php'); } - $url = $request->getPathInfo(); + $url = $request->server->get('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 @@ -168,10 +168,18 @@ class Module } $config = Configuration::getInstance(); + + // rebuild REQUEST_URI and SCRIPT_NAME just in case we need to. This is needed for server aliases and rewrites + $translated_uri = $config->getBasePath().'module.php/'.$module.'/'.$url; + $request->server->set('REQUEST_URI', $translated_uri); + $request->server->set('SCRIPT_NAME', $config->getBasePath().'module.php'); + $request->initialize($request->query->all(), $request->request->all(), $request->attributes->all(), + $request->cookies->all(), $request->files->all(), $request->server->all(), $request->getContent()); + if ($config->getBoolean('usenewui', false) === true) { $router = new Router($module); try { - return $router->process(); + return $router->process($request); } catch (FileLocatorFileNotFoundException $e) { // no routes configured for this module, fall back to the old system } catch (NotFoundHttpException $e) { @@ -264,12 +272,21 @@ class Module } } + $assetConfig = $config->getConfigItem('assets', new Configuration([], '[assets]')); + $cacheConfig = $assetConfig->getConfigItem('caching', new Configuration([], '[assets][caching]')); $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->setCache([ + // "public" allows response caching even if the request was authenticated, + // which is exactly what we want for static resources + 'public' => true, + 'max_age' => (string)$cacheConfig->getInteger('max_age', 86400) + ]); + $response->setAutoLastModified(); + if ($cacheConfig->getBoolean('etag', false)) { + $response->setAutoEtag(); + } + $response->isNotModified($request); $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; diff --git a/lib/SimpleSAML/Session.php b/lib/SimpleSAML/Session.php index dafefa9521e0a527e71a73232e0de2c9917519a9..95c1ad48288486290547f9a87ca89dbd2e1e3002 100644 --- a/lib/SimpleSAML/Session.php +++ b/lib/SimpleSAML/Session.php @@ -158,20 +158,9 @@ class Session implements \Serializable, Utils\ClearableState if ($transient) { // transient session - $sh = SessionHandler::getSessionHandler(); $this->trackid = 'TR'.bin2hex(openssl_random_pseudo_bytes(4)); Logger::setTrackId($this->trackid); $this->transient = true; - - /* - * Initialize the session ID. It might be that we have a session cookie but we couldn't load the session. - * If that's the case, use that ID. If not, create a new ID. - */ - $sessionId = $sh->getCookieSessionId(); - if ($sessionId === null) { - $sessionId = $sh->newSessionId(); - } - $this->sessionId = $sessionId; } else { // regular session $sh = SessionHandler::getSessionHandler(); @@ -275,8 +264,8 @@ class Session implements \Serializable, Utils\ClearableState * session here. Therefore, use just a transient session and throw the exception for someone else to handle * it. */ - Logger::error('Error loading session: '.$e->getMessage()); self::useTransientSession(); + Logger::error('Error loading session: '.$e->getMessage()); if ($e instanceof Error\Exception) { $cause = $e->getCause(); if ($cause instanceof \Exception) { @@ -453,6 +442,9 @@ class Session implements \Serializable, Utils\ClearableState */ public function save() { + // clean out old data + $this->expireData(); + if (!$this->dirty) { // session hasn't changed, don't bother saving it return; @@ -894,9 +886,6 @@ class Session implements \Serializable, Utils\ClearableState assert(is_string($id)); assert(is_int($timeout) || $timeout === null || $timeout === self::DATA_TIMEOUT_SESSION_END); - // clean out old data - $this->expireData(); - if ($timeout === null) { // use the default timeout $timeout = self::$config->getInteger('session.datastore.timeout', null); @@ -934,9 +923,6 @@ class Session implements \Serializable, Utils\ClearableState /** * This function removes expired data from the data store. * - * Note that this function doesn't mark the session object as dirty. This means that - * if the only change to the session object is that some data has expired, it will not be - * written back to the session store. * @return void */ private function expireData() @@ -952,6 +938,7 @@ class Session implements \Serializable, Utils\ClearableState if ($ct > $info['expires']) { unset($typedData[$id]); + $this->markDirty(); } } } @@ -977,8 +964,6 @@ class Session implements \Serializable, Utils\ClearableState return null; } - $this->expireData(); - if (!array_key_exists($type, $this->dataStore)) { return null; } diff --git a/lib/SimpleSAML/SessionHandler.php b/lib/SimpleSAML/SessionHandler.php index 3cfe9bab6cfc5655fb1b014e8b88135c7746811b..91fc08b26df9f640200665288d395aa25a870a8a 100644 --- a/lib/SimpleSAML/SessionHandler.php +++ b/lib/SimpleSAML/SessionHandler.php @@ -32,6 +32,8 @@ abstract class SessionHandler * to this function. * * @return \SimpleSAML\SessionHandler The current session handler. + * + * @throws \Exception If we cannot instantiate the session handler. */ public static function getSessionHandler() { @@ -126,6 +128,8 @@ abstract class SessionHandler * PHP session handler. * * @return void + * + * @throws \Exception If we cannot instantiate the session handler. */ private static function createSessionHandler() { diff --git a/lib/SimpleSAML/SessionHandlerPHP.php b/lib/SimpleSAML/SessionHandlerPHP.php index c1762da701ea5443b3a71c369625f513eeb25e97..280584f70210f32fb873c3aa58b57c3ccff145de 100644 --- a/lib/SimpleSAML/SessionHandlerPHP.php +++ b/lib/SimpleSAML/SessionHandlerPHP.php @@ -194,6 +194,11 @@ class SessionHandlerPHP extends SessionHandler return null; // there's no session cookie, can't return ID } + if (version_compare(PHP_VERSION, '7.2', 'ge') && headers_sent()) { + // latest versions of PHP don't allow loading a session when output sent, get the ID from the cookie + return $_COOKIE[$this->cookie_name]; + } + // do not rely on session_id() as it can return the ID of a previous session. Get it from the cookie instead. session_id($_COOKIE[$this->cookie_name]); @@ -246,7 +251,7 @@ class SessionHandlerPHP extends SessionHandler assert(is_string($sessionId) || $sessionId === null); if ($sessionId !== null) { - if (session_id() === '') { + if (session_id() === '' && !(version_compare(PHP_VERSION, '7.2', 'ge') && headers_sent())) { // session not initiated with getCookieSessionId(), start session without setting cookie $ret = ini_set('session.use_cookies', '0'); if ($ret === false) { diff --git a/lib/SimpleSAML/XHTML/Template.php b/lib/SimpleSAML/XHTML/Template.php index cf6b270e65e8d516f728eae8837f248993e251f1..0db0328c1ce92aeb35570853fcdfbe9123e39490 100644 --- a/lib/SimpleSAML/XHTML/Template.php +++ b/lib/SimpleSAML/XHTML/Template.php @@ -774,6 +774,7 @@ class Template extends Response * Includes a file relative to the template base directory. * This function can be used to include headers and footers etc. * + * @deprecated This function will be removed in SSP 2.0. Use Twig-templates instead * @param string $file * @return void */ diff --git a/modules/admin/lib/FederationController.php b/modules/admin/lib/FederationController.php index b9ee3b4ebb82fb82e89ea294d7099f14f268a1c0..fb83c2e6279b48c33825c6a548ee39a96486033f 100644 --- a/modules/admin/lib/FederationController.php +++ b/modules/admin/lib/FederationController.php @@ -118,7 +118,8 @@ class FederationController } elseif (isset($entity[$old]['en'])) { $entries['remote'][$key][$entityid][$new] = $entity[$old]['en']; } elseif (isset($entries['remote'][$key][$entityid][$old])) { - $entries['remote'][$key][$entityid][$new] = $entries['remote'][$key][$entityid][$old]; + $old_entry = $entries['remote'][$key][$entityid][$old]; + $entries['remote'][$key][$entityid][$new] = is_array($old_entry) ? $entityid : $old_entry; } } } diff --git a/modules/core/lib/Controller.php b/modules/core/lib/Controller.php index efc1799d514b8d75700eae8525ec1751b51c2116..913fe4ec7b2c9e783dd28b673e7817c0316844e0 100644 --- a/modules/core/lib/Controller.php +++ b/modules/core/lib/Controller.php @@ -82,6 +82,8 @@ class Controller } $attributes = $auth->getAttributes(); + + $session = Session::getSessionFromRequest(); $t = new Template($this->config, 'auth_status.twig', 'attributes'); $l = $t->getLocalization(); @@ -91,6 +93,8 @@ class Controller $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); diff --git a/modules/core/lib/Stats/Output/File.php b/modules/core/lib/Stats/Output/File.php index 242372ab04155e9d301ca2cadb46ed548b7ef8ac..00913cdf24f66e5671901d39648f974753e7815e 100644 --- a/modules/core/lib/Stats/Output/File.php +++ b/modules/core/lib/Stats/Output/File.php @@ -20,7 +20,7 @@ class File extends \SimpleSAML\Stats\Output /** * The file handle for the current file. - * @var resource|null|false + * @var resource|null */ private $file = null; @@ -65,14 +65,15 @@ class File extends \SimpleSAML\Stats\Output } $fileName = $this->logDir.'/'.$date.'.log'; - $this->file = @fopen($fileName, 'a'); - if ($this->file === false) { + $fh = @fopen($fileName, 'a'); + if ($fh === false) { throw new Error\Exception('Error opening log file: '.var_export($fileName, true)); } // Disable output buffering - stream_set_write_buffer($this->file, 0); + stream_set_write_buffer($fh, 0); + $this->file = $fh; $this->fileDate = $date; } @@ -87,10 +88,6 @@ class File extends \SimpleSAML\Stats\Output { assert(isset($data['time'])); - if ($this->file === false || $this->file === null) { - throw new Error\Exception('Error opening log file: invalid handle'); - } - $time = $data['time']; $milliseconds = (int) (($time - (int) $time) * 1000); @@ -103,6 +100,7 @@ class File extends \SimpleSAML\Stats\Output } $line = $timestamp.' '.json_encode($data)."\n"; + /** @psalm-suppress PossiblyNullArgument */ fwrite($this->file, $line); } } diff --git a/modules/core/templates/loginuserpass.tpl.php b/modules/core/templates/loginuserpass.tpl.php index a8b0fe60b75fcc22ccab26fb85d986b13447592f..7dbeab9ad703dc74e87f2175df9527a64561e612 100644 --- a/modules/core/templates/loginuserpass.tpl.php +++ b/modules/core/templates/loginuserpass.tpl.php @@ -168,7 +168,7 @@ if ($this->data['errorcode'] !== null) { <tr id="submit"> <td class="loginicon"></td><td></td> <td> - <button id="submit_button" class="btn" tabindex="6"> + <button id="submit_button" class="btn" tabindex="6" type="submit"> <?php echo $this->t('{login:login_button}'); ?> </button> </td> diff --git a/modules/cron/www/assets/css/cron.css b/modules/cron/www/assets/css/cron.css index 459178ed6c2c31566f70bc580c763c100a3e3462..73cc3f9a4ba1091d36e7cc40bdc06a853c63bfbc 100644 --- a/modules/cron/www/assets/css/cron.css +++ b/modules/cron/www/assets/css/cron.css @@ -1,3 +1,4 @@ code#cronlist { font-size: 0.8vw; + white-space: nowrap; } diff --git a/templates/auth_status.twig b/templates/auth_status.twig index bb56e9a47a444b8ccc9e1955e568f39585c98c08..9965dd37601b028f1294b9b560403c133ef1020a 100644 --- a/templates/auth_status.twig +++ b/templates/auth_status.twig @@ -67,6 +67,40 @@ {% endif %} + <dl> + <dt>{% trans %}Debug information to be used by your support staff{% endtrans %}</dt> + {%- embed "includes/expander.twig" %} + {%- block content %} + + <dl> + <dd>{% trans %}Tracking number{% endtrans %}</dd> + <dd class="code-box hljs"> + <div class="pure-button-group top-right-corner"> + <a class="pure-button copy hljs" data-clipboard-target="#trackid" + title="{% trans %}Copy to clipboard{% endtrans %}"><span class="fa fa-copy"></span></a> + </div> + <code id="trackid" class="code-box-content">{{ trackid }}</code> + </dd> + {%- if authData %} + + <dd>{% trans %}Information about your current session{% endtrans %}</dd> + <dd class="code-box hljs"> + <div class="pure-button-group top-right-corner"> + <a class="pure-button copy hljs" data-clipboard-target="#authdata" + title="{% trans %}Copy to clipboard{% endtrans %}"><span class="fa fa-copy"></span></a> + </div> + <div id="authdata" class="code-box-content php"> + {{- authData|json_encode(constant('JSON_UNESCAPED_SLASHES') b-or constant('JSON_PRETTY_PRINT')) |raw -}} + </div> + </dd> + {%- endif %} + + </dl> + {%- endblock content %} + {%- endembed %} + + </dl> + <br> {% if logout %} <h2>{% trans %}Logout{% endtrans %}</h2> diff --git a/templates/includes/expander.twig b/templates/includes/expander.twig index 8096b91332caa22e1df26bf548f9f19e31466d7e..0817520d3fca72894a5c740dba9c0108c57ca058 100644 --- a/templates/includes/expander.twig +++ b/templates/includes/expander.twig @@ -1,11 +1,13 @@ <div class="expandable{% if expanded %} expanded{% endif %}"> + {% if block('general') is defined %} <div class="general"> - {%- block general%}{% endblock %} + {{- block("general") }} </div> + {% endif %} <a tabindex="0" class="expander"></a> <div class="content"> {%- block content %}{% endblock %} </div> - </div> \ No newline at end of file + </div>