silverstripe-framework/src/Security/SecurityToken.php
Damian Mooyman 3873e4ba00 API Refactor bootstrap, request handling
See https://github.com/silverstripe/silverstripe-framework/pull/7037
and https://github.com/silverstripe/silverstripe-framework/issues/6681

Squashed commit of the following:

commit 8f65e56532
Author: Ingo Schommer <me@chillu.com>
Date:   Thu Jun 22 22:25:50 2017 +1200

    Fixed upgrade guide spelling

commit 76f95944fa
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 22 16:38:34 2017 +1200

    BUG Fix non-test class manifest including sapphiretest / functionaltest

commit 9379834cb4
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 22 15:50:47 2017 +1200

    BUG Fix nesting bug in Kernel

commit 188ce35d82
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 22 15:14:51 2017 +1200

    BUG fix db bootstrapping issues

commit 7ed4660e7a
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 22 14:49:07 2017 +1200

    BUG Fix issue in DetailedErrorFormatter

commit 738f50c497
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 22 11:49:19 2017 +1200

    Upgrading notes on mysite/_config.php

commit 6279d28e5e
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 22 11:43:28 2017 +1200

    Update developer documentation

commit 5c90d53a84
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 22 10:48:44 2017 +1200

    Update installer to not use global databaseConfig

commit f9b2ba4755
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Wed Jun 21 21:04:39 2017 +1200

    Fix behat issues

commit 5b59a912b6
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Wed Jun 21 17:07:11 2017 +1200

    Move HTTPApplication to SilverStripe\Control namespace

commit e2c4a18f63
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Wed Jun 21 16:29:03 2017 +1200

    More documentation
    Fix up remaining tests
    Refactor temp DB into TempDatabase class so it’s available outside of unit tests.

commit 5d235e64f3
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Wed Jun 21 12:13:15 2017 +1200

    API HTTPRequestBuilder::createFromEnvironment() now cleans up live globals
    BUG Fix issue with SSViewer
    Fix Security / View tests

commit d88d4ed4e4
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 20 16:39:43 2017 +1200

    API Refactor AppKernel into CoreKernel

commit f7946aec33
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 20 16:00:40 2017 +1200

    Docs and minor cleanup

commit 12bd31f936
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 20 15:34:34 2017 +1200

    API Remove OutputMiddleware
    API Move environment / global / ini management into Environment class
    API Move getTempFolder into TempFolder class
    API Implement HTTPRequestBuilder / CLIRequestBuilder
    BUG Restore SS_ALLOWED_HOSTS check in original location
    API CoreKernel now requires $basePath to be passed in
    API Refactor installer.php to use application to bootstrap
    API move memstring conversion globals to Convert
    BUG Fix error in CoreKernel nesting not un-nesting itself properly.

commit bba9791146
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Mon Jun 19 18:07:53 2017 +1200

    API Create HTTPMiddleware and standardise middleware for request handling

commit 2a10c2397b
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Mon Jun 19 17:42:42 2017 +1200

    Fixed ORM tests

commit d75a8d1d93
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Mon Jun 19 17:15:07 2017 +1200

    FIx i18n tests

commit 06364af3c3
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Mon Jun 19 16:59:34 2017 +1200

    Fix controller namespace
    Move states to sub namespace

commit 2a278e2953
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Mon Jun 19 12:49:45 2017 +1200

    Fix forms namespace

commit b65c21241b
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 15 18:56:48 2017 +1200

    Update API usages

commit d1d4375c95
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Thu Jun 15 18:41:44 2017 +1200

    API Refactor $flush into HTPPApplication
    API Enforce health check in Controller::pushCurrent()
    API Better global backup / restore
    Updated Director::test() to use new API

commit b220534f06
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 13 22:05:57 2017 +1200

    Move app nesting to a test state helper

commit 603704165c
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 13 21:46:04 2017 +1200

    Restore kernel stack to fix multi-level nesting

commit 2f6336a15b
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 13 17:23:21 2017 +1200

    API Implement kernel nesting

commit fc7188da7d
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 13 15:43:13 2017 +1200

    Fix core tests

commit a0ae723514
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 13 15:23:52 2017 +1200

    Fix manifest tests

commit ca03395251
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 13 15:00:00 2017 +1200

    API Move extension management into test state

commit c66d433977
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Tue Jun 13 14:10:59 2017 +1200

    API Refactor SapphireTest state management into SapphireTestState
    API Remove Injector::unregisterAllObjects()
    API Remove FakeController

commit f26ae75c6e
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Mon Jun 12 18:04:34 2017 +1200

    Implement basic CLI application object

commit 001d559662
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Mon Jun 12 17:39:38 2017 +1200

    Remove references to SapphireTest::is_running_test()
    Upgrade various code

commit de079c041d
Author: Damian Mooyman <damian@silverstripe.com>
Date:   Wed Jun 7 18:07:33 2017 +1200

    API Implement APP object
    API Refactor of Session
2017-06-22 22:50:45 +12:00

291 lines
6.8 KiB
PHP

<?php
namespace SilverStripe\Security;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\Session;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\HiddenField;
use SilverStripe\View\TemplateGlobalProvider;
/**
* Cross Site Request Forgery (CSRF) protection for the {@link Form} class and other GET links.
* Can be used globally (through {@link SecurityToken::inst()})
* or on a form-by-form basis {@link Form->getSecurityToken()}.
*
* <b>Usage in forms</b>
*
* This protective measure is automatically turned on for all new {@link Form} instances,
* and can be globally disabled through {@link disable()}.
*
* <b>Usage in custom controller actions</b>
*
* <code>
* class MyController extends Controller {
* function mygetaction($request) {
* if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
*
* // valid action logic ...
* }
* }
* </code>
*
* @todo Make token name form specific for additional forgery protection.
*/
class SecurityToken implements TemplateGlobalProvider
{
use Configurable;
use Injectable;
/**
* @var string
*/
protected static $default_name = 'SecurityID';
/**
* @var SecurityToken
*/
protected static $inst = null;
/**
* @var boolean
*/
protected static $enabled = true;
/**
* @var String $name
*/
protected $name = null;
/**
* @param string $name
*/
public function __construct($name = null)
{
$this->name = $name ?: self::get_default_name();
}
/**
* Gets a global token (or creates one if it doesnt exist already).
*
* @return SecurityToken
*/
public static function inst()
{
if (!self::$inst) {
self::$inst = new SecurityToken();
}
return self::$inst;
}
/**
* Globally disable the token (override with {@link NullSecurityToken})
* implementation. Note: Does not apply for
*/
public static function disable()
{
self::$enabled = false;
self::$inst = new NullSecurityToken();
}
/**
* Globally enable tokens that have been previously disabled through {@link disable}.
*/
public static function enable()
{
self::$enabled = true;
self::$inst = new SecurityToken();
}
/**
* @return boolean
*/
public static function is_enabled()
{
return self::$enabled;
}
/**
* @return String
*/
public static function get_default_name()
{
return self::$default_name;
}
/**
* Returns the value of an the global SecurityToken in the current session
* @return int
*/
public static function getSecurityID()
{
$token = SecurityToken::inst();
return $token->getValue();
}
/**
* @param string $name
*/
public function setName($name)
{
$val = $this->getValue();
$this->name = $name;
$this->setValue($val);
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* @return String
*/
public function getValue()
{
$session = Controller::curr()->getRequest()->getSession();
$value = $session->get($this->getName());
// only regenerate if the token isn't already set in the session
if (!$value) {
$value = $this->generate();
$this->setValue($value);
}
return $value;
}
/**
* @param String $val
*/
public function setValue($val)
{
$session = Controller::curr()->getRequest()->getSession();
$session->set($this->getName(), $val);
}
/**
* Reset the token to a new value.
*/
public function reset()
{
$this->setValue($this->generate());
}
/**
* Checks for an existing CSRF token in the current users session.
* This check is automatically performed in {@link Form->httpSubmission()}
* if a form has security tokens enabled.
* This direct check is mainly used for URL actions on {@link FormField} that are not routed
* through {@link Form->httpSubmission()}.
*
* Typically you'll want to check {@link Form->securityTokenEnabled()} before calling this method.
*
* @param String $compare
* @return Boolean
*/
public function check($compare)
{
return ($compare && $this->getValue() && $compare == $this->getValue());
}
/**
* See {@link check()}.
*
* @param HTTPRequest $request
* @return bool
*/
public function checkRequest($request)
{
$token = $this->getRequestToken($request);
return $this->check($token);
}
/**
* Get security token from request
*
* @param HTTPRequest $request
* @return string
*/
protected function getRequestToken($request)
{
$name = $this->getName();
$header = 'X-' . ucwords(strtolower($name));
if ($token = $request->getHeader($header)) {
return $token;
}
// Get from request var
return $request->requestVar($name);
}
/**
* Note: Doesn't call {@link FormField->setForm()}
* on the returned {@link HiddenField}, you'll need to take
* care of this yourself.
*
* @param FieldList $fieldset
* @return HiddenField|false
*/
public function updateFieldSet(&$fieldset)
{
if (!$fieldset->fieldByName($this->getName())) {
$field = new HiddenField($this->getName(), null, $this->getValue());
$fieldset->push($field);
return $field;
} else {
return false;
}
}
/**
* @param String $url
* @return String
*/
public function addToUrl($url)
{
return Controller::join_links($url, sprintf('?%s=%s', $this->getName(), $this->getValue()));
}
/**
* You can't disable an existing instance, it will need to be overwritten like this:
* <code>
* $old = SecurityToken::inst(); // isEnabled() returns true
* SecurityToken::disable();
* $new = SecurityToken::inst(); // isEnabled() returns false
* </code>
*
* @return boolean
*/
public function isEnabled()
{
return !($this instanceof NullSecurityToken);
}
/**
* @uses RandomGenerator
*
* @return String
*/
protected function generate()
{
$generator = new RandomGenerator();
return $generator->randomToken('sha1');
}
public static function get_template_global_variables()
{
return array(
'getSecurityID',
'SecurityID' => 'getSecurityID'
);
}
}