/security/SecurityToken.php

https://github.com/sminnee/silverstripe-framework · PHP · 302 lines · 109 code · 36 blank · 157 comment · 7 complexity · c4c20ae8ca9a8cbf6343a21ee3cc59c0 MD5 · raw file

  1. <?php
  2. /**
  3. * @package framework
  4. * @subpackage security
  5. */
  6. /**
  7. * Cross Site Request Forgery (CSRF) protection for the {@link Form} class and other GET links.
  8. * Can be used globally (through {@link SecurityToken::inst()})
  9. * or on a form-by-form basis {@link Form->getSecurityToken()}.
  10. *
  11. * <b>Usage in forms</b>
  12. *
  13. * This protective measure is automatically turned on for all new {@link Form} instances,
  14. * and can be globally disabled through {@link disable()}.
  15. *
  16. * <b>Usage in custom controller actions</b>
  17. *
  18. * <code>
  19. * class MyController extends Controller {
  20. * function mygetaction($request) {
  21. * if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
  22. *
  23. * // valid action logic ...
  24. * }
  25. * }
  26. * </code>
  27. *
  28. * @todo Make token name form specific for additional forgery protection.
  29. */
  30. class SecurityToken extends Object implements TemplateGlobalProvider {
  31. /**
  32. * @var String
  33. */
  34. protected static $default_name = 'SecurityID';
  35. /**
  36. * @var SecurityToken
  37. */
  38. protected static $inst = null;
  39. /**
  40. * @var boolean
  41. */
  42. protected static $enabled = true;
  43. /**
  44. * @var String $name
  45. */
  46. protected $name = null;
  47. /**
  48. * @param $name
  49. */
  50. public function __construct($name = null) {
  51. $this->name = ($name) ? $name : self::get_default_name();
  52. parent::__construct();
  53. }
  54. /**
  55. * Gets a global token (or creates one if it doesnt exist already).
  56. *
  57. * @return SecurityToken
  58. */
  59. public static function inst() {
  60. if(!self::$inst) self::$inst = new SecurityToken();
  61. return self::$inst;
  62. }
  63. /**
  64. * Globally disable the token (override with {@link NullSecurityToken})
  65. * implementation. Note: Does not apply for
  66. */
  67. public static function disable() {
  68. self::$enabled = false;
  69. self::$inst = new NullSecurityToken();
  70. }
  71. /**
  72. * Globally enable tokens that have been previously disabled through {@link disable}.
  73. */
  74. public static function enable() {
  75. self::$enabled = true;
  76. self::$inst = new SecurityToken();
  77. }
  78. /**
  79. * @return boolean
  80. */
  81. public static function is_enabled() {
  82. return self::$enabled;
  83. }
  84. /**
  85. * @return String
  86. */
  87. public static function get_default_name() {
  88. return self::$default_name;
  89. }
  90. /**
  91. * Returns the value of an the global SecurityToken in the current session
  92. * @return int
  93. */
  94. public static function getSecurityID() {
  95. $token = SecurityToken::inst();
  96. return $token->getValue();
  97. }
  98. /**
  99. * @return String
  100. */
  101. public function setName($name) {
  102. $val = $this->getValue();
  103. $this->name = $name;
  104. $this->setValue($val);
  105. }
  106. /**
  107. * @return String
  108. */
  109. public function getName() {
  110. return $this->name;
  111. }
  112. /**
  113. * @return String
  114. */
  115. public function getValue() {
  116. $value = Session::get($this->getName());
  117. // only regenerate if the token isn't already set in the session
  118. if(!$value) {
  119. $value = $this->generate();
  120. $this->setValue($value);
  121. }
  122. return $value;
  123. }
  124. /**
  125. * @param String $val
  126. */
  127. public function setValue($val) {
  128. Session::set($this->getName(), $val);
  129. }
  130. /**
  131. * Reset the token to a new value.
  132. */
  133. public function reset() {
  134. $this->setValue($this->generate());
  135. }
  136. /**
  137. * Checks for an existing CSRF token in the current users session.
  138. * This check is automatically performed in {@link Form->httpSubmission()}
  139. * if a form has security tokens enabled.
  140. * This direct check is mainly used for URL actions on {@link FormField} that are not routed
  141. * through {@link Form->httpSubmission()}.
  142. *
  143. * Typically you'll want to check {@link Form->securityTokenEnabled()} before calling this method.
  144. *
  145. * @param String $compare
  146. * @return Boolean
  147. */
  148. public function check($compare) {
  149. return ($compare && $this->getValue() && $compare == $this->getValue());
  150. }
  151. /**
  152. * See {@link check()}.
  153. *
  154. * @param SS_HTTPRequest $request
  155. * @return Boolean
  156. */
  157. public function checkRequest($request) {
  158. return $this->check($request->requestVar($this->getName()));
  159. }
  160. /**
  161. * Note: Doesn't call {@link FormField->setForm()}
  162. * on the returned {@link HiddenField}, you'll need to take
  163. * care of this yourself.
  164. *
  165. * @param FieldList $fieldset
  166. * @return HiddenField|false
  167. */
  168. public function updateFieldSet(&$fieldset) {
  169. if(!$fieldset->fieldByName($this->getName())) {
  170. $field = new HiddenField($this->getName(), null, $this->getValue());
  171. $fieldset->push($field);
  172. return $field;
  173. } else {
  174. return false;
  175. }
  176. }
  177. /**
  178. * @param String $url
  179. * @return String
  180. */
  181. public function addToUrl($url) {
  182. return Controller::join_links($url, sprintf('?%s=%s', $this->getName(), $this->getValue()));
  183. }
  184. /**
  185. * You can't disable an existing instance, it will need to be overwritten like this:
  186. * <code>
  187. * $old = SecurityToken::inst(); // isEnabled() returns true
  188. * SecurityToken::disable();
  189. * $new = SecurityToken::inst(); // isEnabled() returns false
  190. * </code>
  191. *
  192. * @return boolean
  193. */
  194. public function isEnabled() {
  195. return !($this instanceof NullSecurityToken);
  196. }
  197. /**
  198. * @uses RandomGenerator
  199. *
  200. * @return String
  201. */
  202. protected function generate() {
  203. $generator = new RandomGenerator();
  204. return $generator->randomToken('sha1');
  205. }
  206. public static function get_template_global_variables() {
  207. return array(
  208. 'getSecurityID',
  209. 'SecurityID' => 'getSecurityID'
  210. );
  211. }
  212. }
  213. /**
  214. * Specialized subclass for disabled security tokens - always returns
  215. * TRUE for token checks. Use through {@link SecurityToken::disable()}.
  216. */
  217. class NullSecurityToken extends SecurityToken {
  218. /**
  219. * @param String
  220. * @return boolean
  221. */
  222. public function check($compare) {
  223. return true;
  224. }
  225. /**
  226. * @param SS_HTTPRequest $request
  227. * @return Boolean
  228. */
  229. public function checkRequest($request) {
  230. return true;
  231. }
  232. /**
  233. * @param FieldList $fieldset
  234. * @return false
  235. */
  236. public function updateFieldSet(&$fieldset) {
  237. // Remove, in case it was added beforehand
  238. $fieldset->removeByName($this->getName());
  239. return false;
  240. }
  241. /**
  242. * @param String $url
  243. * @return String
  244. */
  245. public function addToUrl($url) {
  246. return $url;
  247. }
  248. /**
  249. * @return String
  250. */
  251. public function getValue() {
  252. return null;
  253. }
  254. /**
  255. * @param String $val
  256. */
  257. public function setValue($val) {
  258. // no-op
  259. }
  260. /**
  261. * @return String
  262. */
  263. public function generate() {
  264. return null;
  265. }
  266. }