From 84f91d256cbc32b9d0eaf01d13a27c95ad5d2587 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Fri, 31 Jan 2025 15:56:01 +0530 Subject: [PATCH 1/3] Adding Support For Federated Login --- auth0/authentication/get_token.py | 48 +++++++++++++++++++++ auth0/management/users.py | 43 ++++++++++++++++++ auth0/test/authentication/test_get_token.py | 33 ++++++++++++++ auth0/test/management/test_users.py | 33 ++++++++++++++ 4 files changed, 157 insertions(+) diff --git a/auth0/authentication/get_token.py b/auth0/authentication/get_token.py index 7b368523..cde2070e 100644 --- a/auth0/authentication/get_token.py +++ b/auth0/authentication/get_token.py @@ -277,3 +277,51 @@ def backchannel_login( "grant_type": grant_type, }, ) + + def federated_login( + self, + subject_token_type: str, + subject_token: str, + requested_token_type: str, + login_hint: str | None = None, + scope: str | None = None, + connection: str | None = None, + grant_type: str = "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token" + ) -> Any: + """Calls /oauth/token endpoint with federated-connection-access-token grant type + + Args: + subject_token_type (str): String containing the typpe of token. + + subject_token (str): String containing the value of subject_token_type. + + requested_token_type (str): String containing the type of rquested token. + + connection (str, optional): Denotes the name of a social identity provider configured to your application + + login_hint (str, optional): String containing information about the user to contact for authentication. + + scope(str, optional): String value of the different scopes the client is asking for. + Multiple scopes are separated with whitespace. + + grant_type (str): Denotes the flow you're using. For Federated Connection Access token use + urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token + + + Returns: + access_token, scope, issued_token_type, token_type + """ + + return self.authenticated_post( + f"{self.protocol}://{self.domain}/oauth/token", + data={ + "client_id": self.client_id, + "grant_type": grant_type, + "subject_token_type": subject_token_type, + "subject_token": subject_token, + "requested_token_type": requested_token_type, + "login_hint": login_hint, + "connection": connection, + "scope": scope, + }, + ) diff --git a/auth0/management/users.py b/auth0/management/users.py index 3ef8f853..1ff527da 100644 --- a/auth0/management/users.py +++ b/auth0/management/users.py @@ -538,3 +538,46 @@ def delete_authentication_method_by_id( url = self._url(f"{user_id}/authentication-methods/{authentication_method_id}") return self.client.delete(url) + + def list_tokensets( + self, id: str, page: int = 0, per_page: int = 25, include_totals: bool = True + ): + """List all the tokenset(s) associated to the user. + + Args: + id (str): The user's id. + + page (int, optional): The result's page number (zero based). By default, + retrieves the first page of results. + + per_page (int, optional): The amount of entries per page. By default, + retrieves 25 results per page. + + include_totals (bool, optional): True if the query summary is + to be included in the result, False otherwise. Defaults to True. + + See https://auth0.com/docs/api/management/v2#!/Users/get_tokensets + """ + + params = { + "per_page": per_page, + "page": page, + "include_totals": str(include_totals).lower(), + } + url = self._url(f"{id}/tokensets") + return self.client.get(url, params=params) + + def delete_tokenset_by_id( + self, user_id: str, tokenset_id: str + ) -> Any: + """Deletes an tokenset by ID. + + Args: + user_id (str): The user_id to delete an authentication method by ID for. + tokenset_id (str): The tokenset_id to delete an tokenset by ID for. + + See: https://auth0.com/docs/api/management/v2#!/Users/delete_tokenset_by_id + """ + + url = self._url(f"{user_id}/tokensets/{tokenset_id}") + return self.client.delete(url) diff --git a/auth0/test/authentication/test_get_token.py b/auth0/test/authentication/test_get_token.py index 4e717588..9d0f8371 100644 --- a/auth0/test/authentication/test_get_token.py +++ b/auth0/test/authentication/test_get_token.py @@ -334,4 +334,37 @@ def test_backchannel_login(self, mock_post): "auth_req_id": "reqid", "grant_type": "urn:openid:params:grant-type:ciba", }, + ) + + @mock.patch("auth0.rest.RestClient.post") + def test_federated_login(self, mock_post): + g = GetToken("my.domain.com", "cid", client_secret="csec") + + g.federated_login( + grant_type="urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token", + subject_token_type="urn:ietf:params:oauth:token-type:refresh_token", + subject_token="refid", + requested_token_type="http://auth0.com/oauth/token-type/federated-connection-access-token", + connection="google-oauth2", + login_hint="idp_user_id" + ) + + args, kwargs = mock_post.call_args + + print(kwargs["data"]) + + self.assertEqual(args[0], "https://my.domain.com/oauth/token") + self.assertEqual( + kwargs["data"], + { + "grant_type": "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token", + "client_id": "cid", + "client_secret": "csec", + "subject_token_type": "urn:ietf:params:oauth:token-type:refresh_token", + "subject_token": "refid", + "requested_token_type": "http://auth0.com/oauth/token-type/federated-connection-access-token", + "connection": "google-oauth2", + "login_hint": "idp_user_id", + "scope": None, + }, ) \ No newline at end of file diff --git a/auth0/test/management/test_users.py b/auth0/test/management/test_users.py index aba7e006..64f9fbce 100644 --- a/auth0/test/management/test_users.py +++ b/auth0/test/management/test_users.py @@ -403,3 +403,36 @@ def test_delete_authentication_method_by_id(self, mock_rc): mock_instance.delete.assert_called_with( "https://domain/api/v2/users/user_id/authentication-methods/authentication_method_id" ) + + @mock.patch("auth0.management.users.RestClient") + def test_list_tokensets(self, mock_rc): + mock_instance = mock_rc.return_value + + u = Users(domain="domain", token="jwttoken") + u.list_tokensets("an-id") + + args, kwargs = mock_instance.get.call_args + self.assertEqual("https://domain/api/v2/users/an-id/tokensets", args[0]) + self.assertEqual( + kwargs["params"], {"per_page": 25, "page": 0, "include_totals": "true"} + ) + + u.list_tokensets(id="an-id", page=1, per_page=50, include_totals=False) + + args, kwargs = mock_instance.get.call_args + + self.assertEqual("https://domain/api/v2/users/an-id/tokensets", args[0]) + self.assertEqual( + kwargs["params"], {"per_page": 50, "page": 1, "include_totals": "false"} + ) + + @mock.patch("auth0.management.users.RestClient") + def test_delete_tokenset_by_id(self, mock_rc): + mock_instance = mock_rc.return_value + + u = Users(domain="domain", token="jwttoken") + u.delete_tokenset_by_id("user_id", "tokenset_id") + + mock_instance.delete.assert_called_with( + "https://domain/api/v2/users/user_id/tokensets/tokenset_id" + ) From 717e7364e70d675d8386a80f3bf69079c8d674f7 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Tue, 4 Feb 2025 11:21:16 +0530 Subject: [PATCH 2/3] Changing the function name and typo correction --- auth0/authentication/get_token.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/auth0/authentication/get_token.py b/auth0/authentication/get_token.py index cde2070e..e1954888 100644 --- a/auth0/authentication/get_token.py +++ b/auth0/authentication/get_token.py @@ -278,7 +278,7 @@ def backchannel_login( }, ) - def federated_login( + def federated_connection_access_token( self, subject_token_type: str, subject_token: str, @@ -291,7 +291,7 @@ def federated_login( """Calls /oauth/token endpoint with federated-connection-access-token grant type Args: - subject_token_type (str): String containing the typpe of token. + subject_token_type (str): String containing the type of token. subject_token (str): String containing the value of subject_token_type. @@ -303,9 +303,6 @@ def federated_login( scope(str, optional): String value of the different scopes the client is asking for. Multiple scopes are separated with whitespace. - - grant_type (str): Denotes the flow you're using. For Federated Connection Access token use - urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token Returns: From e4c1a289845e7c3979d9335aace1ceb2fe9a39a5 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 10 Feb 2025 13:37:43 +0530 Subject: [PATCH 3/3] Removing some attributes --- auth0/authentication/get_token.py | 14 ++------------ auth0/test/authentication/test_get_token.py | 9 +++------ 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/auth0/authentication/get_token.py b/auth0/authentication/get_token.py index e1954888..0126d311 100644 --- a/auth0/authentication/get_token.py +++ b/auth0/authentication/get_token.py @@ -283,8 +283,6 @@ def federated_connection_access_token( subject_token_type: str, subject_token: str, requested_token_type: str, - login_hint: str | None = None, - scope: str | None = None, connection: str | None = None, grant_type: str = "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token" ) -> Any: @@ -297,13 +295,7 @@ def federated_connection_access_token( requested_token_type (str): String containing the type of rquested token. - connection (str, optional): Denotes the name of a social identity provider configured to your application - - login_hint (str, optional): String containing information about the user to contact for authentication. - - scope(str, optional): String value of the different scopes the client is asking for. - Multiple scopes are separated with whitespace. - + connection (str, optional): Denotes the name of a social identity provider configured to your application Returns: access_token, scope, issued_token_type, token_type @@ -317,8 +309,6 @@ def federated_connection_access_token( "subject_token_type": subject_token_type, "subject_token": subject_token, "requested_token_type": requested_token_type, - "login_hint": login_hint, - "connection": connection, - "scope": scope, + "connection": connection, }, ) diff --git a/auth0/test/authentication/test_get_token.py b/auth0/test/authentication/test_get_token.py index 9d0f8371..817660e7 100644 --- a/auth0/test/authentication/test_get_token.py +++ b/auth0/test/authentication/test_get_token.py @@ -340,13 +340,12 @@ def test_backchannel_login(self, mock_post): def test_federated_login(self, mock_post): g = GetToken("my.domain.com", "cid", client_secret="csec") - g.federated_login( + g.federated_connection_access_token( grant_type="urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token", subject_token_type="urn:ietf:params:oauth:token-type:refresh_token", subject_token="refid", requested_token_type="http://auth0.com/oauth/token-type/federated-connection-access-token", - connection="google-oauth2", - login_hint="idp_user_id" + connection="google-oauth2" ) args, kwargs = mock_post.call_args @@ -363,8 +362,6 @@ def test_federated_login(self, mock_post): "subject_token_type": "urn:ietf:params:oauth:token-type:refresh_token", "subject_token": "refid", "requested_token_type": "http://auth0.com/oauth/token-type/federated-connection-access-token", - "connection": "google-oauth2", - "login_hint": "idp_user_id", - "scope": None, + "connection": "google-oauth2" }, ) \ No newline at end of file