33
33
from synapse .logging .opentracing import active_span , force_tracing , start_active_span
34
34
from synapse .storage .databases .main .registration import TokenLookupResult
35
35
from synapse .types import Requester , UserID , create_requester
36
- from synapse .util .caches .lrucache import LruCache
37
- from synapse .util .macaroons import get_value_from_macaroon , satisfy_expiry
38
36
39
37
if TYPE_CHECKING :
40
38
from synapse .server import HomeServer
46
44
GUEST_DEVICE_ID = "guest_device"
47
45
48
46
49
- class _InvalidMacaroonException (Exception ):
50
- pass
51
-
52
-
53
47
class Auth :
54
48
"""
55
49
This class contains functions for authenticating users of our client-server API.
@@ -61,14 +55,10 @@ def __init__(self, hs: "HomeServer"):
61
55
self .store = hs .get_datastores ().main
62
56
self ._account_validity_handler = hs .get_account_validity_handler ()
63
57
self ._storage_controllers = hs .get_storage_controllers ()
64
-
65
- self .token_cache : LruCache [str , Tuple [str , bool ]] = LruCache (
66
- 10000 , "token_cache"
67
- )
58
+ self ._macaroon_generator = hs .get_macaroon_generator ()
68
59
69
60
self ._track_appservice_user_ips = hs .config .appservice .track_appservice_user_ips
70
61
self ._track_puppeted_user_ips = hs .config .api .track_puppeted_user_ips
71
- self ._macaroon_secret_key = hs .config .key .macaroon_secret_key
72
62
self ._force_tracing_for_users = hs .config .tracing .force_tracing_for_users
73
63
74
64
async def check_user_in_room (
@@ -123,7 +113,6 @@ async def get_user_by_req(
123
113
self ,
124
114
request : SynapseRequest ,
125
115
allow_guest : bool = False ,
126
- rights : str = "access" ,
127
116
allow_expired : bool = False ,
128
117
) -> Requester :
129
118
"""Get a registered user's ID.
@@ -132,7 +121,6 @@ async def get_user_by_req(
132
121
request: An HTTP request with an access_token query parameter.
133
122
allow_guest: If False, will raise an AuthError if the user making the
134
123
request is a guest.
135
- rights: The operation being performed; the access token must allow this
136
124
allow_expired: If True, allow the request through even if the account
137
125
is expired, or session token lifetime has ended. Note that
138
126
/login will deliver access tokens regardless of expiration.
@@ -147,7 +135,7 @@ async def get_user_by_req(
147
135
parent_span = active_span ()
148
136
with start_active_span ("get_user_by_req" ):
149
137
requester = await self ._wrapped_get_user_by_req (
150
- request , allow_guest , rights , allow_expired
138
+ request , allow_guest , allow_expired
151
139
)
152
140
153
141
if parent_span :
@@ -173,7 +161,6 @@ async def _wrapped_get_user_by_req(
173
161
self ,
174
162
request : SynapseRequest ,
175
163
allow_guest : bool ,
176
- rights : str ,
177
164
allow_expired : bool ,
178
165
) -> Requester :
179
166
"""Helper for get_user_by_req
@@ -211,7 +198,7 @@ async def _wrapped_get_user_by_req(
211
198
return requester
212
199
213
200
user_info = await self .get_user_by_access_token (
214
- access_token , rights , allow_expired = allow_expired
201
+ access_token , allow_expired = allow_expired
215
202
)
216
203
token_id = user_info .token_id
217
204
is_guest = user_info .is_guest
@@ -391,15 +378,12 @@ async def _get_appservice_user_id_and_device_id(
391
378
async def get_user_by_access_token (
392
379
self ,
393
380
token : str ,
394
- rights : str = "access" ,
395
381
allow_expired : bool = False ,
396
382
) -> TokenLookupResult :
397
383
"""Validate access token and get user_id from it
398
384
399
385
Args:
400
386
token: The access token to get the user by
401
- rights: The operation being performed; the access token must
402
- allow this
403
387
allow_expired: If False, raises an InvalidClientTokenError
404
388
if the token is expired
405
389
@@ -410,70 +394,55 @@ async def get_user_by_access_token(
410
394
is invalid
411
395
"""
412
396
413
- if rights == "access" :
414
- # First look in the database to see if the access token is present
415
- # as an opaque token.
416
- r = await self .store .get_user_by_access_token (token )
417
- if r :
418
- valid_until_ms = r .valid_until_ms
419
- if (
420
- not allow_expired
421
- and valid_until_ms is not None
422
- and valid_until_ms < self .clock .time_msec ()
423
- ):
424
- # there was a valid access token, but it has expired.
425
- # soft-logout the user.
426
- raise InvalidClientTokenError (
427
- msg = "Access token has expired" , soft_logout = True
428
- )
397
+ # First look in the database to see if the access token is present
398
+ # as an opaque token.
399
+ r = await self .store .get_user_by_access_token (token )
400
+ if r :
401
+ valid_until_ms = r .valid_until_ms
402
+ if (
403
+ not allow_expired
404
+ and valid_until_ms is not None
405
+ and valid_until_ms < self .clock .time_msec ()
406
+ ):
407
+ # there was a valid access token, but it has expired.
408
+ # soft-logout the user.
409
+ raise InvalidClientTokenError (
410
+ msg = "Access token has expired" , soft_logout = True
411
+ )
429
412
430
- return r
413
+ return r
431
414
432
415
# If the token isn't found in the database, then it could still be a
433
- # macaroon, so we check that here.
416
+ # macaroon for a guest , so we check that here.
434
417
try :
435
- user_id , guest = self ._parse_and_validate_macaroon (token , rights )
436
-
437
- if rights == "access" :
438
- if not guest :
439
- # non-guest access tokens must be in the database
440
- logger .warning ("Unrecognised access token - not in store." )
441
- raise InvalidClientTokenError ()
442
-
443
- # Guest access tokens are not stored in the database (there can
444
- # only be one access token per guest, anyway).
445
- #
446
- # In order to prevent guest access tokens being used as regular
447
- # user access tokens (and hence getting around the invalidation
448
- # process), we look up the user id and check that it is indeed
449
- # a guest user.
450
- #
451
- # It would of course be much easier to store guest access
452
- # tokens in the database as well, but that would break existing
453
- # guest tokens.
454
- stored_user = await self .store .get_user_by_id (user_id )
455
- if not stored_user :
456
- raise InvalidClientTokenError ("Unknown user_id %s" % user_id )
457
- if not stored_user ["is_guest" ]:
458
- raise InvalidClientTokenError (
459
- "Guest access token used for regular user"
460
- )
461
-
462
- ret = TokenLookupResult (
463
- user_id = user_id ,
464
- is_guest = True ,
465
- # all guests get the same device id
466
- device_id = GUEST_DEVICE_ID ,
418
+ user_id = self ._macaroon_generator .verify_guest_token (token )
419
+
420
+ # Guest access tokens are not stored in the database (there can
421
+ # only be one access token per guest, anyway).
422
+ #
423
+ # In order to prevent guest access tokens being used as regular
424
+ # user access tokens (and hence getting around the invalidation
425
+ # process), we look up the user id and check that it is indeed
426
+ # a guest user.
427
+ #
428
+ # It would of course be much easier to store guest access
429
+ # tokens in the database as well, but that would break existing
430
+ # guest tokens.
431
+ stored_user = await self .store .get_user_by_id (user_id )
432
+ if not stored_user :
433
+ raise InvalidClientTokenError ("Unknown user_id %s" % user_id )
434
+ if not stored_user ["is_guest" ]:
435
+ raise InvalidClientTokenError (
436
+ "Guest access token used for regular user"
467
437
)
468
- elif rights == "delete_pusher" :
469
- # We don't store these tokens in the database
470
438
471
- ret = TokenLookupResult (user_id = user_id , is_guest = False )
472
- else :
473
- raise RuntimeError ("Unknown rights setting %s" , rights )
474
- return ret
439
+ return TokenLookupResult (
440
+ user_id = user_id ,
441
+ is_guest = True ,
442
+ # all guests get the same device id
443
+ device_id = GUEST_DEVICE_ID ,
444
+ )
475
445
except (
476
- _InvalidMacaroonException ,
477
446
pymacaroons .exceptions .MacaroonException ,
478
447
TypeError ,
479
448
ValueError ,
@@ -485,78 +454,6 @@ async def get_user_by_access_token(
485
454
)
486
455
raise InvalidClientTokenError ("Invalid access token passed." )
487
456
488
- def _parse_and_validate_macaroon (
489
- self , token : str , rights : str = "access"
490
- ) -> Tuple [str , bool ]:
491
- """Takes a macaroon and tries to parse and validate it. This is cached
492
- if and only if rights == access and there isn't an expiry.
493
-
494
- On invalid macaroon raises _InvalidMacaroonException
495
-
496
- Returns:
497
- (user_id, is_guest)
498
- """
499
- if rights == "access" :
500
- cached = self .token_cache .get (token , None )
501
- if cached :
502
- return cached
503
-
504
- try :
505
- macaroon = pymacaroons .Macaroon .deserialize (token )
506
- except Exception : # deserialize can throw more-or-less anything
507
- # The access token doesn't look like a macaroon.
508
- raise _InvalidMacaroonException ()
509
-
510
- try :
511
- user_id = get_value_from_macaroon (macaroon , "user_id" )
512
-
513
- guest = False
514
- for caveat in macaroon .caveats :
515
- if caveat .caveat_id == "guest = true" :
516
- guest = True
517
-
518
- self .validate_macaroon (macaroon , rights , user_id = user_id )
519
- except (
520
- pymacaroons .exceptions .MacaroonException ,
521
- KeyError ,
522
- TypeError ,
523
- ValueError ,
524
- ):
525
- raise InvalidClientTokenError ("Invalid macaroon passed." )
526
-
527
- if rights == "access" :
528
- self .token_cache [token ] = (user_id , guest )
529
-
530
- return user_id , guest
531
-
532
- def validate_macaroon (
533
- self , macaroon : pymacaroons .Macaroon , type_string : str , user_id : str
534
- ) -> None :
535
- """
536
- validate that a Macaroon is understood by and was signed by this server.
537
-
538
- Args:
539
- macaroon: The macaroon to validate
540
- type_string: The kind of token required (e.g. "access", "delete_pusher")
541
- user_id: The user_id required
542
- """
543
- v = pymacaroons .Verifier ()
544
-
545
- # the verifier runs a test for every caveat on the macaroon, to check
546
- # that it is met for the current request. Each caveat must match at
547
- # least one of the predicates specified by satisfy_exact or
548
- # specify_general.
549
- v .satisfy_exact ("gen = 1" )
550
- v .satisfy_exact ("type = " + type_string )
551
- v .satisfy_exact ("user_id = %s" % user_id )
552
- v .satisfy_exact ("guest = true" )
553
- satisfy_expiry (v , self .clock .time_msec )
554
-
555
- # access_tokens include a nonce for uniqueness: any value is acceptable
556
- v .satisfy_general (lambda c : c .startswith ("nonce = " ))
557
-
558
- v .verify (macaroon , self ._macaroon_secret_key )
559
-
560
457
def get_appservice_by_req (self , request : SynapseRequest ) -> ApplicationService :
561
458
token = self .get_access_token_from_request (request )
562
459
service = self .store .get_app_service_by_token (token )
0 commit comments