Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like SecurityComponent often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SecurityComponent, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
40 | class SecurityComponent extends Component |
||
41 | { |
||
42 | /** |
||
43 | * Default message used for exceptions thrown |
||
44 | */ |
||
45 | const DEFAULT_EXCEPTION_MESSAGE = 'The request has been black-holed'; |
||
46 | |||
47 | /** |
||
48 | * Default config |
||
49 | * |
||
50 | * - `blackHoleCallback` - The controller method that will be called if this |
||
51 | * request is black-hole'd. |
||
52 | * - `requireSecure` - List of actions that require an SSL-secured connection. |
||
53 | * - `requireAuth` - List of actions that require a valid authentication key. Deprecated as of 3.2.2 |
||
54 | * - `allowedControllers` - Controllers from which actions of the current |
||
55 | * controller are allowed to receive requests. |
||
56 | * - `allowedActions` - Actions from which actions of the current controller |
||
57 | * are allowed to receive requests. |
||
58 | * - `unlockedFields` - Form fields to exclude from POST validation. Fields can |
||
59 | * be unlocked either in the Component, or with FormHelper::unlockField(). |
||
60 | * Fields that have been unlocked are not required to be part of the POST |
||
61 | * and hidden unlocked fields do not have their values checked. |
||
62 | * - `unlockedActions` - Actions to exclude from POST validation checks. |
||
63 | * Other checks like requireAuth(), requireSecure() etc. will still be applied. |
||
64 | * - `validatePost` - Whether to validate POST data. Set to false to disable |
||
65 | * for data coming from 3rd party services, etc. |
||
66 | * |
||
67 | * @var array |
||
68 | */ |
||
69 | protected $_defaultConfig = [ |
||
70 | 'blackHoleCallback' => null, |
||
71 | 'requireSecure' => [], |
||
72 | 'requireAuth' => [], |
||
73 | 'allowedControllers' => [], |
||
74 | 'allowedActions' => [], |
||
75 | 'unlockedFields' => [], |
||
76 | 'unlockedActions' => [], |
||
77 | 'validatePost' => true, |
||
78 | ]; |
||
79 | |||
80 | /** |
||
81 | * Holds the current action of the controller |
||
82 | * |
||
83 | * @var string |
||
84 | */ |
||
85 | protected $_action; |
||
86 | |||
87 | /** |
||
88 | * The Session object |
||
89 | * |
||
90 | * @var \Cake\Http\Session |
||
91 | */ |
||
92 | public $session; |
||
93 | |||
94 | /** |
||
95 | * Component startup. All security checking happens here. |
||
96 | * |
||
97 | * @param \Cake\Event\Event $event An Event instance |
||
98 | * @return mixed |
||
99 | */ |
||
100 | public function startup(Event $event) |
||
101 | { |
||
102 | /** @var \Cake\Controller\Controller $controller */ |
||
103 | $controller = $event->getSubject(); |
||
104 | $request = $controller->request; |
||
105 | $this->session = $request->getSession(); |
||
106 | $this->_action = $request->getParam('action'); |
||
107 | $hasData = ($request->getData() || $request->is(['put', 'post', 'delete', 'patch'])); |
||
108 | try { |
||
109 | $this->_secureRequired($controller); |
||
110 | $this->_authRequired($controller); |
||
|
|||
111 | |||
112 | $isNotRequestAction = !$request->getParam('requested'); |
||
113 | |||
114 | if ($this->_action === $this->_config['blackHoleCallback']) { |
||
115 | throw new AuthSecurityException(sprintf('Action %s is defined as the blackhole callback.', $this->_action)); |
||
116 | } |
||
117 | |||
118 | if ( |
||
119 | !in_array($this->_action, (array)$this->_config['unlockedActions']) && |
||
120 | $hasData && |
||
121 | $isNotRequestAction && |
||
122 | $this->_config['validatePost'] |
||
123 | ) { |
||
124 | $this->_validatePost($controller); |
||
125 | } |
||
126 | } catch (SecurityException $se) { |
||
127 | return $this->blackHole($controller, $se->getType(), $se); |
||
128 | } |
||
129 | |||
130 | $request = $this->generateToken($request); |
||
131 | if ($hasData && is_array($controller->getRequest()->getData())) { |
||
132 | $request = $request->withoutData('_Token'); |
||
133 | } |
||
134 | $controller->setRequest($request); |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Events supported by this component. |
||
139 | * |
||
140 | * @return array |
||
141 | */ |
||
142 | public function implementedEvents() |
||
148 | |||
149 | /** |
||
150 | * Sets the actions that require a request that is SSL-secured, or empty for all actions |
||
151 | * |
||
152 | * @param string|array|null $actions Actions list |
||
153 | * @return void |
||
154 | */ |
||
155 | public function requireSecure($actions = null) |
||
159 | |||
160 | /** |
||
161 | * Sets the actions that require whitelisted form submissions. |
||
162 | * |
||
163 | * Adding actions with this method will enforce the restrictions |
||
164 | * set in SecurityComponent::$allowedControllers and |
||
165 | * SecurityComponent::$allowedActions. |
||
166 | * |
||
167 | * @param string|array $actions Actions list |
||
168 | * @return void |
||
169 | * @deprecated 3.2.2 This feature is confusing and not useful. |
||
170 | */ |
||
171 | public function requireAuth($actions) |
||
176 | |||
177 | /** |
||
178 | * Black-hole an invalid request with a 400 error or custom callback. If SecurityComponent::$blackHoleCallback |
||
179 | * is specified, it will use this callback by executing the method indicated in $error |
||
180 | * |
||
181 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
182 | * @param string $error Error method |
||
183 | * @param \Cake\Controller\Exception\SecurityException|null $exception Additional debug info describing the cause |
||
184 | * @return mixed If specified, controller blackHoleCallback's response, or no return otherwise |
||
185 | * @see \Cake\Controller\Component\SecurityComponent::$blackHoleCallback |
||
186 | * @link https://book.cakephp.org/3/en/controllers/components/security.html#handling-blackhole-callbacks |
||
187 | * @throws \Cake\Http\Exception\BadRequestException |
||
188 | */ |
||
189 | public function blackHole(Controller $controller, $error = '', SecurityException $exception = null) |
||
197 | |||
198 | /** |
||
199 | * Check debug status and throw an Exception based on the existing one |
||
200 | * |
||
201 | * @param \Cake\Controller\Exception\SecurityException|null $exception Additional debug info describing the cause |
||
202 | * @throws \Cake\Http\Exception\BadRequestException |
||
203 | * @return void |
||
204 | */ |
||
205 | protected function _throwException($exception = null) |
||
216 | |||
217 | /** |
||
218 | * Sets the actions that require a $method HTTP request, or empty for all actions |
||
219 | * |
||
220 | * @param string $method The HTTP method to assign controller actions to |
||
221 | * @param array $actions Controller actions to set the required HTTP method to. |
||
222 | * @return void |
||
223 | */ |
||
224 | protected function _requireMethod($method, $actions = []) |
||
231 | |||
232 | /** |
||
233 | * Check if access requires secure connection |
||
234 | * |
||
235 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
236 | * @return bool true if secure connection required |
||
237 | */ |
||
238 | protected function _secureRequired(Controller $controller) |
||
257 | |||
258 | /** |
||
259 | * Check if authentication is required |
||
260 | * |
||
261 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
262 | * @return bool true if authentication required |
||
263 | * @deprecated 3.2.2 This feature is confusing and not useful. |
||
264 | */ |
||
265 | protected function _authRequired(Controller $controller) |
||
317 | |||
318 | /** |
||
319 | * Validate submitted form |
||
320 | * |
||
321 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
322 | * @throws \Cake\Controller\Exception\AuthSecurityException |
||
323 | * @return bool true if submitted form is valid |
||
324 | */ |
||
325 | protected function _validatePost(Controller $controller) |
||
342 | |||
343 | /** |
||
344 | * Check if token is valid |
||
345 | * |
||
346 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
347 | * @throws \Cake\Controller\Exception\SecurityException |
||
348 | * @return string fields token |
||
349 | */ |
||
350 | protected function _validToken(Controller $controller) |
||
378 | |||
379 | /** |
||
380 | * Return hash parts for the Token generation |
||
381 | * |
||
382 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
383 | * @return array |
||
384 | */ |
||
385 | protected function _hashParts(Controller $controller) |
||
404 | |||
405 | /** |
||
406 | * Return the fields list for the hash calculation |
||
407 | * |
||
408 | * @param array $check Data array |
||
409 | * @return array |
||
410 | */ |
||
411 | protected function _fieldsList(array $check) |
||
473 | |||
474 | /** |
||
475 | * Get the unlocked string |
||
476 | * |
||
477 | * @param array $data Data array |
||
478 | * @return string |
||
479 | */ |
||
480 | protected function _unlocked(array $data) |
||
484 | |||
485 | /** |
||
486 | * Get the sorted unlocked string |
||
487 | * |
||
488 | * @param array $data Data array |
||
489 | * @return string |
||
490 | */ |
||
491 | protected function _sortedUnlocked($data) |
||
499 | |||
500 | /** |
||
501 | * Create a message for humans to understand why Security token is not matching |
||
502 | * |
||
503 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
504 | * @param array $hashParts Elements used to generate the Token hash |
||
505 | * @return string Message explaining why the tokens are not matching |
||
506 | */ |
||
507 | protected function _debugPostTokenNotMatching(Controller $controller, $hashParts) |
||
548 | |||
549 | /** |
||
550 | * Iterates data array to check against expected |
||
551 | * |
||
552 | * @param array $dataFields Fields array, containing the POST data fields |
||
553 | * @param array $expectedFields Fields array, containing the expected fields we should have in POST |
||
554 | * @param string $intKeyMessage Message string if unexpected found in data fields indexed by int (not protected) |
||
555 | * @param string $stringKeyMessage Message string if tampered found in data fields indexed by string (protected) |
||
556 | * @param string $missingMessage Message string if missing field |
||
557 | * @return array Messages |
||
558 | */ |
||
559 | protected function _debugCheckFields($dataFields, $expectedFields = [], $intKeyMessage = '', $stringKeyMessage = '', $missingMessage = '') |
||
569 | |||
570 | /** |
||
571 | * Manually add form tampering prevention token information into the provided |
||
572 | * request object. |
||
573 | * |
||
574 | * @param \Cake\Http\ServerRequest $request The request object to add into. |
||
575 | * @return \Cake\Http\ServerRequest The modified request. |
||
576 | */ |
||
577 | public function generateToken(ServerRequest $request) |
||
598 | |||
599 | /** |
||
600 | * Calls a controller callback method |
||
601 | * |
||
602 | * @param \Cake\Controller\Controller $controller Instantiating controller |
||
603 | * @param string $method Method to execute |
||
604 | * @param array $params Parameters to send to method |
||
605 | * @return mixed Controller callback method's response |
||
606 | * @throws \Cake\Http\Exception\BadRequestException When a the blackholeCallback is not callable. |
||
607 | */ |
||
608 | protected function _callback(Controller $controller, $method, $params = []) |
||
616 | |||
617 | /** |
||
618 | * Generate array of messages for the existing fields in POST data, matching dataFields in $expectedFields |
||
619 | * will be unset |
||
620 | * |
||
621 | * @param array $dataFields Fields array, containing the POST data fields |
||
622 | * @param array $expectedFields Fields array, containing the expected fields we should have in POST |
||
623 | * @param string $intKeyMessage Message string if unexpected found in data fields indexed by int (not protected) |
||
624 | * @param string $stringKeyMessage Message string if tampered found in data fields indexed by string (protected) |
||
625 | * @return array Error messages |
||
626 | */ |
||
627 | protected function _matchExistingFields($dataFields, &$expectedFields, $intKeyMessage, $stringKeyMessage) |
||
648 | |||
649 | /** |
||
650 | * Generate debug message for the expected fields |
||
651 | * |
||
652 | * @param array $expectedFields Expected fields |
||
653 | * @param string $missingMessage Message template |
||
654 | * @return string|null Error message about expected fields |
||
655 | */ |
||
656 | protected function _debugExpectedFields($expectedFields = [], $missingMessage = '') |
||
673 | } |
||
674 |
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.