Passed
Push — 4 ( 7df3c4...e4bf9a )
by Daniel
07:58
created

SecurityToken::is_enabled()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Security;
4
5
use Exception;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\Session;
9
use SilverStripe\Core\Config\Configurable;
10
use SilverStripe\Core\Injector\Injectable;
11
use SilverStripe\Core\Injector\Injector;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\HiddenField;
14
use SilverStripe\View\TemplateGlobalProvider;
15
16
/**
17
 * Cross Site Request Forgery (CSRF) protection for the {@link Form} class and other GET links.
18
 * Can be used globally (through {@link SecurityToken::inst()})
19
 * or on a form-by-form basis {@link Form->getSecurityToken()}.
20
 *
21
 * <b>Usage in forms</b>
22
 *
23
 * This protective measure is automatically turned on for all new {@link Form} instances,
24
 * and can be globally disabled through {@link disable()}.
25
 *
26
 * <b>Usage in custom controller actions</b>
27
 *
28
 * <code>
29
 * class MyController extends Controller {
30
 *  function mygetaction($request) {
31
 *      if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
32
 *
33
 *      // valid action logic ...
34
 *  }
35
 * }
36
 * </code>
37
 *
38
 * @todo Make token name form specific for additional forgery protection.
39
 */
40
class SecurityToken implements TemplateGlobalProvider
41
{
42
    use Configurable;
43
    use Injectable;
44
45
    /**
46
     * @var string
47
     */
48
    protected static $default_name = 'SecurityID';
49
50
    /**
51
     * @var SecurityToken
52
     */
53
    protected static $inst = null;
54
55
    /**
56
     * @var boolean
57
     */
58
    protected static $enabled = true;
59
60
    /**
61
     * @var string $name
62
     */
63
    protected $name = null;
64
65
    /**
66
     * @param string $name
67
     */
68
    public function __construct($name = null)
69
    {
70
        $this->name = $name ?: self::get_default_name();
71
    }
72
73
    /**
74
     * Gets a global token (or creates one if it doesnt exist already).
75
     *
76
     * @return SecurityToken
77
     */
78
    public static function inst()
79
    {
80
        if (!self::$inst) {
81
            self::$inst = new SecurityToken();
82
        }
83
84
        return self::$inst;
85
    }
86
87
    /**
88
     * Globally disable the token (override with {@link NullSecurityToken})
89
     * implementation. Note: Does not apply for
90
     */
91
    public static function disable()
92
    {
93
        self::$enabled = false;
94
        self::$inst = new NullSecurityToken();
95
    }
96
97
    /**
98
     * Globally enable tokens that have been previously disabled through {@link disable}.
99
     */
100
    public static function enable()
101
    {
102
        self::$enabled = true;
103
        self::$inst = new SecurityToken();
104
    }
105
106
    /**
107
     * @return boolean
108
     */
109
    public static function is_enabled()
110
    {
111
        return self::$enabled;
112
    }
113
114
    /**
115
     * @return string
116
     */
117
    public static function get_default_name()
118
    {
119
        return self::$default_name;
120
    }
121
122
    /**
123
     * Returns the value of an the global SecurityToken in the current session
124
     * @return int
125
     */
126
    public static function getSecurityID()
127
    {
128
        $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...
129
        return $token->getValue();
130
    }
131
132
    /**
133
     * @param string $name
134
     */
135
    public function setName($name)
136
    {
137
        $val = $this->getValue();
138
        $this->name = $name;
139
        $this->setValue($val);
140
    }
141
142
    /**
143
     * @return string
144
     */
145
    public function getName()
146
    {
147
        return $this->name;
148
    }
149
150
    /**
151
     * @return string
152
     */
153
    public function getValue()
154
    {
155
        $session = $this->getSession();
156
        $value = $session->get($this->getName());
157
158
        // only regenerate if the token isn't already set in the session
159
        if (!$value) {
160
            $value = $this->generate();
161
            $this->setValue($value);
162
        }
163
164
        return $value;
165
    }
166
167
    /**
168
     * @param string $val
169
     * @return $this
170
     */
171
    public function setValue($val)
172
    {
173
        $this->getSession()->set($this->getName(), $val);
174
        return $this;
175
    }
176
177
    /**
178
     * Returns the current session instance from the injector
179
     *
180
     * @return Session
181
     * @throws Exception If the HTTPRequest class hasn't been registered as a service and no controllers exist
182
     */
183
    protected function getSession()
184
    {
185
        $injector = Injector::inst();
186
        if ($injector->has(HTTPRequest::class)) {
187
            return $injector->get(HTTPRequest::class)->getSession();
188
        } elseif (Controller::has_curr()) {
189
            return Controller::curr()->getRequest()->getSession();
190
        }
191
        throw new Exception('No HTTPRequest object or controller available yet!');
192
    }
193
194
    /**
195
     * Reset the token to a new value.
196
     */
197
    public function reset()
198
    {
199
        $this->setValue($this->generate());
200
    }
201
202
    /**
203
     * Checks for an existing CSRF token in the current users session.
204
     * This check is automatically performed in {@link Form->httpSubmission()}
205
     * if a form has security tokens enabled.
206
     * This direct check is mainly used for URL actions on {@link FormField} that are not routed
207
     * through {@link Form->httpSubmission()}.
208
     *
209
     * Typically you'll want to check {@link Form->securityTokenEnabled()} before calling this method.
210
     *
211
     * @param string $compare
212
     * @return boolean
213
     */
214
    public function check($compare)
215
    {
216
        return ($compare && $this->getValue() && $compare == $this->getValue());
217
    }
218
219
    /**
220
     * See {@link check()}.
221
     *
222
     * @param HTTPRequest $request
223
     * @return bool
224
     */
225
    public function checkRequest($request)
226
    {
227
        $token = $this->getRequestToken($request);
228
        return $this->check($token);
229
    }
230
231
    /**
232
     * Get security token from request
233
     *
234
     * @param HTTPRequest $request
235
     * @return string
236
     */
237
    protected function getRequestToken($request)
238
    {
239
        $name = $this->getName();
240
        $header = 'X-' . ucwords(strtolower($name));
241
        if ($token = $request->getHeader($header)) {
242
            return $token;
243
        }
244
245
        // Get from request var
246
        return $request->requestVar($name);
247
    }
248
249
    /**
250
     * Note: Doesn't call {@link FormField->setForm()}
251
     * on the returned {@link HiddenField}, you'll need to take
252
     * care of this yourself.
253
     *
254
     * @param FieldList $fieldset
255
     * @return HiddenField|false
256
     */
257
    public function updateFieldSet(&$fieldset)
258
    {
259
        if (!$fieldset->fieldByName($this->getName())) {
260
            $field = new HiddenField($this->getName(), null, $this->getValue());
261
            $fieldset->push($field);
262
            return $field;
263
        } else {
264
            return false;
265
        }
266
    }
267
268
    /**
269
     * @param string $url
270
     * @return string
271
     */
272
    public function addToUrl($url)
273
    {
274
        return Controller::join_links($url, sprintf('?%s=%s', $this->getName(), $this->getValue()));
275
    }
276
277
    /**
278
     * You can't disable an existing instance, it will need to be overwritten like this:
279
     * <code>
280
     * $old = SecurityToken::inst(); // isEnabled() returns true
281
     * SecurityToken::disable();
282
     * $new = SecurityToken::inst(); // isEnabled() returns false
283
     * </code>
284
     *
285
     * @return boolean
286
     */
287
    public function isEnabled()
288
    {
289
        return !($this instanceof NullSecurityToken);
290
    }
291
292
    /**
293
     * @uses RandomGenerator
294
     *
295
     * @return string
296
     */
297
    protected function generate()
298
    {
299
        $generator = new RandomGenerator();
300
        return $generator->randomToken('sha1');
301
    }
302
303
    public static function get_template_global_variables()
304
    {
305
        return array(
306
            'getSecurityID',
307
            'SecurityID' => 'getSecurityID'
308
        );
309
    }
310
}
311