/**
  * Test a URL request, returning a response object. This method is the counterpart of
  * Director::direct() that is used in functional testing. It will execute the URL given, and
  * return the result as an HTTPResponse object.
  *
  * @uses Controller::handleRequest() Handles the page logic for a Director::direct() call.
  *
  * @param string $url The URL to visit.
  * @param array $postVars The $_POST & $_FILES variables.
  * @param array|Session $session The {@link Session} object representing the current session.
  * By passing the same object to multiple  calls of Director::test(), you can simulate a persisted
  * session.
  * @param string $httpMethod The HTTP method, such as GET or POST.  It will default to POST if
  * postVars is set, GET otherwise. Overwritten by $postVars['_method'] if present.
  * @param string $body The HTTP body.
  * @param array $headers HTTP headers with key-value pairs.
  * @param array|Cookie_Backend $cookies to populate $_COOKIE.
  * @param HTTPRequest $request The {@see SS_HTTP_Request} object generated as a part of this request.
  *
  * @return HTTPResponse
  *
  * @throws HTTPResponse_Exception
  */
 public static function test($url, $postVars = null, $session = array(), $httpMethod = null, $body = null, $headers = array(), $cookies = array(), &$request = null)
 {
     Config::nest();
     Injector::nest();
     // These are needed so that calling Director::test() does not muck with whoever is calling it.
     // Really, it's some inappropriate coupling and should be resolved by making less use of statics.
     $oldReadingMode = Versioned::get_reading_mode();
     $getVars = array();
     if (!$httpMethod) {
         $httpMethod = $postVars || is_array($postVars) ? "POST" : "GET";
     }
     if (!$session) {
         $session = Injector::inst()->create('SilverStripe\\Control\\Session', array());
     }
     $cookieJar = $cookies instanceof Cookie_Backend ? $cookies : Injector::inst()->createWithArgs('SilverStripe\\Control\\Cookie_Backend', array($cookies ?: array()));
     // Back up the current values of the superglobals
     $existingRequestVars = isset($_REQUEST) ? $_REQUEST : array();
     $existingGetVars = isset($_GET) ? $_GET : array();
     $existingPostVars = isset($_POST) ? $_POST : array();
     $existingSessionVars = isset($_SESSION) ? $_SESSION : array();
     $existingCookies = isset($_COOKIE) ? $_COOKIE : array();
     $existingServer = isset($_SERVER) ? $_SERVER : array();
     $existingRequirementsBackend = Requirements::backend();
     Cookie::config()->update('report_errors', false);
     Requirements::set_backend(Requirements_Backend::create());
     // Set callback to invoke prior to return
     $onCleanup = function () use($existingRequestVars, $existingGetVars, $existingPostVars, $existingSessionVars, $existingCookies, $existingServer, $existingRequirementsBackend, $oldReadingMode) {
         // Restore the super globals
         $_REQUEST = $existingRequestVars;
         $_GET = $existingGetVars;
         $_POST = $existingPostVars;
         $_SESSION = $existingSessionVars;
         $_COOKIE = $existingCookies;
         $_SERVER = $existingServer;
         Requirements::set_backend($existingRequirementsBackend);
         // These are needed so that calling Director::test() does not muck with whoever is calling it.
         // Really, it's some inappropriate coupling and should be resolved by making less use of statics
         Versioned::set_reading_mode($oldReadingMode);
         Injector::unnest();
         // Restore old CookieJar, etc
         Config::unnest();
     };
     if (strpos($url, '#') !== false) {
         $url = substr($url, 0, strpos($url, '#'));
     }
     // Handle absolute URLs
     if (parse_url($url, PHP_URL_HOST)) {
         $bits = parse_url($url);
         // If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
         if (isset($bits['port'])) {
             $_SERVER['HTTP_HOST'] = $bits['host'] . ':' . $bits['port'];
         } else {
             $_SERVER['HTTP_HOST'] = $bits['host'];
         }
     }
     // Ensure URL is properly made relative.
     // Example: url passed is "/ss31/my-page" (prefixed with BASE_URL), this should be changed to "my-page"
     $url = self::makeRelative($url);
     $urlWithQuerystring = $url;
     if (strpos($url, '?') !== false) {
         list($url, $getVarsEncoded) = explode('?', $url, 2);
         parse_str($getVarsEncoded, $getVars);
     }
     // Replace the super globals with appropriate test values
     $_REQUEST = ArrayLib::array_merge_recursive((array) $getVars, (array) $postVars);
     $_GET = (array) $getVars;
     $_POST = (array) $postVars;
     $_SESSION = $session ? $session->inst_getAll() : array();
     $_COOKIE = $cookieJar->getAll(false);
     Injector::inst()->registerService($cookieJar, 'SilverStripe\\Control\\Cookie_Backend');
     $_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
     $request = new HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
     if ($headers) {
         foreach ($headers as $k => $v) {
             $request->addHeader($k, $v);
         }
     }
     // Pre-request filtering
     // @see issue #2517
     $model = DataModel::inst();
     $output = Injector::inst()->get('SilverStripe\\Control\\RequestProcessor')->preRequest($request, $session, $model);
     if ($output === false) {
         $onCleanup();
         throw new HTTPResponse_Exception(_t('Director.INVALID_REQUEST', 'Invalid request'), 400);
     }
     // TODO: Pass in the DataModel
     $result = Director::handleRequest($request, $session, $model);
     // Ensure that the result is an HTTPResponse object
     if (is_string($result)) {
         if (substr($result, 0, 9) == 'redirect:') {
             $response = new HTTPResponse();
             $response->redirect(substr($result, 9));
             $result = $response;
         } else {
             $result = new HTTPResponse($result);
         }
     }
     $output = Injector::inst()->get('SilverStripe\\Control\\RequestProcessor')->postRequest($request, $result, $model);
     if ($output === false) {
         $onCleanup();
         throw new HTTPResponse_Exception("Invalid response");
     }
     // Return valid response
     $onCleanup();
     return $result;
 }
 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 testRememberMeMultipleDevices()
 {
     $m1 = $this->objFromFixture('SilverStripe\\Security\\Member', 'noexpiry');
     // First device
     $m1->login(true);
     Cookie::set('alc_device', null);
     // Second device
     $m1->login(true);
     // Hash of first device
     $firstHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->First();
     $this->assertNotNull($firstHash);
     // Hash of second device
     $secondHash = RememberLoginHash::get()->filter('MemberID', $m1->ID)->Last();
     $this->assertNotNull($secondHash);
     // DeviceIDs are different
     $this->assertNotEquals($firstHash->DeviceID, $secondHash->DeviceID);
     // re-generates the hashes so we can get the tokens
     $firstHash->Hash = $firstHash->getNewHash($m1);
     $firstToken = $firstHash->getToken();
     $firstHash->write();
     $secondHash->Hash = $secondHash->getNewHash($m1);
     $secondToken = $secondHash->getToken();
     $secondHash->write();
     // Accessing the login page should show the user's name straight away
     $response = $this->get('Security/login', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $firstToken, 'alc_device' => $firstHash->DeviceID));
     $message = _t('Member.LOGGEDINAS', "You're logged in as {name}.", array('name' => $m1->FirstName));
     $this->assertContains($message, $response->getBody());
     $this->session()->inst_set('loggedInAs', null);
     // Accessing the login page from the second device
     $response = $this->get('Security/login', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $secondToken, 'alc_device' => $secondHash->DeviceID));
     $this->assertContains($message, $response->getBody());
     // Logging out from the second device - only one device being logged out
     RememberLoginHash::config()->update('logout_across_devices', false);
     $response = $this->get('Security/logout', $this->session(), null, array('alc_enc' => $m1->ID . ':' . $secondToken, 'alc_device' => $secondHash->DeviceID));
     $this->assertEquals(RememberLoginHash::get()->filter(array('MemberID' => $m1->ID, 'DeviceID' => $firstHash->DeviceID))->Count(), 1);
     // Logging out from any device when all login hashes should be removed
     RememberLoginHash::config()->update('logout_across_devices', true);
     $m1->login(true);
     $response = $this->get('Security/logout', $this->session());
     $this->assertEquals(RememberLoginHash::get()->filter('MemberID', $m1->ID)->Count(), 0);
 }
 /**
  * Logs this member out.
  */
 public function logOut()
 {
     $this->extend('beforeMemberLoggedOut');
     Session::clear("loggedInAs");
     if (Member::config()->login_marker_cookie) {
         Cookie::set(Member::config()->login_marker_cookie, null, 0);
     }
     Session::destroy();
     $this->extend('memberLoggedOut');
     // Clears any potential previous hashes for this member
     RememberLoginHash::clear($this, Cookie::get('alc_device'));
     Cookie::set('alc_enc', null);
     // // Clear the Remember Me cookie
     Cookie::force_expiry('alc_enc');
     Cookie::set('alc_device', null);
     Cookie::force_expiry('alc_device');
     // Switch back to live in order to avoid infinite loops when
     // redirecting to the login screen (if this login screen is versioned)
     Session::clear('readingMode');
     $this->write();
     // Audit logging hook
     $this->extend('memberLoggedOut');
 }
 public function inst_destroy($removeCookie = true)
 {
     if (session_id()) {
         if ($removeCookie) {
             $path = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_path') ?: Director::baseURL();
             $domain = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_domain');
             $secure = Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_secure');
             Cookie::force_expiry(session_name(), $path, $domain, $secure, true);
         }
         session_destroy();
         // Clean up the superglobal - session_destroy does not do it.
         // http://nz1.php.net/manual/en/function.session-destroy.php
         unset($_SESSION);
         $this->data = array();
     }
 }
 /**
  * The function that actually sets the cookie using PHP
  *
  * @see http://uk3.php.net/manual/en/function.setcookie.php
  *
  * @param string $name The name of the cookie
  * @param string|array $value The value for the cookie to hold
  * @param int $expiry The number of days until expiry
  * @param string $path The path to save the cookie on (falls back to site base)
  * @param string $domain The domain to make the cookie available on
  * @param boolean $secure Can the cookie only be sent over SSL?
  * @param boolean $httpOnly Prevent the cookie being accessible by JS
  * @return boolean If the cookie was set or not; doesn't mean it's accepted by the browser
  */
 protected function outputCookie($name, $value, $expiry = 90, $path = null, $domain = null, $secure = false, $httpOnly = true)
 {
     // if headers aren't sent, we can set the cookie
     if (!headers_sent($file, $line)) {
         return setcookie($name, $value, $expiry, $path, $domain, $secure, $httpOnly);
     }
     if (Cookie::config()->get('report_errors')) {
         throw new LogicException("Cookie '{$name}' can't be set. The site started outputting content at line {$line} in {$file}");
     }
     return false;
 }
 /**
  * Choose the stage the site is currently on.
  *
  * If $_GET['stage'] is set, then it will use that stage, and store it in
  * the session.
  *
  * if $_GET['archiveDate'] is set, it will use that date, and store it in
  * the session.
  *
  * If neither of these are set, it checks the session, otherwise the stage
  * is set to 'Live'.
  */
 public static function choose_site_stage()
 {
     // Check any pre-existing session mode
     $preexistingMode = Session::get('readingMode');
     // Determine the reading mode
     if (isset($_GET['stage'])) {
         $stage = ucfirst(strtolower($_GET['stage']));
         if (!in_array($stage, array(static::DRAFT, static::LIVE))) {
             $stage = static::LIVE;
         }
         $mode = 'Stage.' . $stage;
     } elseif (isset($_GET['archiveDate']) && strtotime($_GET['archiveDate'])) {
         $mode = 'Archive.' . $_GET['archiveDate'];
     } elseif ($preexistingMode) {
         $mode = $preexistingMode;
     } else {
         $mode = static::DEFAULT_MODE;
     }
     // Save reading mode
     Versioned::set_reading_mode($mode);
     // Try not to store the mode in the session if not needed
     if ($preexistingMode && $preexistingMode !== $mode || !$preexistingMode && $mode !== static::DEFAULT_MODE) {
         Session::set('readingMode', $mode);
     }
     if (!headers_sent() && !Director::is_cli()) {
         if (Versioned::get_stage() == 'Live') {
             // clear the cookie if it's set
             if (Cookie::get('bypassStaticCache')) {
                 Cookie::force_expiry('bypassStaticCache', null, null, false, true);
             }
         } else {
             // set the cookie if it's cleared
             if (!Cookie::get('bypassStaticCache')) {
                 Cookie::set('bypassStaticCache', '1', 0, null, null, false, true);
             }
         }
     }
 }
 /**
  * Check we can remove cookies and we can access their original values
  */
 public function testForceExpiry()
 {
     //load an existing cookie
     $cookieJar = new CookieJar(array('cookieExisting' => 'i woz here'));
     Injector::inst()->registerService($cookieJar, 'SilverStripe\\Control\\Cookie_Backend');
     //make sure it's available
     $this->assertEquals('i woz here', Cookie::get('cookieExisting'));
     //remove the cookie
     Cookie::force_expiry('cookieExisting');
     //check it's gone
     $this->assertEmpty(Cookie::get('cookieExisting'));
     //check we can get it's original value
     $this->assertEquals('i woz here', Cookie::get('cookieExisting', false));
     //check we can add a new cookie and remove it and it doesn't leave any phantom values
     Cookie::set('newCookie', 'i am new');
     //check it's set by not recieved
     $this->assertEquals('i am new', Cookie::get('newCookie'));
     $this->assertEmpty(Cookie::get('newCookie', false));
     //remove it
     Cookie::force_expiry('newCookie');
     //check it's neither set nor reveived
     $this->assertEmpty(Cookie::get('newCookie'));
     $this->assertEmpty(Cookie::get('newCookie', false));
 }
Esempio n. 9
0
 /**
  * Get the name of the database in use
  */
 public static function get_alternative_database_name()
 {
     $name = Cookie::get("alternativeDatabaseName");
     $iv = Cookie::get("alternativeDatabaseNameIv");
     if ($name) {
         $key = Config::inst()->get('SilverStripe\\Security\\Security', 'token');
         if (!$key) {
             throw new LogicException('"Security.token" not found, run "sake dev/generatesecuretoken"');
         }
         if (!function_exists('mcrypt_encrypt')) {
             throw new LogicException('DB::set_alternative_database_name() requires the mcrypt PHP extension');
         }
         $key = md5($key);
         // Ensure key is correct length for chosen cypher
         $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, base64_decode($name), MCRYPT_MODE_CFB, base64_decode($iv));
         return self::valid_alternative_database_name($decrypted) ? $decrypted : false;
     } else {
         return false;
     }
 }