Skip to content

Commit 1ff5e65

Browse files
committed
The support for resource owner password credential (ROPC)flow has been introduced
1 parent 5f62ae1 commit 1ff5e65

File tree

7 files changed

+72
-13
lines changed

7 files changed

+72
-13
lines changed

examples/settings.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
settings = {
66
'url': 'https://mediadev88.sharepoint.com/',
7+
'tenant': 'mediadev88.onmicrosoft.com',
8+
'redirect_url': 'https://github.com/vgrem/Office365-REST-Python-Client/',
79
'user_credentials': {
810
'username': user_credentials[0],
911
'password': user_credentials[1]
1012
},
1113
'client_credentials': {
12-
'client_id': '',
13-
'client_secret': '',
14-
'redirect_url': 'https://github.com/vgrem/Office365-REST-Python-Client/'
14+
'client_id': 'd4b2d51e-2d8e-4f08-8bce-961a7a435130',
15+
'client_secret': 'v+lDZaIvLTHRsZ8/opvurqneZnH/DKXls18MVyX+0BY=',
1516
}
1617
}

office365/outlookservices/outlook_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ class OutlookClient(ClientRuntimeContext):
88
"""Office365 Outlook client context"""
99

1010
def __init__(self, ctx_auth):
11-
self.__service_root_url = "https://outlook.office365.com/api/v1.0/"
11+
# self.__service_root_url = "https://outlook.office365.com/api/v1.0/"
12+
self.__service_root_url = "https://graph.microsoft.com/v1.0/"
1213
super(OutlookClient, self).__init__(self.__service_root_url, ctx_auth)
1314
self.json_format = V4JsonFormat("minimal")
1415

office365/runtime/auth/authentication_context.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from office365.runtime.auth.acs_token_provider import ACSTokenProvider
22
from office365.runtime.auth.base_authentication_context import BaseAuthenticationContext
3+
from office365.runtime.auth.oauth_token_provider import OAuthTokenProvider
34
from office365.runtime.auth.saml_token_provider import SamlTokenProvider
45

56

@@ -17,15 +18,20 @@ def acquire_token_for_user(self, username, password):
1718
return self.provider.acquire_token()
1819

1920
def acquire_token_for_app(self, client_id, client_secret):
20-
"""Acquire token via client credentials"""
21+
"""Acquire token via client credentials (SharePoint App Principal)"""
2122
self.provider = ACSTokenProvider(self.url, client_id, client_secret)
2223
return self.provider.acquire_token()
2324

25+
def acquire_token_password_grant(self, client_id, client_secret, user_name, password):
26+
"""Acquire token via resource owner password credential (ROPC) grant"""
27+
self.provider = OAuthTokenProvider(self.url, client_id, client_secret, user_name, password)
28+
return self.provider.acquire_token()
29+
2430
def authenticate_request(self, request_options):
2531
"""Authenticate request"""
2632
if isinstance(self.provider, SamlTokenProvider):
2733
request_options.set_header('Cookie', self.provider.get_authentication_cookie())
28-
elif isinstance(self.provider, ACSTokenProvider):
34+
elif isinstance(self.provider, ACSTokenProvider) or isinstance(self.provider, OAuthTokenProvider):
2935
request_options.set_header('Authorization', self.provider.get_authorization_header())
3036
else:
3137
raise ValueError('Unknown authentication provider')
Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,46 @@
1+
import requests
2+
13
from office365.runtime.auth.base_token_provider import BaseTokenProvider
24

35

46
class OAuthTokenProvider(BaseTokenProvider):
57
""" OAuth security Token Service for O365"""
68

9+
def __init__(self, tenant, client_id, client_secret, user_name, password):
10+
self.tenant = tenant
11+
self.ResourceId = "https://graph.microsoft.com/"
12+
self.AuthorityUrl = "https://login.microsoftonline.com/"
13+
self.TokenEndpoint = "/oauth2/token"
14+
self.error = None
15+
self.access_token = None
16+
self.client_id = client_id
17+
self.client_secret = client_secret
18+
self.user_name = user_name
19+
self.password = password
20+
self.scope = 'user.read openid profile offline_access https://graph.microsoft.com/Contacts.ReadWrite'
21+
722
def acquire_token(self):
8-
pass
23+
try:
24+
self.access_token = self.request_password_type()
25+
return True
26+
except requests.exceptions.RequestException as e:
27+
self.error = "Error: {}".format(e)
28+
return False
29+
30+
def get_authorization_header(self):
31+
return 'Bearer {0}'.format(self.access_token["access_token"])
32+
33+
def request_password_type(self):
34+
url = "https://login.microsoftonline.com/{0}/oauth2/v2.0/token".format(self.tenant)
35+
data = {
36+
'grant_type': 'password',
37+
'client_id': self.client_id,
38+
'client_secret': self.client_secret,
39+
'username': self.user_name,
40+
'password': self.password,
41+
'scope': self.scope
42+
}
43+
44+
response = requests.post(url=url, headers={'Content-Type': 'application/x-www-form-urlencoded'},
45+
data=data)
46+
return response.json()

tests/outlook_client_case.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
from unittest import TestCase
22
from examples.settings import settings
33
from office365.outlookservices.outlook_client import OutlookClient
4-
from office365.runtime.auth.network_credential_context import NetworkCredentialContext
4+
from office365.runtime.auth.authentication_context import AuthenticationContext
55

66

77
class OutlookClientTestCase(TestCase):
88
"""SharePoint specific test case base class"""
99

1010
@classmethod
1111
def setUpClass(cls):
12-
ctx_auth = NetworkCredentialContext(username=settings['user_credentials']['username'],
13-
password=settings['user_credentials']['password'])
12+
# Due to Outlook REST API v1.0 BasicAuth Deprecation
13+
# (refer https://developer.microsoft.com/en-us/office/blogs/outlook-rest-api-v1-0-basicauth-deprecation/)
14+
# NetworkCredentialContext class should be no longer utilized
15+
# ctx_auth = NetworkCredentialContext(username=settings['user_credentials']['username'],
16+
# password=settings['user_credentials']['password'])
17+
ctx_auth = AuthenticationContext(url=settings['tenant'])
18+
ctx_auth.acquire_token_password_grant(client_id=settings['client_credentials']['client_id'],
19+
client_secret=settings['client_credentials']['client_secret'],
20+
user_name=settings['user_credentials']['username'],
21+
password=settings['user_credentials']['password'])
1422
cls.client = OutlookClient(ctx_auth)

tests/test_outlook_calendar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def test3_update_event(self):
4040
self.client.execute_query()
4141
if len(results) == 1:
4242
event = results[0]
43-
self.assertIsNotNone(event.properties["Subject"])
43+
self.assertIsNotNone(event.properties["subject"])
4444
event.set_property("Subject", "Discuss the Calendar REST API (updated)")
4545
event.update()
4646
self.client.execute_query()

tests/test_outlook_contacts.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33

44
class TestOutlookContacts(OutlookClientTestCase):
5+
def test0_ensure_user_context(self):
6+
me = self.client.me
7+
self.client.load(me)
8+
self.client.execute_query()
9+
self.assertIsNotNone(me.properties['userPrincipalName'])
510

611
def test1_create_contacts(self):
712
contact_info = {
@@ -20,7 +25,7 @@ def test1_create_contacts(self):
2025

2126
contact = self.client.me.contacts.add_from_json(contact_info)
2227
self.client.execute_query()
23-
self.assertIsNotNone(contact.properties["GivenName"])
28+
self.assertIsNotNone(contact.properties["givenName"])
2429

2530
def test2_get_contacts(self):
2631
contacts = self.client.me.contacts
@@ -35,7 +40,7 @@ def test3_update_contact(self):
3540
if len(results) == 1:
3641
contact = results[0]
3742
self.assertIsNotNone(contact.url)
38-
contact.set_property("Department", "Media")
43+
contact.set_property("department", "Media")
3944
contact.update()
4045
self.client.execute_query()
4146

0 commit comments

Comments
 (0)