3333from synapse .logging .opentracing import active_span , force_tracing , start_active_span
3434from synapse .storage .databases .main .registration import TokenLookupResult
3535from 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
3836
3937if TYPE_CHECKING :
4038 from synapse .server import HomeServer
4644GUEST_DEVICE_ID = "guest_device"
4745
4846
49- class _InvalidMacaroonException (Exception ):
50- pass
51-
52-
5347class Auth :
5448 """
5549 This class contains functions for authenticating users of our client-server API.
@@ -61,14 +55,10 @@ def __init__(self, hs: "HomeServer"):
6155 self .store = hs .get_datastores ().main
6256 self ._account_validity_handler = hs .get_account_validity_handler ()
6357 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 ()
6859
6960 self ._track_appservice_user_ips = hs .config .appservice .track_appservice_user_ips
7061 self ._track_puppeted_user_ips = hs .config .api .track_puppeted_user_ips
71- self ._macaroon_secret_key = hs .config .key .macaroon_secret_key
7262 self ._force_tracing_for_users = hs .config .tracing .force_tracing_for_users
7363
7464 async def check_user_in_room (
@@ -123,7 +113,6 @@ async def get_user_by_req(
123113 self ,
124114 request : SynapseRequest ,
125115 allow_guest : bool = False ,
126- rights : str = "access" ,
127116 allow_expired : bool = False ,
128117 ) -> Requester :
129118 """Get a registered user's ID.
@@ -132,7 +121,6 @@ async def get_user_by_req(
132121 request: An HTTP request with an access_token query parameter.
133122 allow_guest: If False, will raise an AuthError if the user making the
134123 request is a guest.
135- rights: The operation being performed; the access token must allow this
136124 allow_expired: If True, allow the request through even if the account
137125 is expired, or session token lifetime has ended. Note that
138126 /login will deliver access tokens regardless of expiration.
@@ -147,7 +135,7 @@ async def get_user_by_req(
147135 parent_span = active_span ()
148136 with start_active_span ("get_user_by_req" ):
149137 requester = await self ._wrapped_get_user_by_req (
150- request , allow_guest , rights , allow_expired
138+ request , allow_guest , allow_expired
151139 )
152140
153141 if parent_span :
@@ -173,7 +161,6 @@ async def _wrapped_get_user_by_req(
173161 self ,
174162 request : SynapseRequest ,
175163 allow_guest : bool ,
176- rights : str ,
177164 allow_expired : bool ,
178165 ) -> Requester :
179166 """Helper for get_user_by_req
@@ -211,7 +198,7 @@ async def _wrapped_get_user_by_req(
211198 return requester
212199
213200 user_info = await self .get_user_by_access_token (
214- access_token , rights , allow_expired = allow_expired
201+ access_token , allow_expired = allow_expired
215202 )
216203 token_id = user_info .token_id
217204 is_guest = user_info .is_guest
@@ -391,15 +378,12 @@ async def _get_appservice_user_id_and_device_id(
391378 async def get_user_by_access_token (
392379 self ,
393380 token : str ,
394- rights : str = "access" ,
395381 allow_expired : bool = False ,
396382 ) -> TokenLookupResult :
397383 """Validate access token and get user_id from it
398384
399385 Args:
400386 token: The access token to get the user by
401- rights: The operation being performed; the access token must
402- allow this
403387 allow_expired: If False, raises an InvalidClientTokenError
404388 if the token is expired
405389
@@ -410,70 +394,55 @@ async def get_user_by_access_token(
410394 is invalid
411395 """
412396
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+ )
429412
430- return r
413+ return r
431414
432415 # 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.
434417 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"
467437 )
468- elif rights == "delete_pusher" :
469- # We don't store these tokens in the database
470438
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+ )
475445 except (
476- _InvalidMacaroonException ,
477446 pymacaroons .exceptions .MacaroonException ,
478447 TypeError ,
479448 ValueError ,
@@ -485,78 +454,6 @@ async def get_user_by_access_token(
485454 )
486455 raise InvalidClientTokenError ("Invalid access token passed." )
487456
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-
560457 def get_appservice_by_req (self , request : SynapseRequest ) -> ApplicationService :
561458 token = self .get_access_token_from_request (request )
562459 service = self .store .get_app_service_by_token (token )
0 commit comments