Completed
Push — master ( 4ad6bd...3873e4 )
by Ingo
11:53
created

ParameterConfirmationToken::currentAbsoluteURL()   C

Complexity

Conditions 14
Paths 84

Size

Total Lines 44
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 24
nc 84
nop 0
dl 0
loc 44
rs 5.0864
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A ParameterConfirmationToken::currentURL() 0 8 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SilverStripe\Core\Startup;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Control\HTTPResponse;
8
use SilverStripe\Core\Convert;
9
use SilverStripe\Security\RandomGenerator;
10
11
/**
12
 * Class ParameterConfirmationToken
13
 *
14
 * When you need to use a dangerous GET parameter that needs to be set before core/Core.php is
15
 * established, this class takes care of allowing some other code of confirming the parameter,
16
 * by generating a one-time-use token & redirecting with that token included in the redirected URL
17
 *
18
 * WARNING: This class is experimental and designed specifically for use pre-startup in main.php
19
 * It will likely be heavily refactored before the release of 3.2
20
 */
21
class ParameterConfirmationToken
22
{
23
24
    /**
25
     * The name of the parameter
26
     *
27
     * @var string
28
     */
29
    protected $parameterName = null;
30
31
    /**
32
     * @var HTTPRequest
33
     */
34
    protected $request = null;
35
36
    /**
37
     * The parameter given
38
     *
39
     * @var string|null The string value, or null if not provided
40
     */
41
    protected $parameter = null;
42
43
    /**
44
     * The validated and checked token for this parameter
45
     *
46
     * @var string|null A string value, or null if either not provided or invalid
47
     */
48
    protected $token = null;
49
50
    protected function pathForToken($token)
51
    {
52
        return TEMP_FOLDER.'/token_'.preg_replace('/[^a-z0-9]+/', '', $token);
53
    }
54
55
    /**
56
     * Generate a new random token and store it
57
     *
58
     * @return string Token name
59
     */
60
    protected function genToken()
61
    {
62
        // Generate a new random token (as random as possible)
63
        $rg = new RandomGenerator();
64
        $token = $rg->randomToken('md5');
65
66
        // Store a file in the session save path (safer than /tmp, as open_basedir might limit that)
67
        file_put_contents($this->pathForToken($token), $token);
68
69
        return $token;
70
    }
71
72
    /**
73
     * Validate a token
74
     *
75
     * @param string $token
76
     * @return boolean True if the token is valid
77
     */
78
    protected function checkToken($token)
79
    {
80
        if (!$token) {
81
            return false;
82
        }
83
84
        $file = $this->pathForToken($token);
85
        $content = null;
86
87
        if (file_exists($file)) {
88
            $content = file_get_contents($file);
89
            unlink($file);
90
        }
91
92
        return $content == $token;
93
    }
94
95
    /**
96
     * Create a new ParameterConfirmationToken
97
     *
98
     * @param string $parameterName Name of the querystring parameter to check
99
     * @param HTTPRequest $request
100
     */
101
    public function __construct($parameterName, HTTPRequest $request)
102
    {
103
        // Store the parameter name
104
        $this->parameterName = $parameterName;
105
        $this->request = $request;
106
107
        // Store the parameter value
108
        $this->parameter = $request->getVar($parameterName);
109
110
        // If the token provided is valid, mark it as such
111
        $token = $request->getVar($parameterName.'token');
112
        if ($this->checkToken($token)) {
113
            $this->token = $token;
114
        }
115
    }
116
117
    /**
118
     * Get the name of this token
119
     *
120
     * @return string
121
     */
122
    public function getName()
123
    {
124
        return $this->parameterName;
125
    }
126
127
    /**
128
     * Is the parameter requested?
129
     * ?parameter and ?parameter=1 are both considered requested
130
     *
131
     * @return bool
132
     */
133
    public function parameterProvided()
134
    {
135
        return $this->parameter !== null;
136
    }
137
138
    /**
139
     * Is the necessary token provided for this parameter?
140
     * A value must be provided for the token
141
     *
142
     * @return bool
143
     */
144
    public function tokenProvided()
145
    {
146
        return !empty($this->token);
147
    }
148
149
    /**
150
     * Is this parameter requested without a valid token?
151
     *
152
     * @return bool True if the parameter is given without a valid token
153
     */
154
    public function reloadRequired()
155
    {
156
        return $this->parameterProvided() && !$this->tokenProvided();
157
    }
158
159
    /**
160
     * Suppress the current parameter by unsetting it from $_GET
161
     */
162
    public function suppress()
163
    {
164
        $this->request->offsetUnset($this->parameterName);
165
    }
166
167
    /**
168
     * Determine the querystring parameters to include
169
     *
170
     * @return array List of querystring parameters with name and token parameters
171
     */
172
    public function params()
173
    {
174
        return array(
175
            $this->parameterName => $this->parameter,
176
            $this->parameterName.'token' => $this->genToken()
177
        );
178
    }
179
180
    /**
181
     * Get redirect url, excluding querystring
182
     *
183
     * @return string
184
     */
185
    protected function currentURL()
186
    {
187
        return Controller::join_links(
188
            BASE_URL,
189
            '/',
190
            $this->request->getURL(false)
191
        );
192
    }
193
194
    /**
195
     * Forces a reload of the request with the token included
196
     *
197
     * @return HTTPResponse
198
     */
199
    public function reloadWithToken()
200
    {
201
        // Merge get params with current url
202
        $params = array_merge($this->request->getVars(), $this->params());
203
        $location = Controller::join_links(
204
            $this->currentURL(),
205
            '?'.http_build_query($params)
206
        );
207
        $locationJS = Convert::raw2js($location);
208
        $locationATT = Convert::raw2att($location);
209
        $body = <<<HTML
210
<script>location.href='$locationJS';</script>
211
<noscript><meta http-equiv="refresh" content="0; url=$locationATT"></noscript>
212
You are being redirected. If you are not redirected soon, <a href="$locationATT">click here to continue the flush</a>
213
HTML;
214
215
        // Build response
216
        $result = new HTTPResponse($body);
217
        $result->redirect($location);
218
        return $result;
219
    }
220
221
    /**
222
     * Given a list of token names, suppress all tokens that have not been validated, and
223
     * return the non-validated token with the highest priority
224
     *
225
     * @param array $keys List of token keys in ascending priority (low to high)
226
     * @param HTTPRequest $request
227
     * @return ParameterConfirmationToken The token container for the unvalidated $key given with the highest priority
228
     */
229
    public static function prepare_tokens($keys, HTTPRequest $request)
230
    {
231
        $target = null;
232
        foreach ($keys as $key) {
233
            $token = new ParameterConfirmationToken($key, $request);
234
            // Validate this token
235
            if ($token->reloadRequired()) {
236
                $token->suppress();
237
                $target = $token;
238
            }
239
        }
240
        return $target;
241
    }
242
}
243