From 423f9b35faf0b673bfb2cd68d73717964080b5c9 Mon Sep 17 00:00:00 2001 From: Adam Mcgrath Date: Tue, 25 Jul 2023 12:56:41 +0100 Subject: [PATCH] Add organization name validation --- auth0/authentication/async_token_verifier.py | 2 +- auth0/authentication/token_verifier.py | 36 +++++++++----- .../authentication/test_token_verifier.py | 47 +++++++++++++++++-- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/auth0/authentication/async_token_verifier.py b/auth0/authentication/async_token_verifier.py index 058e493f..367e167d 100644 --- a/auth0/authentication/async_token_verifier.py +++ b/auth0/authentication/async_token_verifier.py @@ -176,7 +176,7 @@ async def verify( token (str): The JWT to verify. nonce (str, optional): The nonce value sent during authentication. max_age (int, optional): The max_age value sent during authentication. - organization (str, optional): The expected organization ID (org_id) claim value. This should be specified + organization (str, optional): The expected organization ID (org_id) or orgnization name (org_name) claim value. This should be specified when logging in to an organization. Returns: diff --git a/auth0/authentication/token_verifier.py b/auth0/authentication/token_verifier.py index 030eda27..10fafeec 100644 --- a/auth0/authentication/token_verifier.py +++ b/auth0/authentication/token_verifier.py @@ -299,7 +299,7 @@ def verify( token (str): The JWT to verify. nonce (str, optional): The nonce value sent during authentication. max_age (int, optional): The max_age value sent during authentication. - organization (str, optional): The expected organization ID (org_id) claim value. This should be specified + organization (str, optional): The expected organization ID (org_id) or orgnization name (org_name) claim value. This should be specified when logging in to an organization. Returns: @@ -402,16 +402,30 @@ def _verify_payload( # Organization if organization: - if "org_id" not in payload or not isinstance(payload["org_id"], str): - raise TokenValidationError( - "Organization (org_id) claim must be a string present in the ID" - " token" - ) - if payload["org_id"] != organization: - raise TokenValidationError( - "Organization (org_id) claim mismatch in the ID token; expected" - ' "{}", found "{}"'.format(organization, payload["org_id"]) - ) + if organization.startswith("org_"): + if "org_id" not in payload or not isinstance(payload["org_id"], str): + raise TokenValidationError( + "Organization (org_id) claim must be a string present in the ID" + " token" + ) + if payload["org_id"] != organization: + raise TokenValidationError( + "Organization (org_id) claim mismatch in the ID token; expected" + ' "{}", found "{}"'.format(organization, payload["org_id"]) + ) + else: + if "org_name" not in payload or not isinstance( + payload["org_name"], str + ): + raise TokenValidationError( + "Organization (org_name) claim must be a string present in the ID" + " token" + ) + if payload["org_name"] != organization.lower(): + raise TokenValidationError( + "Organization (org_name) claim mismatch in the ID token; expected" + ' "{}", found "{}"'.format(organization, payload["org_name"]) + ) # Authorized party if isinstance(payload["aud"], list) and len(payload["aud"]) > 1: diff --git a/auth0/test/authentication/test_token_verifier.py b/auth0/test/authentication/test_token_verifier.py index df2af2ef..33dab693 100644 --- a/auth0/test/authentication/test_token_verifier.py +++ b/auth0/test/authentication/test_token_verifier.py @@ -506,7 +506,48 @@ def test_passes_when_org_present_and_matches(self): tv._clock = MOCKED_CLOCK tv.verify(token, organization="org_123") - def test_fails_when_org_specified_but_not_present(self): + def test_fails_when_org_name_specified_but_not_present(self): + token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.wotJnUdD5IfdZMewF_-BnHc0pI56uwzwr5qaSXvSu9w" + self.assert_fails_with_error( + token, + "Organization (org_name) claim must be a string present in the ID token", + signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), + organization="org-123", + ) + + def test_fails_when_org_name_specified_but_not_string(self): + token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfbmFtZSI6NDIsImlzcyI6Imh0dHBzOi8vdG9rZW5zLXRlc3QuYXV0aDAuY29tLyIsImV4cCI6MTU4Nzc2NTM2MSwiaWF0IjoxNTg3NTkyNTYxfQ.RXu-dz1u2pftk_iInk1To8z9g1B6TVA-5FAwoCx85T0" + self.assert_fails_with_error( + token, + "Organization (org_name) claim must be a string present in the ID token", + signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), + organization="org-123", + ) + + def test_fails_when_org_name_specified_but_does_not_match(self): + token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfbmFtZSI6Im9yZy1hYmMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.P_ldJGEaFg58cARwGMtog_KTsqv7cGJZXoS9xdTEkvQ" + self.assert_fails_with_error( + token, + 'Organization (org_name) claim mismatch in the ID token; expected "org-123",' + ' found "org-abc"', + signature_verifier=SymmetricSignatureVerifier(HMAC_SHARED_SECRET), + organization="org-123", + ) + + def test_succeeds_when_org_name_specified_matches(self): + token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfbmFtZSI6Im9yZy0xMjMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.P8Kba8Fgamyiw1qw_lBfp2OAzWn6NOLL6fBCDQhGvyc" + sv = SymmetricSignatureVerifier(HMAC_SHARED_SECRET) + tv = TokenVerifier( + signature_verifier=sv, + issuer=expectations["issuer"], + audience=expectations["audience"], + ) + tv._clock = MOCKED_CLOCK + response = tv.verify(token) + self.assertIn("org_name", response) + self.assertEqual("org-123", response["org_name"]) + + def test_fails_when_org_id_specified_but_not_present(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.wotJnUdD5IfdZMewF_-BnHc0pI56uwzwr5qaSXvSu9w" self.assert_fails_with_error( token, @@ -515,7 +556,7 @@ def test_fails_when_org_specified_but_not_present(self): organization="org_123", ) - def test_fails_when_org_specified_but_not_(self): + def test_fails_when_org_id_specified_but_not_string(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfaWQiOjQyLCJpc3MiOiJodHRwczovL3Rva2Vucy10ZXN0LmF1dGgwLmNvbS8iLCJleHAiOjE1ODc3NjUzNjEsImlhdCI6MTU4NzU5MjU2MX0.fGL1_akaHikdovS7NRYla3flne1xdtCjP0ei_CRxO6k" self.assert_fails_with_error( token, @@ -524,7 +565,7 @@ def test_fails_when_org_specified_but_not_(self): organization="org_123", ) - def test_fails_when_org_specified_but_does_not_match(self): + def test_fails_when_org_id_specified_but_does_not_match(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhdXRoMHxzZGs0NThma3MiLCJhdWQiOiJ0b2tlbnMtdGVzdC0xMjMiLCJvcmdfaWQiOiJvcmdfMTIzIiwiaXNzIjoiaHR0cHM6Ly90b2tlbnMtdGVzdC5hdXRoMC5jb20vIiwiZXhwIjoxNTg3NzY1MzYxLCJpYXQiOjE1ODc1OTI1NjF9.hjSPgJpg0Dn2z0giCdGqVLD5Kmqy_yMYlSkgwKD7ahQ" self.assert_fails_with_error( token,