diff --git a/examples/sharepoint/connect_with_client_certificate.py b/examples/sharepoint/connect_with_client_certificate.py index 60893781..3f3d5f41 100644 --- a/examples/sharepoint/connect_with_client_certificate.py +++ b/examples/sharepoint/connect_with_client_certificate.py @@ -1,14 +1,14 @@ import os from office365.sharepoint.client_context import ClientContext -from tests import test_site_url, test_tenant +from tests import test_tenant, test_team_site_url cert_settings = { 'client_id': '51d03106-4726-442c-86db-70b32fa7547f', - 'thumbprint': "61C754D8D9629BE91972B6A0C1999DC678FB0145", + 'thumbprint': "78CA7402E8A2508A9772CB1B2E085945147D8050", 'cert_path': '{0}/selfsigncert.pem'.format(os.path.dirname(__file__)), - 'scopes': ['{0}.default'.format(test_site_url)] + #'scopes': ['{0}.default'.format(test_site_url)] } -ctx = ClientContext(test_site_url).with_client_certificate(test_tenant, **cert_settings) +ctx = ClientContext(test_team_site_url).with_client_certificate(test_tenant, **cert_settings) current_web = ctx.web.get().execute_query() print("{0}".format(current_web.url)) diff --git a/office365/runtime/auth/authentication_context.py b/office365/runtime/auth/authentication_context.py index b6cf0d09..526d16ae 100644 --- a/office365/runtime/auth/authentication_context.py +++ b/office365/runtime/auth/authentication_context.py @@ -4,17 +4,18 @@ from office365.runtime.auth.providers.saml_token_provider import SamlTokenProvider from office365.runtime.auth.token_response import TokenResponse from office365.runtime.auth.user_credential import UserCredential +from office365.runtime.compat import get_absolute_url class AuthenticationContext(object): - def __init__(self, authority_url): + def __init__(self, url): """ Authentication context for SharePoint Online/OneDrive For Business - :param str authority_url: authority url + :param str url: Absolute Web or Site Url """ - self.authority_url = authority_url + self.url = url.rstrip("/") self._provider = None 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 def _acquire_token_for_client_certificate(): authority_url = 'https://login.microsoftonline.com/{0}'.format(tenant) credentials = {"thumbprint": thumbprint, "private_key": open(cert_path).read()} - scopes = kwargs.get('scopes', ["{url}/.default".format(url=self.authority_url)]) + resource = get_absolute_url(self.url) + scopes = kwargs.get('scopes', ["{url}/.default".format(url=resource)]) import msal app = msal.ConfidentialClientApplication( client_id, @@ -40,25 +42,23 @@ def _acquire_token_for_client_certificate(): result = app.acquire_token_for_client(scopes) return TokenResponse.from_json(result) - self.register_provider(_acquire_token_for_client_certificate) + self.with_access_token(_acquire_token_for_client_certificate) return self - def register_provider(self, credentials_or_token_func, **kwargs): - if callable(credentials_or_token_func): - self._provider = OAuthTokenProvider(credentials_or_token_func) - elif isinstance(credentials_or_token_func, ClientCredential): - self._provider = ACSTokenProvider(self.authority_url, credentials_or_token_func.clientId, - credentials_or_token_func.clientSecret) - elif isinstance(credentials_or_token_func, UserCredential): + def with_access_token(self, token_func): + self._provider = OAuthTokenProvider(token_func) + + def with_credentials(self, credentials, **kwargs): + if isinstance(credentials, ClientCredential): + self._provider = ACSTokenProvider(self.url, credentials.clientId, credentials.clientSecret) + elif isinstance(credentials, UserCredential): allow_ntlm = kwargs.get('allow_ntlm', False) if allow_ntlm: from office365.runtime.auth.providers.ntlm_provider import NtlmProvider - self._provider = NtlmProvider(credentials_or_token_func.userName, - credentials_or_token_func.password) + self._provider = NtlmProvider(credentials.userName, credentials.password) else: browser_mode = kwargs.get('browser_mode', False) - self._provider = SamlTokenProvider(self.authority_url, credentials_or_token_func.userName, - credentials_or_token_func.password, browser_mode) + self._provider = SamlTokenProvider(self.url, credentials.userName, credentials.password, browser_mode) else: raise ValueError("Unknown credential type") @@ -70,7 +70,7 @@ def acquire_token_for_user(self, username, password, browser_mode=False): :type username: str :type browser_mode: str """ - self._provider = SamlTokenProvider(url=self.authority_url, username=username, password=password, + self._provider = SamlTokenProvider(url=self.url, username=username, password=password, browser_mode=browser_mode) return self._provider.ensure_authentication_cookie() @@ -78,12 +78,13 @@ def acquire_token_for_app(self, client_id, client_secret): """Acquire token via client credentials (SharePoint App Principal) Status: deprecated! """ - self._provider = ACSTokenProvider(url=self.authority_url, client_id=client_id, client_secret=client_secret) + self._provider = ACSTokenProvider(url=self.url, client_id=client_id, client_secret=client_secret) return self._provider.ensure_app_only_access_token() def authenticate_request(self, request): """ Authenticate request + :type request: office365.runtime.http.request_options.RequestOptions """ self._provider.authenticate_request(request) diff --git a/office365/runtime/compat.py b/office365/runtime/compat.py index 8b9871b3..d02d56ad 100644 --- a/office365/runtime/compat.py +++ b/office365/runtime/compat.py @@ -19,14 +19,12 @@ from urlparse import urljoin import pytz as timezone from email import message_from_string as message_from_bytes_or_string - from __builtin__ import xrange as range_or_xrange elif is_py3: from urllib.parse import urlparse from urllib.parse import quote from urllib.parse import urljoin from datetime import timezone from email import message_from_bytes as message_from_bytes_or_string - from builtins import range as range_or_xrange def message_as_bytes_or_string(message): @@ -47,6 +45,11 @@ def is_absolute_url(url): return bool(urlparse(url).netloc) +def get_absolute_url(url): + path = urlparse(url).path + return url.replace(path, "") + + def parse_query_string(url, key): if is_py2: import urlparse diff --git a/office365/sharepoint/client_context.py b/office365/sharepoint/client_context.py index e36abac5..f560984d 100644 --- a/office365/sharepoint/client_context.py +++ b/office365/sharepoint/client_context.py @@ -1,7 +1,6 @@ import copy from office365.runtime.auth.authentication_context import AuthenticationContext -from office365.runtime.auth.providers.saml_token_provider import resolve_base_url from office365.runtime.auth.user_credential import UserCredential from office365.runtime.client_result import ClientResult from office365.runtime.client_runtime_context import ClientRuntimeContext @@ -20,7 +19,7 @@ from office365.sharepoint.tenant.administration.hub_site_collection import HubSiteCollection from office365.sharepoint.webs.context_web_information import ContextWebInformation from office365.sharepoint.webs.web import Web -from office365.runtime.compat import urlparse, is_absolute_url +from office365.runtime.compat import urlparse, is_absolute_url, get_absolute_url class ClientContext(ClientRuntimeContext): @@ -33,16 +32,12 @@ def __init__(self, base_url, auth_context=None): :param str base_url: Absolute Web or Site Url :param AuthenticationContext or None auth_context: Authentication context """ - if base_url.endswith("/"): - base_url = base_url[:len(base_url) - 1] - if auth_context is None: - self._auth_context = AuthenticationContext(authority_url=base_url) - else: - self._auth_context = auth_context super(ClientContext, self).__init__() + if auth_context is None: + auth_context = AuthenticationContext(url=base_url) + self._auth_context = auth_context self._web = None self._site = None - self._base_url = base_url self._ctx_web_info = None self._pending_request = None @@ -62,23 +57,23 @@ def create_safe_url(self, orig_url, relative=True): return url if relative else "".join([root_site_url, url]) @staticmethod - def from_url(abs_url): + def from_url(full_url): """ Constructs ClientContext from absolute Url - :param str abs_url: Absolute Url to resource + :param str full_url: Full Url to a resource :return: ClientContext """ - base_url = resolve_base_url(abs_url) - ctx = ClientContext(base_url) - result = Web.get_web_url_from_page_url(ctx, abs_url) + root_site_url = get_absolute_url(full_url) + ctx = ClientContext(root_site_url) + result = Web.get_web_url_from_page_url(ctx, full_url) def _init_context_for_web(resp): """ :type resp: requests.Response """ resp.raise_for_status() - ctx._base_url = result.value + ctx._auth_context.url = result.value ctx.after_execute(_init_context_for_web) return ctx @@ -100,7 +95,7 @@ def with_access_token(self, token_func): """ :type token_func: () -> TokenResponse """ - self.authentication_context.register_provider(token_func) + self.authentication_context.with_access_token(token_func) return self 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 :type allow_ntlm: bool :type browser_mode: bool """ - self.authentication_context.register_provider( + self.authentication_context.with_credentials( UserCredential(username, password), allow_ntlm=allow_ntlm, browser_mode=browser_mode) @@ -124,7 +119,7 @@ def with_credentials(self, credentials): :type credentials: UserCredential or ClientCredential """ - self.authentication_context.register_provider(credentials) + self.authentication_context.with_credentials(credentials) return self def execute_batch(self, items_per_batch=100): @@ -232,14 +227,14 @@ def clone(self, url, clear_queries=True): :return ClientContext """ ctx = copy.deepcopy(self) - ctx._base_url = url + ctx._auth_context.url = url ctx._ctx_web_info = None if clear_queries: ctx.clear() return ctx def authenticate_request(self, request): - self._auth_context.authenticate_request(request) + self.authentication_context.authenticate_request(request) def _build_modification_query(self, request): """ @@ -372,7 +367,7 @@ def search(self): @property def base_url(self): """Represents absolute Web or Site Url""" - return self._base_url + return self.authentication_context.url @property def authentication_context(self): diff --git a/tests/sharepoint/test_client.py b/tests/sharepoint/test_client.py index 23b3261e..c6804712 100644 --- a/tests/sharepoint/test_client.py +++ b/tests/sharepoint/test_client.py @@ -23,7 +23,7 @@ def test1_connect_with_app_principal(self): self.assertIsInstance(ctx.authentication_context._provider, ACSTokenProvider) def test2_connect_with_app_principal_alt(self): - context_auth = AuthenticationContext(authority_url=test_site_url) + context_auth = AuthenticationContext(url=test_site_url) context_auth.acquire_token_for_app(client_id=settings.get('client_credentials', 'client_id'), client_secret=settings.get('client_credentials', 'client_secret')) ctx = ClientContext(test_site_url, context_auth) @@ -34,7 +34,8 @@ def test4_connect_with_user_credentials(self): self.assertIsInstance(ctx.authentication_context._provider, SamlTokenProvider) def test5_init_from_url(self): - ctx = ClientContext.from_url(test_site_url).with_credentials(test_user_credentials) + page_url = "{site_url}/SitePages/Home.aspx".format(site_url=test_team_site_url) + ctx = ClientContext.from_url(page_url).with_credentials(test_user_credentials) web = ctx.web.get().execute_query() self.assertIsNotNone(web.url)