Kohana_Auth_Jam::access()   A
last analyzed

Complexity

Conditions 6
Paths 20

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.2222
c 0
b 0
f 0
cc 6
nc 20
nop 2
crap 6
1
<?php defined('SYSPATH') OR die('No direct access allowed.');
2
/**
3
 * Jam Auth driver.
4
 *
5
 * @package    Kohana/Auth
6
 * @author     Ivan Kerin
7
 * @copyright  (c) 2012 Despark Ltd.
8
 * @license    http://creativecommons.org/licenses/by-sa/3.0/legalcode
9
 */
10
abstract class Kohana_Auth_Jam extends Auth {
11
12
	protected $_services = array();
13
14
	public static function clear_cache()
15
	{
16
		Auth_Jam::$_instance = NULL;
0 ignored issues
show
Bug introduced by
The property _instance cannot be accessed from this context as it is declared protected in class Kohana_Auth.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
17
	}
18
19 15
	public static function access($action, $access = array())
20
	{
21 15
		$access = (array) $access;
22 15
		$default_access = Arr::get($access, 0, 'public');
23 15
		$opposite_access = $default_access === 'public' ? 'private' : 'public';
24
25 15
		$except_actions = (array) Arr::get($access, 'except', array());
26 15
		$only_actions = (array) Arr::get($access, 'only', array());
27
28 15
		return (($except_actions AND in_array($action, $except_actions)) OR ( $only_actions AND ! in_array($action, $only_actions))) ? $opposite_access : $default_access;
29
	}
30
31 37
	public function __construct($config = array())
32
	{
33 37
		parent::__construct($config);
34
35 37
		foreach ($config['services'] as $service => $config)
36
		{
37 37
			$class = 'Auth_Service_'.Jam::capitalize_class_name($service);
38 37
			$this->_services[$service] = new $class($config);
39
		}
40 37
	}
41
42
	/**
43
	 * Get all the available services, or only one service if provided a name
44
	 * @param  string $name the name of the service, e.g. 'facebook'
45
	 * @return array|Auth_Service
46
	 */
47 13
	public function services($name = NULL)
48
	{
49 13
		return $name === NULL ? $this->_services : Arr::get($this->_services, $name);
50
	}
51
52
	/**
53
	 * Checks if a session is active.
54
	 *
55
	 * @param   mixed    $role Role name string, role Jam object, or array with role names
56
	 * @return  boolean
57
	 */
58 12
	public function logged_in($role = NULL)
59
	{
60
		// Get the user from the session
61 12
		$user = $this->get_user();
62
63 12
		if ( ! $user)
64 10
			return FALSE;
65
66 7
		if ($user instanceof Model_Auth_User AND $user->loaded())
67
		{
68
			// If we don't have a roll no further checking is needed
69 7
			if ( ! $role)
70 7
				return TRUE;
71
72
			if (is_array($role))
73
			{
74
				return ! array_diff($role, $user->roles->as_array(NULL, 'name'));
0 ignored issues
show
Documentation introduced by
The property roles does not exist on object<Model_Auth_User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
75
			}
76
			elseif (is_string($role) OR $role instanceof Model_Auth_Role)
77
			{
78
				return $user->roles->has($role);
0 ignored issues
show
Documentation introduced by
The property roles does not exist on object<Model_Auth_User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
79
			}
80
			else
81
			{
82
				throw new Kohana_Exception('Invalid Role ":role"', array("role" => (string) $role));
83
			}
84
		}
85
		return FALSE;
86
	}
87
88
	/**
89
	 * Getter
90
	 * The session instance for the configured session_type
91
	 * @return Session
92
	 */
93 37
	public function session()
94
	{
95 37
		if ( ! $this->_session)
96
		{
97
			$this->_session = Session::instance($this->_config['session_type']);
98
		}
99 37
		return $this->_session;
100
	}
101
102
	/**
103
	 * Logs a user in.
104
	 *
105
	 * @param   string   $username  username
0 ignored issues
show
Bug introduced by
There is no parameter named $username. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
106
	 * @param   string   $password  password
107
	 * @param   boolean  $remember  enable autologin
108
	 * @return  boolean
109
	 */
110 6
	protected function _login($user, $password, $remember)
111
	{
112 6
		if (is_string($password))
113
		{
114
			// Create a hashed password
115 6
			$password = $this->hash($password);
116
		}
117
118 6
		$user = $this->_load_user($user);
119
120
		// If the passwords match, perform a login
121 6
		if ($user AND $user->roles->has('login') AND $user->password === $password)
122
		{
123 2
			if ($remember === TRUE)
124
			{
125
				$this->remember($user);
126
			}
127
128
			// Finish the login
129 2
			$this->complete_login($user);
130
131 2
			return TRUE;
132
		}
133
134
		// Login failed
135 4
		return FALSE;
136
	}
137
138
	/**
139
	 * Create autologin token
140
	 * @param  Model_User $user
141
	 * @return Model_User_Token
142
	 */
143 1
	public function remember($user)
144
	{
145 1
		$token = $user->build_user_token(array('user_agent' => sha1(Request::$user_agent)))->save();
146
147
		// Set the autologin cookie
148 1
		$this->_autologin_cookie($token->token, $this->_config['lifetime']);
149
150 1
		return $token;
151
	}
152
153
	/**
154
	 * Forces a user to be logged in, without specifying a password.
155
	 *
156
	 * @param   mixed    $user                    username string, or user Jam object
157
	 * @param   boolean  $mark_session_as_forced  mark the session as forced
158
	 * @param   boolean  $remember                force to remeber the user
159
	 * @return  boolean
160
	 */
161 2
	public function force_login($user, $mark_session_as_forced = FALSE, $remember = FALSE)
162
	{
163 2
		$user = $this->_load_user($user);
164
165 2
		if ($mark_session_as_forced === TRUE)
166
		{
167
			// Mark the session as forced, to prevent users from changing account information
168
			$this->session()->set('auth_forced', TRUE);
169
		}
170
171 2
		if ($remember)
172
		{
173
			$this->remember($user);
0 ignored issues
show
Documentation introduced by
$user is of type null|object, but the function expects a object<Model_User>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
174
		}
175
176
		// Run the standard completion
177 2
		return $this->complete_login($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->_load_user($user) on line 163 can also be of type null; however, Kohana_Auth_Jam::complete_login() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
178
	}
179
180
	/**
181
	 * login using a spesific token
182
	 * @param  string $token token hash
183
	 * @return Model_User|NULL
184
	 */
185 4
	public function login_with_token($token)
186
	{
187 4
		if ( ! $token)
188
			return NULL;
189
190
		// Load the token and user
191 4
		$token = $this->_load_token($token);
192
193 4
		if ($token AND $token->user AND $token->user->loaded())
194
		{
195 3
			if ( ! $token->user_agent OR $token->user_agent === sha1(Request::$user_agent))
196
			{
197
				// Save the token to create a new unique token
198 3
				$token->save();
199
200
				// Set the new token
201 3
				$this->_autologin_cookie($token->token, $token->expires - time());
202
203
				// Complete the login with the found data
204 3
				$this->complete_login($token->user);
205
206
				// Automatic login was successful
207 3
				return $token->user;
208
			}
209
210
			// Token is invalid
211
			$token->delete();
212
		}
213 1
	}
214
215
	/**
216
	 * Attempt to login the user using specific service
217
	 * @param  string  $name     the name of the service
218
	 * @return Model_User|NULL
219
	 */
220
	public function login_with_service($name)
221
	{
222
		return $this->services($name)->login();
223
	}
224
225
	/**
226
	 * Attempt to login the user using specific service
227
	 * @param  string  $name     the name of the service
228
	 * @param  boolean $remember create autologin token
229
	 * @return Model_User|NULL
230
	 */
231 1
	public function complete_login_with_service($name, $remember = FALSE)
232
	{
233 1
		if ($user = $this->services($name)->complete_login())
234
		{
235 1
			if ($remember === TRUE)
236
			{
237
				$this->remember($user);
238
			}
239
240 1
			$this->complete_login($user);
241
242 1
			return $user;
243
		}
244
245 1
		return NULL;
246
	}
247
248
	/**
249
	 * Logs a user in, based on the authautologin cookie.
250
	 *
251
	 * @return  mixed
252
	 */
253 12
	public function auto_login()
254
	{
255 12
		if ($token = $this->_autologin_cookie())
256
		{
257
			if ($user = $this->login_with_token($token))
0 ignored issues
show
Bug introduced by
It seems like $token defined by $this->_autologin_cookie() on line 255 can also be of type this<Kohana_Auth_Jam>; however, Kohana_Auth_Jam::login_with_token() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
258
			{
259
				return $user;
260
			}
261
			else
262
			{
263
				$this->_autologin_cookie(FALSE);
0 ignored issues
show
Documentation introduced by
FALSE is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
264
			}
265
		}
266
267 12
		if (Request::current() AND $token = Arr::get(Request::current()->query(), '_token'))
268
		{
269
			if ($user = $this->login_with_token($token))
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $user is correct as $this->login_with_token($token) (which targets Kohana_Auth_Jam::login_with_token()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
270
				return $user;
271
		}
272
273 12
		foreach ($this->services() as $service)
0 ignored issues
show
Bug introduced by
The expression $this->services() of type array|object<Auth_Service> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
274
		{
275 12
			if ($service->auto_login_enabled() AND $user = $service->get_user())
276
			{
277
				// $this->remember($user);
278
				$this->complete_login($user);
279
				return $user;
280
			}
281
		}
282
283 12
		return FALSE;
284
	}
285
286
	/**
287
	 * Gets the currently logged in user from the session (with auto_login check).
288
	 * Returns FALSE if no user is currently logged in.
289
	 *
290
	 * @return  mixed
291
	 */
292 14
	public function get_user($default = NULL)
293
	{
294
		// Load the session for the parent method
295 14
		$this->session();
296 14
		$user = $this->_load_user(parent::get_user($default));
297
298 14
		if ( ! ($user AND $user->loaded()))
299
		{
300
			// check for "remembered" login
301 11
			$user = $this->auto_login();
302
		}
303
304 14
		return $user;
305
	}
306
307
	protected function _load_user($user)
308
	{
309
		if ( ! $user)
310
			return NULL;
311
312
		return is_object($user) ? $user : Jam::find('user', $user);
313
	}
314
315
	protected function _load_token($token)
316
	{
317
		if ( ! $token)
318
			return NULL;
319
320
		return is_object($token) ? $token : Jam::all('user_token')->valid_token($token)->first();
321
	}
322
323
	/**
324
	 * Getter / Setter of the autologin cookie
325
	 * Extend this method in your tests so you can remove dependance on cookies there
326
	 *
327
	 * @param  string $token
328
	 * @param  integer $expires days lifetime
329
	 * @return mixed
330
	 */
331
	protected function _autologin_cookie($token = NULL, $expires = NULL)
332
	{
333
		if ($token === FALSE)
334
		{
335
			Cookie::delete('authautologin');
336
		}
337
		elseif ($token !== NULL)
338
		{
339
			Cookie::set('authautologin', $token, $expires);
340
		}
341
		else
342
		{
343
			return Cookie::get('authautologin');
344
		}
345
		return $this;
346
	}
347
348
	/**
349
	 * Log a user out and remove any autologin cookies, and goes to each service to log out the user there
350
	 *
351
	 * @param   boolean  $destroy     completely destroy the session
352
	 * @param	boolean  $logout_all  remove all tokens for user
353
	 * @return  boolean
354
	 */
355 9
	public function logout($destroy = FALSE, $logout_all = FALSE)
356
	{
357
		// Set by force_login()
358 9
		$this->session()->delete('auth_forced');
359
360 9
		if ($token = $this->_autologin_cookie())
361
		{
362
			// Delete the autologin cookie to prevent re-login
363 2
			$this->_autologin_cookie(FALSE);
0 ignored issues
show
Documentation introduced by
FALSE is of type boolean, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
364
365
			// Clear the autologin token from the database
366 2
			$token = $this->_load_token($token);
367
368 2
			if ($token AND $token->loaded() AND $token->user)
369
			{
370 2
				if ($logout_all)
371
				{
372
					$token->user->user_tokens->clear();
373
				}
374 2
				$token->delete();
375
			}
376
		}
377
378 9
		foreach ($this->services() as $service)
0 ignored issues
show
Bug introduced by
The expression $this->services() of type array|object<Auth_Service> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
379
		{
380 9
			if ($user = $service->get_user())
0 ignored issues
show
Unused Code introduced by
$user is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
381
			{
382
				$service->logout();
383
			}
384
		}
385
386 9
		return parent::logout($destroy);
387
	}
388
389
	/**
390
	 * Get the stored password for a username.
391
	 *
392
	 * @param   mixed   $user  username string, or user Jam object
393
	 * @return  string
394
	 */
395
	public function password($user)
396
	{
397
		return $this->_load_user($user)->password;
398
	}
399
400
	/**
401
	 * Complete the login for a user by incrementing the logins and setting
402
	 * session data: user_id, username, roles.
403
	 *
404
	 * @param   object  $user Jam object
405
	 * @return  bool
406
	 */
407 8
	protected function complete_login($user)
408
	{
409 8
		$user->last_login_ip = Request::$client_ip;
410
411 8
		$user->complete_login();
412
413
		// Regenerate session_id
414 8
		$this->session()->regenerate();
415
416
		// Store username in session
417 8
		$this->session()->set($this->_config['session_key'], $user->id);
418
419 8
		return TRUE;
420
	}
421
422
	/**
423
	 * Compare password with original (hashed). Works for current (logged in) user
424
	 *
425
	 * @param   string  $password
426
	 * @return  boolean
427
	 */
428 1
	public function check_password($password)
429
	{
430 1
		$user = $this->get_user();
431
432 1
		if ( ! $user)
433
			return FALSE;
434
435 1
		return ($this->hash($password) === $user->password);
436
	}
437
438
} // End Auth Jam
439