Skip to content
Merged
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,14 +31,13 @@ device_credential = DeviceCodeCredential(

```

Create an authorization provider object and a list of scopes
Pass the credential object and scopes to the GraphSession constructor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a couple of the imports are no longer needed above on line 17.

```python
scopes = ['mail.send', 'user.read']
auth_provider = TokenCredentialAuthProvider(scopes, device_credential)
graph_session = GraphSession(device_credential, scopes)
```

```python
graph_session = GraphSession(auth_provider)
result = graph_session.get('/me')
print(result.json())
```
Expand Down
1 change: 0 additions & 1 deletion msgraphcore/__init__.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion msgraphcore/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
15 changes: 8 additions & 7 deletions msgraphcore/graph_session.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -15,15 +14,17 @@ 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._append_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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,3 @@ class TokenCredential(ABC):
def get_token(self, *scopes, **kwargs):
pass


class AuthProviderBase(ABC):
@abstractmethod
def get_access_token(self):
pass
41 changes: 17 additions & 24 deletions msgraphcore/middleware/authorization.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,34 @@
from ._base_auth import AuthProviderBase, TokenCredential
from .abc_token_credential import 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: TokenCredential, scopes: [str]):
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

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)
def _get_access_token(self):
return self.credential.get_token(*self.get_scopes())[0]


class TokenCredentialAuthProvider(AuthProviderBase):
def __init__(self, credential: TokenCredential, scopes: [str] = ['.default']):
self.credential = credential
self.scopes = scopes

def get_access_token(self):
return self.credential.get_token(*self.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:
return auth_options_present.scopes
else:
return self.scopes
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ssl
from urllib3 import PoolManager

from requests.adapters import HTTPAdapter
from urllib3 import PoolManager


class MiddlewarePipeline(HTTPAdapter):
Expand Down
7 changes: 2 additions & 5 deletions samples/samples.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import json
from pprint import pprint

from azure.identity import InteractiveBrowserCredential
from msgraphcore.middleware.authorization import TokenCredentialAuthProvider

from msgraphcore import GraphSession

scopes = ['user.read']
browser_credential = InteractiveBrowserCredential(client_id='ENTER_YOUR_CLIENT_ID')
auth_provider = TokenCredentialAuthProvider(browser_credential)
graph_session = GraphSession(auth_provider)
graph_session = GraphSession(browser_credential, scopes)


def post_sample():
Expand Down
14 changes: 5 additions & 9 deletions tests/integration/test_middleware_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from unittest import TestCase

from msgraphcore.graph_session import GraphSession
from msgraphcore.middleware.authorization import AuthProviderBase


class MiddlewarePipelineTest(TestCase):
Expand All @@ -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/}']



30 changes: 30 additions & 0 deletions tests/unit/test_auth_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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_auth_options_override_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)

auth_handler_scopes = auth_handler.get_scopes()
self.assertEqual(auth_option, auth_handler_scopes)

def test_auth_handler_get_scopes_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)
auth_handler.get_scopes()

self.assertEqual(auth_handler.scopes, default_scopes)


16 changes: 6 additions & 10 deletions tests/unit/test_graph_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@

from msgraphcore.graph_session import GraphSession
from msgraphcore.constants import BASE_URL, SDK_VERSION
from msgraphcore.middleware._base_auth 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
Expand All @@ -31,7 +30,7 @@ def test_updated_sdk_version(self):
.startswith('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)
Expand All @@ -57,9 +56,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/}']
2 changes: 1 addition & 1 deletion tests/unit/test_middleware_pipeline.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down