Passed
Push — master ( d4a9da...674f4e )
by Julius
15:49 queued 12s
created

PublicKeyTokenMapper::getToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 1
dl 0
loc 15
rs 9.9
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2018 Roeland Jago Douma <[email protected]>
7
 *
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Daniel Kesselberg <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 *
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
namespace OC\Authentication\Token;
29
30
use OCP\AppFramework\Db\DoesNotExistException;
31
use OCP\AppFramework\Db\QBMapper;
32
use OCP\DB\QueryBuilder\IQueryBuilder;
33
use OCP\IDBConnection;
34
35
/**
36
 * @template-extends QBMapper<PublicKeyToken>
37
 */
38
class PublicKeyTokenMapper extends QBMapper {
39
	public function __construct(IDBConnection $db) {
40
		parent::__construct($db, 'authtoken');
41
	}
42
43
	/**
44
	 * Invalidate (delete) a given token
45
	 *
46
	 * @param string $token
47
	 */
48
	public function invalidate(string $token) {
49
		/* @var $qb IQueryBuilder */
50
		$qb = $this->db->getQueryBuilder();
51
		$qb->delete($this->tableName)
52
			->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
53
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
54
			->execute();
55
	}
56
57
	/**
58
	 * @param int $olderThan
59
	 * @param int $remember
60
	 */
61
	public function invalidateOld(int $olderThan, int $remember = IToken::DO_NOT_REMEMBER) {
62
		/* @var $qb IQueryBuilder */
63
		$qb = $this->db->getQueryBuilder();
64
		$qb->delete($this->tableName)
65
			->where($qb->expr()->lt('last_activity', $qb->createNamedParameter($olderThan, IQueryBuilder::PARAM_INT)))
66
			->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN, IQueryBuilder::PARAM_INT)))
67
			->andWhere($qb->expr()->eq('remember', $qb->createNamedParameter($remember, IQueryBuilder::PARAM_INT)))
68
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
69
			->execute();
70
	}
71
72
	/**
73
	 * Get the user UID for the given token
74
	 *
75
	 * @throws DoesNotExistException
76
	 */
77
	public function getToken(string $token): PublicKeyToken {
78
		/* @var $qb IQueryBuilder */
79
		$qb = $this->db->getQueryBuilder();
80
		$result = $qb->select('*')
81
			->from($this->tableName)
82
			->where($qb->expr()->eq('token', $qb->createNamedParameter($token)))
83
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
84
			->execute();
85
86
		$data = $result->fetch();
87
		$result->closeCursor();
88
		if ($data === false) {
89
			throw new DoesNotExistException('token does not exist');
90
		}
91
		return PublicKeyToken::fromRow($data);
92
	}
93
94
	/**
95
	 * Get the token for $id
96
	 *
97
	 * @throws DoesNotExistException
98
	 */
99
	public function getTokenById(int $id): PublicKeyToken {
100
		/* @var $qb IQueryBuilder */
101
		$qb = $this->db->getQueryBuilder();
102
		$result = $qb->select('*')
103
			->from($this->tableName)
104
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
105
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
106
			->execute();
107
108
		$data = $result->fetch();
109
		$result->closeCursor();
110
		if ($data === false) {
111
			throw new DoesNotExistException('token does not exist');
112
		}
113
		return PublicKeyToken::fromRow($data);
114
	}
115
116
	/**
117
	 * Get all tokens of a user
118
	 *
119
	 * The provider may limit the number of result rows in case of an abuse
120
	 * where a high number of (session) tokens is generated
121
	 *
122
	 * @param string $uid
123
	 * @return PublicKeyToken[]
124
	 */
125
	public function getTokenByUser(string $uid): array {
126
		/* @var $qb IQueryBuilder */
127
		$qb = $this->db->getQueryBuilder();
128
		$qb->select('*')
129
			->from($this->tableName)
130
			->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
131
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)))
132
			->setMaxResults(1000);
133
		$result = $qb->execute();
134
		$data = $result->fetchAll();
135
		$result->closeCursor();
136
137
		$entities = array_map(function ($row) {
138
			return PublicKeyToken::fromRow($row);
139
		}, $data);
140
141
		return $entities;
142
	}
143
144
	public function deleteById(string $uid, int $id) {
145
		/* @var $qb IQueryBuilder */
146
		$qb = $this->db->getQueryBuilder();
147
		$qb->delete($this->tableName)
148
			->where($qb->expr()->eq('id', $qb->createNamedParameter($id)))
149
			->andWhere($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
150
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
151
		$qb->execute();
152
	}
153
154
	/**
155
	 * delete all auth token which belong to a specific client if the client was deleted
156
	 *
157
	 * @param string $name
158
	 */
159
	public function deleteByName(string $name) {
160
		$qb = $this->db->getQueryBuilder();
161
		$qb->delete($this->tableName)
162
			->where($qb->expr()->eq('name', $qb->createNamedParameter($name), IQueryBuilder::PARAM_STR))
163
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
164
		$qb->execute();
165
	}
166
167
	public function deleteTempToken(PublicKeyToken $except) {
168
		$qb = $this->db->getQueryBuilder();
169
170
		$qb->delete($this->tableName)
171
			->where($qb->expr()->eq('uid', $qb->createNamedParameter($except->getUID())))
172
			->andWhere($qb->expr()->eq('type', $qb->createNamedParameter(IToken::TEMPORARY_TOKEN)))
173
			->andWhere($qb->expr()->neq('id', $qb->createNamedParameter($except->getId())))
174
			->andWhere($qb->expr()->eq('version', $qb->createNamedParameter(PublicKeyToken::VERSION, IQueryBuilder::PARAM_INT)));
175
176
		$qb->execute();
177
	}
178
179
	public function hasExpiredTokens(string $uid): bool {
180
		$qb = $this->db->getQueryBuilder();
181
		$qb->select('*')
182
			->from($this->tableName)
183
			->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)))
184
			->andWhere($qb->expr()->eq('password_invalid', $qb->createNamedParameter(true), IQueryBuilder::PARAM_BOOL))
185
			->setMaxResults(1);
186
187
		$cursor = $qb->execute();
188
		$data = $cursor->fetchAll();
189
		$cursor->closeCursor();
190
191
		return count($data) === 1;
192
	}
193
194
	/**
195
	 * Update the last activity timestamp
196
	 *
197
	 * In highly concurrent setups it can happen that two parallel processes
198
	 * trigger the update at (nearly) the same time. In that special case it's
199
	 * not necessary to hit the database with two actual updates. Therefore the
200
	 * target last activity is included in the WHERE clause with a few seconds
201
	 * of tolerance.
202
	 *
203
	 * Example:
204
	 * - process 1 (P1) reads the token at timestamp 1500
205
	 * - process 1 (P2) reads the token at timestamp 1501
206
	 * - activity update interval is 100
207
	 *
208
	 * This means
209
	 *
210
	 * - P1 will see a last_activity smaller than the current time and update
211
	 *   the token row
212
	 * - If P2 reads after P1 had written, it will see 1600 as last activity
213
	 *   and the comparison on last_activity won't be truthy. This means no rows
214
	 *   need to be updated a second time
215
	 * - If P2 reads before P1 had written, it will see 1501 as last activity,
216
	 *   but the comparison on last_activity will still not be truthy and the
217
	 *   token row is not updated a second time
218
	 *
219
	 * @param IToken $token
220
	 * @param int $now
221
	 */
222
	public function updateActivity(IToken $token, int $now): void {
223
		$qb = $this->db->getQueryBuilder();
224
		$update = $qb->update($this->getTableName())
225
			->set('last_activity', $qb->createNamedParameter($now, IQueryBuilder::PARAM_INT))
226
			->where(
227
				$qb->expr()->eq('id', $qb->createNamedParameter($token->getId(), IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT),
228
				$qb->expr()->lt('last_activity', $qb->createNamedParameter($now - 15, IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT)
229
			);
230
		$update->executeStatement();
231
	}
232
233
	public function updateHashesForUser(string $userId, string $passwordHash): void {
234
		$qb = $this->db->getQueryBuilder();
235
		$update = $qb->update($this->getTableName())
236
			->set('password_hash', $qb->createNamedParameter($passwordHash))
237
			->where(
238
				$qb->expr()->eq('uid', $qb->createNamedParameter($userId))
239
			);
240
		$update->executeStatement();
241
	}
242
243
	public function getFirstTokenForUser(string $userId): ?PublicKeyToken {
244
		$qb = $this->db->getQueryBuilder();
245
		$qb->select('*')
246
			->from($this->getTableName())
247
			->where($qb->expr()->eq('uid', $qb->createNamedParameter($userId)))
248
			->setMaxResults(1)
249
			->orderBy('id');
250
		$result = $qb->executeQuery();
251
252
		$data = $result->fetch();
253
		$result->closeCursor();
254
		if ($data === false) {
255
			return null;
256
		}
257
		return PublicKeyToken::fromRow($data);
258
	}
259
}
260