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 ( ce69c9...06e1bf )
by Lonnie
06:20
created

APIAuthentication::tryDigestAuthentication()   D

Complexity

Conditions 13
Paths 207

Size

Total Lines 84
Code Lines 39

Duplication

Lines 20
Ratio 23.81 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 20
loc 84
rs 4.5364
cc 13
eloc 39
nc 207
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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