protected function init() { parent::init(); if (!Director::is_cli() && !Permission::check('ADMIN')) { return Security::permissionFailure(); } }
public function getEditForm($id = null, $fields = null) { // TODO Duplicate record fetching (see parent implementation) if (!$id) { $id = $this->currentPageID(); } $form = parent::getEditForm($id); // TODO Duplicate record fetching (see parent implementation) $record = $this->getRecord($id); if ($record && !$record->canView()) { return Security::permissionFailure($this); } $memberList = GridField::create('Members', false, Member::get(), $memberListConfig = GridFieldConfig_RecordEditor::create()->addComponent(new GridFieldButtonRow('after'))->addComponent(new GridFieldExportButton('buttons-after-left')))->addExtraClass("members_grid"); if ($record && method_exists($record, 'getValidator')) { $validator = $record->getValidator(); } else { $validator = Member::singleton()->getValidator(); } $memberListConfig->getComponentByType('GridFieldDetailForm')->setValidator($validator); $groupList = GridField::create('Groups', false, Group::get(), GridFieldConfig_RecordEditor::create()); $columns = $groupList->getConfig()->getComponentByType('GridFieldDataColumns'); $columns->setDisplayFields(array('Breadcrumbs' => singleton('SilverStripe\\Security\\Group')->fieldLabel('Title'))); $columns->setFieldFormatting(array('Breadcrumbs' => function ($val, $item) { return Convert::raw2xml($item->getBreadcrumbs(' > ')); })); $fields = new FieldList($root = new TabSet('Root', $usersTab = new Tab('Users', _t('SecurityAdmin.Users', 'Users'), new LiteralField('MembersCautionText', sprintf('<div class="alert alert-warning" role="alert">%s</div>', _t('SecurityAdmin.MemberListCaution', 'Caution: Removing members from this list will remove them from all groups and the database'))), $memberList), $groupsTab = new Tab('Groups', singleton('SilverStripe\\Security\\Group')->i18n_plural_name(), $groupList)), new HiddenField('ID', false, 0)); // Add import capabilities. Limit to admin since the import logic can affect assigned permissions if (Permission::check('ADMIN')) { $fields->addFieldsToTab('Root.Users', array(new HeaderField(_t('SecurityAdmin.IMPORTUSERS', 'Import users'), 3), new LiteralField('MemberImportFormIframe', sprintf('<iframe src="%s" id="MemberImportFormIframe" width="100%%" height="250px" frameBorder="0">' . '</iframe>', $this->Link('memberimport'))))); $fields->addFieldsToTab('Root.Groups', array(new HeaderField(_t('SecurityAdmin.IMPORTGROUPS', 'Import groups'), 3), new LiteralField('GroupImportFormIframe', sprintf('<iframe src="%s" id="GroupImportFormIframe" width="100%%" height="250px" frameBorder="0">' . '</iframe>', $this->Link('groupimport'))))); } // Tab nav in CMS is rendered through separate template $root->setTemplate('CMSTabSet'); // Add roles editing interface if (Permission::check('APPLY_ROLES')) { $rolesField = GridField::create('Roles', false, PermissionRole::get(), GridFieldConfig_RecordEditor::create()); $rolesTab = $fields->findOrMakeTab('Root.Roles', _t('SecurityAdmin.TABROLES', 'Roles')); $rolesTab->push($rolesField); } $actionParam = $this->getRequest()->param('Action'); if ($actionParam == 'groups') { $groupsTab->addExtraClass('ui-state-active'); } elseif ($actionParam == 'users') { $usersTab->addExtraClass('ui-state-active'); } elseif ($actionParam == 'roles') { $rolesTab->addExtraClass('ui-state-active'); } $actions = new FieldList(); $form = Form::create($this, 'EditForm', $fields, $actions)->setHTMLID('Form_EditForm'); $form->addExtraClass('cms-edit-form'); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); // Tab nav in CMS is rendered through separate template if ($form->Fields()->hasTabset()) { $form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet'); } $form->addExtraClass('center ss-tabset cms-tabset ' . $this->BaseCSSClasses()); $form->setAttribute('data-pjax-fragment', 'CurrentForm'); $this->extend('updateEditForm', $form); return $form; }
public function run($request) { $algo = Security::config()->password_encryption_algorithm; if ($algo == 'none') { $this->debugMessage('Password encryption disabled'); return; } // Are there members with a clear text password? $members = Member::get()->where(array('"Member"."PasswordEncryption"' => 'none', '"Member"."Password" IS NOT NULL')); if (!$members) { $this->debugMessage('No passwords to encrypt'); return; } // Encrypt the passwords... $this->debugMessage('Encrypting all passwords'); $this->debugMessage(sprintf('The passwords will be encrypted using the %s algorithm', $algo)); foreach ($members as $member) { // Force the update of the member record, as new passwords get // automatically encrypted according to the settings, this will do all // the work for us $member->PasswordEncryption = $algo; $member->forceChange(); $member->write(); $this->debugMessage(sprintf('Encrypted credentials for member #%d;', $member->ID)); } }
public function preRequest(HTTPRequest $request, Session $session, DataModel $model) { // Bootstrap session so that Session::get() accesses the right instance $dummyController = new Controller(); $dummyController->setSession($session); $dummyController->setRequest($request); $dummyController->pushCurrent(); // Block non-authenticated users from setting the stage mode if (!Versioned::can_choose_site_stage($request)) { $permissionMessage = sprintf(_t("ContentController.DRAFT_SITE_ACCESS_RESTRICTION", 'You must log in with your CMS password in order to view the draft or archived content. ' . '<a href="%s">Click here to go back to the published site.</a>'), Convert::raw2xml(Controller::join_links(Director::baseURL(), $request->getURL(), "?stage=Live"))); // Force output since RequestFilter::preRequest doesn't support response overriding $response = Security::permissionFailure($dummyController, $permissionMessage); $session->inst_save(); $dummyController->popCurrent(); // Prevent output in testing if (class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test()) { throw new HTTPResponse_Exception($response); } $response->output(); die; } Versioned::choose_site_stage(); $dummyController->popCurrent(); return true; }
public function init() { parent::init(); $canAccess = Director::isDev() || Director::is_cli() || Permission::check("ADMIN"); if (!$canAccess) { return Security::permissionFailure($this); } }
protected function init() { parent::init(); // Unless called from the command line, all CliControllers need ADMIN privileges if (!Director::is_cli() && !Permission::check("ADMIN")) { return Security::permissionFailure(); } }
protected function init() { parent::init(); $isRunningTests = class_exists('SilverStripe\\Dev\\SapphireTest', false) && SapphireTest::is_running_test(); $canAccess = Director::isDev() || Director::is_cli() && !$isRunningTests || Permission::check("ADMIN"); if (!$canAccess) { Security::permissionFailure($this); } }
public function setUp() { parent::setUp(); // Fixtures assume Email is the field used to identify the log in identity Member::config()->unique_identifier_field = 'Email'; Security::$force_database_is_ready = true; // Prevents Member test subclasses breaking ready test Member::config()->lock_out_after_incorrect_logins = 10; }
public function testDefaultAdmin() { $adminMembers = Permission::get_members_by_permission('ADMIN'); $this->assertEquals(0, $adminMembers->count()); $admin = Member::default_admin(); $this->assertInstanceOf('SilverStripe\\Security\\Member', $admin); $this->assertTrue(Permission::checkMember($admin, 'ADMIN')); $this->assertEquals($admin->Email, Security::default_admin_username()); $this->assertNull($admin->Password); }
/** * 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; }
public function run($request) { if (!Permission::check('ADMIN') && !Director::is_cli()) { $response = Security::permissionFailure(); if ($response) { $response->output(); } die; } SapphireTest::delete_all_temp_dbs(); }
public function testCleartextPasswordsAreHashedWithDefaultAlgo() { $loader = new MemberCsvBulkLoader(); $results = $loader->load($this->getCurrentRelativePath() . '/MemberCsvBulkLoaderTest_cleartextpws.csv'); $member = $results->Created()->First(); $memberID = $member->ID; DataObject::flush_and_destroy_cache(); $member = DataObject::get_by_id('SilverStripe\\Security\\Member', $memberID); // TODO Direct getter doesn't work, wtf! $this->assertEquals(Security::config()->password_encryption_algorithm, $member->getField('PasswordEncryption')); $result = $member->checkPassword('mypassword'); $this->assertTrue($result->valid()); }
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."); } }
public function __construct(Controller $controller, $name) { // Set default fields $fields = new FieldList(HiddenField::create("AuthenticationMethod", null, $this->authenticator_class, $this), HiddenField::create('tempid', null, $controller->getRequest()->requestVar('tempid')), PasswordField::create("Password", _t('Member.PASSWORD', 'Password')), LiteralField::create('forgotPassword', sprintf('<p id="ForgotPassword"><a href="%s" target="_top">%s</a></p>', $this->getExternalLink('lostpassword'), _t('CMSMemberLoginForm.BUTTONFORGOTPASSWORD', "Forgot password?")))); if (Security::config()->autologin_enabled) { $fields->push(CheckboxField::create("Remember", _t('Member.REMEMBERME', "Remember me next time?"))); } // Determine returnurl to redirect to parent page $logoutLink = $this->getExternalLink('logout'); if ($returnURL = $controller->getRequest()->requestVar('BackURL')) { $logoutLink = Controller::join_links($logoutLink, '?BackURL=' . urlencode($returnURL)); } // Make actions $actions = new FieldList(FormAction::create('dologin', _t('CMSMemberLoginForm.BUTTONLOGIN', "Log back in")), LiteralField::create('doLogout', sprintf('<p id="doLogout"><a href="%s" target="_top">%s</a></p>', $logoutLink, _t('CMSMemberLoginForm.BUTTONLOGOUT', "Log out")))); parent::__construct($controller, $name, $fields, $actions); }
protected function init() { parent::init(); // Special case for dev/build: Defer permission checks to DatabaseAdmin->init() (see #4957) $requestedDevBuild = stripos($this->getRequest()->getURL(), 'dev/build') === 0 && stripos($this->getRequest()->getURL(), 'dev/build/defaults') === false; // We allow access to this controller regardless of live-status or ADMIN permission only // if on CLI. Access to this controller is always allowed in "dev-mode", or of the user is ADMIN. $canAccess = $requestedDevBuild || Director::isDev() || Director::is_cli() || Permission::check("ADMIN"); if (!$canAccess) { Security::permissionFailure($this); return; } // check for valid url mapping // lacking this information can cause really nasty bugs, // e.g. when running Director::test() from a FunctionalTest instance global $_FILE_TO_URL_MAPPING; if (Director::is_cli()) { if (isset($_FILE_TO_URL_MAPPING)) { $testPath = BASE_PATH; $matched = false; while ($testPath && $testPath != "/" && !preg_match('/^[A-Z]:\\\\$/', $testPath)) { if (isset($_FILE_TO_URL_MAPPING[$testPath])) { $matched = true; break; } $testPath = dirname($testPath); } if (!$matched) { echo 'Warning: You probably want to define ' . 'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"' . "\n"; } } else { echo 'Warning: You probably want to define $_FILE_TO_URL_MAPPING in ' . 'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.org wiki' . "\n"; } } // Backwards compat: Default to "draft" stage, which is important // for tasks like dev/build which call DataObject->requireDefaultRecords(), // but also for other administrative tasks which have assumptions about the default stage. Versioned::set_stage(Versioned::DRAFT); }
/** * Log login attempt * TODO We could handle this with an extension * * @param array $data * @param Member $member * @param bool $success */ protected static function record_login_attempt($data, $member, $success) { if (!Security::config()->login_recording) { return; } // Check email is valid $email = isset($data['Email']) ? $data['Email'] : null; if (is_array($email)) { throw new InvalidArgumentException("Bad email passed to MemberAuthenticator::authenticate(): {$email}"); } $attempt = new LoginAttempt(); if ($success) { // successful login (member is existing with matching password) $attempt->MemberID = $member->ID; $attempt->Status = 'Success'; // Audit logging hook $member->extend('authenticated'); } else { // Failed login - we're trying to see if a user exists with this email (disregarding wrong passwords) $attempt->Status = 'Failure'; if ($member) { // Audit logging hook $attempt->MemberID = $member->ID; $member->extend('authenticationFailed'); } else { // Audit logging hook Member::singleton()->extend('authenticationFailedUnknownUser', $data); } } $attempt->Email = $email; $attempt->IP = Controller::curr()->getRequest()->getIP(); $attempt->write(); }
/** * Test that the default admin can be authenticated */ public function testDefaultAdmin() { // Make form $controller = new Security(); $form = new Form($controller, 'Form', new FieldList(), new FieldList()); // Test correct login $result = MemberAuthenticator::authenticate(array('Email' => 'admin', 'Password' => 'password'), $form); $this->assertNotEmpty($result); $this->assertEquals($result->Email, Security::default_admin_username()); $this->assertEmpty($form->Message()); // Test incorrect login $form->clearMessage(); $result = MemberAuthenticator::authenticate(array('Email' => 'admin', 'Password' => 'notmypassword'), $form); $this->assertEmpty($result); $this->assertEquals('The provided details don't seem to be correct. Please try again.', $form->Message()); $this->assertEquals('bad', $form->MessageType()); }
/** * Forgot password form handler method. * Called when the user clicks on "I've lost my password". * Extensions can use the 'forgotPassword' method to veto executing * the logic, by returning FALSE. In this case, the user will be redirected back * to the form without further action. It is recommended to set a message * in the form detailing why the action was denied. * * @param array $data Submitted data * @return SS_HTTPResponse */ public function forgotPassword($data) { // Ensure password is given if (empty($data['Email'])) { $this->sessionMessage(_t('Member.ENTEREMAIL', 'Please enter an email address to get a password reset link.'), 'bad'); $this->controller->redirect('Security/lostpassword'); return; } // Find existing member $member = Member::get()->filter("Email", $data['Email'])->first(); // Allow vetoing forgot password requests $results = $this->extend('forgotPassword', $member); if ($results && is_array($results) && in_array(false, $results, true)) { return $this->controller->redirect('Security/lostpassword'); } if ($member) { $token = $member->generateAutologinTokenAndStoreHash(); /** @var Email $e */ $e = Email::create(); $e->setSubject(_t('Member.SUBJECTPASSWORDRESET', "Your password reset link", 'Email subject')); $e->setTemplate('ForgotPasswordEmail'); $e->populateTemplate($member); $e->populateTemplate(array('PasswordResetLink' => Security::getPasswordResetLink($member, $token))); $e->setTo($member->Email); $e->send(); $this->controller->redirect('Security/passwordsent/' . urlencode($data['Email'])); } elseif ($data['Email']) { // Avoid information disclosure by displaying the same status, // regardless wether the email address actually exists $this->controller->redirect('Security/passwordsent/' . rawurlencode($data['Email'])); } else { $this->sessionMessage(_t('Member.ENTEREMAIL', 'Please enter an email address to get a password reset link.'), 'bad'); $this->controller->redirect('Security/lostpassword'); } }
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']) { if (!file_exists(BASE_PATH . '/install.php')) { header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error"); die('SilverStripe Framework requires a $databaseConfig defined.'); }
// For SQlite3 memory databases (mainly for testing purposes) if (defined('SS_DATABASE_MEMORY')) { $databaseConfig["memory"] = SS_DATABASE_MEMORY; } } if (defined('SS_SEND_ALL_EMAILS_TO')) { Email::config()->send_all_emails_to = SS_SEND_ALL_EMAILS_TO; } if (defined('SS_SEND_ALL_EMAILS_FROM')) { Email::config()->send_all_emails_from = SS_SEND_ALL_EMAILS_FROM; } if (defined('SS_DEFAULT_ADMIN_USERNAME')) { if (!defined('SS_DEFAULT_ADMIN_PASSWORD')) { user_error("SS_DEFAULT_ADMIN_PASSWORD must be defined in your _ss_environment.php," . "if SS_DEFAULT_ADMIN_USERNAME is defined. See " . "http://doc.silverstripe.org/framework/en/topics/environment-management for more information", E_USER_ERROR); } else { Security::setDefaultAdmin(SS_DEFAULT_ADMIN_USERNAME, SS_DEFAULT_ADMIN_PASSWORD); } } if (defined('SS_USE_BASIC_AUTH') && SS_USE_BASIC_AUTH) { BasicAuth::config()->entire_site_protected = SS_USE_BASIC_AUTH; } if (defined('SS_ERROR_LOG')) { $logger = Injector::inst()->get('Logger'); if ($logger instanceof Logger) { $logger->pushHandler(new StreamHandler(BASE_PATH . '/' . SS_ERROR_LOG, Logger::WARNING)); } else { user_error("SS_ERROR_LOG setting only works with Monolog, you are using another logger", E_USER_WARNING); } } // Allow database adapters to handle their own configuration DatabaseAdapterRegistry::autoconfigure();
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 index() { if (!Permission::check('ADMIN')) { return Security::permissionFailure($this); } return 'Success'; }
/** * Calls {@link SiteTree->getCMSFields()} * * @param Int $id * @param FieldList $fields * @return Form */ public function getEditForm($id = null, $fields = null) { if (!$id) { $id = $this->currentPageID(); } if (is_object($id)) { $record = $id; } else { $record = $this->getRecord($id); if ($record && !$record->canView()) { return Security::permissionFailure($this); } } if ($record) { $fields = $fields ? $fields : $record->getCMSFields(); if ($fields == null) { user_error("getCMSFields() returned null - it should return a FieldList object.\n\t\t\t\t\tPerhaps you forgot to put a return statement at the end of your method?", E_USER_ERROR); } // Add hidden fields which are required for saving the record // and loading the UI state if (!$fields->dataFieldByName('ClassName')) { $fields->push(new HiddenField('ClassName')); } $tree_class = $this->stat('tree_class'); if ($tree_class::has_extension(Hierarchy::class) && !$fields->dataFieldByName('ParentID')) { $fields->push(new HiddenField('ParentID')); } // Added in-line to the form, but plucked into different view by frontend scripts. if ($record instanceof CMSPreviewable) { /** @skipUpgrade */ $navField = new LiteralField('SilverStripeNavigator', $this->getSilverStripeNavigator()); $navField->setAllowHTML(true); $fields->push($navField); } if ($record->hasMethod('getAllCMSActions')) { $actions = $record->getAllCMSActions(); } else { $actions = $record->getCMSActions(); // add default actions if none are defined if (!$actions || !$actions->count()) { if ($record->hasMethod('canEdit') && $record->canEdit()) { $actions->push(FormAction::create('save', _t('CMSMain.SAVE', 'Save'))->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')); } if ($record->hasMethod('canDelete') && $record->canDelete()) { $actions->push(FormAction::create('delete', _t('ModelAdmin.DELETE', 'Delete'))->addExtraClass('ss-ui-action-destructive')); } } } // Use <button> to allow full jQuery UI styling $actionsFlattened = $actions->dataFields(); if ($actionsFlattened) { /** @var FormAction $action */ foreach ($actionsFlattened as $action) { $action->setUseButtonTag(true); } } $negotiator = $this->getResponseNegotiator(); $form = Form::create($this, "EditForm", $fields, $actions)->setHTMLID('Form_EditForm'); $form->addExtraClass('cms-edit-form'); $form->loadDataFrom($record); $form->setTemplate($this->getTemplatesWithSuffix('_EditForm')); $form->setAttribute('data-pjax-fragment', 'CurrentForm'); $form->setValidationResponseCallback(function () use($negotiator, $form) { $request = $this->getRequest(); if ($request->isAjax() && $negotiator) { $form->setupFormErrors(); $result = $form->forTemplate(); return $negotiator->respond($request, array('CurrentForm' => function () use($result) { return $result; })); } }); // Announce the capability so the frontend can decide whether to allow preview or not. if ($record instanceof CMSPreviewable) { $form->addExtraClass('cms-previewable'); } $form->addExtraClass('fill-height'); // Set this if you want to split up tabs into a separate header row // if($form->Fields()->hasTabset()) { // $form->Fields()->findOrMakeTab('Root')->setTemplate('SilverStripe\\Forms\\CMSTabSet'); // } // Add a default or custom validator. // @todo Currently the default Validator.js implementation // adds javascript to the document body, meaning it won't // be included properly if the associated fields are loaded // through ajax. This means only serverside validation // will kick in for pages+validation loaded through ajax. // This will be solved by using less obtrusive javascript validation // in the future, see http://open.silverstripe.com/ticket/2915 and // http://open.silverstripe.com/ticket/3386 if ($record->hasMethod('getCMSValidator')) { $validator = $record->getCMSValidator(); // The clientside (mainly LeftAndMain*.js) rely on ajax responses // which can be evaluated as javascript, hence we need // to override any global changes to the validation handler. if ($validator != NULL) { $form->setValidator($validator); } } else { $form->unsetValidator(); } if ($record->hasMethod('canEdit') && !$record->canEdit()) { $readonlyFields = $form->Fields()->makeReadonly(); $form->setFields($readonlyFields); } } else { $form = $this->EmptyForm(); } return $form; }
/** * Cleanup function to reset all the Filename fields. Visit File/fixfiles to call. */ public function fixfiles() { if (!Permission::check('ADMIN')) { return Security::permissionFailure($this); } $files = DataObject::get("File"); foreach ($files as $file) { $file->updateFilesystem(); echo "<li>", $file->Filename; $file->write(); } echo "<p>Done!"; }
public function getIncludeTemplate($name) { return array("CMSSecurity_{$name}") + parent::getIncludeTemplate($name); }
/** * Check if the user has permissions to run URL debug tools, * else redirect them to log in. */ public static function require_developer_login() { if (Director::isDev()) { return; } if (isset($_SESSION['loggedInAs'])) { // We have to do some raw SQL here, because this method is called in Object::defineMethods(). // This means we have to be careful about what objects we create, as we don't want Object::defineMethods() // being called again. // This basically calls Permission::checkMember($_SESSION['loggedInAs'], 'ADMIN'); // @TODO - Rewrite safely using DataList::filter $memberID = $_SESSION['loggedInAs']; $permission = DB::prepared_query(' SELECT "ID" FROM "Permission" INNER JOIN "Group_Members" ON "Permission"."GroupID" = "Group_Members"."GroupID" WHERE "Permission"."Code" = ? AND "Permission"."Type" = ? AND "Group_Members"."MemberID" = ?', array('ADMIN', Permission::GRANT_PERMISSION, $memberID))->value(); if ($permission) { return; } } // This basically does the same as // Security::permissionFailure(null, "You need to login with developer access to make use of debugging tools.") // We have to do this because of how early this method is called in execution. $_SESSION['SilverStripe\\Security\\Security']['Message']['message'] = "You need to login with developer access to make use of debugging tools."; $_SESSION['SilverStripe\\Security\\Security']['Message']['type'] = 'warning'; $_SESSION['BackURL'] = $_SERVER['REQUEST_URI']; header($_SERVER['SERVER_PROTOCOL'] . " 302 Found"); header("Location: " . Director::baseURL() . Security::login_url()); die; }
/** * Event handler called before writing to the database. */ public function onBeforeWrite() { if ($this->SetPassword) { $this->Password = $this->SetPassword; } // If a member with the same "unique identifier" already exists with a different ID, don't allow merging. // Note: This does not a full replacement for safeguards in the controller layer (e.g. in a registration form), // but rather a last line of defense against data inconsistencies. $identifierField = Member::config()->unique_identifier_field; if ($this->{$identifierField}) { // Note: Same logic as Member_Validator class $filter = array("\"{$identifierField}\"" => $this->{$identifierField}); if ($this->ID) { $filter[] = array('"Member"."ID" <> ?' => $this->ID); } $existingRecord = DataObject::get_one('SilverStripe\\Security\\Member', $filter); if ($existingRecord) { throw new ValidationException(ValidationResult::create(false, _t('Member.ValidationIdentifierFailed', 'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))', 'Values in brackets show "fieldname = value", usually denoting an existing email address', array('id' => $existingRecord->ID, 'name' => $identifierField, 'value' => $this->{$identifierField})))); } } // We don't send emails out on dev/tests sites to prevent accidentally spamming users. // However, if TestMailer is in use this isn't a risk. if ((Director::isLive() || Email::mailer() instanceof TestMailer) && $this->isChanged('Password') && $this->record['Password'] && $this->config()->notify_password_change) { /** @var Email $e */ $e = Email::create(); $e->setSubject(_t('Member.SUBJECTPASSWORDCHANGED', "Your password has been changed", 'Email subject')); $e->setTemplate('ChangePasswordEmail'); $e->populateTemplate($this); $e->setTo($this->Email); $e->send(); } // The test on $this->ID is used for when records are initially created. // Note that this only works with cleartext passwords, as we can't rehash // existing passwords. if (!$this->ID && $this->Password || $this->isChanged('Password')) { // Password was changed: encrypt the password according the settings $encryption_details = Security::encrypt_password($this->Password, $this->Salt, $this->PasswordEncryption ? $this->PasswordEncryption : Security::config()->password_encryption_algorithm, $this); // Overwrite the Password property with the hashed value $this->Password = $encryption_details['password']; $this->Salt = $encryption_details['salt']; $this->PasswordEncryption = $encryption_details['algorithm']; // If we haven't manually set a password expiry if (!$this->isChanged('PasswordExpiry')) { // then set it for us if (self::config()->password_expiry_days) { $this->PasswordExpiry = date('Y-m-d', time() + 86400 * self::config()->password_expiry_days); } else { $this->PasswordExpiry = null; } } } // save locale if (!$this->Locale) { $this->Locale = i18n::get_locale(); } parent::onBeforeWrite(); }
/** * Handles URL requests. * * - ViewableData::handleRequest() iterates through each rule in {@link self::$url_handlers}. * - If the rule matches, the named method will be called. * - If there is still more URL to be processed, then handleRequest() * is called on the object that that method returns. * * Once all of the URL has been processed, the final result is returned. * However, if the final result is an array, this * array is interpreted as being additional template data to customise the * 2nd to last result with, rather than an object * in its own right. This is most frequently used when a Controller's * action will return an array of data with which to * customise the controller. * * @param SS_HTTPRequest $request The object that is reponsible for distributing URL parsing * @param DataModel $model * @return SS_HTTPResponse|RequestHandler|string|array */ public function handleRequest(SS_HTTPRequest $request, DataModel $model) { // $handlerClass is used to step up the class hierarchy to implement url_handlers inheritance $handlerClass = $this->class ? $this->class : get_class($this); if ($this->brokenOnConstruct) { user_error("parent::__construct() needs to be called on {$handlerClass}::__construct()", E_USER_WARNING); } $this->setRequest($request); $this->setDataModel($model); $match = $this->findAction($request); // If nothing matches, return this object if (!$match) { return $this; } // Start to find what action to call. Start by using what findAction returned $action = $match['action']; // We used to put "handleAction" as the action on controllers, but (a) this could only be called when // you had $Action in your rule, and (b) RequestHandler didn't have one. $Action is better if ($action == 'handleAction') { // TODO Fix LeftAndMain usage // Deprecation::notice('3.2.0', 'Calling handleAction directly is deprecated - use $Action instead'); $action = '$Action'; } // Actions can reference URL parameters, eg, '$Action/$ID/$OtherID' => '$Action', if ($action[0] == '$') { $action = str_replace("-", "_", $request->latestParam(substr($action, 1))); } if (!$action) { if (isset($_REQUEST['debug_request'])) { Debug::message("Action not set; using default action method name 'index'"); } $action = "index"; } else { if (!is_string($action)) { user_error("Non-string method name: " . var_export($action, true), E_USER_ERROR); } } $classMessage = Director::isLive() ? 'on this handler' : 'on class ' . get_class($this); try { if (!$this->hasAction($action)) { return $this->httpError(404, "Action '{$action}' isn't available {$classMessage}."); } if (!$this->checkAccessAction($action) || in_array(strtolower($action), array('run', 'doInit'))) { return $this->httpError(403, "Action '{$action}' isn't allowed {$classMessage}."); } $result = $this->handleAction($request, $action); } catch (SS_HTTPResponse_Exception $e) { return $e->getResponse(); } catch (PermissionFailureException $e) { $result = Security::permissionFailure(null, $e->getMessage()); } if ($result instanceof SS_HTTPResponse && $result->isError()) { if (isset($_REQUEST['debug_request'])) { Debug::message("Rule resulted in HTTP error; breaking"); } return $result; } // If we return a RequestHandler, call handleRequest() on that, even if there is no more URL to // parse. It might have its own handler. However, we only do this if we haven't just parsed an // empty rule ourselves, to prevent infinite loops. Also prevent further handling of controller // actions which return themselves to avoid infinite loops. $matchedRuleWasEmpty = $request->isEmptyPattern($match['rule']); $resultIsRequestHandler = is_object($result) && $result instanceof RequestHandler; if ($this !== $result && !$matchedRuleWasEmpty && $resultIsRequestHandler) { $returnValue = $result->handleRequest($request, $model); // Array results can be used to handle if (is_array($returnValue)) { $returnValue = $this->customise($returnValue); } return $returnValue; // If we return some other data, and all the URL is parsed, then return that } else { if ($request->allParsed()) { return $result; // But if we have more content on the URL and we don't know what to do with it, return an error. } else { return $this->httpError(404, "I can't handle sub-URLs {$classMessage}."); } } return $this; }
/** * Register that we've had a permission failure trying to view the given page * * This will redirect to a login page. * If you don't provide a messageSet, a default will be used. * * @param Controller $controller The controller that you were on to cause the permission * failure. * @param string|array $messageSet The message to show to the user. This * can be a string, or a map of different * messages for different contexts. * If you pass an array, you can use the * following keys: * - default: The default message * - alreadyLoggedIn: The message to * show if the user * is already logged * in and lacks the * permission to * access the item. * * The alreadyLoggedIn value can contain a '%s' placeholder that will be replaced with a link * to log in. * @return SS_HTTPResponse */ public static function permissionFailure($controller = null, $messageSet = null) { self::set_ignore_disallowed_actions(true); if (!$controller) { $controller = Controller::curr(); } if (Director::is_ajax()) { $response = $controller ? $controller->getResponse() : new SS_HTTPResponse(); $response->setStatusCode(403); if (!Member::currentUser()) { $response->setBody(_t('ContentController.NOTLOGGEDIN', 'Not logged in')); $response->setStatusDescription(_t('ContentController.NOTLOGGEDIN', 'Not logged in')); // Tell the CMS to allow re-aunthentication if (CMSSecurity::enabled()) { $response->addHeader('X-Reauthenticate', '1'); } } return $response; } // Prepare the messageSet provided if (!$messageSet) { if ($configMessageSet = static::config()->get('default_message_set')) { $messageSet = $configMessageSet; } else { $messageSet = array('default' => _t('Security.NOTEPAGESECURED', "That page is secured. Enter your credentials below and we will send " . "you right along."), 'alreadyLoggedIn' => _t('Security.ALREADYLOGGEDIN', "You don't have access to this page. If you have another account that " . "can access that page, you can log in again below.", "%s will be replaced with a link to log in.")); } } if (!is_array($messageSet)) { $messageSet = array('default' => $messageSet); } $member = Member::currentUser(); // Work out the right message to show if ($member && $member->exists()) { $response = $controller ? $controller->getResponse() : new SS_HTTPResponse(); $response->setStatusCode(403); //If 'alreadyLoggedIn' is not specified in the array, then use the default //which should have been specified in the lines above if (isset($messageSet['alreadyLoggedIn'])) { $message = $messageSet['alreadyLoggedIn']; } else { $message = $messageSet['default']; } // Somewhat hackish way to render a login form with an error message. $me = new Security(); $form = $me->LoginForm(); $form->sessionMessage($message, 'warning'); Session::set('MemberLoginForm.force_message', 1); $loginResponse = $me->login(); if ($loginResponse instanceof SS_HTTPResponse) { return $loginResponse; } $response->setBody((string) $loginResponse); $controller->extend('permissionDenied', $member); return $response; } else { $message = $messageSet['default']; } Session::set("Security.Message.message", $message); Session::set("Security.Message.type", 'warning'); Session::set("BackURL", $_SERVER['REQUEST_URI']); // TODO AccessLogEntry needs an extension to handle permission denied errors // Audit logging hook $controller->extend('permissionDenied', $member); return $controller->redirect(Config::inst()->get('SilverStripe\\Security\\Security', 'login_url') . "?BackURL=" . urlencode($_SERVER['REQUEST_URI'])); }