<?php namespace SimpleSAML\Module\admin; use SimpleSAML\HTTP\RunnableResponse; use SimpleSAML\Locale\Translate; use SimpleSAML\Utils\HTTP; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** * Controller class for the admin module. * * This class serves the configuration views available in the module. * * @package SimpleSAML\Module\admin */ class ConfigController { const LATEST_VERSION_STATE_KEY = 'core:latest_simplesamlphp_version'; const RELEASES_API = 'https://api.github.com/repos/simplesamlphp/simplesamlphp/releases/latest'; /** @var \SimpleSAML\Configuration */ protected $config; /** @var Menu */ protected $menu; /** @var \SimpleSAML\Session */ protected $session; /** * ConfigController constructor. * * @param \SimpleSAML\Configuration $config The configuration to use. * @param \SimpleSAML\Session $session The current user session. */ public function __construct(\SimpleSAML\Configuration $config, \SimpleSAML\Session $session) { $this->config = $config; $this->session = $session; $this->menu = new Menu(); } /** * Display basic diagnostic information on hostname, port and protocol. * * @param Request $request The current request. * * @return \SimpleSAML\XHTML\Template */ public function diagnostics(Request $request) { \SimpleSAML\Utils\Auth::requireAdmin(); $t = new \SimpleSAML\XHTML\Template($this->config, 'admin:diagnostics.twig'); $t->data = [ 'remaining' => $this->session->getAuthData('admin', 'Expire') - time(), 'logouturl' => \SimpleSAML\Utils\Auth::getAdminLogoutURL(), 'items' => [ 'HTTP_HOST' => [$request->getHost()], 'HTTPS' => $request->isSecure() ? ['on'] : [], 'SERVER_PROTOCOL' => [$request->getProtocolVersion()], 'getBaseURL()' => [HTTP::getBaseURL()], 'getSelfHost()' => [HTTP::getSelfHost()], 'getSelfHostWithNonStandardPort()' => [HTTP::getSelfHostWithNonStandardPort()], 'getSelfURLHost()' => [HTTP::getSelfURLHost()], 'getSelfURLNoQuery()' => [HTTP::getSelfURLNoQuery()], 'getSelfHostWithPath()' => [HTTP::getSelfHostWithPath()], 'getFirstPathElement()' => [HTTP::getFirstPathElement()], 'getSelfURL()' => [HTTP::getSelfURL()], ], ]; $this->menu->addOption('logout', \SimpleSAML\Utils\Auth::getAdminLogoutURL(), Translate::noop('Log out')); return $this->menu->insert($t); } /** * Display the main admin page. * * @return \SimpleSAML\XHTML\Template */ public function main() { \SimpleSAML\Utils\Auth::requireAdmin(); $t = new \SimpleSAML\XHTML\Template($this->config, 'admin:config.twig'); $t->data = [ 'warnings' => $this->getWarnings(), 'directory' => $this->config->getBaseDir(), 'version' => $this->config->getVersion(), 'links' => [ [ 'href' => \SimpleSAML\Module::getModuleURL('admin/diagnostics'), 'text' => Translate::noop('Diagnostics on hostname, port and protocol') ], [ 'href' => \SimpleSAML\Module::getModuleURL('admin/phpinfo'), 'text' => Translate::noop('Information on your PHP installation') ] ], 'enablematrix' => [ 'saml20idp' => $this->config->getBoolean('enable.saml20-idp', false), 'shib13idp' => $this->config->getBoolean('enable.shib13-idp', false), ], 'funcmatrix' => $this->getPrerequisiteChecks(), 'logouturl' => \SimpleSAML\Utils\Auth::getAdminLogoutURL(), ]; \SimpleSAML\Module::callHooks('configpage', $t); $this->menu->addOption('logout', \SimpleSAML\Utils\Auth::getAdminLogoutURL(), Translate::noop('Log out')); return $this->menu->insert($t); } /** * Display the output of phpinfo(). * * @return RunnableResponse */ public function phpinfo() { return new RunnableResponse('phpinfo'); } /** * Perform a list of checks on the current installation, and return the results as an array. * * The elements in the array returned are also arrays with the following keys: * * - required: Whether this prerequisite is mandatory or not. One of "required" or "optional". * - descr: A translatable text that describes the prerequisite. If the text uses parameters, the value must be an * array where the first value is the text to translate, and the second is a hashed array containing the * parameters needed to properly translate the text. * - enabled: True if the prerequisite is met, false otherwise. * * @return array */ protected function getPrerequisiteChecks() { $matrix = [ [ 'required' => 'required', 'descr' => [ Translate::noop('PHP %minimum% or newer is needed. You are running: %current%'), [ '%minimum%' => '5.5', '%current%' => explode('-', phpversion())[0] ] ], 'enabled' => version_compare(phpversion(), '5.5', '>=') ] ]; $store = $this->config->getString('store.type', ''); // check dependencies used via normal functions $functions = [ 'time' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('Date/Time Extension'), ] ], 'hash' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('Hashing function'), ] ], 'gzinflate' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('ZLib'), ] ], 'openssl_sign' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('OpenSSL'), ] ], 'dom_import_simplexml' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('XML DOM'), ] ], 'preg_match' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('Regular expression support'), ] ], 'json_decode' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('JSON support'), ] ], 'class_implements' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('Standard PHP library (SPL)'), ] ], 'mb_strlen' => [ 'required' => 'required', 'descr' => [ 'required' => Translate::noop('Multibyte String extension'), ] ], 'curl_init' => [ 'required' => $this->config->getBoolean('admin.checkforupdates', true) ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop( 'cURL (might be required by some modules)' ), 'required' => Translate::noop( 'cURL (required if automatic version checks are used, also by some modules)' ), ] ], 'session_start' => [ 'required' => $store === 'phpsession' ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop('Session extension (required if PHP extensions are used)'), 'required' => Translate::noop('Session extension'), ] ], 'pdo_drivers' => [ 'required' => $store === 'sql' ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop('Session extension (required if PHP extensions are used)'), 'required' => Translate::noop('Session extension'), ] ], 'ldap_bind' => [ 'required' => \SimpleSAML\Module::isModuleEnabled('ldap') ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop('LDAP extension (required if an LDAP backend is used)'), 'required' => Translate::noop('LDAP extension'), ] ], 'radius_auth_open' => [ 'required' => \SimpleSAML\Module::isModuleEnabled('radius') ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop('Radius extension (required if a radius backend is used)'), 'required' => Translate::noop('Radius extension'), ] ], ]; foreach ($functions as $function => $description) { $matrix[] = [ 'required' => $description['required'], 'descr' => $description['descr'][$description['required']], 'enabled' => function_exists($function), ]; } // check object-oriented external libraries and extensions $libs = [ [ 'classes' => ['\Predis\Predis'], 'required' => $store === 'redis' ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop('predis/predis (required if the redis data store is used'), 'required' => Translate::noop('predis/predis library'), ] ], [ 'classes' => ['\Memcache', '\Memcached'], 'required' => $store === 'memcache' ? 'required' : 'optional', 'descr' => [ 'optional' => Translate::noop( 'Memcache or Memcached extension (required if the memcache backend is used' ), 'required' => Translate::noop('Memcache or Memcached extension'), ] ] ]; foreach ($libs as $lib) { $enabled = false; foreach ($lib['classes'] as $class) { $enabled |= class_exists($class); } $matrix[] = [ 'required' => $lib['required'], 'descr' => $lib['descr'][$lib['required']], 'enabled' => $enabled, ]; } // perform some basic configuration checks $matrix[] = [ 'required' => 'optional', 'descr' => Translate::noop('The <code>technicalcontact_email</code> configuration option should be set'), 'enabled' => $this->config->getString('technicalcontact_email', 'na@example.org') !== 'na@example.org', ]; $matrix[] = [ 'required' => 'required', 'descr' => Translate::noop('The auth.adminpassword configuration option must be set'), 'enabled' => $this->config->getString('auth.adminpassword', '123') !== '123', ]; return $matrix; } /** * Compile a list of warnings about the current deployment. * * The returned array can contain either strings that can be translated directly, or arrays. If an element is an * array, the first value in that array is a string that can be translated, and the second value will be a hashed * array that contains the substitutions that must be applied to the translation, with its corresponding value. This * can be used in twig like this, assuming an element called "e": * * {{ e[0]|trans(e[1])|raw }} * * @return array */ protected function getWarnings() { $warnings = []; // make sure we're using HTTPS if (!\SimpleSAML\Utils\HTTP::isHTTPS()) { $warnings[] = Translate::noop( '<strong>You are not using HTTPS</strong> to protect communications with your users. HTTP works fine '. 'for testing purposes, but in a production environment you should use HTTPS. <a '. 'href="https://simplesamlphp.org/docs/stable/simplesamlphp-maintenance">Read more about the '. 'maintenance of SimpleSAMLphp</a>.' ); } // make sure we have a secret salt set if ($this->config->getValue('secretsalt') === 'defaultsecretsalt') { $warnings[] = Translate::noop( '<strong>The configuration uses the default secret salt</strong>. Make sure to modify the <code>'. 'secretsalt</code> option in the SimpleSAMLphp configuration in production environments. <a '. 'href="https://simplesamlphp.org/docs/stable/simplesamlphp-install">Read more about the '. 'maintenance of SimpleSAMLphp</a>.' ); } // check for URL limitations if (extension_loaded('suhosin')) { $len = ini_get('suhosin.get.max_value_length'); if (empty($len) || $len < 2048) { $warnings[] = Translate::noop( 'The length of query parameters is limited by the PHP Suhosin extension. Please increase the '. '<code>suhosin.get.max_value_length</code> option in your php.ini to at least 2048 bytes.' ); } } /* * Check for updates. Store the remote result in the session so we don't need to fetch it on every access to * this page. */ if ($this->config->getBoolean('admin.checkforupdates', true) && $this->config->getVersion() !== 'master') { if (!function_exists('curl_init')) { $warnings[] = Translate::noop( 'The cURL PHP extension is missing. Cannot check for SimpleSAMLphp updates.' ); } else { $latest = $this->session->getData(self::LATEST_VERSION_STATE_KEY, "version"); if (!$latest) { $ch = curl_init(self::RELEASES_API); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_USERAGENT, 'SimpleSAMLphp'); curl_setopt($ch, CURLOPT_TIMEOUT, 2); curl_setopt($ch, CURLOPT_PROXY, $this->config->getString('proxy', null)); curl_setopt($ch, CURLOPT_PROXYUSERPWD, $this->config->getValue('proxy.auth', null)); $response = curl_exec($ch); if (curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200) { $latest = json_decode($response, true); $this->session->setData(self::LATEST_VERSION_STATE_KEY, 'version', $latest); } curl_close($ch); } if ($latest && version_compare($this->config->getVersion(), ltrim($latest['tag_name'], 'v'), 'lt')) { $warnings[] = [ Translate::noop( 'You are running an outdated version of SimpleSAMLphp. Please update to <a href="'. '%latest%">the latest version</a> as soon as possible.' ), [ '%latest%' => $latest['html_url'] ] ]; } } } return $warnings; } }