Total Complexity | 66 |
Total Lines | 522 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Pdo 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.
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 Pdo, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | class Pdo implements |
||
21 | AuthorizationCodeInterface, |
||
22 | AccessTokenInterface, |
||
23 | ClientCredentialsInterface, |
||
24 | UserCredentialsInterface, |
||
25 | RefreshTokenInterface, |
||
26 | JwtBearerInterface, |
||
27 | ScopeInterface, |
||
28 | PublicKeyInterface, |
||
29 | UserClaimsInterface, |
||
30 | OpenIDAuthorizationCodeInterface |
||
31 | { |
||
32 | protected $db; |
||
33 | protected $config; |
||
34 | |||
35 | public function __construct($connection, $config = array()) |
||
36 | { |
||
37 | if (!$connection instanceof \PDO) { |
||
38 | if (is_string($connection)) { |
||
39 | $connection = array('dsn' => $connection); |
||
40 | } |
||
41 | if (!is_array($connection)) { |
||
42 | throw new \InvalidArgumentException('First argument to OAuth2\Storage\Pdo must be an instance of PDO, a DSN string, or a configuration array'); |
||
43 | } |
||
44 | if (!isset($connection['dsn'])) { |
||
45 | throw new \InvalidArgumentException('configuration array must contain "dsn"'); |
||
46 | } |
||
47 | // merge optional parameters |
||
48 | $connection = array_merge(array( |
||
49 | 'username' => null, |
||
50 | 'password' => null, |
||
51 | 'options' => array(), |
||
52 | ), $connection); |
||
53 | $connection = new \PDO($connection['dsn'], $connection['username'], $connection['password'], $connection['options']); |
||
54 | } |
||
55 | $this->db = $connection; |
||
56 | |||
57 | // debugging |
||
58 | $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); |
||
59 | |||
60 | $this->config = array_merge(array( |
||
61 | 'client_table' => 'oauth_clients', |
||
62 | 'access_token_table' => 'oauth_access_tokens', |
||
63 | 'refresh_token_table' => 'oauth_refresh_tokens', |
||
64 | 'code_table' => 'oauth_authorization_codes', |
||
65 | 'user_table' => 'oauth_users', |
||
66 | 'jwt_table' => 'oauth_jwt', |
||
67 | 'jti_table' => 'oauth_jti', |
||
68 | 'scope_table' => 'oauth_scopes', |
||
69 | 'public_key_table' => 'oauth_public_keys', |
||
70 | ), $config); |
||
71 | } |
||
72 | |||
73 | /* OAuth2\Storage\ClientCredentialsInterface */ |
||
74 | public function checkClientCredentials($client_id, $client_secret = null) |
||
75 | { |
||
76 | $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); |
||
77 | $stmt->execute(compact('client_id')); |
||
78 | $result = $stmt->fetch(\PDO::FETCH_ASSOC); |
||
79 | |||
80 | // make this extensible |
||
81 | return $result && $result['client_secret'] == $client_secret; |
||
82 | } |
||
83 | |||
84 | public function isPublicClient($client_id) |
||
85 | { |
||
86 | $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); |
||
87 | $stmt->execute(compact('client_id')); |
||
88 | |||
89 | if (!$result = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
90 | return false; |
||
91 | } |
||
92 | |||
93 | return empty($result['client_secret']); |
||
94 | } |
||
95 | |||
96 | /* OAuth2\Storage\ClientInterface */ |
||
97 | public function getClientDetails($client_id) |
||
98 | { |
||
99 | $stmt = $this->db->prepare(sprintf('SELECT * from %s where client_id = :client_id', $this->config['client_table'])); |
||
100 | $stmt->execute(compact('client_id')); |
||
101 | |||
102 | return $stmt->fetch(\PDO::FETCH_ASSOC); |
||
103 | } |
||
104 | |||
105 | public function setClientDetails($client_id, $client_secret = null, $redirect_uri = null, $grant_types = null, $scope = null, $user_id = null) |
||
106 | { |
||
107 | // if it exists, update it. |
||
108 | if ($this->getClientDetails($client_id)) { |
||
109 | $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_secret=:client_secret, redirect_uri=:redirect_uri, grant_types=:grant_types, scope=:scope, user_id=:user_id where client_id=:client_id', $this->config['client_table'])); |
||
110 | } else { |
||
111 | $stmt = $this->db->prepare(sprintf('INSERT INTO %s (client_id, client_secret, redirect_uri, grant_types, scope, user_id) VALUES (:client_id, :client_secret, :redirect_uri, :grant_types, :scope, :user_id)', $this->config['client_table'])); |
||
112 | } |
||
113 | |||
114 | return $stmt->execute(compact('client_id', 'client_secret', 'redirect_uri', 'grant_types', 'scope', 'user_id')); |
||
115 | } |
||
116 | |||
117 | public function checkRestrictedGrantType($client_id, $grant_type) |
||
118 | { |
||
119 | $details = $this->getClientDetails($client_id); |
||
120 | if (isset($details['grant_types'])) { |
||
121 | $grant_types = explode(' ', $details['grant_types']); |
||
122 | |||
123 | return in_array($grant_type, (array) $grant_types); |
||
124 | } |
||
125 | |||
126 | // if grant_types are not defined, then none are restricted |
||
127 | return true; |
||
128 | } |
||
129 | |||
130 | /* OAuth2\Storage\AccessTokenInterface */ |
||
131 | public function getAccessToken($access_token) |
||
132 | { |
||
133 | $stmt = $this->db->prepare(sprintf('SELECT * from %s where access_token = :access_token', $this->config['access_token_table'])); |
||
134 | |||
135 | $token = $stmt->execute(compact('access_token')); |
||
|
|||
136 | if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
137 | // convert date string back to timestamp |
||
138 | $token['expires'] = strtotime($token['expires']); |
||
139 | } |
||
140 | |||
141 | return $token; |
||
142 | } |
||
143 | |||
144 | public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null) |
||
145 | { |
||
146 | // convert expires to datestring |
||
147 | $expires = date('Y-m-d H:i:s', $expires); |
||
148 | |||
149 | // if it exists, update it. |
||
150 | if ($this->getAccessToken($access_token)) { |
||
151 | $stmt = $this->db->prepare(sprintf('UPDATE %s SET client_id=:client_id, expires=:expires, user_id=:user_id, scope=:scope where access_token=:access_token', $this->config['access_token_table'])); |
||
152 | } else { |
||
153 | $stmt = $this->db->prepare(sprintf('INSERT INTO %s (access_token, client_id, expires, user_id, scope) VALUES (:access_token, :client_id, :expires, :user_id, :scope)', $this->config['access_token_table'])); |
||
154 | } |
||
155 | |||
156 | return $stmt->execute(compact('access_token', 'client_id', 'user_id', 'expires', 'scope')); |
||
157 | } |
||
158 | |||
159 | public function unsetAccessToken($access_token) |
||
160 | { |
||
161 | $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE access_token = :access_token', $this->config['access_token_table'])); |
||
162 | |||
163 | return $stmt->execute(compact('access_token')); |
||
164 | } |
||
165 | |||
166 | /* OAuth2\Storage\AuthorizationCodeInterface */ |
||
167 | public function getAuthorizationCode($code) |
||
168 | { |
||
169 | $stmt = $this->db->prepare(sprintf('SELECT * from %s where authorization_code = :code', $this->config['code_table'])); |
||
170 | $stmt->execute(compact('code')); |
||
171 | |||
172 | if ($code = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
173 | // convert date string back to timestamp |
||
174 | $code['expires'] = strtotime($code['expires']); |
||
175 | } |
||
176 | |||
177 | return $code; |
||
178 | } |
||
179 | |||
180 | public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) |
||
181 | { |
||
182 | if (func_num_args() > 6) { |
||
183 | // we are calling with an id token |
||
184 | return call_user_func_array(array($this, 'setAuthorizationCodeWithIdToken'), func_get_args()); |
||
185 | } |
||
186 | |||
187 | // convert expires to datestring |
||
188 | $expires = date('Y-m-d H:i:s', $expires); |
||
189 | |||
190 | // if it exists, update it. |
||
191 | if ($this->getAuthorizationCode($code)) { |
||
192 | $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope where authorization_code=:code', $this->config['code_table'])); |
||
193 | } else { |
||
194 | $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope)', $this->config['code_table'])); |
||
195 | } |
||
196 | |||
197 | return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope')); |
||
198 | } |
||
199 | |||
200 | private function setAuthorizationCodeWithIdToken($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null, $id_token = null) |
||
201 | { |
||
202 | // convert expires to datestring |
||
203 | $expires = date('Y-m-d H:i:s', $expires); |
||
204 | |||
205 | // if it exists, update it. |
||
206 | if ($this->getAuthorizationCode($code)) { |
||
207 | $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET client_id=:client_id, user_id=:user_id, redirect_uri=:redirect_uri, expires=:expires, scope=:scope, id_token =:id_token where authorization_code=:code', $this->config['code_table'])); |
||
208 | } else { |
||
209 | $stmt = $this->db->prepare(sprintf('INSERT INTO %s (authorization_code, client_id, user_id, redirect_uri, expires, scope, id_token) VALUES (:code, :client_id, :user_id, :redirect_uri, :expires, :scope, :id_token)', $this->config['code_table'])); |
||
210 | } |
||
211 | |||
212 | return $stmt->execute(compact('code', 'client_id', 'user_id', 'redirect_uri', 'expires', 'scope', 'id_token')); |
||
213 | } |
||
214 | |||
215 | public function expireAuthorizationCode($code) |
||
216 | { |
||
217 | $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE authorization_code = :code', $this->config['code_table'])); |
||
218 | |||
219 | return $stmt->execute(compact('code')); |
||
220 | } |
||
221 | |||
222 | /* OAuth2\Storage\UserCredentialsInterface */ |
||
223 | public function checkUserCredentials($username, $password) |
||
224 | { |
||
225 | if ($user = $this->getUser($username)) { |
||
226 | return $this->checkPassword($user, $password); |
||
227 | } |
||
228 | |||
229 | return false; |
||
230 | } |
||
231 | |||
232 | public function getUserDetails($username) |
||
233 | { |
||
234 | return $this->getUser($username); |
||
235 | } |
||
236 | |||
237 | /* UserClaimsInterface */ |
||
238 | public function getUserClaims($user_id, $claims) |
||
239 | { |
||
240 | if (!$userDetails = $this->getUserDetails($user_id)) { |
||
241 | return false; |
||
242 | } |
||
243 | |||
244 | $claims = explode(' ', trim($claims)); |
||
245 | $userClaims = array(); |
||
246 | |||
247 | // for each requested claim, if the user has the claim, set it in the response |
||
248 | $validClaims = explode(' ', self::VALID_CLAIMS); |
||
249 | foreach ($validClaims as $validClaim) { |
||
250 | if (in_array($validClaim, $claims)) { |
||
251 | if ($validClaim == 'address') { |
||
252 | // address is an object with subfields |
||
253 | $userClaims['address'] = $this->getUserClaim($validClaim, $userDetails['address'] ?: $userDetails); |
||
254 | } else { |
||
255 | $userClaims = array_merge($userClaims, $this->getUserClaim($validClaim, $userDetails)); |
||
256 | } |
||
257 | } |
||
258 | } |
||
259 | |||
260 | return $userClaims; |
||
261 | } |
||
262 | |||
263 | protected function getUserClaim($claim, $userDetails) |
||
264 | { |
||
265 | $userClaims = array(); |
||
266 | $claimValuesString = constant(sprintf('self::%s_CLAIM_VALUES', strtoupper($claim))); |
||
267 | $claimValues = explode(' ', $claimValuesString); |
||
268 | |||
269 | foreach ($claimValues as $value) { |
||
270 | $userClaims[$value] = isset($userDetails[$value]) ? $userDetails[$value] : null; |
||
271 | } |
||
272 | |||
273 | return $userClaims; |
||
274 | } |
||
275 | |||
276 | /* OAuth2\Storage\RefreshTokenInterface */ |
||
277 | public function getRefreshToken($refresh_token) |
||
278 | { |
||
279 | $stmt = $this->db->prepare(sprintf('SELECT * FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table'])); |
||
280 | |||
281 | $token = $stmt->execute(compact('refresh_token')); |
||
282 | if ($token = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
283 | // convert expires to epoch time |
||
284 | $token['expires'] = strtotime($token['expires']); |
||
285 | } |
||
286 | |||
287 | return $token; |
||
288 | } |
||
289 | |||
290 | public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null) |
||
291 | { |
||
292 | // convert expires to datestring |
||
293 | $expires = date('Y-m-d H:i:s', $expires); |
||
294 | |||
295 | $stmt = $this->db->prepare(sprintf('INSERT INTO %s (refresh_token, client_id, user_id, expires, scope) VALUES (:refresh_token, :client_id, :user_id, :expires, :scope)', $this->config['refresh_token_table'])); |
||
296 | |||
297 | return $stmt->execute(compact('refresh_token', 'client_id', 'user_id', 'expires', 'scope')); |
||
298 | } |
||
299 | |||
300 | public function unsetRefreshToken($refresh_token) |
||
301 | { |
||
302 | $stmt = $this->db->prepare(sprintf('DELETE FROM %s WHERE refresh_token = :refresh_token', $this->config['refresh_token_table'])); |
||
303 | |||
304 | return $stmt->execute(compact('refresh_token')); |
||
305 | } |
||
306 | |||
307 | // plaintext passwords are bad! Override this for your application |
||
308 | protected function checkPassword($user, $password) |
||
309 | { |
||
310 | return $user['password'] == sha1($password); |
||
311 | } |
||
312 | |||
313 | public function getUser($username) |
||
314 | { |
||
315 | $stmt = $this->db->prepare($sql = sprintf('SELECT * from %s where username=:username', $this->config['user_table'])); |
||
316 | $stmt->execute(array('username' => $username)); |
||
317 | |||
318 | if (!$userInfo = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
319 | return false; |
||
320 | } |
||
321 | |||
322 | // the default behavior is to use "username" as the user_id |
||
323 | return array_merge(array( |
||
324 | 'user_id' => $username |
||
325 | ), $userInfo); |
||
326 | } |
||
327 | |||
328 | public function setUser($username, $password, $firstName = null, $lastName = null) |
||
329 | { |
||
330 | // do not store in plaintext |
||
331 | $password = sha1($password); |
||
332 | |||
333 | // if it exists, update it. |
||
334 | if ($this->getUser($username)) { |
||
335 | $stmt = $this->db->prepare($sql = sprintf('UPDATE %s SET password=:password, first_name=:firstName, last_name=:lastName where username=:username', $this->config['user_table'])); |
||
336 | } else { |
||
337 | $stmt = $this->db->prepare(sprintf('INSERT INTO %s (username, password, first_name, last_name) VALUES (:username, :password, :firstName, :lastName)', $this->config['user_table'])); |
||
338 | } |
||
339 | |||
340 | return $stmt->execute(compact('username', 'password', 'firstName', 'lastName')); |
||
341 | } |
||
342 | |||
343 | /* ScopeInterface */ |
||
344 | public function scopeExists($scope) |
||
345 | { |
||
346 | $scope = explode(' ', $scope); |
||
347 | $whereIn = implode(',', array_fill(0, count($scope), '?')); |
||
348 | $stmt = $this->db->prepare(sprintf('SELECT count(scope) as count FROM %s WHERE scope IN (%s)', $this->config['scope_table'], $whereIn)); |
||
349 | $stmt->execute($scope); |
||
350 | |||
351 | if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
352 | return $result['count'] == count($scope); |
||
353 | } |
||
354 | |||
355 | return false; |
||
356 | } |
||
357 | |||
358 | public function getDefaultScope($client_id = null) |
||
359 | { |
||
360 | $stmt = $this->db->prepare(sprintf('SELECT scope FROM %s WHERE is_default=:is_default', $this->config['scope_table'])); |
||
361 | $stmt->execute(array('is_default' => true)); |
||
362 | |||
363 | if ($result = $stmt->fetchAll(\PDO::FETCH_ASSOC)) { |
||
364 | $defaultScope = array_map(function ($row) { |
||
365 | return $row['scope']; |
||
366 | }, $result); |
||
367 | |||
368 | return implode(' ', $defaultScope); |
||
369 | } |
||
370 | |||
371 | return null; |
||
372 | } |
||
373 | |||
374 | /* JWTBearerInterface */ |
||
375 | public function getClientKey($client_id, $subject) |
||
376 | { |
||
377 | $stmt = $this->db->prepare($sql = sprintf('SELECT public_key from %s where client_id=:client_id AND subject=:subject', $this->config['jwt_table'])); |
||
378 | |||
379 | $stmt->execute(array('client_id' => $client_id, 'subject' => $subject)); |
||
380 | |||
381 | return $stmt->fetchColumn(); |
||
382 | } |
||
383 | |||
384 | public function getClientScope($client_id) |
||
385 | { |
||
386 | if (!$clientDetails = $this->getClientDetails($client_id)) { |
||
387 | return false; |
||
388 | } |
||
389 | |||
390 | if (isset($clientDetails['scope'])) { |
||
391 | return $clientDetails['scope']; |
||
392 | } |
||
393 | |||
394 | return null; |
||
395 | } |
||
396 | |||
397 | public function getJti($client_id, $subject, $audience, $expires, $jti) |
||
414 | } |
||
415 | |||
416 | public function setJti($client_id, $subject, $audience, $expires, $jti) |
||
417 | { |
||
418 | $stmt = $this->db->prepare(sprintf('INSERT INTO %s (issuer, subject, audience, expires, jti) VALUES (:client_id, :subject, :audience, :expires, :jti)', $this->config['jti_table'])); |
||
419 | |||
420 | return $stmt->execute(compact('client_id', 'subject', 'audience', 'expires', 'jti')); |
||
421 | } |
||
422 | |||
423 | /* PublicKeyInterface */ |
||
424 | public function getPublicKey($client_id = null) |
||
425 | { |
||
426 | $stmt = $this->db->prepare($sql = sprintf('SELECT public_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); |
||
427 | |||
428 | $stmt->execute(compact('client_id')); |
||
429 | if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
430 | return $result['public_key']; |
||
431 | } |
||
432 | } |
||
433 | |||
434 | public function getPrivateKey($client_id = null) |
||
435 | { |
||
436 | $stmt = $this->db->prepare($sql = sprintf('SELECT private_key FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); |
||
437 | |||
438 | $stmt->execute(compact('client_id')); |
||
439 | if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
440 | return $result['private_key']; |
||
441 | } |
||
442 | } |
||
443 | |||
444 | public function getEncryptionAlgorithm($client_id = null) |
||
445 | { |
||
446 | $stmt = $this->db->prepare($sql = sprintf('SELECT encryption_algorithm FROM %s WHERE client_id=:client_id OR client_id IS NULL ORDER BY client_id IS NOT NULL DESC', $this->config['public_key_table'])); |
||
447 | |||
448 | $stmt->execute(compact('client_id')); |
||
449 | if ($result = $stmt->fetch(\PDO::FETCH_ASSOC)) { |
||
450 | return $result['encryption_algorithm']; |
||
451 | } |
||
452 | |||
453 | return 'RS256'; |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * DDL to create OAuth2 database and tables for PDO storage |
||
458 | * |
||
459 | * @see https://github.com/dsquier/oauth2-server-php-mysql |
||
460 | */ |
||
461 | public function getBuildSql($dbName = 'oauth2_server_php') |
||
542 | } |
||
543 | } |
||
544 |