Skip to content

Commit 45fa370

Browse files
committed
Certificate auth flow: fix for passing valid scope vgrem#541, AuthenticationContext enhancements
1 parent 8daf2df commit 45fa370

File tree

5 files changed

+47
-47
lines changed

5 files changed

+47
-47
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import os
22
from office365.sharepoint.client_context import ClientContext
3-
from tests import test_site_url, test_tenant
3+
from tests import test_tenant, test_team_site_url
44

55
cert_settings = {
66
'client_id': '51d03106-4726-442c-86db-70b32fa7547f',
7-
'thumbprint': "61C754D8D9629BE91972B6A0C1999DC678FB0145",
7+
'thumbprint': "78CA7402E8A2508A9772CB1B2E085945147D8050",
88
'cert_path': '{0}/selfsigncert.pem'.format(os.path.dirname(__file__)),
9-
'scopes': ['{0}.default'.format(test_site_url)]
9+
#'scopes': ['{0}.default'.format(test_site_url)]
1010
}
1111

12-
ctx = ClientContext(test_site_url).with_client_certificate(test_tenant, **cert_settings)
12+
ctx = ClientContext(test_team_site_url).with_client_certificate(test_tenant, **cert_settings)
1313
current_web = ctx.web.get().execute_query()
1414
print("{0}".format(current_web.url))

office365/runtime/auth/authentication_context.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
from office365.runtime.auth.providers.saml_token_provider import SamlTokenProvider
55
from office365.runtime.auth.token_response import TokenResponse
66
from office365.runtime.auth.user_credential import UserCredential
7+
from office365.runtime.compat import get_absolute_url
78

89

910
class AuthenticationContext(object):
1011

11-
def __init__(self, authority_url):
12+
def __init__(self, url):
1213
"""
1314
Authentication context for SharePoint Online/OneDrive For Business
1415
15-
:param str authority_url: authority url
16+
:param str url: Absolute Web or Site Url
1617
"""
17-
self.authority_url = authority_url
18+
self.url = url.rstrip("/")
1819
self._provider = None
1920

2021
def with_client_certificate(self, tenant, client_id, thumbprint, cert_path, **kwargs):
@@ -30,7 +31,8 @@ def with_client_certificate(self, tenant, client_id, thumbprint, cert_path, **kw
3031
def _acquire_token_for_client_certificate():
3132
authority_url = 'https://login.microsoftonline.com/{0}'.format(tenant)
3233
credentials = {"thumbprint": thumbprint, "private_key": open(cert_path).read()}
33-
scopes = kwargs.get('scopes', ["{url}/.default".format(url=self.authority_url)])
34+
resource = get_absolute_url(self.url)
35+
scopes = kwargs.get('scopes', ["{url}/.default".format(url=resource)])
3436
import msal
3537
app = msal.ConfidentialClientApplication(
3638
client_id,
@@ -40,25 +42,23 @@ def _acquire_token_for_client_certificate():
4042
result = app.acquire_token_for_client(scopes)
4143
return TokenResponse.from_json(result)
4244

43-
self.register_provider(_acquire_token_for_client_certificate)
45+
self.with_access_token(_acquire_token_for_client_certificate)
4446
return self
4547

46-
def register_provider(self, credentials_or_token_func, **kwargs):
47-
if callable(credentials_or_token_func):
48-
self._provider = OAuthTokenProvider(credentials_or_token_func)
49-
elif isinstance(credentials_or_token_func, ClientCredential):
50-
self._provider = ACSTokenProvider(self.authority_url, credentials_or_token_func.clientId,
51-
credentials_or_token_func.clientSecret)
52-
elif isinstance(credentials_or_token_func, UserCredential):
48+
def with_access_token(self, token_func):
49+
self._provider = OAuthTokenProvider(token_func)
50+
51+
def with_credentials(self, credentials, **kwargs):
52+
if isinstance(credentials, ClientCredential):
53+
self._provider = ACSTokenProvider(self.url, credentials.clientId, credentials.clientSecret)
54+
elif isinstance(credentials, UserCredential):
5355
allow_ntlm = kwargs.get('allow_ntlm', False)
5456
if allow_ntlm:
5557
from office365.runtime.auth.providers.ntlm_provider import NtlmProvider
56-
self._provider = NtlmProvider(credentials_or_token_func.userName,
57-
credentials_or_token_func.password)
58+
self._provider = NtlmProvider(credentials.userName, credentials.password)
5859
else:
5960
browser_mode = kwargs.get('browser_mode', False)
60-
self._provider = SamlTokenProvider(self.authority_url, credentials_or_token_func.userName,
61-
credentials_or_token_func.password, browser_mode)
61+
self._provider = SamlTokenProvider(self.url, credentials.userName, credentials.password, browser_mode)
6262
else:
6363
raise ValueError("Unknown credential type")
6464

@@ -70,20 +70,21 @@ def acquire_token_for_user(self, username, password, browser_mode=False):
7070
:type username: str
7171
:type browser_mode: str
7272
"""
73-
self._provider = SamlTokenProvider(url=self.authority_url, username=username, password=password,
73+
self._provider = SamlTokenProvider(url=self.url, username=username, password=password,
7474
browser_mode=browser_mode)
7575
return self._provider.ensure_authentication_cookie()
7676

7777
def acquire_token_for_app(self, client_id, client_secret):
7878
"""Acquire token via client credentials (SharePoint App Principal)
7979
Status: deprecated!
8080
"""
81-
self._provider = ACSTokenProvider(url=self.authority_url, client_id=client_id, client_secret=client_secret)
81+
self._provider = ACSTokenProvider(url=self.url, client_id=client_id, client_secret=client_secret)
8282
return self._provider.ensure_app_only_access_token()
8383

8484
def authenticate_request(self, request):
8585
"""
8686
Authenticate request
87+
8788
:type request: office365.runtime.http.request_options.RequestOptions
8889
"""
8990
self._provider.authenticate_request(request)

office365/runtime/compat.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,12 @@
1919
from urlparse import urljoin
2020
import pytz as timezone
2121
from email import message_from_string as message_from_bytes_or_string
22-
from __builtin__ import xrange as range_or_xrange
2322
elif is_py3:
2423
from urllib.parse import urlparse
2524
from urllib.parse import quote
2625
from urllib.parse import urljoin
2726
from datetime import timezone
2827
from email import message_from_bytes as message_from_bytes_or_string
29-
from builtins import range as range_or_xrange
3028

3129

3230
def message_as_bytes_or_string(message):
@@ -47,6 +45,11 @@ def is_absolute_url(url):
4745
return bool(urlparse(url).netloc)
4846

4947

48+
def get_absolute_url(url):
49+
path = urlparse(url).path
50+
return url.replace(path, "")
51+
52+
5053
def parse_query_string(url, key):
5154
if is_py2:
5255
import urlparse

office365/sharepoint/client_context.py

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import copy
22

33
from office365.runtime.auth.authentication_context import AuthenticationContext
4-
from office365.runtime.auth.providers.saml_token_provider import resolve_base_url
54
from office365.runtime.auth.user_credential import UserCredential
65
from office365.runtime.client_result import ClientResult
76
from office365.runtime.client_runtime_context import ClientRuntimeContext
@@ -20,7 +19,7 @@
2019
from office365.sharepoint.tenant.administration.hub_site_collection import HubSiteCollection
2120
from office365.sharepoint.webs.context_web_information import ContextWebInformation
2221
from office365.sharepoint.webs.web import Web
23-
from office365.runtime.compat import urlparse, is_absolute_url
22+
from office365.runtime.compat import urlparse, is_absolute_url, get_absolute_url
2423

2524

2625
class ClientContext(ClientRuntimeContext):
@@ -33,16 +32,12 @@ def __init__(self, base_url, auth_context=None):
3332
:param str base_url: Absolute Web or Site Url
3433
:param AuthenticationContext or None auth_context: Authentication context
3534
"""
36-
if base_url.endswith("/"):
37-
base_url = base_url[:len(base_url) - 1]
38-
if auth_context is None:
39-
self._auth_context = AuthenticationContext(authority_url=base_url)
40-
else:
41-
self._auth_context = auth_context
4235
super(ClientContext, self).__init__()
36+
if auth_context is None:
37+
auth_context = AuthenticationContext(url=base_url)
38+
self._auth_context = auth_context
4339
self._web = None
4440
self._site = None
45-
self._base_url = base_url
4641
self._ctx_web_info = None
4742
self._pending_request = None
4843

@@ -62,23 +57,23 @@ def create_safe_url(self, orig_url, relative=True):
6257
return url if relative else "".join([root_site_url, url])
6358

6459
@staticmethod
65-
def from_url(abs_url):
60+
def from_url(full_url):
6661
"""
6762
Constructs ClientContext from absolute Url
6863
69-
:param str abs_url: Absolute Url to resource
64+
:param str full_url: Full Url to a resource
7065
:return: ClientContext
7166
"""
72-
base_url = resolve_base_url(abs_url)
73-
ctx = ClientContext(base_url)
74-
result = Web.get_web_url_from_page_url(ctx, abs_url)
67+
root_site_url = get_absolute_url(full_url)
68+
ctx = ClientContext(root_site_url)
69+
result = Web.get_web_url_from_page_url(ctx, full_url)
7570

7671
def _init_context_for_web(resp):
7772
"""
7873
:type resp: requests.Response
7974
"""
8075
resp.raise_for_status()
81-
ctx._base_url = result.value
76+
ctx._auth_context.url = result.value
8277

8378
ctx.after_execute(_init_context_for_web)
8479
return ctx
@@ -100,7 +95,7 @@ def with_access_token(self, token_func):
10095
"""
10196
:type token_func: () -> TokenResponse
10297
"""
103-
self.authentication_context.register_provider(token_func)
98+
self.authentication_context.with_access_token(token_func)
10499
return self
105100

106101
def with_user_credentials(self, username, password, allow_ntlm=False, browser_mode=False):
@@ -112,7 +107,7 @@ def with_user_credentials(self, username, password, allow_ntlm=False, browser_mo
112107
:type allow_ntlm: bool
113108
:type browser_mode: bool
114109
"""
115-
self.authentication_context.register_provider(
110+
self.authentication_context.with_credentials(
116111
UserCredential(username, password),
117112
allow_ntlm=allow_ntlm,
118113
browser_mode=browser_mode)
@@ -124,7 +119,7 @@ def with_credentials(self, credentials):
124119
125120
:type credentials: UserCredential or ClientCredential
126121
"""
127-
self.authentication_context.register_provider(credentials)
122+
self.authentication_context.with_credentials(credentials)
128123
return self
129124

130125
def execute_batch(self, items_per_batch=100):
@@ -232,14 +227,14 @@ def clone(self, url, clear_queries=True):
232227
:return ClientContext
233228
"""
234229
ctx = copy.deepcopy(self)
235-
ctx._base_url = url
230+
ctx._auth_context.url = url
236231
ctx._ctx_web_info = None
237232
if clear_queries:
238233
ctx.clear()
239234
return ctx
240235

241236
def authenticate_request(self, request):
242-
self._auth_context.authenticate_request(request)
237+
self.authentication_context.authenticate_request(request)
243238

244239
def _build_modification_query(self, request):
245240
"""
@@ -372,7 +367,7 @@ def search(self):
372367
@property
373368
def base_url(self):
374369
"""Represents absolute Web or Site Url"""
375-
return self._base_url
370+
return self.authentication_context.url
376371

377372
@property
378373
def authentication_context(self):

tests/sharepoint/test_client.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test1_connect_with_app_principal(self):
2323
self.assertIsInstance(ctx.authentication_context._provider, ACSTokenProvider)
2424

2525
def test2_connect_with_app_principal_alt(self):
26-
context_auth = AuthenticationContext(authority_url=test_site_url)
26+
context_auth = AuthenticationContext(url=test_site_url)
2727
context_auth.acquire_token_for_app(client_id=settings.get('client_credentials', 'client_id'),
2828
client_secret=settings.get('client_credentials', 'client_secret'))
2929
ctx = ClientContext(test_site_url, context_auth)
@@ -34,7 +34,8 @@ def test4_connect_with_user_credentials(self):
3434
self.assertIsInstance(ctx.authentication_context._provider, SamlTokenProvider)
3535

3636
def test5_init_from_url(self):
37-
ctx = ClientContext.from_url(test_site_url).with_credentials(test_user_credentials)
37+
page_url = "{site_url}/SitePages/Home.aspx".format(site_url=test_team_site_url)
38+
ctx = ClientContext.from_url(page_url).with_credentials(test_user_credentials)
3839
web = ctx.web.get().execute_query()
3940
self.assertIsNotNone(web.url)
4041

0 commit comments

Comments
 (0)