Passed
Pull Request — 4 (#10222)
by Steve
07:01
created

ConfirmationMiddleware::processItems()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 16
nc 10
nop 3
dl 0
loc 28
rs 8.8333
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control\Middleware;
4
5
use SilverStripe\Core\Injector\Injector;
6
use SilverStripe\Control\Controller;
7
use SilverStripe\Control\Director;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\HTTPResponse;
10
use SilverStripe\Control\Session;
11
use SilverStripe\Security\Confirmation;
12
use SilverStripe\Security\Security;
13
14
/**
15
 * Checks whether user manual confirmation is required for HTTPRequest
16
 * depending on the rules given.
17
 *
18
 * How it works:
19
 *  - Gives the request to every single rule
20
 *  - If no confirmation items are found by the rules, then move on to the next middleware
21
 *  - initialize the Confirmation\Storage with all the confirmation items found
22
 *  - Check whether the storage has them confirmed already and if yes, move on to the next middleware
23
 *  - Otherwise redirect to the confirmation URL
24
 */
25
class ConfirmationMiddleware implements HTTPMiddleware
26
{
27
    /**
28
     * The confirmation storage identifier
29
     *
30
     * @var string
31
     */
32
    protected $confirmationId = 'middleware';
33
34
    /**
35
     * Confirmation form URL
36
     * WARNING: excluding SS_BASE_URL
37
     *
38
     * @var string
39
     */
40
    protected $confirmationFormUrl = '/dev/confirm';
41
42
    /**
43
     * The list of rules to check requests against
44
     *
45
     * @var ConfirmationMiddleware\Rule[]
46
     */
47
    protected $rules;
48
49
    /**
50
     * The list of bypasses
51
     *
52
     * @var ConfirmationMiddleware\Bypass[]
53
     */
54
    protected $bypasses = [];
55
56
    /**
57
     * Where user should be redirected when refusing
58
     * the action on the confirmation form
59
     *
60
     * @var string
61
     */
62
    private $declineUrl;
63
64
    /**
65
     * Init the middleware with the rules
66
     *
67
     * @param ConfirmationMiddleware\Rule[] $rules Rules to check requests against
68
     */
69
    public function __construct(...$rules)
70
    {
71
        $this->rules = $rules;
72
        $this->declineUrl = Director::baseURL();
73
    }
74
75
    /**
76
     * The URL of the confirmation form ("Security/confirm/middleware" by default)
77
     *
78
     * @param HTTPRequest $request Active request
79
     * @param string $confirmationStorageId ID of the confirmation storage to be used
80
     *
81
     * @return string URL of the confirmation form
82
     */
83
    protected function getConfirmationUrl(HTTPRequest $request, $confirmationStorageId)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

83
    protected function getConfirmationUrl(/** @scrutinizer ignore-unused */ HTTPRequest $request, $confirmationStorageId)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
84
    {
85
        $url = $this->confirmationFormUrl;
86
87
        if (substr((string) $url, 0, 1) === '/') {
88
            // add BASE_URL explicitly if not absolute
89
            $url = Controller::join_links(Director::baseURL(), $url);
90
        }
91
92
        return Controller::join_links(
93
            $url,
94
            urlencode($confirmationStorageId)
95
        );
96
    }
97
98
    /**
99
     * Returns the URL where the user to be redirected
100
     * when declining the action (on the confirmation form)
101
     *
102
     * @param HTTPRequest $request Active request
103
     *
104
     * @return string URL
105
     */
106
    protected function generateDeclineUrlForRequest(HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

106
    protected function generateDeclineUrlForRequest(/** @scrutinizer ignore-unused */ HTTPRequest $request)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
107
    {
108
        return $this->declineUrl;
109
    }
110
111
    /**
112
     * Override the default decline url
113
     *
114
     * @param string $url
115
     *
116
     * @return $this
117
     */
118
    public function setDeclineUrl($url)
119
    {
120
        $this->declineUrl = $url;
121
        return $this;
122
    }
123
124
    /**
125
     * Check whether the rules can be bypassed
126
     * without user confirmation
127
     *
128
     * @param HTTPRequest $request
129
     *
130
     * @return bool
131
     */
132
    public function canBypass(HTTPRequest $request)
133
    {
134
        foreach ($this->bypasses as $bypass) {
135
            if ($bypass->checkRequestForBypass($request)) {
136
                return true;
137
            }
138
        }
139
140
        return false;
141
    }
142
143
    /**
144
     * Extract the confirmation items from the request and return
145
     *
146
     * @param HTTPRequest $request
147
     *
148
     * @return Confirmation\Item[] list of confirmation items
149
     */
150
    public function getConfirmationItems(HTTPRequest $request)
151
    {
152
        $confirmationItems = [];
153
154
        foreach ($this->rules as $rule) {
155
            if ($item = $rule->getRequestConfirmationItem($request)) {
156
                $confirmationItems[] = $item;
157
            }
158
        }
159
160
        return $confirmationItems;
161
    }
162
163
    /**
164
     * Initialize the confirmation session storage
165
     * with the confirmation items and return an HTTPResponse
166
     * redirecting to the according confirmation form.
167
     *
168
     * @param HTTPRequest $request
169
     * @param Confirmation\Storage $storage
170
     * @param Confirmation\Item[] $confirmationItems
171
     *
172
     * @return HTTPResponse
173
     */
174
    protected function buildConfirmationRedirect(HTTPRequest $request, Confirmation\Storage $storage, array $confirmationItems)
175
    {
176
        $storage->cleanup();
177
178
        foreach ($confirmationItems as $item) {
179
            $storage->putItem($item);
180
        }
181
182
        $storage->setSuccessRequest($request);
183
        $storage->setFailureUrl($this->generateDeclineUrlForRequest($request));
184
185
        $result = new HTTPResponse();
186
        $result->redirect($this->getConfirmationUrl($request, $this->confirmationId));
187
188
        return $result;
189
    }
190
191
    /**
192
     * Process the confirmation items and either perform the confirmedEffect
193
     * and pass the request to the next middleware, or return a redirect to
194
     * the confirmation form
195
     *
196
     * @param HTTPRequest $request
197
     * @param callable $delegate
198
     * @param Confirmation\Item[] $items
199
     *
200
     * @return HTTPResponse
201
     */
202
    protected function processItems(HTTPRequest $request, callable $delegate, $items)
203
    {
204
        $storage = Injector::inst()->createWithArgs(Confirmation\Storage::class, [$request->getSession(), $this->confirmationId, false]);
205
206
        if (!count($storage->getItems() ?: [])) {
207
            return $this->buildConfirmationRedirect($request, $storage, $items);
208
        }
209
210
        $confirmed = false;
211
        if ($storage->getHttpMethod() === 'POST') {
212
            $postVars = $request->postVars();
213
            $csrfToken = $storage->getCsrfToken();
214
215
            $confirmed = $storage->confirm($postVars) && isset($postVars[$csrfToken]);
216
        } else {
217
            $confirmed = $storage->check($items);
218
        }
219
220
        if (!$confirmed) {
221
            return $this->buildConfirmationRedirect($request, $storage, $items);
222
        }
223
224
        if ($response = $this->confirmedEffect($request)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $response is correct as $this->confirmedEffect($request) targeting SilverStripe\Control\Mid...ware::confirmedEffect() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
225
            return $response;
226
        }
227
228
        $storage->cleanup();
229
        return $delegate($request);
230
    }
231
232
    /**
233
     * The middleware own effects that should be performed on confirmation
234
     *
235
     * This method is getting called before the confirmation storage cleanup
236
     * so that any responses returned here don't trigger a new confirmtation
237
     * for the same request traits
238
     *
239
     * @param HTTPRequest $request
240
     *
241
     * @return null|HTTPResponse
242
     */
243
    protected function confirmedEffect(HTTPRequest $request)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

243
    protected function confirmedEffect(/** @scrutinizer ignore-unused */ HTTPRequest $request)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
244
    {
245
        return null;
246
    }
247
248
    public function process(HTTPRequest $request, callable $delegate)
249
    {
250
        if ($this->canBypass($request)) {
251
            if ($response = $this->confirmedEffect($request)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $response is correct as $this->confirmedEffect($request) targeting SilverStripe\Control\Mid...ware::confirmedEffect() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
252
                return $response;
253
            } else {
254
                return $delegate($request);
255
            }
256
        }
257
258
        if (!$items = $this->getConfirmationItems($request)) {
259
            return $delegate($request);
260
        }
261
262
        return $this->processItems($request, $delegate, $items);
263
    }
264
265
    /**
266
     * Override the confirmation storage ID
267
     *
268
     * @param string $id
269
     *
270
     * @return $this
271
     */
272
    public function setConfirmationStorageId($id)
273
    {
274
        $this->confirmationId = $id;
275
        return $this;
276
    }
277
278
    /**
279
     * Override the confirmation form url
280
     *
281
     * @param string $url
282
     *
283
     * @return $this
284
     */
285
    public function setConfirmationFormUrl($url)
286
    {
287
        $this->confirmationFormUrl = $url;
288
        return $this;
289
    }
290
291
    /**
292
     * Set the list of bypasses for the confirmation
293
     *
294
     * @param ConfirmationMiddleware\Bypass[] $bypasses
295
     *
296
     * @return $this
297
     */
298
    public function setBypasses($bypasses)
299
    {
300
        $this->bypasses = $bypasses;
301
        return $this;
302
    }
303
}
304