Passed
Pull Request — develop ( #58 )
by
unknown
32:22 queued 14:42
created

UserApiToken::isValidToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
ccs 0
cts 2
cp 0
crap 6
1
<?php
2
3
namespace neon\user\services\apiTokenManager\models;
4
5
use Carbon\Carbon;
6
use neon\core\db\ActiveRecord;
7
use neon\user\models\User;
8
use yii\web\IdentityInterface;
9
10
/**
11
 * Represents the user api token table - the token allows the system to validate users - note you should also check the
12
 * validity of the connected user specifically - their status - are they active? are they suspended and do they have access
13
 *
14
 * @property string $token
15
 * @property bool $active
16
 * @property int $user_id
17
 * @property string $name
18
 * @property string $last_used - MYSQL datetime format Y-m-d H:i:g
19
 * @property integer $used_count - A simple count tracking the number of times the token was used
20
 */
21
class UserApiToken extends ActiveRecord
22
{
23
	/**
24
	 * The length of the token to generate (max 64)
25
	 * @var int
26
	 */
27
	private static $_tokenLength = 64;
28
29
	/**
30
	 * @inheritdoc
31
	 */
32
	public static function tableName()
33
	{
34
		return '{{%user_api_token}}';
35
	}
36
37
	/**
38
	 * Basic token sanity check
39
	 * @param string $token
40
	 * @return boolean true if valid false if not
41
	 */
42
	public static function isValidToken($token)
43
	{
44
		// Check the token is valid. A record must exist and the token needs to be a string of the correct length
45
		// Testing the length prevents 0 length tokens or easy to guess tokens being found, and valid, if somehow they were created.
46
		return ($token !== null && strlen($token) >= static::$_tokenLength);
0 ignored issues
show
Bug introduced by
Since $_tokenLength is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $_tokenLength to at least protected.
Loading history...
47
	}
48
49
	/**
50
	 * Returns the valid user by auth token
51
	 * The user returned can be used to log in
52
	 * @param string $token
53
	 * @return IdentityInterface
54
	 */
55
	public static function getValidUserByToken($token)
56
	{
57
		$validToken = self::isValidToken($token);
58
		if (!$validToken)
59
			return null;
60
61
		$apiToken = self::findOne(['active' => 1, 'token' => (string) $token]);
62
		if ($apiToken == null)
63
			return null;
64
65
		// TODO: SO 27/10/2018 - update this to be User::findIndentity() - migrate to uuids.
66
		$user = User::findOne(['id' => $apiToken->user_id, 'status' => User::STATUS_ACTIVE]);
67
		if ($user == null)
68
			return null;
69
70
		// Usage successful
71
		$apiToken->touchTokenUsed();
72
73
		return $user;
74
	}
75
76
	/**
77
	 * Updates a token row with last used date and increments the usage count
78
	 * @return boolean true on success false on failure
79
	 */
80
	public function touchTokenUsed()
81
	{
82
		$this->last_used = date('Y-m-d H:i:g');
83
		$this->used_count = $this->used_count + 1;
84
		return $this->save();
85
	}
86
87
	/**
88
	 * Generate the authorization token to include in basic auth http request "Authorization" header
89
	 * @return string
90
	 */
91
	public function getHttpBasicAuthorizationBase64()
92
	{
93
		return base64_encode($this->user_id . ':' . $this->token);
94
	}
95
96
	/**
97
	 * Get the token for the specified user
98
	 *
99
	 * @param int $userId
100
	 * @return self|null
101
	 */
102
	public static function getTokenRecord($userId)
103
	{
104
		return static::findOne(['user_id' => $userId]);
105
	}
106
107
	/**
108
	 * Generate a token for a specified userId
109
	 * @param int $userId
110
	 * @param string $name
111
	 * @return UserApiToken|null
112
	 */
113
	public static function generateTokenFor($userId, $name='')
114
	{
115
		$token = UserApiToken::getTokenRecord($userId);
116
		// extra security:
117
		// validate the user id points to a valid user
118
		$user = User::findOne(['id' => $userId, 'status'=> User::STATUS_ACTIVE]);
119
		if ($user === null) {
120
			throw new \InvalidArgumentException("Tokens can only be generated for valid users. No active user exists with id '$userId' ");
121
		}
122
		$token = ($token === null) ? new UserApiToken() : $token;
123
		$token->name = $name;
124
		$token->token = neon()->security->generateRandomString(static::$_tokenLength);
0 ignored issues
show
Bug introduced by
Since $_tokenLength is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $_tokenLength to at least protected.
Loading history...
125
		$token->user_id = $userId;
126
		$token->active = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $active was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
127
		$token->save();
128
		return $token;
129
	}
130
131
	/**
132
	 * Generate or refresh a token for the current logged in user.
133
	 * @return UserApiToken
134
	 */
135
	public static function generateToken()
136
	{
137
		if (neon()->user->isGuest) {
138
			throw new \InvalidArgumentException('You can not generate a token for a guest user');
139
		}
140
		$userId = neon()->user->id;
141
		return self::generateTokenFor($userId);
0 ignored issues
show
Bug introduced by
It seems like $userId can also be of type string; however, parameter $userId of neon\user\services\apiTo...ken::generateTokenFor() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
		return self::generateTokenFor(/** @scrutinizer ignore-type */ $userId);
Loading history...
142
	}
143
}
144