silverstripe-framework/src/Security/SecurityToken.php

311 lines
7.4 KiB
PHP
Raw Normal View History

<?php
2016-06-23 01:37:22 +02:00
namespace SilverStripe\Security;
use Exception;
2017-06-09 05:07:35 +02:00
use SilverStripe\Control\Controller;
2016-09-09 08:43:05 +02:00
use SilverStripe\Control\HTTPRequest;
2017-06-09 05:07:35 +02:00
use SilverStripe\Control\Session;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
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()}.
2014-08-15 08:53:05 +02:00
*
* <b>Usage in forms</b>
2014-08-15 08:53:05 +02:00
*
* This protective measure is automatically turned on for all new {@link Form} instances,
* and can be globally disabled through {@link disable()}.
2014-08-15 08:53:05 +02:00
*
* <b>Usage in custom controller actions</b>
2014-08-15 08:53:05 +02:00
*
* <code>
* class MyController extends Controller {
2016-11-29 00:31:16 +01:00
* function mygetaction($request) {
* if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
2014-08-15 08:53:05 +02:00
*
2016-11-29 00:31:16 +01:00
* // valid action logic ...
* }
* }
* </code>
2014-08-15 08:53:05 +02:00
*
* @todo Make token name form specific for additional forgery protection.
*/
class SecurityToken implements TemplateGlobalProvider
2016-11-29 00:31:16 +01:00
{
use Configurable;
use Injectable;
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @var string
*/
protected static $default_name = 'SecurityID';
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @var SecurityToken
*/
protected static $inst = null;
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @var boolean
*/
protected static $enabled = true;
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @var string $name
2016-11-29 00:31:16 +01:00
*/
protected $name = null;
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
2017-06-09 05:07:35 +02:00
* @param string $name
2016-11-29 00:31:16 +01:00
*/
public function __construct($name = null)
{
2017-06-09 05:07:35 +02:00
$this->name = $name ?: self::get_default_name();
2016-11-29 00:31:16 +01:00
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* 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();
}
2016-11-29 00:31:16 +01:00
return self::$inst;
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* 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();
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* Globally enable tokens that have been previously disabled through {@link disable}.
*/
public static function enable()
{
self::$enabled = true;
self::$inst = new SecurityToken();
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @return boolean
*/
public static function is_enabled()
{
return self::$enabled;
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @return string
2016-11-29 00:31:16 +01:00
*/
public static function get_default_name()
{
return self::$default_name;
}
2016-11-29 00:31:16 +01:00
/**
* Returns the value of an the global SecurityToken in the current session
* @return int
*/
public static function getSecurityID()
{
$token = SecurityToken::inst();
return $token->getValue();
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @param string $name
*/
public function setName($name)
{
$val = $this->getValue();
$this->name = $name;
$this->setValue($val);
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @return string
*/
public function getName()
{
return $this->name;
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @return string
2016-11-29 00:31:16 +01:00
*/
public function getValue()
{
$session = $this->getSession();
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 8f65e5653211240650eaa4fa65bb83b45aae6d58 Author: Ingo Schommer <me@chillu.com> Date: Thu Jun 22 22:25:50 2017 +1200 Fixed upgrade guide spelling commit 76f95944fa89b0b540704b8d744329f690f9698c 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 9379834cb4b2e5177a2600049feec05bf111c16b Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 15:50:47 2017 +1200 BUG Fix nesting bug in Kernel commit 188ce35d82599360c40f0f2de29579c56fb90761 Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 15:14:51 2017 +1200 BUG fix db bootstrapping issues commit 7ed4660e7a63915e8e974deeaba9807bc4d38b0d Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 14:49:07 2017 +1200 BUG Fix issue in DetailedErrorFormatter commit 738f50c497166f81ccbe3f40fbcff895ce71f82f Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 11:49:19 2017 +1200 Upgrading notes on mysite/_config.php commit 6279d28e5e455916f902a2f963c014d8899f7fc7 Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 11:43:28 2017 +1200 Update developer documentation commit 5c90d53a84ef0139c729396949a7857fae60436f Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 22 10:48:44 2017 +1200 Update installer to not use global databaseConfig commit f9b2ba4755371f08bd95f6908ac612fcbb7ca205 Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 21:04:39 2017 +1200 Fix behat issues commit 5b59a912b60282b4dad4ef10ed3b97c5d0a761ac Author: Damian Mooyman <damian@silverstripe.com> Date: Wed Jun 21 17:07:11 2017 +1200 Move HTTPApplication to SilverStripe\Control namespace commit e2c4a18f637bdd3d276619554de60ee8b4d95ced 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 5d235e64f341d6251bfe9f4833f15cc8593c5034 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 d88d4ed4e48291cb65407f222f190064b1f1deeb Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 16:39:43 2017 +1200 API Refactor AppKernel into CoreKernel commit f7946aec3391139ae1b4029c353c327a36552b36 Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 20 16:00:40 2017 +1200 Docs and minor cleanup commit 12bd31f9366327650b5c0c0f96cd0327d44faf0a 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 bba979114624247cf463cf2a8c9e4be9a7c3a772 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 2a10c2397bdc53001013f607b5d38087ce6c0730 Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 17:42:42 2017 +1200 Fixed ORM tests commit d75a8d1d93398af4bd0432df9e4bc6295c15a3fe Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 17:15:07 2017 +1200 FIx i18n tests commit 06364af3c379c931889c4cc34dd920fee3db204a Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 16:59:34 2017 +1200 Fix controller namespace Move states to sub namespace commit 2a278e2953d2dbb19f78d91c919048e1fc935436 Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 19 12:49:45 2017 +1200 Fix forms namespace commit b65c21241bee019730027071d815dbf7571197a4 Author: Damian Mooyman <damian@silverstripe.com> Date: Thu Jun 15 18:56:48 2017 +1200 Update API usages commit d1d4375c95a264a6b63cbaefc2c1d12f808bfd82 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 b220534f06732db4fa940d8724c2a85c0ba2495a Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 22:05:57 2017 +1200 Move app nesting to a test state helper commit 603704165c08d0c1c81fd5e6bb9506326eeee17b Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 21:46:04 2017 +1200 Restore kernel stack to fix multi-level nesting commit 2f6336a15bf79dc8c2edd44cec1931da2dd51c28 Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 17:23:21 2017 +1200 API Implement kernel nesting commit fc7188da7d6ad6785354bab61f08700454c81d91 Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:43:13 2017 +1200 Fix core tests commit a0ae7235148fffd71f2f02d1fe7fe45bf3aa39eb Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:23:52 2017 +1200 Fix manifest tests commit ca033952513633e182040d3d13e1caa9000ca184 Author: Damian Mooyman <damian@silverstripe.com> Date: Tue Jun 13 15:00:00 2017 +1200 API Move extension management into test state commit c66d4339777663a8a04661fea32a0cf35b95d20f 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 f26ae75c6ecaafa0dec1093264e0187191e6764d Author: Damian Mooyman <damian@silverstripe.com> Date: Mon Jun 12 18:04:34 2017 +1200 Implement basic CLI application object commit 001d5596621404892de0a5413392379eff990641 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 de079c041dacd96bc4f4b66421fa2b2cc4c320f8 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 12:50:45 +02:00
$value = $session->get($this->getName());
2016-11-29 00:31:16 +01:00
// only regenerate if the token isn't already set in the session
if (!$value) {
$value = $this->generate();
$this->setValue($value);
}
2016-11-29 00:31:16 +01:00
return $value;
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @param string $val
* @return $this
2016-11-29 00:31:16 +01:00
*/
public function setValue($val)
{
$this->getSession()->set($this->getName(), $val);
return $this;
}
/**
* Returns the current session instance from the injector
*
* @return Session
* @throws Exception If the HTTPRequest class hasn't been registered as a service and no controllers exist
*/
protected function getSession()
{
$injector = Injector::inst();
if ($injector->has(HTTPRequest::class)) {
return $injector->get(HTTPRequest::class)->getSession();
} elseif (Controller::has_curr()) {
return Controller::curr()->getRequest()->getSession();
}
throw new Exception('No HTTPRequest object or controller available yet!');
2016-11-29 00:31:16 +01:00
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* Reset the token to a new value.
*/
public function reset()
{
$this->setValue($this->generate());
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* 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
2016-11-29 00:31:16 +01:00
*/
public function check($compare)
{
return ($compare && $this->getValue() && $compare == $this->getValue());
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* See {@link check()}.
*
* @param HTTPRequest $request
* @return bool
*/
public function checkRequest($request)
{
$token = $this->getRequestToken($request);
return $this->check($token);
}
2016-05-09 06:00:43 +02:00
2016-11-29 00:31:16 +01:00
/**
* Get security token from request
*
* @param HTTPRequest $request
* @return string
*/
protected function getRequestToken($request)
{
$name = $this->getName();
2022-04-14 03:12:59 +02:00
$header = 'X-' . ucwords(strtolower($name ?? ''));
2016-11-29 00:31:16 +01:00
if ($token = $request->getHeader($header)) {
return $token;
}
2016-05-09 06:00:43 +02:00
2016-11-29 00:31:16 +01:00
// Get from request var
return $request->requestVar($name);
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* 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;
}
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @param string $url
* @return string
2016-11-29 00:31:16 +01:00
*/
public function addToUrl($url)
{
return Controller::join_links($url, sprintf('?%s=%s', $this->getName(), $this->getValue()));
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* 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);
}
2014-08-15 08:53:05 +02:00
2016-11-29 00:31:16 +01:00
/**
* @uses RandomGenerator
*
* @return string
2016-11-29 00:31:16 +01:00
*/
protected function generate()
{
$generator = new RandomGenerator();
return $generator->randomToken('sha1');
}
2016-11-29 00:31:16 +01:00
public static function get_template_global_variables()
{
return [
2016-11-29 00:31:16 +01:00
'getSecurityID',
'SecurityID' => 'getSecurityID'
];
2016-11-29 00:31:16 +01:00
}
}