Completed
Pull Request — master (#5741)
by Damian
12:40
created

NullSecurityToken::getValue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace SilverStripe\Security;
4
5
use FieldList;
6
use Object;
7
use SS_HTTPRequest;
8
use TemplateGlobalProvider;
9
use Session;
10
use HiddenField;
11
use Controller;
12
13
/**
14
 * @package framework
15
 * @subpackage security
16
 */
17
18
/**
19
 * Cross Site Request Forgery (CSRF) protection for the {@link Form} class and other GET links.
20
 * Can be used globally (through {@link SecurityToken::inst()})
21
 * or on a form-by-form basis {@link Form->getSecurityToken()}.
22
 *
23
 * <b>Usage in forms</b>
24
 *
25
 * This protective measure is automatically turned on for all new {@link Form} instances,
26
 * and can be globally disabled through {@link disable()}.
27
 *
28
 * <b>Usage in custom controller actions</b>
29
 *
30
 * <code>
31
 * class MyController extends Controller {
32
 * 	function mygetaction($request) {
33
 * 		if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
34
 *
35
 * 		// valid action logic ...
36
 * 	}
37
 * }
38
 * </code>
39
 *
40
 * @todo Make token name form specific for additional forgery protection.
41
 */
42
class SecurityToken extends Object implements TemplateGlobalProvider {
43
44
	/**
45
	 * @var String
46
	 */
47
	protected static $default_name = 'SecurityID';
48
49
	/**
50
	 * @var SecurityToken
51
	 */
52
	protected static $inst = null;
53
54
	/**
55
	 * @var boolean
56
	 */
57
	protected static $enabled = true;
58
59
	/**
60
	 * @var String $name
61
	 */
62
	protected $name = null;
63
64
	/**
65
	 * @param $name
66
	 */
67
	public function __construct($name = null) {
68
		$this->name = ($name) ? $name : self::get_default_name();
69
		parent::__construct();
70
	}
71
72
	/**
73
	 * Gets a global token (or creates one if it doesnt exist already).
74
	 *
75
	 * @return SecurityToken
76
	 */
77
	public static function inst() {
78
		if(!self::$inst) self::$inst = new SecurityToken();
79
80
		return self::$inst;
81
	}
82
83
	/**
84
	 * Globally disable the token (override with {@link NullSecurityToken})
85
	 * implementation. Note: Does not apply for
86
	 */
87
	public static function disable() {
88
		self::$enabled = false;
89
		self::$inst = new NullSecurityToken();
90
	}
91
92
	/**
93
	 * Globally enable tokens that have been previously disabled through {@link disable}.
94
	 */
95
	public static function enable() {
96
		self::$enabled = true;
97
		self::$inst = new SecurityToken();
98
	}
99
100
	/**
101
	 * @return boolean
102
	 */
103
	public static function is_enabled() {
104
		return self::$enabled;
105
	}
106
107
	/**
108
	 * @return String
109
	 */
110
	public static function get_default_name() {
111
		return self::$default_name;
112
	}
113
114
	/**
115
	 * Returns the value of an the global SecurityToken in the current session
116
	 * @return int
117
	 */
118
	public static function getSecurityID() {
119
		$token = SecurityToken::inst();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
120
		return $token->getValue();
121
	}
122
123
	/**
124
	 * @param string $name
125
	 * @return string
126
	 */
127
	public function setName($name) {
128
		$val = $this->getValue();
129
		$this->name = $name;
130
		$this->setValue($val);
131
	}
132
133
	/**
134
	 * @return String
135
	 */
136
	public function getName() {
137
		return $this->name;
138
	}
139
140
	/**
141
	 * @return String
142
	 */
143
	public function getValue() {
144
		$value = Session::get($this->getName());
145
146
		// only regenerate if the token isn't already set in the session
147
		if(!$value) {
148
			$value = $this->generate();
149
			$this->setValue($value);
150
		}
151
152
		return $value;
153
	}
154
155
	/**
156
	 * @param String $val
157
	 */
158
	public function setValue($val) {
159
		Session::set($this->getName(), $val);
160
	}
161
162
	/**
163
	 * Reset the token to a new value.
164
	 */
165
	public function reset() {
166
		$this->setValue($this->generate());
167
	}
168
169
	/**
170
	 * Checks for an existing CSRF token in the current users session.
171
	 * This check is automatically performed in {@link Form->httpSubmission()}
172
	 * if a form has security tokens enabled.
173
	 * This direct check is mainly used for URL actions on {@link FormField} that are not routed
174
	 * through {@link Form->httpSubmission()}.
175
	 *
176
	 * Typically you'll want to check {@link Form->securityTokenEnabled()} before calling this method.
177
	 *
178
	 * @param String $compare
179
	 * @return Boolean
180
	 */
181
	public function check($compare) {
182
		return ($compare && $this->getValue() && $compare == $this->getValue());
183
	}
184
185
	/**
186
	 * See {@link check()}.
187
	 *
188
	 * @param SS_HTTPRequest $request
189
	 * @return bool
190
	 */
191
	public function checkRequest($request) {
192
		$token = $this->getRequestToken($request);
193
		return $this->check($token);
194
	}
195
196
	/**
197
	 * Get security token from request
198
	 *
199
	 * @param SS_HTTPREquest $request
200
	 * @return string
201
	 */
202
	protected function getRequestToken($request) {
203
		$name = $this->getName();
204
		$header = 'X-' . ucwords(strtolower($name));
205
		if($token = $request->getHeader($header)) {
206
			return $token;
207
		}
208
209
		// Get from request var
210
		return $request->requestVar($name);
211
	}
212
213
	/**
214
	 * Note: Doesn't call {@link FormField->setForm()}
215
	 * on the returned {@link HiddenField}, you'll need to take
216
	 * care of this yourself.
217
	 *
218
	 * @param FieldList $fieldset
219
	 * @return HiddenField|false
220
	 */
221
	public function updateFieldSet(&$fieldset) {
222
		if(!$fieldset->fieldByName($this->getName())) {
223
			$field = new HiddenField($this->getName(), null, $this->getValue());
224
			$fieldset->push($field);
225
			return $field;
226
		} else {
227
			return false;
228
		}
229
	}
230
231
	/**
232
	 * @param String $url
233
	 * @return String
234
	 */
235
	public function addToUrl($url) {
236
		return Controller::join_links($url, sprintf('?%s=%s', $this->getName(), $this->getValue()));
237
	}
238
239
	/**
240
	 * You can't disable an existing instance, it will need to be overwritten like this:
241
	 * <code>
242
	 * $old = SecurityToken::inst(); // isEnabled() returns true
243
	 * SecurityToken::disable();
244
	 * $new = SecurityToken::inst(); // isEnabled() returns false
245
	 * </code>
246
	 *
247
	 * @return boolean
248
	 */
249
	public function isEnabled() {
250
		return !($this instanceof NullSecurityToken);
251
	}
252
253
	/**
254
	 * @uses RandomGenerator
255
	 *
256
	 * @return String
257
	 */
258
	protected function generate() {
259
		$generator = new RandomGenerator();
260
		return $generator->randomToken('sha1');
261
	}
262
263
	public static function get_template_global_variables() {
264
		return array(
265
			'getSecurityID',
266
			'SecurityID' => 'getSecurityID'
267
		);
268
	}
269
}
270
271
/**
272
 * Specialized subclass for disabled security tokens - always returns
273
 * TRUE for token checks. Use through {@link SecurityToken::disable()}.
274
 */
275
class NullSecurityToken extends SecurityToken {
276
277
	/**
278
	 * @param String
279
	 * @return boolean
280
	 */
281
	public function check($compare) {
282
		return true;
283
	}
284
285
	/**
286
	 * @param SS_HTTPRequest $request
287
	 * @return Boolean
288
	 */
289
	public function checkRequest($request) {
290
		return true;
291
	}
292
293
	/**
294
	 * @param FieldList $fieldset
295
	 * @return false
296
	 */
297
	public function updateFieldSet(&$fieldset) {
298
		// Remove, in case it was added beforehand
299
		$fieldset->removeByName($this->getName());
300
301
		return false;
302
	}
303
304
	/**
305
	 * @param String $url
306
	 * @return String
307
	 */
308
	public function addToUrl($url) {
309
		return $url;
310
	}
311
312
	/**
313
	 * @return String
314
	 */
315
	public function getValue() {
316
		return null;
317
	}
318
319
	/**
320
	 * @param String $val
321
	 */
322
	public function setValue($val) {
323
		// no-op
324
	}
325
326
	/**
327
	 * @return String
328
	 */
329
	public function generate() {
330
		return null;
331
	}
332
}
333