From 527e29a41e3b489bd110733d0f9835e5f31a4331 Mon Sep 17 00:00:00 2001 From: jobala Date: Tue, 28 Apr 2020 09:43:59 +0300 Subject: [PATCH 01/12] Refactor GraphSession to take scopes and credential through the constructor. --- msgraphcore/graph_session.py | 11 +++--- ...{_base_auth.py => abc_token_credential.py} | 5 --- msgraphcore/middleware/authorization.py | 36 ++++++++----------- .../{_middleware.py => middleware.py} | 2 +- tests/unit/test_graph_session.py | 2 +- tests/unit/test_middleware_pipeline.py | 2 +- 6 files changed, 21 insertions(+), 37 deletions(-) rename msgraphcore/middleware/{_base_auth.py => abc_token_credential.py} (60%) rename msgraphcore/middleware/{_middleware.py => middleware.py} (100%) diff --git a/msgraphcore/graph_session.py b/msgraphcore/graph_session.py index bbd44972..de41eedd 100644 --- a/msgraphcore/graph_session.py +++ b/msgraphcore/graph_session.py @@ -1,12 +1,11 @@ """ Graph Session """ - from requests import Session from msgraphcore.constants import BASE_URL, SDK_VERSION -from msgraphcore.middleware._middleware import MiddlewarePipeline, BaseMiddleware -from msgraphcore.middleware._base_auth import AuthProviderBase +from msgraphcore.middleware.middleware import MiddlewarePipeline, BaseMiddleware +from msgraphcore.middleware.abc_token_credential import TokenCredential from msgraphcore.middleware.authorization import AuthorizationHandler from msgraphcore.middleware.options.middleware_control import middleware_control @@ -15,15 +14,13 @@ class GraphSession(Session): """Extends Session with Graph functionality Extends Session by adding support for middleware options and middleware pipeline - - """ - def __init__(self, auth_provider: AuthProviderBase, middleware: list = []): + def __init__(self, credential: TokenCredential, scopes: [str] = ['.default'], middleware: list = []): super().__init__() self.headers.update({'sdkVersion': 'graph-python-' + SDK_VERSION}) self._base_url = BASE_URL - auth_handler = AuthorizationHandler(auth_provider) + auth_handler = AuthorizationHandler(credential, scopes) # The authorization handler should be the first middleware in the pipeline. middleware.insert(0, auth_handler) diff --git a/msgraphcore/middleware/_base_auth.py b/msgraphcore/middleware/abc_token_credential.py similarity index 60% rename from msgraphcore/middleware/_base_auth.py rename to msgraphcore/middleware/abc_token_credential.py index e3dcbb86..c887eb58 100644 --- a/msgraphcore/middleware/_base_auth.py +++ b/msgraphcore/middleware/abc_token_credential.py @@ -6,8 +6,3 @@ class TokenCredential(ABC): def get_token(self, *scopes, **kwargs): pass - -class AuthProviderBase(ABC): - @abstractmethod - def get_access_token(self): - pass diff --git a/msgraphcore/middleware/authorization.py b/msgraphcore/middleware/authorization.py index 59f8265b..64ed559a 100644 --- a/msgraphcore/middleware/authorization.py +++ b/msgraphcore/middleware/authorization.py @@ -1,41 +1,33 @@ -from ._base_auth import AuthProviderBase, TokenCredential from ..constants import AUTH_MIDDLEWARE_OPTIONS -from ._middleware import BaseMiddleware +from .middleware import BaseMiddleware from .options.middleware_control import middleware_control class AuthorizationHandler(BaseMiddleware): - def __init__(self, auth_provider: AuthProviderBase): + def __init__(self, credential, scopes): super().__init__() - self.auth_provider = auth_provider + self.credential = credential + self.scopes = scopes self.retry_count = 0 def send(self, request, **kwargs): - # Checks if there are any options for this middleware - options = self._get_middleware_options() - # If there is, get the scopes from the options - if options: - self.auth_provider.scopes = options.scopes + self._update_scopes_from_middleware_options() - token = self.auth_provider.get_access_token() - request.headers.update({'Authorization': 'Bearer {}'.format(token)}) + request.headers.update({'Authorization': 'Bearer {}'.format(self._get_access_token())}) response = super().send(request, **kwargs) - # Token might have expired just before transmission, retry the request + # Token might have expired just before transmission, retry the request one more time if response.status_code == 401 and self.retry_count < 2: self.retry_count += 1 return self.send(request, **kwargs) - return response - def _get_middleware_options(self): - return middleware_control.get(AUTH_MIDDLEWARE_OPTIONS) - - -class TokenCredentialAuthProvider(AuthProviderBase): - def __init__(self, credential: TokenCredential, scopes: [str] = ['.default']): - self.credential = credential - self.scopes = scopes + def _update_scopes_from_middleware_options(self): + # Checks if there are any options for this middleware + auth_options_present = middleware_control.get(AUTH_MIDDLEWARE_OPTIONS) + # If there is, get the scopes from the options + if auth_options_present: + self.scopes = auth_options_present.scopes - def get_access_token(self): + def _get_access_token(self): return self.credential.get_token(*self.scopes)[0] diff --git a/msgraphcore/middleware/_middleware.py b/msgraphcore/middleware/middleware.py similarity index 100% rename from msgraphcore/middleware/_middleware.py rename to msgraphcore/middleware/middleware.py index 901d62e4..0d1e7687 100644 --- a/msgraphcore/middleware/_middleware.py +++ b/msgraphcore/middleware/middleware.py @@ -1,7 +1,7 @@ import ssl +from urllib3 import PoolManager from requests.adapters import HTTPAdapter -from urllib3 import PoolManager class MiddlewarePipeline(HTTPAdapter): diff --git a/tests/unit/test_graph_session.py b/tests/unit/test_graph_session.py index 3c73432e..80171ffd 100644 --- a/tests/unit/test_graph_session.py +++ b/tests/unit/test_graph_session.py @@ -6,7 +6,7 @@ from msgraphcore.graph_session import GraphSession from msgraphcore.constants import BASE_URL, SDK_VERSION -from msgraphcore.middleware._base_auth import AuthProviderBase +from msgraphcore.middleware.abc_token_credential import AuthProviderBase class GraphSessionTest(TestCase): diff --git a/tests/unit/test_middleware_pipeline.py b/tests/unit/test_middleware_pipeline.py index c5e69c56..8bf666f2 100644 --- a/tests/unit/test_middleware_pipeline.py +++ b/tests/unit/test_middleware_pipeline.py @@ -1,7 +1,7 @@ from collections import OrderedDict from unittest import TestCase -from msgraphcore.middleware._middleware import MiddlewarePipeline, BaseMiddleware +from msgraphcore.middleware.middleware import MiddlewarePipeline, BaseMiddleware class MiddlewarePipelineTest(TestCase): From d322378febce01a45098d974c9b223c684bcf0d1 Mon Sep 17 00:00:00 2001 From: jobala Date: Tue, 28 Apr 2020 09:51:56 +0300 Subject: [PATCH 02/12] Update samples --- samples/samples.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/samples/samples.py b/samples/samples.py index ab1e7a6e..db96830c 100644 --- a/samples/samples.py +++ b/samples/samples.py @@ -1,14 +1,10 @@ import json from pprint import pprint - from azure.identity import InteractiveBrowserCredential -from msgraphcore.middleware.authorization import TokenCredentialAuthProvider - from msgraphcore import GraphSession browser_credential = InteractiveBrowserCredential(client_id='ENTER_YOUR_CLIENT_ID') -auth_provider = TokenCredentialAuthProvider(browser_credential) -graph_session = GraphSession(auth_provider) +graph_session = GraphSession(browser_credential) def post_sample(): From cc2dd9bbe5baad4f3c6a0cb1206de7f0f7206dec Mon Sep 17 00:00:00 2001 From: jobala Date: Tue, 28 Apr 2020 09:53:36 +0300 Subject: [PATCH 03/12] Update README.md --- README.md | 3 +-- msgraphcore/__init__.py | 1 - samples/samples.py | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 01f52393..e7585e12 100644 --- a/README.md +++ b/README.md @@ -34,11 +34,10 @@ device_credential = DeviceCodeCredential( Create an authorization provider object and a list of scopes ```python scopes = ['mail.send', 'user.read'] -auth_provider = TokenCredentialAuthProvider(scopes, device_credential) ``` ```python -graph_session = GraphSession(auth_provider) +graph_session = GraphSession(device_credential, scopes) result = graph_session.get('/me') print(result.json()) ``` diff --git a/msgraphcore/__init__.py b/msgraphcore/__init__.py index 89fd08ff..a9d0437f 100644 --- a/msgraphcore/__init__.py +++ b/msgraphcore/__init__.py @@ -1,7 +1,6 @@ """msgraph-core""" from msgraphcore.graph_session import GraphSession -from .middleware.authorization import AuthProviderBase, TokenCredentialAuthProvider from .constants import SDK_VERSION __version__ = SDK_VERSION diff --git a/samples/samples.py b/samples/samples.py index db96830c..f5538007 100644 --- a/samples/samples.py +++ b/samples/samples.py @@ -3,8 +3,9 @@ from azure.identity import InteractiveBrowserCredential from msgraphcore import GraphSession +scopes = ['user.read'] browser_credential = InteractiveBrowserCredential(client_id='ENTER_YOUR_CLIENT_ID') -graph_session = GraphSession(browser_credential) +graph_session = GraphSession(browser_credential, scopes) def post_sample(): From 790b020d6aa82f1c71c8d0bee40ac9c36c15e5d0 Mon Sep 17 00:00:00 2001 From: jobala Date: Tue, 28 Apr 2020 09:58:37 +0300 Subject: [PATCH 04/12] Update integration tests to use new GraphSession API --- tests/integration/test_middleware_pipeline.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/integration/test_middleware_pipeline.py b/tests/integration/test_middleware_pipeline.py index 987e4406..7ae8df22 100644 --- a/tests/integration/test_middleware_pipeline.py +++ b/tests/integration/test_middleware_pipeline.py @@ -2,7 +2,6 @@ from unittest import TestCase from msgraphcore.graph_session import GraphSession -from msgraphcore.middleware.authorization import AuthProviderBase class MiddlewarePipelineTest(TestCase): @@ -12,19 +11,16 @@ def setUp(self): def test_middleware_pipeline(self): url = 'https://proxy.apisandbox.msdn.microsoft.com/svc?url=https://graph.microsoft.com/v1.0/me' scopes = ['user.read'] - auth_provider = _CustomAuthProvider(scopes) - graph_session = GraphSession(auth_provider) + credential = _CustomTokenCredential() + graph_session = GraphSession(credential, scopes) result = graph_session.get(url) self.assertEqual(result.status_code, 200) -class _CustomAuthProvider(AuthProviderBase): - def __init__(self, scopes): - pass - - def get_access_token(self): - return '{token:https://graph.microsoft.com/}' +class _CustomTokenCredential: + def get_token(self, scopes = []): + return ['{token:https://graph.microsoft.com/}'] From 4f2a1fdec1fd6f6314d86cb9dcc59f5b4116de3d Mon Sep 17 00:00:00 2001 From: jobala Date: Tue, 28 Apr 2020 10:00:45 +0300 Subject: [PATCH 05/12] Update unit tests --- tests/integration/test_middleware_pipeline.py | 2 +- tests/unit/test_graph_session.py | 16 ++++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/integration/test_middleware_pipeline.py b/tests/integration/test_middleware_pipeline.py index 7ae8df22..042ea5d4 100644 --- a/tests/integration/test_middleware_pipeline.py +++ b/tests/integration/test_middleware_pipeline.py @@ -19,7 +19,7 @@ def test_middleware_pipeline(self): class _CustomTokenCredential: - def get_token(self, scopes = []): + def get_token(self, scopes): return ['{token:https://graph.microsoft.com/}'] diff --git a/tests/unit/test_graph_session.py b/tests/unit/test_graph_session.py index 80171ffd..d2e979d0 100644 --- a/tests/unit/test_graph_session.py +++ b/tests/unit/test_graph_session.py @@ -6,13 +6,12 @@ from msgraphcore.graph_session import GraphSession from msgraphcore.constants import BASE_URL, SDK_VERSION -from msgraphcore.middleware.abc_token_credential import AuthProviderBase class GraphSessionTest(TestCase): def setUp(self) -> None: - self.auth_provider = _CustomAuthProvider(['user.read']) - self.requests = GraphSession(self.auth_provider) + self.credential = _CustomTokenCredential() + self.requests = GraphSession(self.credential, ['user.read']) def tearDown(self) -> None: self.requests = None @@ -27,7 +26,7 @@ def test_has_sdk_version_header(self): self.assertEqual(self.requests.headers.get('sdkVersion'), 'graph-python-'+SDK_VERSION) def test_initialized_with_middlewares(self): - graph_session = GraphSession(self.auth_provider) + graph_session = GraphSession(self.credential) mocked_middleware = graph_session.get_adapter('https://') self.assertIsInstance(mocked_middleware, HTTPAdapter) @@ -53,9 +52,6 @@ def test_does_not_build_graph_urls_for_full_urls(self): self.assertEqual(other_url, request_url) -class _CustomAuthProvider(AuthProviderBase): - def __init__(self, scopes): - pass - - def get_access_token(self): - return '{token:https://graph.microsoft.com/}' +class _CustomTokenCredential: + def get_token(self, scopes): + return ['{token:https://graph.microsoft.com/}'] From 612bc15c480975b152161dc21338b45509d9a59a Mon Sep 17 00:00:00 2001 From: jobala Date: Tue, 28 Apr 2020 10:11:09 +0300 Subject: [PATCH 06/12] Fix lint errors --- msgraphcore/graph_session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgraphcore/graph_session.py b/msgraphcore/graph_session.py index de41eedd..f407a29f 100644 --- a/msgraphcore/graph_session.py +++ b/msgraphcore/graph_session.py @@ -15,7 +15,7 @@ class GraphSession(Session): Extends Session by adding support for middleware options and middleware pipeline """ - def __init__(self, credential: TokenCredential, scopes: [str] = ['.default'], middleware: list = []): + def __init__(self, credential: TokenCredential, scopes: [str] = ['.default'], middleware: list = []): super().__init__() self.headers.update({'sdkVersion': 'graph-python-' + SDK_VERSION}) self._base_url = BASE_URL From 4bc0cb8f91e8ac7b3533036221e7b3cc528df4bd Mon Sep 17 00:00:00 2001 From: jobala Date: Tue, 28 Apr 2020 10:19:49 +0300 Subject: [PATCH 07/12] Fix lint errors --- msgraphcore/graph_session.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/msgraphcore/graph_session.py b/msgraphcore/graph_session.py index f407a29f..7e54849e 100644 --- a/msgraphcore/graph_session.py +++ b/msgraphcore/graph_session.py @@ -15,7 +15,11 @@ class GraphSession(Session): Extends Session by adding support for middleware options and middleware pipeline """ - def __init__(self, credential: TokenCredential, scopes: [str] = ['.default'], middleware: list = []): + def __init__(self, + credential: TokenCredential, + scopes: [str] = ['.default'], + middleware: list = [] + ): super().__init__() self.headers.update({'sdkVersion': 'graph-python-' + SDK_VERSION}) self._base_url = BASE_URL From 519bb082e5cc0335bb49201ebf0c782f912e64d4 Mon Sep 17 00:00:00 2001 From: jobala Date: Wed, 29 Apr 2020 09:55:45 +0300 Subject: [PATCH 08/12] Add AuthorizationHandler tests --- msgraphcore/constants.py | 2 +- msgraphcore/middleware/authorization.py | 17 ++++++++------- tests/unit/test_auth_handler.py | 29 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 tests/unit/test_auth_handler.py diff --git a/msgraphcore/constants.py b/msgraphcore/constants.py index 6279eab6..49ab9330 100644 --- a/msgraphcore/constants.py +++ b/msgraphcore/constants.py @@ -4,7 +4,7 @@ REQUEST_TIMEOUT = 100 CONNECTION_TIMEOUT = 30 BASE_URL = 'https://graph.microsoft.com/v1.0' -SDK_VERSION = '0.0.1-0' +SDK_VERSION = '0.0.3' # Used as the key for AuthMiddlewareOption in MiddlewareControl AUTH_MIDDLEWARE_OPTIONS = 'AUTH_MIDDLEWARE_OPTIONS' diff --git a/msgraphcore/middleware/authorization.py b/msgraphcore/middleware/authorization.py index 64ed559a..fa030cb4 100644 --- a/msgraphcore/middleware/authorization.py +++ b/msgraphcore/middleware/authorization.py @@ -1,18 +1,17 @@ +from .abc_token_credential import TokenCredential from ..constants import AUTH_MIDDLEWARE_OPTIONS from .middleware import BaseMiddleware from .options.middleware_control import middleware_control class AuthorizationHandler(BaseMiddleware): - def __init__(self, credential, scopes): + def __init__(self, credential: TokenCredential, scopes: [str]): super().__init__() self.credential = credential self.scopes = scopes self.retry_count = 0 def send(self, request, **kwargs): - self._update_scopes_from_middleware_options() - request.headers.update({'Authorization': 'Bearer {}'.format(self._get_access_token())}) response = super().send(request, **kwargs) @@ -22,12 +21,14 @@ def send(self, request, **kwargs): return self.send(request, **kwargs) return response - def _update_scopes_from_middleware_options(self): + def _get_access_token(self): + return self.credential.get_token(*self.get_scopes())[0] + + def get_scopes(self): # Checks if there are any options for this middleware auth_options_present = middleware_control.get(AUTH_MIDDLEWARE_OPTIONS) # If there is, get the scopes from the options if auth_options_present: - self.scopes = auth_options_present.scopes - - def _get_access_token(self): - return self.credential.get_token(*self.scopes)[0] + return auth_options_present.scopes + else: + return self.scopes diff --git a/tests/unit/test_auth_handler.py b/tests/unit/test_auth_handler.py new file mode 100644 index 00000000..f1da6768 --- /dev/null +++ b/tests/unit/test_auth_handler.py @@ -0,0 +1,29 @@ +import unittest + +from msgraphcore.constants import AUTH_MIDDLEWARE_OPTIONS +from msgraphcore.middleware.authorization import AuthorizationHandler +from msgraphcore.middleware.options.middleware_control import middleware_control +from msgraphcore.middleware.options.auth_middleware_options import AuthMiddlewareOptions + + +class TestAuthorizationHandler(unittest.TestCase): + def test_uses_scopes_from_auth_options(self): + auth_option = ['email.read'] + default_scopes = ['.default'] + + middleware_control.set(AUTH_MIDDLEWARE_OPTIONS, AuthMiddlewareOptions(auth_option)) + auth_handler = AuthorizationHandler(None, default_scopes) + + auth_handler_scopes = auth_handler.get_scopes() + self.assertEqual(auth_option, auth_handler_scopes) + + def test_auth_option_does_not_overwrite_default_scopes(self): + auth_option = ['email.read'] + default_scopes = ['.default'] + + middleware_control.set(AUTH_MIDDLEWARE_OPTIONS, AuthMiddlewareOptions(auth_option)) + auth_handler = AuthorizationHandler(None, default_scopes) + + self.assertEqual(auth_handler.scopes, default_scopes) + + From 8e738e32486bcaefb4bd00613be7381176a0e50a Mon Sep 17 00:00:00 2001 From: jobala Date: Wed, 29 Apr 2020 10:09:31 +0300 Subject: [PATCH 09/12] Update test --- tests/unit/test_auth_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_auth_handler.py b/tests/unit/test_auth_handler.py index f1da6768..aff12a22 100644 --- a/tests/unit/test_auth_handler.py +++ b/tests/unit/test_auth_handler.py @@ -23,6 +23,7 @@ def test_auth_option_does_not_overwrite_default_scopes(self): middleware_control.set(AUTH_MIDDLEWARE_OPTIONS, AuthMiddlewareOptions(auth_option)) auth_handler = AuthorizationHandler(None, default_scopes) + auth_handler.get_scopes() self.assertEqual(auth_handler.scopes, default_scopes) From e0cb6e70db3b8b9ab5b328905b26333d940cd462 Mon Sep 17 00:00:00 2001 From: jobala Date: Wed, 29 Apr 2020 10:11:21 +0300 Subject: [PATCH 10/12] Update test --- tests/unit/test_auth_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_auth_handler.py b/tests/unit/test_auth_handler.py index aff12a22..5169d549 100644 --- a/tests/unit/test_auth_handler.py +++ b/tests/unit/test_auth_handler.py @@ -7,7 +7,7 @@ class TestAuthorizationHandler(unittest.TestCase): - def test_uses_scopes_from_auth_options(self): + def test_auth_options_override_default_scopes(self): auth_option = ['email.read'] default_scopes = ['.default'] @@ -17,7 +17,7 @@ def test_uses_scopes_from_auth_options(self): auth_handler_scopes = auth_handler.get_scopes() self.assertEqual(auth_option, auth_handler_scopes) - def test_auth_option_does_not_overwrite_default_scopes(self): + def test_auth_handler_get_scopes_does_not_overwrite_default_scopes(self): auth_option = ['email.read'] default_scopes = ['.default'] From 2df2546a47796710634b61c4a16ccd0d9ce786f6 Mon Sep 17 00:00:00 2001 From: jobala Date: Wed, 29 Apr 2020 10:23:30 +0300 Subject: [PATCH 11/12] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e7585e12..d8f3b132 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,14 @@ device_credential = DeviceCodeCredential( ``` -Create an authorization provider object and a list of scopes +Pass the credential object and scopes to the GraphSession constructor. ```python scopes = ['mail.send', 'user.read'] +graph_session = GraphSession(device_credential, scopes) + ``` ```python -graph_session = GraphSession(device_credential, scopes) result = graph_session.get('/me') print(result.json()) ``` From db67357bef74e2746507c7a9d022844772982c55 Mon Sep 17 00:00:00 2001 From: jobala Date: Fri, 1 May 2020 20:40:02 +0300 Subject: [PATCH 12/12] Remove unused imports from README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d8f3b132..c02b9b79 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Import modules ```python from azure.identity import UsernamePasswordCredential, DeviceCodeCredential -from msgraphcore import GraphSession, AuthorizationHandler, AuthMiddlewareOptions, TokenCredentialAuthProvider +from msgraphcore import GraphSession ``` Configure Credential Object @@ -35,7 +35,6 @@ Pass the credential object and scopes to the GraphSession constructor. ```python scopes = ['mail.send', 'user.read'] graph_session = GraphSession(device_credential, scopes) - ``` ```python