Skip to content

Commit 00f5e53

Browse files
committed
Initial
0 parents  commit 00f5e53

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1371
-0
lines changed

Diff for: .gitignore

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
27+
# PyInstaller
28+
# Usually these files are written by a python script from a template
29+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
30+
*.manifest
31+
*.spec
32+
33+
# Installer logs
34+
pip-log.txt
35+
pip-delete-this-directory.txt
36+
37+
# Unit test / coverage reports
38+
htmlcov/
39+
.tox/
40+
.coverage
41+
.coverage.*
42+
.cache
43+
nosetests.xml
44+
coverage.xml
45+
*,cover
46+
.hypothesis/
47+
48+
# Translations
49+
*.mo
50+
*.pot
51+
52+
# Django stuff:
53+
*.log
54+
55+
# Sphinx documentation
56+
docs/_build/
57+
58+
# PyBuilder
59+
target/
60+
61+
#Ipython Notebook
62+
.ipynb_checkpoints

Diff for: .vs/SharePointOnline-REST-Python-Client/v14/.suo

47.5 KB
Binary file not shown.

Diff for: LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Vadim Gremyachev
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Diff for: README.md

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# About
2+
This is a SharePoint Online REST API client for Python
3+
4+
5+
# Installation
6+
7+
Todo
8+
9+
10+
# Usage
11+
12+
The first example demonstrates how to read Web client object
13+
14+
```
15+
ctxAuth = AuthenticationContext(url)
16+
if ctxAuth.acquireTokenForUser(username, password):
17+
request = ClientRequest(url,ctxAuth)
18+
requestUrl = "/_api/web/" #Web resource endpoint
19+
data = request.executeQuery(requestUrl=requestUrl)
20+
21+
webTitle = data['d']['Title']
22+
print "Web title: {0}".format(webTitle)
23+
24+
else:
25+
print ctxAuth.getLastErrorMessage()
26+
```
27+
28+
# Python Version
29+
Python 2.7 is fully supported.
30+
31+
32+
# Third Party Libraries and Dependencies
33+
The following libraries will be installed when you install the client library:
34+
* [requests](https://github.com/kennethreitz/requests)
35+
36+
37+
38+

Diff for: client/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

Diff for: client/auth/SAML.xml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
2+
<s:Header>
3+
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
4+
<a:ReplyTo>
5+
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
6+
</a:ReplyTo>
7+
<a:To s:mustUnderstand="1">https://login.microsoftonline.com/extSTS.srf</a:To>
8+
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
9+
<o:UsernameToken>
10+
<o:Username>[username]</o:Username>
11+
<o:Password>[password]</o:Password>
12+
</o:UsernameToken>
13+
</o:Security>
14+
</s:Header>
15+
<s:Body>
16+
<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
17+
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
18+
<a:EndpointReference>
19+
<a:Address>[endpoint]</a:Address>
20+
</a:EndpointReference>
21+
</wsp:AppliesTo>
22+
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
23+
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
24+
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
25+
</t:RequestSecurityToken>
26+
</s:Body>
27+
</s:Envelope>

Diff for: client/auth/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

Diff for: client/auth/authentication_context.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import os
2+
from xml.etree import ElementTree
3+
4+
import requests
5+
import requests.utils
6+
import urlparse
7+
8+
9+
class AuthenticationContext(object):
10+
"""SharePoint Online Authentication Context"""
11+
12+
def __init__(self, url):
13+
14+
self.url = url
15+
16+
# External Security Token Service for SPO
17+
self.sts = {
18+
'host': 'login.microsoftonline.com',
19+
'path': '/extSTS.srf'
20+
}
21+
22+
# Sign in page url
23+
self.login = '/_forms/default.aspx?wa=wsignin1.0'
24+
25+
# Last occurred error
26+
self.error = ''
27+
28+
def acquire_token_for_user(self, username, password):
29+
"""Acquire user token"""
30+
try:
31+
url = urlparse.urlparse(self.url)
32+
options = {
33+
'username': username,
34+
'password': password,
35+
'sts': self.sts,
36+
'endpoint': url.scheme + '://' + url.hostname + self.login
37+
}
38+
39+
if self.acquire_service_token(options) and self.acquire_authentication_cookie(options):
40+
return True
41+
return False
42+
except requests.exceptions.RequestException as e:
43+
self.error = "Error: {}".format(e)
44+
return False
45+
46+
def get_authentication_cookie(self):
47+
"""Generate Auth Cookie"""
48+
return 'FedAuth=' + self.FedAuth + '; rtFa=' + self.rtFa
49+
50+
def get_last_error(self):
51+
return self.error
52+
53+
def acquire_service_token(self, options):
54+
"""Retrieve service token"""
55+
samlMessage = self.prepare_saml_message({
56+
'username': options['username'],
57+
'password': options['password'],
58+
'endpoint': self.url
59+
})
60+
61+
stsUrl = 'https://' + options['sts']['host'] + options['sts']['path']
62+
response = requests.post(stsUrl, data=samlMessage)
63+
token = self.process_service_token_response(response)
64+
if (token):
65+
self.token = token
66+
return True
67+
return False
68+
69+
def process_service_token_response(self, response):
70+
xml = ElementTree.fromstring(response.content)
71+
nsPrefixes = {'S': '{http://www.w3.org/2003/05/soap-envelope}',
72+
'psf': '{http://schemas.microsoft.com/Passport/SoapServices/SOAPFault}',
73+
'wst': '{http://schemas.xmlsoap.org/ws/2005/02/trust}',
74+
'wsse': '{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}'}
75+
76+
# check for errors
77+
if xml.find('{0}Body/{0}Fault'.format(nsPrefixes['S'])) is not None:
78+
error = xml.find('{0}Body/{0}Fault/{0}Detail/{1}error/{1}internalerror/{1}text'.format(nsPrefixes['S'],
79+
nsPrefixes['psf']))
80+
self.error = 'An error occurred while retrieving token: {0}'.format(error.text)
81+
return None
82+
83+
# extract token
84+
token = xml.find(
85+
'{0}Body/{1}RequestSecurityTokenResponse/{1}RequestedSecurityToken/{2}BinarySecurityToken'.format(
86+
nsPrefixes['S'], nsPrefixes['wst'], nsPrefixes['wsse']))
87+
return token.text
88+
89+
def acquire_authentication_cookie(self, options):
90+
"""Retrieve SPO auth cookie"""
91+
url = options['endpoint']
92+
93+
session = requests.session()
94+
response = session.post(url, data=self.token)
95+
cookies = requests.utils.dict_from_cookiejar(session.cookies);
96+
if 'FedAuth' in cookies and 'rtFa' in cookies:
97+
self.FedAuth = cookies['FedAuth']
98+
self.rtFa = cookies['rtFa']
99+
return True
100+
self.error = "An error occurred while retrieving auth cookies"
101+
return False
102+
103+
@staticmethod
104+
def prepare_saml_message(params):
105+
"""Read & prepare SAML envelope"""
106+
f = open(os.path.join(os.path.dirname(__file__), 'SAML.xml'))
107+
saml = f.read()
108+
for key in params:
109+
saml = saml.replace('[' + key + ']', params[key]);
110+
return saml

Diff for: client/client_context.py

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from site import Site
2+
from web import Web
3+
from client.client_object import ClientObject
4+
from client.runtime.client_action_type import ClientActionType
5+
from client.runtime.client_query import ClientQuery
6+
from client.runtime.client_request import ClientRequest
7+
8+
9+
class ClientContext(object):
10+
"""SharePoint client context"""
11+
12+
def __init__(self, url, auth_context):
13+
self.__base_url = url
14+
self.__auth_context = auth_context
15+
self.__web = None
16+
self.__site = None
17+
self.__pending_request = None
18+
self.__queries = []
19+
20+
@property
21+
def web(self):
22+
"""Get Web client object"""
23+
if not self.__web:
24+
self.__web = Web(self)
25+
return self.__web
26+
27+
@property
28+
def site(self):
29+
"""Get Site client object"""
30+
if not self.__site:
31+
self.__site = Site(self)
32+
return self.__site
33+
34+
@property
35+
def pending_request(self):
36+
if not self.__pending_request:
37+
self.__pending_request = ClientRequest(self.__base_url, self.__auth_context)
38+
return self.__pending_request
39+
40+
def load(self, client_object):
41+
"""Prepare query for the server"""
42+
qry = ClientQuery(client_object.url, ClientActionType.Read)
43+
qry.add_result_object(client_object)
44+
self.add_query(qry)
45+
46+
def execute_query(self):
47+
"""Submit pending request to the server"""
48+
for qry in self.__queries:
49+
data = self.pending_request.execute_query(qry)
50+
if any(data):
51+
if 'results' in data['d']:
52+
for item in data['d']['results']:
53+
clientObject = ClientObject.create_typed_object(self, item)
54+
qry.result_object.add_child(clientObject)
55+
else:
56+
qry.result_object.properties = data['d']
57+
self.__queries.remove(qry)
58+
59+
def add_query(self, query):
60+
self.__queries.append(query)
61+
62+
@property
63+
def url(self):
64+
"""Get base url"""
65+
return self.__base_url

0 commit comments

Comments
 (0)