GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( 368a03...ce69c9 )
by Lonnie
25:24 queued 19:04
created

APIAuthentication   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 383
Duplicated Lines 12.27 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 37
c 1
b 0
f 0
lcom 1
cbo 1
dl 47
loc 383
rs 8.6

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 3
A setRealm() 0 5 1
C tryBasicAuthentication() 0 43 7
B viaRemember() 0 44 6
A checkIPBlacklist() 15 15 2
A checkIPWhitelist() 17 17 2
A loginUser() 0 16 2
A login() 0 4 1
A logout() 0 4 1
A isLoggedIn() 0 4 1
C tryDigestAuthentication() 15 74 11

How to fix   Duplicated Code   

Duplicated Code

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:

1
<?php namespace Myth\Api\Auth;
2
/**
3
 * Sprint
4
 *
5
 * A set of power tools to enhance the CodeIgniter framework and provide consistent workflow.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in
15
 * all copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 * THE SOFTWARE.
24
 *
25
 * @package     Sprint
26
 * @author      Lonnie Ezell
27
 * @copyright   Copyright 2014-2015, New Myth Media, LLC (http://newmythmedia.com)
28
 * @license     http://opensource.org/licenses/MIT  (MIT)
29
 * @link        http://sprintphp.com
30
 * @since       Version 1.0
31
 */
32
33
use Myth\Auth\LocalAuthentication;
34
use Myth\Events;
35
36
/**
37
 * Class APIAuthentication
38
 * 
39
 * @package Myth\Api\Auth
40
 */
41
class APIAuthentication extends LocalAuthentication {
42
43
	protected $logged_in = false;
44
45
	protected $realm = 'WallyWorld';
46
47
	protected $email = null;
48
49
	//--------------------------------------------------------------------
50
51
	public function __construct($ci=null)
52
	{
53
		parent::__construct($ci);
54
55
		$this->ci->config->load('api');
56
		$this->ci->lang->load('api');
57
58
		// Has the IP address been blacklisted?
59
		if (config_item('auth.ip_blacklist_enabled'))
60
		{
61
			$this->checkIPBlacklist();
62
		}
63
64
		// Do we need to do whitelisting?
65
		if (config_item('auth.ip_whitelist_enabled'))
66
		{
67
			$this->checkIPWhitelist();
68
		}
69
	}
70
71
	//--------------------------------------------------------------------
72
73
	/**
74
	 * Sets the realm used by the authentication. The system truly only
75
	 * supports a single realm across the entire application, but this
76
	 * allows it to be set by the controller.
77
	 *
78
	 * @param $realm
79
	 *
80
	 * @return $this
81
	 */
82
	public function setRealm($realm)
83
	{
84
	    $this->realm = $realm;
85
		return $this;
86
	}
87
88
	//--------------------------------------------------------------------
89
90
	/**
91
	 * Checks to see if someone is authorized via HTTP Basic Authentication.
92
	 *
93
	 * @return bool
94
	 */
95
	public function tryBasicAuthentication()
96
	{
97
		$username = null;
98
		$password = null;
99
100
		// mod_php
101
		if ($this->ci->input->server('PHP_AUTH_USER')) {
102
			$username = $this->ci->input->server('PHP_AUTH_USER');
103
			$password = $this->ci->input->server('PHP_AUTH_PW');
104
		}
105
106
		// most other servers
107
		elseif ($this->ci->input->server('HTTP_AUTHENTICATION')) {
108
			if (strpos(strtolower($this->ci->input->server('HTTP_AUTHENTICATION')), 'basic') === 0) {
109
				list($username, $password) = explode(':', base64_decode(substr($this->ci->input->server('HTTP_AUTHORIZATION'), 6)));
110
			}
111
		}
112
113
		// If credentials weren't provided, we can't do anything
114
		// so request authorization by the client.
115
		if (empty($username) || empty($password))
116
		{
117
			$this->ci->output->set_header('WWW-Authenticate: Basic realm="'. config_item('api.realm') .'"');
118
			return false;
119
		}
120
121
		$data = [
122
			config_item('api.auth_field') => $username,
123
			'password'  => $password
124
		];
125
126
		// Set email for later throttling check
127
		if (config_item('api.auth_field') === 'email')
128
		{
129
			$this->email = $username;
130
		}
131
132
	    $user = $this->validate($data, true);
133
134
		$this->user = $user;
135
136
		return $user;
137
	}
138
139
	//--------------------------------------------------------------------
140
141
	/**
142
	 * Checks to see if someone is authorized via HTTP Digest Authentication.
143
	 *
144
	 * NOTE: This requires that a new field, 'digest_key', be added to the user's
145
	 * table and, during new user creation, or password reset, that the digest_key
146
	 * be calculated as md5({username}:{realm}:{password})
147
	 *
148
	 * References:
149
	 *  - http://www.faqs.org/rfcs/rfc2617.html
150
	 *  - http://www.sitepoint.com/understanding-http-digest-access-authentication/
151
	 */
152
	public function tryDigestAuthentication()
153
	{
154
		$digest_string = '';
155
156
		// We need to test which server authentication variable to use
157
		// because the PHP ISAPI module in IIS acts different from CGI
158
		if ($this->ci->input->server('PHP_AUTH_DIGEST'))
159
		{
160
			$digest_string = $this->ci->input->server('PHP_AUTH_DIGEST');
161
		}
162
		elseif ($this->ci->input->server('HTTP_AUTHORIZATION'))
163
		{
164
			$digest_string = $this->ci->input->server('HTTP_AUTHORIZATION');
165
		}
166
167
		$nonce = md5(uniqid());
168
		$opaque = md5(uniqid());
169
170
		// No digest string? Then you're done. Go home.
171 View Code Duplication
		if (empty($digest_string))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
172
		{
173
			$this->ci->output->set_header( sprintf('WWW-Authenticate: Digest realm="%s", nonce="%s", opaque="%s"', config_item('api.realm'), $nonce, $opaque) );
174
			return false;
175
		}
176
177
		// Grab the parts from the digest string.
178
		// They will be provided as an array of the parts: username, nonce, uri, nc, cnonce, qop, response
179
		$matches = [];
180
		preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches);
181
		$digest = (empty($matches[1]) || empty($matches[2])) ? array() : array_combine($matches[1], $matches[2]);
182
183 View Code Duplication
		if (! array_key_exists('username', $digest))
184
		{
185
			$this->ci->output->set_header( sprintf('WWW-Authenticate: Digest realm="%s", nonce="%s", opaque="%s"', config_item('api.realm'), $nonce, $opaque) );
186
			return false;
187
		}
188
189
		// Set email for later throttling check
190
		if (config_item('api.auth_field') === 'email')
191
		{
192
			$this->email = $digest['username'];
193
		}
194
195
		// Grab the user that corresponds to that "username"
196
		// exact field determined in the api config file - api.auth_field setting.
197
		$user = $this->user_model->as_array()->find_by( config_item('api.auth_field'), $digest['username'] );
198
		if (!  $user)
199
		{
200
			$this->ci->output->set_header( sprintf('WWW-Authenticate: Digest realm="%s", nonce="%s", opaque="%s"', config_item('api.realm'), $nonce, $opaque) );
201
			return false;
202
		}
203
204
		// Calc the correct response
205
		$A1 = $user['digest_key'];
206
207
		if ($digest['qop'] == 'auth')
208
		{
209
			$A2 = md5( strtoupper( $_SERVER['REQUEST_METHOD'] ) .':'. $digest['uri'] );
210
		} else {
211
			$body = file_get_contents('php://input');
212
			$A2 = md5( strtoupper( $_SERVER['REQUEST_METHOD'] ) .':'. $digest['uri'] .':'. md5($body) );
213
		}
214
		$valid_response = md5($A1 .':'. $digest['nonce'].':'. $digest['nc'] .':'. $digest['cnonce'] .':'. $digest['qop'] .':'. $A2);
215
216 View Code Duplication
		if ($digest['response'] != $valid_response)
217
		{
218
			$this->ci->output->set_header( sprintf('WWW-Authenticate: Digest realm="%s", nonce="%s", opaque="%s"', config_item('api.realm'), $nonce, $opaque) );
219
			return false;
220
		}
221
222
		$this->user = $user;
223
224
		return $user;
225
	}
226
227
	//--------------------------------------------------------------------
228
229
	/**
230
	 * Attempts to log a user into the API via the configured 'api.auth_type'
231
	 * config variable in config/api.php.
232
	 *
233
	 * NOTE: Since this is intended for API use, it is a STATELESS implementation
234
	 * and does not support remember me functionality.
235
	 *
236
	 * This basically replaces the login() method due to the way the AuthTrait
237
	 * works.
238
	 *
239
	 * @return bool
240
	 */
241
	public function viaRemember()
242
	{
243
		$user = false;
244
245
		switch (config_item('api.auth_type'))
246
		{
247
			case 'basic':
248
				$user = $this->tryBasicAuthentication();
249
				break;
250
			case 'digest':
251
				$user = $this->tryDigestAuthentication();
252
				break;
253
		}
254
255
		// If the user is throttled due to too many invalid logins
256
		// or the system is under attack, kick them back.
257
		// We need to test for this after validation because we
258
		// don't want it to affect a valid login.
259
260
		if ($this->email)
261
		{
262
			// If throttling time is above zero, we can't allow
263
			// logins now.
264
			if ($time = (int)$this->isThrottled($this->email) > 0)
265
			{
266
				$this->error = sprintf(lang('api.throttled'), $time);
267
				return false;
268
			}
269
270
			$this->email = null;
271
		}
272
273
		if (! $user)
274
		{
275
			$this->user = null;
276
			return $user;
277
		}
278
279
		$this->loginUser($user);
280
281
		Events::trigger('didLogin', [$user]);
282
283
		return true;
284
	}
285
286
	//--------------------------------------------------------------------
287
288
	//--------------------------------------------------------------------
289
	// Protected Methods
290
	//--------------------------------------------------------------------
291
292
	/**
293
	 * Checks the client's IP address against any IP addresses specified
294
	 * in the api config file. If any are found, the client is refused
295
	 * access immediately.
296
	 */
297 View Code Duplication
	public function checkIPBlacklist()
298
	{
299
	    $blacklist = explode(',', config_item('api.ip_blacklist'));
300
301
		array_walk($blacklist, function (&$item, $key) {
302
			$item = trim($item);
303
		});
304
305
		if (in_array($this->ci->input->ip_address(), $blacklist))
306
		{
307
			throw new \Exception( lang('api.ip_denied'), 401);
308
		}
309
310
		return true;
311
	}
312
	
313
	//--------------------------------------------------------------------
314
315
	/**
316
	 * Checks the client's IP address against any IP addresses specified
317
	 * in the api config file. If the client is not accessing the site
318
	 * from one of those addresses then their access is denied.
319
	 */
320 View Code Duplication
	public function checkIPWhitelist()
321
	{
322
		$whitelist = explode(',', config_item('api.ip_whitelist'));
323
324
		array_push($whitelist, '127.0.0.1', '0.0.0.0');
325
326
		array_walk($whitelist, function (&$item, $key) {
327
			$item = trim($item);
328
		});
329
330
		if (! in_array($this->ci->input->ip_address(), $whitelist))
331
		{
332
			throw new \Exception( lang('api.ip_denied'), 401);
333
		}
334
335
		return true;
336
	}
337
338
	//--------------------------------------------------------------------
339
340
	/**
341
	 * Handles the nitty gritty of actually logging our user into the system.
342
	 * Does NOT perform the authentication, just sets the system up so that
343
	 * it knows we're here.
344
	 *
345
	 * @param $user
346
	 */
347
	protected function loginUser($user)
348
	{
349
		// Save the user for later access
350
		$this->user = $user;
351
352
		// Clear our login attempts
353
		$this->ci->login_model->purgeLoginAttempts($user['email']);
354
355
		// We'll give a 20% chance to need to do a purge since we
356
		// don't need to purge THAT often, it's just a maintenance issue.
357
		// to keep the table from getting out of control.
358
		if (mt_rand(1, 100) < 20)
359
		{
360
			$this->ci->login_model->purgeOldRememberTokens();
361
		}
362
	}
363
364
	//--------------------------------------------------------------------
365
	
366
	//--------------------------------------------------------------------
367
	// UNUSED METHOD OVERRIDES
368
	//--------------------------------------------------------------------
369
370
	/**
371
	 * Attempt to log a user into the system.
372
	 *
373
	 * $credentials is an array of key/value pairs needed to log the user in.
374
	 * This is often email/password, or username/password.
375
	 *
376
	 * NOTE: Since this is intended for API use, it is a STATELESS implementation
377
	 * and does not support remember me functionality.
378
	 *
379
	 * Valid credentials:
380
	 *  - username
381
	 *  - email
382
	 *  - realm
383
	 *
384
	 * @param $credentials
385
	 * @param bool $remember
386
	 *
387
	 * @return bool|mixed|void
388
	 */
389
	public function login($credentials, $remember=false)
390
	{
391
		throw new \BadMethodCallException( lang('api.unused_method') );
392
	}
393
394
	//--------------------------------------------------------------------
395
396
	/**
397
	 * Logs a user out and removes all session information.
398
	 *
399
	 * NOTE: Since this is intended for API use, it is a STATELESS implementation
400
	 * and does not support remember me functionality.
401
	 *
402
	 * @return mixed
403
	 */
404
	public function logout()
405
	{
406
		throw new \BadMethodCallException( lang('api.unused_method') );
407
	}
408
409
	//--------------------------------------------------------------------
410
411
	/**
412
	 * Checks whether a user is logged in or not.
413
	 *
414
	 * @return bool
415
	 */
416
	public function isLoggedIn()
417
	{
418
		return $this->logged_in;
419
	}
420
421
	//--------------------------------------------------------------------
422
423
}
424