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 OAuth2 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 OAuth2, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 18 | class OAuth2 extends Controllers\User\Base |
||
| 19 | { |
||
| 20 | /** |
||
| 21 | * OAuth2 Callback for testing app callback |
||
| 22 | * |
||
| 23 | * @param \Base $f3 |
||
| 24 | * @param array $params |
||
| 25 | * @return void |
||
| 26 | */ |
||
| 27 | public function Callback(\Base $f3, array $params) |
||
| 95 | |||
| 96 | |||
| 97 | /** |
||
| 98 | * Authenticate an incoming OAuth2 request for user access |
||
| 99 | * |
||
| 100 | * @param \Base $f3 |
||
| 101 | * @param array $params |
||
| 102 | * @return void |
||
| 103 | */ |
||
| 104 | public function Authenticate(\Base $f3, array $params) |
||
| 105 | { |
||
| 106 | $this->csrf('@api_apps'); |
||
| 107 | |||
| 108 | $view = 'oauth2/authenticate.phtml'; |
||
| 109 | |||
| 110 | // redirect to user login if user not logged in |
||
| 111 | $redirect_uri = $this->url($params[0], $f3->get('REQUEST')); |
||
| 112 | |||
| 113 | $this->redirectLoggedOutUser('@login', [ |
||
| 114 | 'redirect_uri' => $redirect_uri |
||
| 115 | ]); |
||
| 116 | |||
| 117 | $oAuth2Model = Models\OAuth2::instance(); |
||
| 118 | $appsMapper = $oAuth2Model->getAppsMapper(); |
||
| 119 | $permissions = []; |
||
| 120 | |||
| 121 | // assume there's problems! |
||
| 122 | $f3->set('errors', true); |
||
| 123 | |||
| 124 | // check valid fields |
||
| 125 | $this->filterRules([ |
||
| 126 | 'client_id' => 'trim|sanitize_string', |
||
| 127 | 'scope' => 'trim|sanitize_string', |
||
| 128 | 'state' => 'trim|sanitize_string', |
||
| 129 | 'response_type' => 'trim|sanitize_string', |
||
| 130 | 'redirect_uri' => 'trim|sanitize_string', |
||
| 131 | ]); |
||
| 132 | $request = $this->filter($f3->get('REQUEST')); |
||
| 133 | foreach ($request as $k => $v) { |
||
| 134 | $f3->set('REQUEST.' . $k, $v); |
||
| 135 | } |
||
| 136 | |||
| 137 | // check valid fields |
||
| 138 | $this->validationRules([ |
||
| 139 | 'client_id' => 'required|alpha_dash|exact_len,36', |
||
| 140 | 'scope' => 'required|min_len,3|max_len,4096', |
||
| 141 | 'state' => 'required|min_len,1|max_len,255', |
||
| 142 | 'response_type' => 'required|min_len,1|max_len,16', |
||
| 143 | 'redirect_uri' => 'valid_url', |
||
| 144 | ]); |
||
| 145 | $errors = $this->validate(false, $f3->get('REQUEST')); |
||
| 146 | |||
| 147 | // if errors display form |
||
| 148 | View Code Duplication | if (is_array($errors)) { |
|
| 149 | $this->notify(['info' => $oAuth2Model->validationErrors($errors)]); |
||
| 150 | $f3->set('form', $f3->get('REQUEST')); |
||
| 151 | echo \View::instance()->render($view); |
||
| 152 | return; |
||
| 153 | } |
||
| 154 | |||
| 155 | // validate response_type - only one type is allowed anyway |
||
| 156 | if ('code' !== $request['response_type']) { |
||
| 157 | $request['response_type'] = 'token'; |
||
| 158 | } |
||
| 159 | $f3->set('REQUEST.response_type', $request['response_type']); |
||
| 160 | |||
| 161 | // validate scope(s) |
||
| 162 | $allScopes = $oAuth2Model->SCOPES; |
||
| 163 | $scopes = empty($request['scope']) ? [] : preg_split("/[\s,]+/", $request['scope']); |
||
| 164 | |||
| 165 | foreach ($scopes as $k => $scope) { |
||
| 166 | |||
| 167 | if (!array_key_exists($scope, $allScopes)) { |
||
| 168 | $this->notify(_('Unknown scope specified ') . $scope, 'warning'); |
||
| 169 | unset($scopes[$k]); |
||
| 170 | } else { |
||
| 171 | $permissions[$scope] = $allScopes[$scope]; |
||
| 172 | } |
||
| 173 | |||
| 174 | } |
||
| 175 | |||
| 176 | // no valid scopes |
||
| 177 | View Code Duplication | if (empty($scopes)) { |
|
| 178 | $this->notify(_('No valid scope(s) specified'), 'error'); |
||
| 179 | $f3->set('form', $f3->get('REQUEST')); |
||
| 180 | echo \View::instance()->render($view); |
||
| 181 | return; |
||
| 182 | } |
||
| 183 | |||
| 184 | // verify client id is valid |
||
| 185 | $appsMapper->load(['client_id = ?', $request['client_id']]); |
||
| 186 | View Code Duplication | if (empty($appsMapper->client_id)) { |
|
| 187 | $this->notify(_('Unknown client id!'), 'error'); |
||
| 188 | $f3->set('form', $f3->get('REQUEST')); |
||
| 189 | echo \View::instance()->render($view); |
||
| 190 | return; |
||
| 191 | } |
||
| 192 | |||
| 193 | // verify client app status |
||
| 194 | View Code Duplication | if ('approved' !== $appsMapper->status) { |
|
| 195 | $this->notify(sprintf(_('Application status %s currently forbids access.'), $appsMapper->status), 'error'); |
||
| 196 | $f3->set('form', $f3->get('REQUEST')); |
||
| 197 | echo \View::instance()->render($view); |
||
| 198 | return; |
||
| 199 | } |
||
| 200 | |||
| 201 | if (empty($request['redirect_uri'])) { |
||
| 202 | $request['redirect_uri'] = $appsMapper->callback_uri; |
||
| 203 | } elseif ($appsMapper->callback_uri !== $request['redirect_uri']) { |
||
| 204 | $redirect_uris = empty($appsMapper->redirect_uris) ? [] : preg_split("/[\s,]+/", $appsMapper->redirect_uris); |
||
| 205 | if (!in_array($request['redirect_uri'], $redirect_uris)) { |
||
| 206 | $this->notify(_('Unregistered redirect_uri!'), $appsMapper->status, 'error'); |
||
| 207 | $f3->set('form', $f3->get('REQUEST')); |
||
| 208 | echo \View::instance()->render($view); |
||
| 209 | return; |
||
| 210 | } |
||
| 211 | } |
||
| 212 | $f3->set('REQUEST.redirect_uri', $request['redirect_uri']); |
||
| 213 | |||
| 214 | // verify client_id from session on accept/deny click |
||
| 215 | $f3->set('SESSION.client_id', $appsMapper->client_id); |
||
| 216 | |||
| 217 | // allowed scopes |
||
| 218 | $f3->set('SESSION.scope', join(',', array_keys($permissions))); |
||
| 219 | |||
| 220 | // validate client_id |
||
| 221 | $client = true; |
||
| 222 | |||
| 223 | if (!empty($client)) { |
||
| 224 | // get client permissions requested |
||
| 225 | // if valid, create code and access token for it |
||
| 226 | |||
| 227 | $f3->set('confirmUrl', |
||
| 228 | $this->url('@oauth_confirm', $f3->get('REQUEST'))); |
||
| 229 | |||
| 230 | $f3->set('denyUrl', |
||
| 231 | $this->url('@oauth_deny', $f3->get('REQUEST'))); |
||
| 232 | |||
| 233 | $f3->set('errors', false); |
||
| 234 | } |
||
| 235 | |||
| 236 | $f3->set('permissions', $permissions); |
||
| 237 | |||
| 238 | $f3->set('form', $f3->get('REQUEST')); |
||
| 239 | echo \View::instance()->render($view); |
||
| 240 | } |
||
| 241 | |||
| 242 | |||
| 243 | /** |
||
| 244 | * Accept incoming OAuth2 request for user access |
||
| 245 | * |
||
| 246 | * @param \Base $f3 |
||
| 247 | * @param array $params |
||
| 248 | * @return void |
||
| 249 | */ |
||
| 250 | public function ConfirmPost(\Base $f3, array $params) |
||
| 344 | |||
| 345 | |||
| 346 | /** |
||
| 347 | * Deny incoming OAuth2 request for user access |
||
| 348 | * |
||
| 349 | * @param \Base $f3 |
||
| 350 | * @param array $params |
||
| 351 | * @return void |
||
| 352 | */ |
||
| 353 | public function Deny(\Base $f3, array $params) |
||
| 398 | |||
| 399 | } |
||
| 400 |