Skip to content

Commit 4d22033

Browse files
committed
Introduced support for SharePoint authentication via OAuth (app principal)
1 parent c537e83 commit 4d22033

File tree

4 files changed

+124
-3
lines changed

4 files changed

+124
-3
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import json
2+
3+
from office365.runtime.auth.authentication_context import AuthenticationContext
4+
from office365.runtime.client_request import ClientRequest
5+
from office365.runtime.utilities.request_options import RequestOptions
6+
from office365.sharepoint.client_context import ClientContext
7+
8+
app_settings = {
9+
'url': 'https://contoso.sharepoint.com/',
10+
'client_id': '8efc226b-ba3b-4def-a195-4acdb8d20ca9',
11+
'client_secret': '',
12+
'redirect_url': 'https://github.com/vgrem/Office365-REST-Python-Client/'
13+
}
14+
15+
16+
if __name__ == '__main__':
17+
context_auth = AuthenticationContext(url=app_settings['url'])
18+
if context_auth.acquire_token_for_app(client_id=app_settings['client_id'], client_secret=app_settings['client_secret']):
19+
"""Read Web client object"""
20+
ctx = ClientContext(app_settings['url'], context_auth)
21+
22+
request = ClientRequest(ctx)
23+
options = RequestOptions("{0}/_api/web/".format(app_settings['url']))
24+
options.set_header('Accept', 'application/json')
25+
options.set_header('Content-Type', 'application/json')
26+
data = ctx.execute_request_direct(options)
27+
s = json.loads(data.content)
28+
web_title = s['Title']
29+
print("Web title: {0}".format(web_title))
30+
31+
else:
32+
print(context_auth.get_last_error())
33+
34+
35+
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import requests
2+
3+
import office365.logger
4+
from office365.runtime.auth.base_token_provider import BaseTokenProvider
5+
6+
7+
class ACSTokenProvider(BaseTokenProvider, office365.logger.LoggerContext):
8+
""" Provider to acquire the access token from a Microsoft Azure Access Control Service (ACS)"""
9+
10+
def __init__(self, url, client_id, client_secret):
11+
self.url = url
12+
self.client_id = client_id
13+
self.client_secret = client_secret
14+
self.redirect_url = None
15+
self.access_token = None
16+
self.error = None
17+
self.SharePointPrincipal = "00000003-0000-0ff1-ce00-000000000000"
18+
19+
def acquire_token(self):
20+
try:
21+
realm = self.get_realm_from_target_url()
22+
try:
23+
from urlparse import urlparse # Python 2.X
24+
except ImportError:
25+
from urllib.parse import urlparse # Python 3+
26+
url_info = urlparse(self.url)
27+
self.access_token = self.get_app_only_access_token(url_info.hostname, realm)
28+
return True
29+
except requests.exceptions.RequestException as e:
30+
self.error = "Error: {}".format(e)
31+
return False
32+
33+
def get_realm_from_target_url(self):
34+
response = requests.head(url=self.url, headers={'Authorization': 'Bearer'})
35+
return self.process_realm_response(response)
36+
37+
def get_app_only_access_token(self, target_host, target_realm):
38+
resource = self.get_formatted_principal(self.SharePointPrincipal, target_host, target_realm)
39+
client_id = self.get_formatted_principal(self.client_id, None, target_realm)
40+
sts_url = self.get_security_token_service_url(target_realm)
41+
oauth2_request = self.create_access_token_request(client_id, self.client_secret, resource)
42+
response = requests.post(url=sts_url, headers={'Content-Type': 'application/x-www-form-urlencoded'}, data=oauth2_request)
43+
return response.json()
44+
45+
@staticmethod
46+
def process_realm_response(response):
47+
header_key = "WWW-Authenticate"
48+
if header_key in response.headers:
49+
auth_values = response.headers[header_key].split(",")
50+
bearer = auth_values[0].split("=")
51+
return bearer[1].replace('"', '')
52+
return None
53+
54+
@staticmethod
55+
def get_formatted_principal(principal_name, host_name, realm):
56+
if host_name:
57+
return "{0}/{1}@{2}".format(principal_name, host_name, realm)
58+
return "{0}@{1}".format(principal_name, realm)
59+
60+
@staticmethod
61+
def get_security_token_service_url(realm):
62+
return "https://accounts.accesscontrol.windows.net/{0}/tokens/OAuth/2".format(realm)
63+
64+
@staticmethod
65+
def create_access_token_request(client_id, client_secret, scope):
66+
data = {
67+
'grant_type': 'client_credentials',
68+
'client_id': client_id,
69+
'client_secret': client_secret,
70+
'scope': scope,
71+
'resource': scope
72+
}
73+
return data
74+
75+
def get_authorization_header(self):
76+
return 'Bearer {0}'.format(self.access_token["access_token"])

office365/runtime/auth/authentication_context.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from office365.runtime.auth.acs_token_provider import ACSTokenProvider
12
from office365.runtime.auth.base_authentication_context import BaseAuthenticationContext
23
from office365.runtime.auth.saml_token_provider import SamlTokenProvider
34

@@ -11,13 +12,23 @@ def __init__(self, url):
1112
self.provider = None
1213

1314
def acquire_token_for_user(self, username, password):
14-
"""Acquire user token"""
15+
"""Acquire token via user credentials"""
1516
self.provider = SamlTokenProvider(self.url, username, password)
1617
return self.provider.acquire_token()
1718

19+
def acquire_token_for_app(self, client_id, client_secret):
20+
"""Acquire token via client credentials"""
21+
self.provider = ACSTokenProvider(self.url, client_id, client_secret)
22+
return self.provider.acquire_token()
23+
1824
def authenticate_request(self, request_options):
1925
"""Authenticate request"""
20-
request_options.set_header('Cookie', self.provider.get_authentication_cookie())
26+
if isinstance(self.provider, SamlTokenProvider):
27+
request_options.set_header('Cookie', self.provider.get_authentication_cookie())
28+
elif isinstance(self.provider, ACSTokenProvider):
29+
request_options.set_header('Authorization', self.provider.get_authorization_header())
30+
else:
31+
raise ValueError('Unknown authentication provider')
2132

2233
def get_auth_url(self, redirect_url):
2334
return ""

office365/runtime/auth/saml_token_provider.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import os
2-
import sys
32
from xml.etree import ElementTree
43

54
import requests

0 commit comments

Comments
 (0)