2424use OCP \Authentication \Exceptions \ExpiredTokenException ;
2525use OCP \Authentication \Exceptions \InvalidTokenException ;
2626use OCP \DB \Exception ;
27+ use OCP \IDBConnection ;
2728use OCP \IRequest ;
2829use OCP \Security \Bruteforce \IThrottler ;
2930use OCP \Security \ICrypto ;
@@ -47,6 +48,7 @@ public function __construct(
4748 private LoggerInterface $ logger ,
4849 private IThrottler $ throttler ,
4950 private ITimeFactory $ timeFactory ,
51+ private IDBConnection $ db ,
5052 ) {
5153 parent ::__construct ($ appName , $ request );
5254 }
@@ -177,27 +179,55 @@ public function getToken(
177179
178180 // Rotate the apptoken (so the old one becomes invalid basically)
179181 $ newToken = $ this ->secureRandom ->generate (72 , ISecureRandom::CHAR_ALPHANUMERIC );
182+ $ newCode = $ this ->secureRandom ->generate (128 , ISecureRandom::CHAR_ALPHANUMERIC );
183+ $ newEncryptedToken = $ this ->crypto ->encrypt ($ newToken , $ newCode );
184+ $ tokenRotated = false ;
180185
181- $ appToken = $ this ->tokenProvider ->rotate (
182- $ appToken ,
183- $ decryptedToken ,
184- $ newToken
185- );
186+ $ this ->db ->beginTransaction ();
187+ try {
188+ $ appToken = $ this ->tokenProvider ->rotate (
189+ $ appToken ,
190+ $ decryptedToken ,
191+ $ newToken
192+ );
193+ $ tokenRotated = true ;
194+
195+ // Expiration is in 1 hour again
196+ $ appToken ->setExpires ($ this ->time ->getTime () + 3600 );
197+ $ this ->tokenProvider ->updateToken ($ appToken );
198+
199+ $ updatedRows = $ this ->accessTokenMapper ->rotateToken (
200+ $ accessToken ->getId (),
201+ $ code ,
202+ $ newCode ,
203+ $ newEncryptedToken ,
204+ $ grant_type === 'authorization_code ' ,
205+ );
206+
207+ if ($ updatedRows !== 1 ) {
208+ $ this ->db ->rollBack ();
209+ // tokenProvider->rotate() updates the auth token cache, so we have to clear the new token on rollback
210+ $ this ->tokenProvider ->invalidateToken ($ newToken );
211+ $ response = new JSONResponse ([
212+ 'error ' => 'invalid_request ' ,
213+ ], Http::STATUS_BAD_REQUEST );
214+ $ response ->throttle (['invalid_request ' => 'token already redeemed ' ]);
215+ return $ response ;
216+ }
186217
187- // Expiration is in 1 hour again
188- $ appToken ->setExpires ($ this ->time ->getTime () + 3600 );
189- $ this ->tokenProvider ->updateToken ($ appToken );
218+ $ this ->db ->commit ();
219+ } catch (\Throwable $ e ) {
220+ if ($ this ->db ->inTransaction ()) {
221+ $ this ->db ->rollBack ();
222+ }
190223
191- // Generate a new refresh token and encrypt the new apptoken in the DB
192- $ newCode = $ this ->secureRandom ->generate (128 , ISecureRandom::CHAR_ALPHANUMERIC );
193- $ accessToken ->setHashedCode (hash ('sha512 ' , $ newCode ));
194- $ accessToken ->setEncryptedToken ($ this ->crypto ->encrypt ($ newToken , $ newCode ));
195- // increase the number of delivered oauth token
196- // this helps with cleaning up DB access token when authorization code has expired
197- // and it never delivered any oauth token
198- $ tokenCount = $ accessToken ->getTokenCount ();
199- $ accessToken ->setTokenCount ($ tokenCount + 1 );
200- $ this ->accessTokenMapper ->update ($ accessToken );
224+ if ($ tokenRotated ) {
225+ // tokenProvider->rotate() updates the auth token cache, so we have to clear the new token on rollback
226+ $ this ->tokenProvider ->invalidateToken ($ newToken );
227+ }
228+
229+ throw $ e ;
230+ }
201231
202232 $ this ->throttler ->resetDelay ($ this ->request ->getRemoteAddress (), 'login ' , ['user ' => $ appToken ->getUID ()]);
203233
0 commit comments