65
65
from synapse .types import JsonDict , Requester , UserID
66
66
from synapse .util import stringutils as stringutils
67
67
from synapse .util .async_helpers import maybe_awaitable
68
+ from synapse .util .macaroons import get_value_from_macaroon , satisfy_expiry
68
69
from synapse .util .msisdn import phone_number_to_msisdn
69
70
from synapse .util .threepids import canonicalise_email
70
71
@@ -170,6 +171,16 @@ class SsoLoginExtraAttributes:
170
171
extra_attributes = attr .ib (type = JsonDict )
171
172
172
173
174
+ @attr .s (slots = True , frozen = True )
175
+ class LoginTokenAttributes :
176
+ """Data we store in a short-term login token"""
177
+
178
+ user_id = attr .ib (type = str )
179
+
180
+ # the SSO Identity Provider that the user authenticated with, to get this token
181
+ auth_provider_id = attr .ib (type = str )
182
+
183
+
173
184
class AuthHandler (BaseHandler ):
174
185
SESSION_EXPIRE_MS = 48 * 60 * 60 * 1000
175
186
@@ -1164,18 +1175,16 @@ async def _check_local_password(self, user_id: str, password: str) -> Optional[s
1164
1175
return None
1165
1176
return user_id
1166
1177
1167
- async def validate_short_term_login_token_and_get_user_id ( self , login_token : str ):
1168
- auth_api = self . hs . get_auth ()
1169
- user_id = None
1178
+ async def validate_short_term_login_token (
1179
+ self , login_token : str
1180
+ ) -> LoginTokenAttributes :
1170
1181
try :
1171
- macaroon = pymacaroons .Macaroon .deserialize (login_token )
1172
- user_id = auth_api .get_user_id_from_macaroon (macaroon )
1173
- auth_api .validate_macaroon (macaroon , "login" , user_id )
1182
+ res = self .macaroon_gen .verify_short_term_login_token (login_token )
1174
1183
except Exception :
1175
1184
raise AuthError (403 , "Invalid token" , errcode = Codes .FORBIDDEN )
1176
1185
1177
- await self .auth .check_auth_blocking (user_id )
1178
- return user_id
1186
+ await self .auth .check_auth_blocking (res . user_id )
1187
+ return res
1179
1188
1180
1189
async def delete_access_token (self , access_token : str ):
1181
1190
"""Invalidate a single access token
@@ -1397,6 +1406,7 @@ async def start_sso_ui_auth(self, request: SynapseRequest, session_id: str) -> s
1397
1406
async def complete_sso_login (
1398
1407
self ,
1399
1408
registered_user_id : str ,
1409
+ auth_provider_id : str ,
1400
1410
request : Request ,
1401
1411
client_redirect_url : str ,
1402
1412
extra_attributes : Optional [JsonDict ] = None ,
@@ -1406,6 +1416,9 @@ async def complete_sso_login(
1406
1416
1407
1417
Args:
1408
1418
registered_user_id: The registered user ID to complete SSO login for.
1419
+ auth_provider_id: The id of the SSO Identity provider that was used for
1420
+ login. This will be stored in the login token for future tracking in
1421
+ prometheus metrics.
1409
1422
request: The request to complete.
1410
1423
client_redirect_url: The URL to which to redirect the user at the end of the
1411
1424
process.
@@ -1427,6 +1440,7 @@ async def complete_sso_login(
1427
1440
1428
1441
self ._complete_sso_login (
1429
1442
registered_user_id ,
1443
+ auth_provider_id ,
1430
1444
request ,
1431
1445
client_redirect_url ,
1432
1446
extra_attributes ,
@@ -1437,6 +1451,7 @@ async def complete_sso_login(
1437
1451
def _complete_sso_login (
1438
1452
self ,
1439
1453
registered_user_id : str ,
1454
+ auth_provider_id : str ,
1440
1455
request : Request ,
1441
1456
client_redirect_url : str ,
1442
1457
extra_attributes : Optional [JsonDict ] = None ,
@@ -1463,7 +1478,7 @@ def _complete_sso_login(
1463
1478
1464
1479
# Create a login token
1465
1480
login_token = self .macaroon_gen .generate_short_term_login_token (
1466
- registered_user_id
1481
+ registered_user_id , auth_provider_id = auth_provider_id
1467
1482
)
1468
1483
1469
1484
# Append the login token to the original redirect URL (i.e. with its query
@@ -1569,15 +1584,48 @@ def generate_access_token(
1569
1584
return macaroon .serialize ()
1570
1585
1571
1586
def generate_short_term_login_token (
1572
- self , user_id : str , duration_in_ms : int = (2 * 60 * 1000 )
1587
+ self ,
1588
+ user_id : str ,
1589
+ auth_provider_id : str ,
1590
+ duration_in_ms : int = (2 * 60 * 1000 ),
1573
1591
) -> str :
1574
1592
macaroon = self ._generate_base_macaroon (user_id )
1575
1593
macaroon .add_first_party_caveat ("type = login" )
1576
1594
now = self .hs .get_clock ().time_msec ()
1577
1595
expiry = now + duration_in_ms
1578
1596
macaroon .add_first_party_caveat ("time < %d" % (expiry ,))
1597
+ macaroon .add_first_party_caveat ("auth_provider_id = %s" % (auth_provider_id ,))
1579
1598
return macaroon .serialize ()
1580
1599
1600
+ def verify_short_term_login_token (self , token : str ) -> LoginTokenAttributes :
1601
+ """Verify a short-term-login macaroon
1602
+
1603
+ Checks that the given token is a valid, unexpired short-term-login token
1604
+ minted by this server.
1605
+
1606
+ Args:
1607
+ token: the login token to verify
1608
+
1609
+ Returns:
1610
+ the user_id that this token is valid for
1611
+
1612
+ Raises:
1613
+ MacaroonVerificationFailedException if the verification failed
1614
+ """
1615
+ macaroon = pymacaroons .Macaroon .deserialize (token )
1616
+ user_id = get_value_from_macaroon (macaroon , "user_id" )
1617
+ auth_provider_id = get_value_from_macaroon (macaroon , "auth_provider_id" )
1618
+
1619
+ v = pymacaroons .Verifier ()
1620
+ v .satisfy_exact ("gen = 1" )
1621
+ v .satisfy_exact ("type = login" )
1622
+ v .satisfy_general (lambda c : c .startswith ("user_id = " ))
1623
+ v .satisfy_general (lambda c : c .startswith ("auth_provider_id = " ))
1624
+ satisfy_expiry (v , self .hs .get_clock ().time_msec )
1625
+ v .verify (macaroon , self .hs .config .key .macaroon_secret_key )
1626
+
1627
+ return LoginTokenAttributes (user_id = user_id , auth_provider_id = auth_provider_id )
1628
+
1581
1629
def generate_delete_pusher_token (self , user_id : str ) -> str :
1582
1630
macaroon = self ._generate_base_macaroon (user_id )
1583
1631
macaroon .add_first_party_caveat ("type = delete_pusher" )
0 commit comments