Skip to content

Commit 02c8f99

Browse files
committed
send_mail and refresh_token methods
1 parent b7f3d79 commit 02c8f99

File tree

1 file changed

+115
-16
lines changed

1 file changed

+115
-16
lines changed

microsoftgraph/client.py

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import base64
2+
import mimetypes
13
import requests
24
from urllib.parse import urlencode, urlparse
35

@@ -18,12 +20,21 @@ def __init__(self, client_id, client_secret, api_version='v1.0', account_type='c
1820
self.base_url = self.RESOURCE + self.api_version + '/'
1921
self.token = None
2022

21-
def authorization_url(self, redirect_uri, scope):
23+
def authorization_url(self, redirect_uri, scope, state=None):
2224
"""
2325
2426
Args:
25-
redirect_uri:
26-
scope:
27+
redirect_uri: The redirect_uri of your app, where authentication responses can be sent and received by
28+
your app. It must exactly match one of the redirect_uris you registered in the app registration portal
29+
30+
scope: A list of the Microsoft Graph permissions that you want the user to consent to. This may also
31+
include OpenID scopes.
32+
33+
state: A value included in the request that will also be returned in the token response.
34+
It can be a string of any content that you wish. A randomly generated unique value is typically
35+
used for preventing cross-site request forgery attacks. The state is also used to encode information
36+
about the user's state in the app before the authentication request occurred, such as the page or view
37+
they were on.
2738
2839
Returns:
2940
@@ -32,17 +43,24 @@ def authorization_url(self, redirect_uri, scope):
3243
'client_id': self.client_id,
3344
'redirect_uri': redirect_uri,
3445
'scope': ' '.join(scope),
35-
'response_type': 'code'
46+
'response_type': 'code',
47+
'response_mode': 'query'
3648
}
49+
50+
if state:
51+
params['state'] = None
52+
3753
url = self.AUTHORITY_URL + self.account_type + self.AUTH_ENDPOINT + urlencode(params)
3854
return url
3955

40-
def exchange_code(self, redirect_uri, code, scope):
56+
def exchange_code(self, redirect_uri, code):
4157
"""Exchanges a code for a Token.
4258
4359
Args:
44-
redirect_uri: A string with the redirect_uri set in the app config.
45-
code: A string containing the code to exchange.
60+
redirect_uri: The redirect_uri of your app, where authentication responses can be sent and received by
61+
your app. It must exactly match one of the redirect_uris you registered in the app registration portal
62+
63+
code: The authorization_code that you acquired in the first leg of the flow.
4664
4765
Returns:
4866
A dict.
@@ -57,31 +75,112 @@ def exchange_code(self, redirect_uri, code, scope):
5775
}
5876
return requests.post(self.AUTHORITY_URL + self.account_type + self.TOKEN_ENDPOINT, data=params).json()
5977

78+
def refresh_token(self, redirect_uri, refresh_token):
79+
"""
80+
81+
Args:
82+
redirect_uri: The redirect_uri of your app, where authentication responses can be sent and received by
83+
your app. It must exactly match one of the redirect_uris you registered in the app registration portal
84+
85+
refresh_token: An OAuth 2.0 refresh token. Your app can use this token acquire additional access tokens
86+
after the current access token expires. Refresh tokens are long-lived, and can be used to retain access
87+
to resources for extended periods of time.
88+
89+
Returns:
90+
91+
"""
92+
params = {
93+
'client_id': self.client_id,
94+
'redirect_uri': redirect_uri,
95+
'client_secret': self.client_secret,
96+
'refresh_token': refresh_token,
97+
'grant_type': 'refresh_token',
98+
}
99+
return requests.post(self.AUTHORITY_URL + self.account_type + self.TOKEN_ENDPOINT, data=params).json()
100+
60101
def set_token(self, token):
61-
"""Sets the Access Token for its use in this library.
102+
"""Sets the Token for its use in this library.
62103
63104
Args:
64-
token: A string with the Access Token.
105+
token: A string with the Token.
65106
66107
"""
67108
self.token = token
68109

69-
def get_me(self):
70-
return self._get('me')
110+
def get_me(self, params=None):
111+
"""Retrieve the properties and relationships of user object.
112+
113+
Note: Getting a user returns a default set of properties only (businessPhones, displayName, givenName, id,
114+
jobTitle, mail, mobilePhone, officeLocation, preferredLanguage, surname, userPrincipalName).
115+
Use $select to get the other properties and relationships for the user object.
116+
117+
Args:
118+
params: A dict.
119+
120+
Returns:
121+
122+
123+
"""
124+
return self._get('me', params=None)
125+
126+
def send_mail(self, subject=None, recipients=None, body='', content_type='HTML', attachments=None):
127+
"""Helper to send email from current user.
128+
129+
Args:
130+
subject: email subject (required)
131+
recipients: list of recipient email addresses (required)
132+
body: body of the message
133+
content_type: content type (default is 'HTML')
134+
attachments: list of file attachments (local filenames)
135+
136+
Returns:
137+
Returns the response from the POST to the sendmail API.
138+
"""
139+
140+
# Verify that required arguments have been passed.
141+
if not all([subject, recipients]):
142+
raise ValueError('sendmail(): required arguments missing')
143+
144+
# Create recipient list in required format.
145+
recipient_list = [{'EmailAddress': {'Address': address}} for address in recipients]
146+
147+
# Create list of attachments in required format.
148+
attached_files = []
149+
if attachments:
150+
for filename in attachments:
151+
b64_content = base64.b64encode(open(filename, 'rb').read())
152+
mime_type = mimetypes.guess_type(filename)[0]
153+
mime_type = mime_type if mime_type else ''
154+
attached_files.append(
155+
{'@odata.type': '#microsoft.graph.fileAttachment', 'ContentBytes': b64_content.decode('utf-8'),
156+
'ContentType': mime_type, 'Name': filename})
157+
158+
# Create email message in required format.
159+
email_msg = {'Message': {'Subject': subject,
160+
'Body': {'ContentType': content_type, 'Content': body},
161+
'ToRecipients': recipient_list,
162+
'Attachments': attached_files},
163+
'SaveToSentItems': 'true'}
164+
165+
# Do a POST to Graph's sendMail API and return the response.
166+
return self._post('me/microsoft.graph.sendMail', data=email_msg)
167+
168+
def _get_headers(self):
169+
return {'Authorization': 'Bearer ' + self.token['access_token']}
71170

72171
def _get(self, endpoint, params=None):
73-
headers = {'Authorization': 'Bearer ' + self.token['access_token']}
74-
response = requests.get(self.base_url + endpoint, params=params, headers=headers)
172+
response = requests.get(self.base_url + endpoint, params=params, headers=self._get_headers())
75173
return self._parse(response)
76174

77175
def _post(self, endpoint, params=None, data=None):
78-
response = requests.post(self.base_url + endpoint, params=params, data=data)
176+
response = requests.post(self.base_url + endpoint, params=params, json=data, headers=self._get_headers())
79177
return self._parse(response)
80178

81179
def _delete(self, endpoint, params=None):
82180
response = requests.delete(self.base_url + endpoint, params=params)
83181
return self._parse(response)
84182

85183
def _parse(self, response):
86-
return response.json()
87-
184+
if 'application/json' in response.headers['Content-Type']:
185+
return response.json()
186+
return response.text

0 commit comments

Comments
 (0)