2929 */
3030namespace OC \Authentication \Token ;
3131
32+ use OCP \ICache ;
33+ use OCP \ICacheFactory ;
3234use OC \Authentication \Exceptions \ExpiredTokenException ;
3335use OC \Authentication \Exceptions \InvalidTokenException ;
3436use OC \Authentication \Exceptions \TokenPasswordExpiredException ;
3537use OC \Authentication \Exceptions \PasswordlessTokenException ;
3638use OC \Authentication \Exceptions \WipeTokenException ;
3739use OCP \AppFramework \Db \TTransactional ;
38- use OCP \Cache \CappedMemoryCache ;
3940use OCP \AppFramework \Db \DoesNotExistException ;
4041use OCP \AppFramework \Utility \ITimeFactory ;
4142use OCP \IConfig ;
4748
4849class PublicKeyTokenProvider implements IProvider {
4950 public const TOKEN_MIN_LENGTH = 22 ;
51+ /** Token cache TTL in seconds */
52+ private const TOKEN_CACHE_TTL = 10 ;
5053
5154 use TTransactional;
5255
@@ -67,26 +70,28 @@ class PublicKeyTokenProvider implements IProvider {
6770 /** @var ITimeFactory */
6871 private $ time ;
6972
70- /** @var CappedMemoryCache */
73+ /** @var ICache */
7174 private $ cache ;
7275
73- private IHasher $ hasher ;
76+ /** @var IHasher */
77+ private $ hasher ;
7478
7579 public function __construct (PublicKeyTokenMapper $ mapper ,
7680 ICrypto $ crypto ,
7781 IConfig $ config ,
7882 IDBConnection $ db ,
7983 LoggerInterface $ logger ,
8084 ITimeFactory $ time ,
81- IHasher $ hasher ) {
85+ IHasher $ hasher ,
86+ ICacheFactory $ cacheFactory ) {
8287 $ this ->mapper = $ mapper ;
8388 $ this ->crypto = $ crypto ;
8489 $ this ->config = $ config ;
8590 $ this ->db = $ db ;
8691 $ this ->logger = $ logger ;
8792 $ this ->time = $ time ;
8893
89- $ this ->cache = new CappedMemoryCache ( );
94+ $ this ->cache = $ cacheFactory -> createLocal ( ' authtoken_ ' );
9095 $ this ->hasher = $ hasher ;
9196 }
9297
@@ -128,7 +133,7 @@ public function generateToken(string $token,
128133 }
129134
130135 // Add the token to the cache
131- $ this ->cache [ $ dbToken -> getToken ()] = $ dbToken ;
136+ $ this ->cacheToken ( $ dbToken) ;
132137
133138 return $ dbToken ;
134139 }
@@ -156,43 +161,56 @@ public function getToken(string $tokenId): IToken {
156161 }
157162
158163 $ tokenHash = $ this ->hashToken ($ tokenId );
164+ if ($ token = $ this ->getTokenFromCache ($ tokenHash )) {
165+ $ this ->checkToken ($ token );
166+ return $ token ;
167+ }
159168
160- if (isset ($ this ->cache [$ tokenHash ])) {
161- if ($ this ->cache [$ tokenHash ] instanceof DoesNotExistException) {
162- $ ex = $ this ->cache [$ tokenHash ];
163- throw new InvalidTokenException ("Token does not exist: " . $ ex ->getMessage (), 0 , $ ex );
164- }
165- $ token = $ this ->cache [$ tokenHash ];
166- } else {
169+ try {
170+ $ token = $ this ->mapper ->getToken ($ tokenHash );
171+ $ this ->cacheToken ($ token );
172+ } catch (DoesNotExistException $ ex ) {
167173 try {
168- $ token = $ this ->mapper ->getToken ($ tokenHash );
169- $ this ->cache [$ token ->getToken ()] = $ token ;
170- } catch (DoesNotExistException $ ex ) {
171- try {
172- $ token = $ this ->mapper ->getToken ($ this ->hashTokenWithEmptySecret ($ tokenId ));
173- $ this ->cache [$ token ->getToken ()] = $ token ;
174- $ this ->rotate ($ token , $ tokenId , $ tokenId );
175- } catch (DoesNotExistException $ ex2 ) {
176- $ this ->cache [$ tokenHash ] = $ ex2 ;
177- throw new InvalidTokenException ("Token does not exist: " . $ ex ->getMessage (), 0 , $ ex );
178- }
174+ $ token = $ this ->mapper ->getToken ($ this ->hashTokenWithEmptySecret ($ tokenId ));
175+ $ this ->rotate ($ token , $ tokenId , $ tokenId );
176+ } catch (DoesNotExistException ) {
177+ $ this ->cacheInvalidHash ($ tokenHash );
178+ throw new InvalidTokenException ("Token does not exist: " . $ ex ->getMessage (), 0 , $ ex );
179179 }
180180 }
181181
182- if ((int )$ token ->getExpires () !== 0 && $ token ->getExpires () < $ this ->time ->getTime ()) {
183- throw new ExpiredTokenException ($ token );
184- }
182+ $ this ->checkToken ($ token );
185183
186- if ($ token ->getType () === IToken::WIPE_TOKEN ) {
187- throw new WipeTokenException ($ token );
188- }
184+ return $ token ;
185+ }
189186
190- if ($ token ->getPasswordInvalid () === true ) {
191- //The password is invalid we should throw an TokenPasswordExpiredException
192- throw new TokenPasswordExpiredException ($ token );
187+ /**
188+ * @throws InvalidTokenException when token doesn't exist
189+ */
190+ private function getTokenFromCache (string $ tokenHash ): ?PublicKeyToken {
191+ $ serializedToken = $ this ->cache ->get ($ tokenHash );
192+ if (null === $ serializedToken ) {
193+ if ($ this ->cache ->hasKey ($ tokenHash )) {
194+ throw new InvalidTokenException ('Token does not exist: ' . $ tokenHash );
195+ }
196+
197+ return null ;
193198 }
194199
195- return $ token ;
200+ $ token = unserialize ($ serializedToken , [
201+ 'allowed_classes ' => [PublicKeyToken::class],
202+ ]);
203+
204+ return $ token instanceof PublicKeyToken ? $ token : null ;
205+ }
206+
207+ private function cacheToken (PublicKeyToken $ token ): void {
208+ $ this ->cache ->set ($ token ->getToken (), serialize ($ token ), self ::TOKEN_CACHE_TTL );
209+ }
210+
211+ private function cacheInvalidHash (string $ tokenHash ) {
212+ // Invalid entries can be kept longer in cache since it’s unlikely to reuse them
213+ $ this ->cache ->set ($ tokenHash , null , self ::TOKEN_CACHE_TTL * 2 );
196214 }
197215
198216 public function getTokenById (int $ tokenId ): IToken {
@@ -202,6 +220,12 @@ public function getTokenById(int $tokenId): IToken {
202220 throw new InvalidTokenException ("Token with ID $ tokenId does not exist: " . $ ex ->getMessage (), 0 , $ ex );
203221 }
204222
223+ $ this ->checkToken ($ token );
224+
225+ return $ token ;
226+ }
227+
228+ private function checkToken ($ token ): void {
205229 if ((int )$ token ->getExpires () !== 0 && $ token ->getExpires () < $ this ->time ->getTime ()) {
206230 throw new ExpiredTokenException ($ token );
207231 }
@@ -214,13 +238,9 @@ public function getTokenById(int $tokenId): IToken {
214238 //The password is invalid we should throw an TokenPasswordExpiredException
215239 throw new TokenPasswordExpiredException ($ token );
216240 }
217-
218- return $ token ;
219241 }
220242
221243 public function renewSessionToken (string $ oldSessionId , string $ sessionId ): IToken {
222- $ this ->cache ->clear ();
223-
224244 return $ this ->atomic (function () use ($ oldSessionId , $ sessionId ) {
225245 $ token = $ this ->getToken ($ oldSessionId );
226246
@@ -242,29 +262,32 @@ public function renewSessionToken(string $oldSessionId, string $sessionId): ITok
242262 IToken::TEMPORARY_TOKEN ,
243263 $ token ->getRemember ()
244264 );
265+ $ this ->cacheToken ($ newToken );
245266
267+ $ this ->cacheInvalidHash ($ token ->getToken ());
246268 $ this ->mapper ->delete ($ token );
247269
248270 return $ newToken ;
249271 }, $ this ->db );
250272 }
251273
252274 public function invalidateToken (string $ token ) {
253- $ this ->cache ->clear ();
254-
275+ $ tokenHash = $ this ->hashToken ($ token );
255276 $ this ->mapper ->invalidate ($ this ->hashToken ($ token ));
256277 $ this ->mapper ->invalidate ($ this ->hashTokenWithEmptySecret ($ token ));
278+ $ this ->cacheInvalidHash ($ tokenHash );
257279 }
258280
259281 public function invalidateTokenById (string $ uid , int $ id ) {
260- $ this ->cache ->clear ();
261-
262- $ this ->mapper ->deleteById ($ uid , $ id );
282+ $ token = $ this ->mapper ->getTokenById ($ id );
283+ if ($ token ->getUID () !== $ uid ) {
284+ return ;
285+ }
286+ $ this ->mapper ->invalidate ($ token ->getToken ());
287+ $ this ->cacheInvalidHash ($ token ->getToken ());
263288 }
264289
265290 public function invalidateOldTokens () {
266- $ this ->cache ->clear ();
267-
268291 $ olderThan = $ this ->time ->getTime () - $ this ->config ->getSystemValueInt ('session_lifetime ' , 60 * 60 * 24 );
269292 $ this ->logger ->debug ('Invalidating session tokens older than ' . date ('c ' , $ olderThan ), ['app ' => 'cron ' ]);
270293 $ this ->mapper ->invalidateOld ($ olderThan , IToken::DO_NOT_REMEMBER );
@@ -274,17 +297,14 @@ public function invalidateOldTokens() {
274297 }
275298
276299 public function updateToken (IToken $ token ) {
277- $ this ->cache ->clear ();
278-
279300 if (!($ token instanceof PublicKeyToken)) {
280301 throw new InvalidTokenException ("Invalid token type " );
281302 }
282303 $ this ->mapper ->update ($ token );
304+ $ this ->cacheToken ($ token );
283305 }
284306
285307 public function updateTokenActivity (IToken $ token ) {
286- $ this ->cache ->clear ();
287-
288308 if (!($ token instanceof PublicKeyToken)) {
289309 throw new InvalidTokenException ("Invalid token type " );
290310 }
@@ -297,6 +317,7 @@ public function updateTokenActivity(IToken $token) {
297317 if ($ token ->getLastActivity () < ($ now - $ activityInterval )) {
298318 $ token ->setLastActivity ($ now );
299319 $ this ->mapper ->updateActivity ($ token , $ now );
320+ $ this ->cacheToken ($ token );
300321 }
301322 }
302323
@@ -321,8 +342,6 @@ public function getPassword(IToken $savedToken, string $tokenId): string {
321342 }
322343
323344 public function setPassword (IToken $ token , string $ tokenId , string $ password ) {
324- $ this ->cache ->clear ();
325-
326345 if (!($ token instanceof PublicKeyToken)) {
327346 throw new InvalidTokenException ("Invalid token type " );
328347 }
@@ -348,8 +367,6 @@ private function hashPassword(string $password): string {
348367 }
349368
350369 public function rotate (IToken $ token , string $ oldTokenId , string $ newTokenId ): IToken {
351- $ this ->cache ->clear ();
352-
353370 if (!($ token instanceof PublicKeyToken)) {
354371 throw new InvalidTokenException ("Invalid token type " );
355372 }
@@ -473,19 +490,16 @@ private function newToken(string $token,
473490 }
474491
475492 public function markPasswordInvalid (IToken $ token , string $ tokenId ) {
476- $ this ->cache ->clear ();
477-
478493 if (!($ token instanceof PublicKeyToken)) {
479494 throw new InvalidTokenException ("Invalid token type " );
480495 }
481496
482497 $ token ->setPasswordInvalid (true );
483498 $ this ->mapper ->update ($ token );
499+ $ this ->cacheToken ($ token );
484500 }
485501
486502 public function updatePasswords (string $ uid , string $ password ) {
487- $ this ->cache ->clear ();
488-
489503 // prevent setting an empty pw as result of pw-less-login
490504 if ($ password === '' || !$ this ->config ->getSystemValueBool ('auth.storeCryptedPassword ' , true )) {
491505 return ;
0 commit comments