silverstripe-framework/security/SecurityToken.php

321 lines
6.5 KiB
PHP
Raw Normal View History

<?php
/**
* @package framework
* @subpackage security
*/
/**
* 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 {
* function mygetaction($request) {
* if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
2014-08-15 08:53:05 +02: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 extends Object implements TemplateGlobalProvider {
2014-08-15 08:53:05 +02:00
/**
* @var String
*/
protected static $default_name = 'SecurityID';
2014-08-15 08:53:05 +02:00
/**
* @var SecurityToken
*/
protected static $inst = null;
2014-08-15 08:53:05 +02:00
/**
* @var boolean
*/
protected static $enabled = true;
2014-08-15 08:53:05 +02:00
/**
* @var String $name
*/
protected $name = null;
2014-08-15 08:53:05 +02:00
/**
* @param $name
*/
public function __construct($name = null) {
$this->name = ($name) ? $name : self::get_default_name();
parent::__construct();
}
2014-08-15 08:53:05 +02:00
/**
* Gets a global token (or creates one if it doesnt exist already).
2014-08-15 08:53:05 +02:00
*
* @return SecurityToken
*/
public static function inst() {
if(!self::$inst) self::$inst = new SecurityToken();
return self::$inst;
}
2014-08-15 08:53:05 +02:00
/**
* Globally disable the token (override with {@link NullSecurityToken})
2014-08-15 08:53:05 +02:00
* 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
/**
* 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
/**
* @return boolean
*/
public static function is_enabled() {
return self::$enabled;
}
2014-08-15 08:53:05 +02:00
/**
* @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();
}
2014-08-15 08:53:05 +02:00
/**
* @return String
*/
public function setName($name) {
$val = $this->getValue();
$this->name = $name;
$this->setValue($val);
}
2014-08-15 08:53:05 +02:00
/**
* @return String
*/
public function getName() {
return $this->name;
}
2014-08-15 08:53:05 +02:00
/**
* @return String
*/
public function getValue() {
$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;
}
2014-08-15 08:53:05 +02:00
/**
* @param String $val
*/
public function setValue($val) {
Session::set($this->getName(), $val);
}
2014-08-15 08:53:05 +02:00
/**
* Reset the token to a new value.
*/
public function reset() {
$this->setValue($this->generate());
}
2014-08-15 08:53:05 +02: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()}.
2014-08-15 08:53:05 +02:00
*
* Typically you'll want to check {@link Form->securityTokenEnabled()} before calling this method.
2014-08-15 08:53:05 +02:00
*
* @param String $compare
* @return Boolean
*/
public function check($compare) {
return ($compare && $this->getValue() && $compare == $this->getValue());
}
2014-08-15 08:53:05 +02:00
/**
* See {@link check()}.
2014-08-15 08:53:05 +02:00
*
* @param SS_HTTPRequest $request
2016-05-09 06:00:43 +02:00
* @return bool
*/
public function checkRequest($request) {
2016-05-09 06:00:43 +02:00
$token = $this->getRequestToken($request);
return $this->check($token);
}
/**
* Get security token from request
*
* @param SS_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);
}
2014-08-15 08:53:05 +02:00
/**
* Note: Doesn't call {@link FormField->setForm()}
* on the returned {@link HiddenField}, you'll need to take
* care of this yourself.
2014-08-15 08:53:05 +02:00
*
* @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
/**
* @param String $url
* @return String
*/
public function addToUrl($url) {
return Controller::join_links($url, sprintf('?%s=%s', $this->getName(), $this->getValue()));
}
2014-08-15 08:53:05 +02: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>
2014-08-15 08:53:05 +02:00
*
* @return boolean
*/
public function isEnabled() {
return !($this instanceof NullSecurityToken);
}
2014-08-15 08:53:05 +02:00
/**
* @uses RandomGenerator
2014-08-15 08:53:05 +02:00
*
* @return String
*/
protected function generate() {
$generator = new RandomGenerator();
return $generator->randomToken('sha1');
}
public static function get_template_global_variables() {
return array(
'getSecurityID',
'SecurityID' => 'getSecurityID'
);
}
}
/**
* Specialized subclass for disabled security tokens - always returns
* TRUE for token checks. Use through {@link SecurityToken::disable()}.
*/
class NullSecurityToken extends SecurityToken {
2014-08-15 08:53:05 +02:00
/**
* @param String
* @return boolean
*/
public function check($compare) {
return true;
}
2014-08-15 08:53:05 +02:00
/**
* @param SS_HTTPRequest $request
* @return Boolean
*/
public function checkRequest($request) {
return true;
}
2014-08-15 08:53:05 +02:00
/**
* @param FieldList $fieldset
* @return false
*/
public function updateFieldSet(&$fieldset) {
// Remove, in case it was added beforehand
$fieldset->removeByName($this->getName());
2014-08-15 08:53:05 +02:00
return false;
}
2014-08-15 08:53:05 +02:00
/**
* @param String $url
* @return String
*/
public function addToUrl($url) {
return $url;
}
2014-08-15 08:53:05 +02:00
/**
* @return String
*/
public function getValue() {
return null;
}
2014-08-15 08:53:05 +02:00
/**
* @param String $val
*/
public function setValue($val) {
// no-op
}
2014-08-15 08:53:05 +02:00
/**
* @return String
*/
public function generate() {
return null;
2014-08-15 08:53:05 +02:00
}
}