/** * Require basic authentication. Will request a username and password if none is given. * * Used by {@link Controller::init()}. * * @throws HTTPResponse_Exception * * @param string $realm * @param string|array $permissionCode Optional * @param boolean $tryUsingSessionLogin If true, then the method with authenticate against the * session log-in if those credentials are disabled. * @return Member|bool $member */ public static function requireLogin($realm, $permissionCode = null, $tryUsingSessionLogin = true) { $isRunningTests = class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test(); if (!Security::database_is_ready() || Director::is_cli() && !$isRunningTests) { return true; } /* * Enable HTTP Basic authentication workaround for PHP running in CGI mode with Apache * Depending on server configuration the auth header may be in HTTP_AUTHORIZATION or * REDIRECT_HTTP_AUTHORIZATION * * The follow rewrite rule must be in the sites .htaccess file to enable this workaround * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] */ $authHeader = isset($_SERVER['HTTP_AUTHORIZATION']) ? $_SERVER['HTTP_AUTHORIZATION'] : (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) ? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] : null); $matches = array(); if ($authHeader && preg_match('/Basic\\s+(.*)$/i', $authHeader, $matches)) { list($name, $password) = explode(':', base64_decode($matches[1])); $_SERVER['PHP_AUTH_USER'] = strip_tags($name); $_SERVER['PHP_AUTH_PW'] = strip_tags($password); } $member = null; if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) { $member = MemberAuthenticator::authenticate(array('Email' => $_SERVER['PHP_AUTH_USER'], 'Password' => $_SERVER['PHP_AUTH_PW']), null); } if (!$member && $tryUsingSessionLogin) { $member = Member::currentUser(); } // If we've failed the authentication mechanism, then show the login form if (!$member) { $response = new HTTPResponse(null, 401); $response->addHeader('WWW-Authenticate', "Basic realm=\"{$realm}\""); if (isset($_SERVER['PHP_AUTH_USER'])) { $response->setBody(_t('BasicAuth.ERRORNOTREC', "That username / password isn't recognised")); } else { $response->setBody(_t('BasicAuth.ENTERINFO', "Please enter a username and password.")); } // Exception is caught by RequestHandler->handleRequest() and will halt further execution $e = new HTTPResponse_Exception(null, 401); $e->setResponse($response); throw $e; } if ($permissionCode && !Permission::checkMember($member->ID, $permissionCode)) { $response = new HTTPResponse(null, 401); $response->addHeader('WWW-Authenticate', "Basic realm=\"{$realm}\""); if (isset($_SERVER['PHP_AUTH_USER'])) { $response->setBody(_t('BasicAuth.ERRORNOTADMIN', "That user is not an administrator.")); } // Exception is caught by RequestHandler->handleRequest() and will halt further execution $e = new HTTPResponse_Exception(null, 401); $e->setResponse($response); throw $e; } return $member; }
protected function init() { parent::init(); // We allow access to this controller regardless of live-status or ADMIN permission only // if on CLI or with the database not ready. The latter makes it less errorprone to do an // initial schema build without requiring a default-admin login. // Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. $isRunningTests = class_exists('SapphireTest', false) && SapphireTest::is_running_test(); $canAccess = Director::isDev() || !Security::database_is_ready() || Director::is_cli() && !$isRunningTests || Permission::check("ADMIN"); if (!$canAccess) { return Security::permissionFailure($this, "This page is secured and you need administrator rights to access it. " . "Enter your credentials below and we will send you right along."); } }
require_once 'core/Core.php'; // Connect to database global $databaseConfig; if ($databaseConfig) { DB::connect($databaseConfig); } // Check if a token is requesting a redirect if (!$reloadToken) { return; } // Otherwise, we start up the session if needed if (!isset($_SESSION) && Session::request_contains_session_id()) { Session::start(); } // Next, check if we're in dev mode, or the database doesn't have any security data, or we are admin if (Director::isDev() || !Security::database_is_ready() || Permission::check('ADMIN')) { return $reloadToken->reloadWithToken(); } // Fail and redirect the user to the login page $loginPage = Director::absoluteURL(Security::config()->login_url); $loginPage .= "?BackURL=" . urlencode($_SERVER['REQUEST_URI']); header('location: ' . $loginPage, true, 302); die; })->thenIfErrored(function () use($reloadToken) { if ($reloadToken) { $reloadToken->reloadWithToken(); } })->execute(); global $databaseConfig; // Redirect to the installer if no database is selected if (!isset($databaseConfig) || !isset($databaseConfig['database']) || !$databaseConfig['database']) {
public function setUp() { //nest config and injector for each test so they are effectively sandboxed per test Config::nest(); Injector::nest(); $this->originalReadingMode = Versioned::get_reading_mode(); // We cannot run the tests on this abstract class. if (get_class($this) == "SapphireTest") { $this->markTestSkipped(sprintf('Skipping %s ', get_class($this))); return; } // Mark test as being run $this->originalIsRunningTest = self::$is_running_test; self::$is_running_test = true; // i18n needs to be set to the defaults or tests fail i18n::set_locale(Config::inst()->get('i18n', 'default_locale')); i18n::config()->date_format = null; i18n::config()->time_format = null; // Set default timezone consistently to avoid NZ-specific dependencies date_default_timezone_set('UTC'); // Remove password validation $this->originalMemberPasswordValidator = Member::password_validator(); $this->originalRequirements = Requirements::backend(); Member::set_password_validator(null); Config::inst()->update('Cookie', 'report_errors', false); if (class_exists('SilverStripe\\CMS\\Controllers\\RootURLController')) { RootURLController::reset(); } if (class_exists('Translatable')) { Translatable::reset(); } Versioned::reset(); DataObject::reset(); if (class_exists('SilverStripe\\CMS\\Model\\SiteTree')) { SiteTree::reset(); } Hierarchy::reset(); if (Controller::has_curr()) { Controller::curr()->setSession(Injector::inst()->create('Session', array())); } Security::$database_is_ready = null; // Add controller-name auto-routing Config::inst()->update('Director', 'rules', array('$Controller//$Action/$ID/$OtherID' => '*')); $fixtureFile = static::get_fixture_file(); $prefix = defined('SS_DATABASE_PREFIX') ? SS_DATABASE_PREFIX : 'ss_'; // Todo: this could be a special test model $this->model = DataModel::inst(); // Set up fixture if ($fixtureFile || $this->usesDatabase) { if (!self::using_temp_db()) { self::create_temp_db(); } singleton('SilverStripe\\ORM\\DataObject')->flushCache(); self::empty_temp_db(); foreach ($this->requireDefaultRecordsFrom as $className) { $instance = singleton($className); if (method_exists($instance, 'requireDefaultRecords')) { $instance->requireDefaultRecords(); } if (method_exists($instance, 'augmentDefaultRecords')) { $instance->augmentDefaultRecords(); } } if ($fixtureFile) { $pathForClass = $this->getCurrentAbsolutePath(); $fixtureFiles = is_array($fixtureFile) ? $fixtureFile : array($fixtureFile); $i = 0; foreach ($fixtureFiles as $fixtureFilePath) { // Support fixture paths relative to the test class, rather than relative to webroot // String checking is faster than file_exists() calls. $isRelativeToFile = strpos('/', $fixtureFilePath) === false || preg_match('/^\\.\\./', $fixtureFilePath); if ($isRelativeToFile) { $resolvedPath = realpath($pathForClass . '/' . $fixtureFilePath); if ($resolvedPath) { $fixtureFilePath = $resolvedPath; } } $fixture = Injector::inst()->create('YamlFixture', $fixtureFilePath); $fixture->writeInto($this->getFixtureFactory()); $this->fixtures[] = $fixture; // backwards compatibility: Load first fixture into $this->fixture if ($i == 0) { $this->fixture = $fixture; } $i++; } } $this->logInWithPermission("ADMIN"); } // Preserve memory settings $this->originalMemoryLimit = ini_get('memory_limit'); // turn off template debugging Config::inst()->update('SSViewer', 'source_file_comments', false); // Clear requirements Requirements::clear(); // Set up email $this->originalMailer = Email::mailer(); $this->mailer = new TestMailer(); Injector::inst()->registerService($this->mailer, 'Mailer'); Config::inst()->remove('Email', 'send_all_emails_to'); }
public function setUp() { //nest config and injector for each test so they are effectively sandboxed per test Config::nest(); Injector::nest(); $this->originalReadingMode = Versioned::get_reading_mode(); // We cannot run the tests on this abstract class. if (get_class($this) == __CLASS__) { $this->markTestSkipped(sprintf('Skipping %s ', get_class($this))); return; } // Mark test as being run $this->originalIsRunningTest = self::$is_running_test; self::$is_running_test = true; // i18n needs to be set to the defaults or tests fail i18n::set_locale(i18n::config()->get('default_locale')); i18n::config()->date_format = null; i18n::config()->time_format = null; // Set default timezone consistently to avoid NZ-specific dependencies date_default_timezone_set('UTC'); // Remove password validation $this->originalMemberPasswordValidator = Member::password_validator(); $this->originalRequirements = Requirements::backend(); Member::set_password_validator(null); Cookie::config()->update('report_errors', false); if (class_exists('SilverStripe\\CMS\\Controllers\\RootURLController')) { RootURLController::reset(); } if (class_exists('Translatable')) { Translatable::reset(); } Versioned::reset(); DataObject::reset(); if (class_exists('SilverStripe\\CMS\\Model\\SiteTree')) { SiteTree::reset(); } Hierarchy::reset(); if (Controller::has_curr()) { Controller::curr()->setSession(Session::create(array())); } Security::$database_is_ready = null; // Add controller-name auto-routing // @todo Fix to work with namespaced controllers Director::config()->update('rules', array('$Controller//$Action/$ID/$OtherID' => '*')); $fixtureFiles = $this->getFixturePaths(); // Todo: this could be a special test model $this->model = DataModel::inst(); // Set up fixture if ($fixtureFiles || $this->usesDatabase) { if (!self::using_temp_db()) { self::create_temp_db(); } DataObject::singleton()->flushCache(); self::empty_temp_db(); foreach ($this->requireDefaultRecordsFrom as $className) { $instance = singleton($className); if (method_exists($instance, 'requireDefaultRecords')) { $instance->requireDefaultRecords(); } if (method_exists($instance, 'augmentDefaultRecords')) { $instance->augmentDefaultRecords(); } } foreach ($fixtureFiles as $fixtureFilePath) { $fixture = YamlFixture::create($fixtureFilePath); $fixture->writeInto($this->getFixtureFactory()); } $this->logInWithPermission("ADMIN"); } // Preserve memory settings $this->originalMemoryLimit = ini_get('memory_limit'); // turn off template debugging SSViewer::config()->update('source_file_comments', false); // Clear requirements Requirements::clear(); // Set up email $this->mailer = new TestMailer(); Injector::inst()->registerService($this->mailer, 'SilverStripe\\Control\\Email\\Mailer'); Email::config()->remove('send_all_emails_to'); }
public function testDatabaseIsReadyWithInsufficientMemberColumns() { $old = Security::$force_database_is_ready; Security::$force_database_is_ready = null; Security::$database_is_ready = false; DBClassName::clear_classname_cache(); // Assumption: The database has been built correctly by the test runner, // and has all columns present in the ORM /** @skipUpgrade */ DB::get_schema()->renameField('Member', 'Email', 'Email_renamed'); // Email column is now missing, which means we're not ready to do permission checks $this->assertFalse(Security::database_is_ready()); // Rebuild the database (which re-adds the Email column), and try again $this->resetDBSchema(true); $this->assertTrue(Security::database_is_ready()); Security::$force_database_is_ready = $old; }
/** * Log the user in if the "remember login" cookie is set * * The <i>remember login token</i> will be changed on every successful * auto-login. */ public static function autoLogin() { // Don't bother trying this multiple times if (!class_exists('SapphireTest', false) || !SapphireTest::is_running_test()) { self::$_already_tried_to_auto_log_in = true; } if (strpos(Cookie::get('alc_enc'), ':') === false || Session::get("loggedInAs") || !Security::database_is_ready()) { return; } if (strpos(Cookie::get('alc_enc'), ':') && Cookie::get('alc_device') && !Session::get("loggedInAs")) { list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2); if (!$uid || !$token) { return; } $deviceID = Cookie::get('alc_device'); $member = Member::get()->byID($uid); $rememberLoginHash = null; // check if autologin token matches if ($member) { $hash = $member->encryptWithUserSettings($token); $rememberLoginHash = RememberLoginHash::get()->filter(array('MemberID' => $member->ID, 'DeviceID' => $deviceID, 'Hash' => $hash))->First(); if (!$rememberLoginHash) { $member = null; } else { // Check for expired token $expiryDate = new DateTime($rememberLoginHash->ExpiryDate); $now = DBDatetime::now(); $now = new DateTime($now->Rfc2822()); if ($now > $expiryDate) { $member = null; } } } if ($member) { self::session_regenerate_id(); Session::set("loggedInAs", $member->ID); // This lets apache rules detect whether the user has logged in if (Member::config()->login_marker_cookie) { Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true); } if ($rememberLoginHash) { $rememberLoginHash->renew(); $tokenExpiryDays = Config::inst()->get('SilverStripe\\Security\\RememberLoginHash', 'token_expiry_days'); Cookie::set('alc_enc', $member->ID . ':' . $rememberLoginHash->getToken(), $tokenExpiryDays, null, null, false, true); } $member->write(); // Audit logging hook $member->extend('memberAutoLoggedIn'); } } }