├── LICENSE ├── README.md ├── config.py ├── email.html ├── helpers.py ├── images ├── deviceflow.png ├── imports.png ├── registration1.png ├── registration2.png ├── running1.png ├── running2.png ├── running3.png ├── running4.png └── running5.png ├── requirements.txt └── sample.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Microsoft Graph 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python console application for Microsoft Graph 2 | 3 | ![language:Python](https://img.shields.io/badge/Language-Python-blue.svg?style=flat-square) ![license:MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square) 4 | 5 | This sample uses Microsoft Graph to read your user profile, download your profile photo, upload the photo to OneDrive, create a sharing link, and send an email on your behalf (to yourself by default). 6 | 7 | Authentication is handled via [device flow authentication](#device-flow-authentication), the recommended approach for console applications. If you're looking for examples of how to work with Microsoft Graph from Python _web applications_, see [Python authentication samples for Microsoft Graph](https://github.com/microsoftgraph/python-sample-auth). For a web app sample that does the same things as this console app sample, see [Sending mail via Microsoft Graph from Python](https://github.com/microsoftgraph/python-sample-send-mail). 8 | 9 | * [Installation](#installation) 10 | * [Running the sample](#running-the-sample) 11 | * [Device Flow authentication](#device-flow-authentication) 12 | * [Helper functions](#helper-functions) 13 | * [Contributing](#contributing) 14 | * [Resources](#resources) 15 | 16 | ## Installation 17 | 18 | Verify that you have the following prerequisites in place before installing the sample: 19 | 20 | * Install Python from [https://www.python.org/](https://www.python.org/). You'll need Python 3.6 or later, primarily because of the use of [f-strings](https://www.python.org/dev/peps/pep-0498/) &mdash change those to [format strings](https://docs.python.org/3/library/stdtypes.html#str.format) if you need to use an earlier Python 3.x version. If your code base is running under Python 2.7, you may find it helpful to use the [3to2](https://pypi.python.org/pypi/3to2) tools to port the code to Python 2.7. 21 | * The sample can be run on any operating system that supports Python 3.x, including recent versions of Windows, Linux, and Mac OS. In some cases, you may need to use a different command to launch Python — for example, some Linux distros reserve the command ```python``` for Python 2.7, so you need to use ```python3``` to launch an installed Python 3.x version. 22 | * This sample requires an [Office 365 for business account](https://msdn.microsoft.com/en-us/office/office365/howto/setup-development-environment#bk_Office365Account). 23 | * To register your application in the Azure Portal, you will need an Azure account associated with your Office 365 account. No charges will be incurred for registering your application in the Azure Portal, but you must have an account. If you don't have one, you can sign up for an [Azure Free Account](https://azure.microsoft.com/en-us/free/free-account-faq/). 24 | 25 | Follow these steps to install the sample code on your computer: 26 | 27 | 1. Clone the repo with this command: 28 | * ```git clone https://github.com/microsoftgraph/python-sample-console-app.git``` 29 | 30 | 2. Create and activate a virtual environment (optional). If you're new to Python virtual environments, [Miniconda](https://conda.io/miniconda.html) is a great place to start. 31 | 32 | 3. In the root folder of your cloned repo, install the dependencies for the sample as listed in the ```requirements.txt``` file with this command: ```pip install -r requirements.txt```. 33 | 34 | ## Application Registration 35 | 36 | To run the sample, you will need to register an application and add the registered application's ID to the configuration information in the [config.py](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py) file. Follow these steps to register and configure your application: 37 | 38 | 1. Navigate to the [Azure portal > App registrations](https://go.microsoft.com/fwlink/?linkid=2083908) to register your app. Sign in using a work or school account, or a personal Microsoft account. 39 | 40 | 2. Select **New registration**. 41 | 42 | 3. When the **Register an application page** appears, set the values as follows: 43 | 1. Set **Name** to `PythonConsoleApp`. 44 | 2. Set **Supported account types** to **Accounts in any organizational directory and personal Microsoft accounts**. 45 | 3. Leave **Redirect URI** empty. 46 | 4. Choose **Register**. 47 | 48 | 4. On the **PythonConsoleApp** overview page, copy and save the value for the **Application (client) ID**. You'll need it later. 49 | 50 | 5. Select **API permissions**. 51 | 1. Choose the **Add a permission** button and then make sure that the **Microsoft APIs** tab is selected. 52 | 2. In the **Commonly used Microsoft APIs** section, select **Microsoft Graph**, and then select **Delegated permissions**. 53 | 3. Use the **Select permissions** search box to search for the `Files.ReadWrite` and `Mail.Send` permissions. 54 | 4. Select the checkbox for each permission as it appears. 55 | > **NOTE:** Permissions will not remain visible in the list as you select each one. 56 | 57 | 6. Go to the **Authentication** page. 58 | 1. Check the box next to `https://login.microsoftonline.com/common/oauth2/nativeclient`. 59 | 2. Find the setting labeled **Default client type** and set it to `Yes`. 60 | 3. Select **Save** at the top of the page. 61 | 62 | After registering your application, modify the ```config.py``` file in the root folder of your cloned repo, and follow the instructions to enter your Client ID (the Application ID value you copied in Step 3 earlier). Save the change, and you're ready to run the sample. 63 | 64 | ## Running the sample 65 | 66 | Follow these steps to run the sample app: 67 | 68 | 1. At the command prompt, run the command ```python sample.py```. You'll see a message telling you to open a page in your browser and enter a code. 69 | ![launch the sample](images/running1.png) 70 | 71 | 2. After entering the code at https://aka.ms/devicelogin, you'll be prompted to select an identity or enter an email address to identify yourself. The identity you use must be in the same organization/tenant where the application was registered. Sign in, and then you'll be asked to consent to the application's delegated permissions as shown below. Choose **Accept**. 72 | ![consenting to permissions](images/running2.png) 73 | 74 | 3. After consenting to permissions, you'll see a message saying "You have signed in to the console-app-sample application on your device. You may now close this window." Close the browser and return to the console. You are now authenticated, and the app has a token that can be used for Microsoft Graph requests. The app will request your user profile and display your name and email address, then prompt you for a destination email address. You may enter one or more email recipients (delimited with ```;```), or press **Enter** to send the email to yourself. 75 | ![entering recipients](images/running3.png) 76 | 77 | 4. After entering email recipients, you'll see console output showing the Graph endpoints and responses for the remaining steps in the sample app: getting your profile photo, uploading it to OneDrive, creating a sharing link, and sending the email. 78 | ![sending the mail](images/running4.png) 79 | 80 | Check your email, and you'll see the email that has been sent. It includes your profile photo as an attachment, as well as a view-only sharing link to the copy of your profile photo that was uploaded to the root folder of your OneDrive account. 81 | 82 | ![receiving the mail](images/running5.png) 83 | 84 | ## Device Flow authentication 85 | 86 | Microsoft Graph uses Azure Active Directory (Azure AD) for authentication, and Azure AD supports a variety of [OAuth 2.0](http://www.rfc-editor.org/rfc/rfc6749.txt) authentication flows. The recommended authorization flow for Python console apps is [device flow](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-07), and this sample uses [Microsoft ADAL for Python](https://github.com/AzureAD/azure-activedirectory-library-for-python) to implement device flow as shown in the diagram below. 87 | 88 | ![device flow](images/deviceflow.png) 89 | 90 | The ```device_flow_session()``` function of [helpers.py](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py) handles the authentication details. As you can see in [sample.py](https://github.com/microsoftgraph/python-sample-console-app/blob/master/sample.py#L82-L84), we try to create a session and then if successfully created we use that session to send mail: 91 | 92 | ```python 93 | GRAPH_SESSION = device_flow_session(config.CLIENT_ID) 94 | if GRAPH_SESSION: 95 | sendmail_sample(GRAPH_SESSION) 96 | ``` 97 | 98 | The first step in ```device_flow_session()``` is to [call ADAL's acquire_user_code() method](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py#L39-L40) to get a _user code_. This code is [displayed on the console](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py#L42-L50), and that code must be entered on the _authorization URL_ page before it expires. While waiting for this to happen, the app [calls ADAL's acquire_token_with_device_code() method](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py#L52-L54), which begins polling to check whether the code has been entered. When the code is entered and the user consents to the requested permissions, an access token is returned. The app then [creates a Requests session and stores the access token in the session's Authorization header](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py#L58-L61), where it will be sent with calls to Microsoft Graph via the session's HTTP verb methods such as ```get()``` or ```post()```. 99 | 100 | This sample doesn't use a **refresh token**, but it's easy to obtain a refresh token if you'd like to provide your users with a "remember me" experience that doesn't require logging in every time they run your app. To get a refresh token, register the application with ```offline_access``` permission, and then you'll receive a refresh token which you can use with ADAL's [acquire_token_with_refresh_token](https://github.com/AzureAD/azure-activedirectory-library-for-python/blob/dev/sample/refresh_token_sample.py#L47-L69) method to refresh a Graph access token. 101 | 102 | ## Helper functions 103 | 104 | Several helper functions in [helpers.py](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py) provide simple wrappers for common Graph operations, and provide examples of how to make authenticated Graph requests via the methods of the session object. These helper functions can be used with any auth library — the only requirement is that the session object has a valid Graph access token stored in its ```Authorization``` header. 105 | 106 | ### A note on HTTP headers 107 | 108 | In this sample, the session object sends the required ```Authorization``` header (which contains the access token) as well as optional headers to identify the libraries used. These headers are set [during the authentication process](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py#L59-L61). In addition, you may want to create other headers for certain Graph calls. You can do this by passing a ```headers``` dictionary to the Graph call, and this dictionary will be merged with the default headers on the session object. You can see an example of this technique in parameter for any of the ```send_mail``` helper function, which adds a ```Content-Type``` header as shown [here](https://github.com/microsoftgraph/python-sample-console-app/blob/master/helpers.py#L138-L138). 109 | 110 | ### api_endpoint(url) 111 | 112 | Converts a relative path such as ```/me/photo/$value``` to a full URI based on the current RESOURCE and API_VERSION settings in config.py. 113 | 114 | ### device_flow_session(client_id, auto=False) 115 | 116 | Obtains an access token from Azure AD (via device flow) and create a Requests session instance ready to make authenticated calls to Microsoft Graph. The only required argument is the **client_id** of the [registered application](#application-registration). 117 | 118 | The optional **auto** argument can be set to ```True``` to automatically launch the authorization URL in your default browser and copy the user code to the clipboard. This can save time during repetitive dev/test activities. 119 | 120 | ### profile_photo(session, *, user_id='me', save_as=None) 121 | 122 | Gets a profile photo, and optionally saves a local copy. Returns a tuple of the raw photo data, HTTP status code, content type, and saved filename. 123 | 124 | ### send_mail(session, *, subject, recipients, body='', content_type='HTML', attachments=None) 125 | 126 | Sends email from current user. Returns the Requests response object for the POST. 127 | 128 | ### sharing_link(session, *, item_id, link_type='view') 129 | 130 | Creates a sharing link for an item in OneDrive. 131 | 132 | ### upload_file(session, *, filename, folder=None) 133 | 134 | Uploads a file to OneDrive for Business. 135 | 136 | ## Contributing 137 | 138 | These samples are open source, released under the [MIT License](https://github.com/microsoftgraph/python-sample-console-app/blob/master/LICENSE). Issues (including feature requests and/or questions about this sample) and [pull requests](https://github.com/microsoftgraph/python-sample-console-app/pulls) are welcome. If there's another Python sample you'd like to see for Microsoft Graph, we're interested in that feedback as well — please log an [issue](https://github.com/microsoftgraph/python-sample-console-app/issues) and let us know! 139 | 140 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information, see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 141 | 142 | ## Resources 143 | 144 | * Authentication: 145 | * [Microsoft ADAL for Python](https://github.com/AzureAD/azure-activedirectory-library-for-python) 146 | * [Python authentication samples for Microsoft Graph](https://github.com/microsoftgraph/python-sample-auth) 147 | * [OAuth 2.0 Device Flow for Browserless and Input Constrained Devices](https://tools.ietf.org/html/draft-ietf-oauth-device-flow-07) 148 | * Graph API documentation: 149 | * [Get a user](https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_get) 150 | * [Get photo](https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/profilephoto_get) 151 | * [Upload or replace the contents of a DriveItem](https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/driveitem_put_content) 152 | * [Create a sharing link for a DriveItem](https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/driveitem_createlink) 153 | * [Send mail](https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_sendmail) 154 | * Other Python samples for Microsoft Graph: 155 | * [Sending mail via Microsoft Graph from Python](https://github.com/microsoftgraph/python-sample-send-mail) (web app) 156 | * [Working with paginated Microsoft Graph responses in Python](https://github.com/microsoftgraph/python-sample-pagination) 157 | * [Working with Graph open extensions in Python](https://github.com/microsoftgraph/python-sample-open-extensions) 158 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | """Configuration settings for console app using device flow authentication 2 | """ 3 | 4 | CLIENT_ID = 'ENTER_YOUR_CLIENT_ID' 5 | 6 | AUTHORITY_URL = 'https://login.microsoftonline.com/common' 7 | RESOURCE = 'https://graph.microsoft.com' 8 | API_VERSION = 'beta' 9 | 10 | # This code can be removed after configuring CLIENT_ID and CLIENT_SECRET above. 11 | if 'ENTER_YOUR' in CLIENT_ID: 12 | print('ERROR: config.py does not contain valid CLIENT_ID.') 13 | import sys 14 | sys.exit(1) 15 | -------------------------------------------------------------------------------- /email.html: -------------------------------------------------------------------------------- 1 |

Congratulations {name},

2 |

This is a message from the Python quick start sample for Microsoft Graph. You are well on your way to incorporating Microsoft Graph services into your own apps!

3 |

Here is a view-only sharing link for the profile photo you just uploaded to OneDrive. A copy of the photo is also attached to this message.

4 |

What’s next?

5 | 11 |

Give us feedback

12 | 18 |

Thanks and happy coding!

- the Microsoft Graph team

19 | -------------------------------------------------------------------------------- /helpers.py: -------------------------------------------------------------------------------- 1 | """helper functions for Microsoft Graph""" 2 | # Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | # See LICENSE in the project root for license information. 4 | import base64 5 | import mimetypes 6 | import os 7 | import urllib 8 | import webbrowser 9 | 10 | from adal import AuthenticationContext 11 | import pyperclip 12 | import requests 13 | 14 | import config 15 | 16 | def api_endpoint(url): 17 | """Convert a relative path such as /me/photo/$value to a full URI based 18 | on the current RESOURCE and API_VERSION settings in config.py. 19 | """ 20 | if urllib.parse.urlparse(url).scheme in ['http', 'https']: 21 | return url # url is already complete 22 | return urllib.parse.urljoin(f'{config.RESOURCE}/{config.API_VERSION}/', 23 | url.lstrip('/')) 24 | 25 | def device_flow_session(client_id, auto=False): 26 | """Obtain an access token from Azure AD (via device flow) and create 27 | a Requests session instance ready to make authenticated calls to 28 | Microsoft Graph. 29 | 30 | client_id = Application ID for registered "Azure AD only" V1-endpoint app 31 | auto = whether to copy device code to clipboard and auto-launch browser 32 | 33 | Returns Requests session object if user signed in successfully. The session 34 | includes the access token in an Authorization header. 35 | 36 | User identity must be an organizational account (ADAL does not support MSAs). 37 | """ 38 | ctx = AuthenticationContext(config.AUTHORITY_URL, api_version=None) 39 | device_code = ctx.acquire_user_code(config.RESOURCE, 40 | client_id) 41 | 42 | # display user instructions 43 | if auto: 44 | pyperclip.copy(device_code['user_code']) # copy user code to clipboard 45 | webbrowser.open(device_code['verification_url']) # open browser 46 | print(f'The code {device_code["user_code"]} has been copied to your clipboard, ' 47 | f'and your web browser is opening {device_code["verification_url"]}. ' 48 | 'Paste the code to sign in.') 49 | else: 50 | print(device_code['message']) 51 | 52 | token_response = ctx.acquire_token_with_device_code(config.RESOURCE, 53 | device_code, 54 | client_id) 55 | if not token_response.get('accessToken', None): 56 | return None 57 | 58 | session = requests.Session() 59 | session.headers.update({'Authorization': f'Bearer {token_response["accessToken"]}', 60 | 'SdkVersion': 'sample-python-adal', 61 | 'x-client-SKU': 'sample-python-adal'}) 62 | return session 63 | 64 | def profile_photo(session, *, user_id='me', save_as=None): 65 | """Get profile photo, and optionally save a local copy. 66 | 67 | session = requests.Session() instance with Graph access token 68 | user_id = Graph id value for the user, or 'me' (default) for current user 69 | save_as = optional filename to save the photo locally. Should not include an 70 | extension - the extension is determined by photo's content type. 71 | 72 | Returns a tuple of the photo (raw data), HTTP status code, content type, saved filename. 73 | """ 74 | 75 | endpoint = 'me/photo/$value' if user_id == 'me' else f'users/{user_id}/$value' 76 | photo_response = session.get(api_endpoint(endpoint), 77 | stream=True) 78 | photo_status_code = photo_response.status_code 79 | if photo_response.ok: 80 | photo = photo_response.raw.read() 81 | # note we remove /$value from endpoint to get metadata endpoint 82 | metadata_response = session.get(api_endpoint(endpoint[:-7])) 83 | content_type = metadata_response.json().get('@odata.mediaContentType', '') 84 | else: 85 | photo = '' 86 | content_type = '' 87 | 88 | if photo and save_as: 89 | extension = content_type.split('/')[1] 90 | filename = save_as + '.' + extension 91 | with open(filename, 'wb') as fhandle: 92 | fhandle.write(photo) 93 | else: 94 | filename = '' 95 | 96 | return (photo, photo_status_code, content_type, filename) 97 | 98 | def send_mail(session, *, subject, recipients, body='', content_type='HTML', 99 | attachments=None): 100 | """Send email from current user. 101 | 102 | session = requests.Session() instance with Graph access token 103 | subject = email subject (required) 104 | recipients = list of recipient email addresses (required) 105 | body = body of the message 106 | content_type = content type (default is 'HTML') 107 | attachments = list of file attachments (local filenames) 108 | 109 | Returns the response from the POST to the sendmail API. 110 | """ 111 | 112 | # Create recipient list in required format. 113 | recipient_list = [{'EmailAddress': {'Address': address}} 114 | for address in recipients] 115 | 116 | # Create list of attachments in required format. 117 | attached_files = [] 118 | if attachments: 119 | for filename in attachments: 120 | b64_content = base64.b64encode(open(filename, 'rb').read()) 121 | mime_type = mimetypes.guess_type(filename)[0] 122 | mime_type = mime_type if mime_type else '' 123 | attached_files.append( \ 124 | {'@odata.type': '#microsoft.graph.fileAttachment', 125 | 'ContentBytes': b64_content.decode('utf-8'), 126 | 'ContentType': mime_type, 127 | 'Name': filename}) 128 | 129 | # Create email message in required format. 130 | email_msg = {'Message': {'Subject': subject, 131 | 'Body': {'ContentType': content_type, 'Content': body}, 132 | 'ToRecipients': recipient_list, 133 | 'Attachments': attached_files}, 134 | 'SaveToSentItems': 'true'} 135 | 136 | # Do a POST to Graph's sendMail API and return the response. 137 | return session.post(api_endpoint('me/microsoft.graph.sendMail'), 138 | headers={'Content-Type': 'application/json'}, 139 | json=email_msg) 140 | 141 | def sharing_link(session, *, item_id, link_type='view'): 142 | """Get a sharing link for an item in OneDrive. 143 | 144 | session = requests.Session() instance with Graph access token 145 | item_id = the id of the DriveItem (the target of the link) 146 | link_type = 'view' (default), 'edit', or 'embed' (OneDrive Personal only) 147 | 148 | Returns a tuple of the response object and the sharing link. 149 | """ 150 | endpoint = f'me/drive/items/{item_id}/createLink' 151 | response = session.post(api_endpoint(endpoint), 152 | headers={'Content-Type': 'application/json'}, 153 | json={'type': link_type}) 154 | 155 | if response.ok: 156 | # status 201 = link created, status 200 = existing link returned 157 | return (response, response.json()['link']['webUrl']) 158 | return (response, '') 159 | 160 | def upload_file(session, *, filename, folder=None): 161 | """Upload a file to OneDrive for Business. 162 | 163 | session = requests.Session() instance with Graph access token 164 | filename = local filename; may include a path 165 | folder = destination subfolder/path in OneDrive for Business 166 | None (default) = root folder 167 | 168 | File is uploaded and the response object is returned. 169 | If file already exists, it is overwritten. 170 | If folder does not exist, it is created. 171 | 172 | API documentation: 173 | https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/driveitem_put_content 174 | """ 175 | fname_only = os.path.basename(filename) 176 | 177 | # create the Graph endpoint to be used 178 | if folder: 179 | # create endpoint for upload to a subfolder 180 | endpoint = f'me/drive/root:/{folder}/{fname_only}:/content' 181 | else: 182 | # create endpoint for upload to drive root folder 183 | endpoint = f'me/drive/root/children/{fname_only}/content' 184 | 185 | content_type, _ = mimetypes.guess_type(fname_only) 186 | with open(filename, 'rb') as fhandle: 187 | file_content = fhandle.read() 188 | 189 | return session.put(api_endpoint(endpoint), 190 | headers={'content-type': content_type}, 191 | data=file_content) 192 | -------------------------------------------------------------------------------- /images/deviceflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/deviceflow.png -------------------------------------------------------------------------------- /images/imports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/imports.png -------------------------------------------------------------------------------- /images/registration1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/registration1.png -------------------------------------------------------------------------------- /images/registration2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/registration2.png -------------------------------------------------------------------------------- /images/running1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/running1.png -------------------------------------------------------------------------------- /images/running2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/running2.png -------------------------------------------------------------------------------- /images/running3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/running3.png -------------------------------------------------------------------------------- /images/running4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/running4.png -------------------------------------------------------------------------------- /images/running5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoftgraph/python-sample-console-app/4b576ce65ba4deaa84cd1fb694948a87856c5b9c/images/running5.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyperclip>=1.6.0 2 | requests>=2.18.4 3 | adal>=1.0.0 -------------------------------------------------------------------------------- /sample.py: -------------------------------------------------------------------------------- 1 | """Python console app with device flow authentication.""" 2 | # Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. 3 | # See LICENSE in the project root for license information. 4 | import pprint 5 | 6 | import config 7 | 8 | from helpers import api_endpoint, device_flow_session, profile_photo, \ 9 | send_mail, sharing_link, upload_file 10 | 11 | def sendmail_sample(session): 12 | """Send email from authenticated user. 13 | 14 | session = requests.Session() instance with a valid access token for 15 | Microsoft Graph in its default HTTP headers 16 | 17 | This sample retrieves the user's profile photo, uploads it to OneDrive, 18 | creates a view-only sharing link for the photo, and sends an email 19 | with the photo attached. 20 | 21 | The code in this function includes many print statements to provide 22 | information about which endpoints are being called and the status and 23 | size of Microsoft Graph responses. This information is helpful for 24 | understanding how the sample works with Graph, but would not be included 25 | in a typical production application. 26 | """ 27 | 28 | print('\nGet user profile ---------> https://graph.microsoft.com/beta/me') 29 | user_profile = session.get(api_endpoint('me')) 30 | print(28*' ' + f'', f'bytes returned: {len(user_profile.text)}\n') 31 | if not user_profile.ok: 32 | pprint.pprint(user_profile.json()) # display error 33 | return 34 | user_data = user_profile.json() 35 | email = user_data['mail'] 36 | display_name = user_data['displayName'] 37 | 38 | print(f'Your name ----------------> {display_name}') 39 | print(f'Your email ---------------> {email}') 40 | email_to = input(f'Send-to (ENTER=self) -----> ') or email 41 | 42 | print('\nGet profile photo --------> https://graph.microsoft.com/beta/me/photo/$value') 43 | photo, photo_status_code, _, profile_pic = profile_photo(session, save_as='me') 44 | print(28*' ' + f'', 45 | f'bytes returned: {len(photo)}, saved as: {profile_pic}') 46 | if not 200 <= photo_status_code <= 299: 47 | return 48 | 49 | print(f'Upload to OneDrive ------->', 50 | f'https://graph.microsoft.com/beta/me/drive/root/children/{profile_pic}/content') 51 | upload_response = upload_file(session, filename=profile_pic) 52 | print(28*' ' + f'') 53 | if not upload_response.ok: 54 | pprint.pprint(upload_response.json()) # show error message 55 | return 56 | 57 | print('Create sharing link ------>', 58 | 'https://graph.microsoft.com/beta/me/drive/items/{id}/createLink') 59 | response, link_url = sharing_link(session, item_id=upload_response.json()['id']) 60 | print(28*' ' + f'', 61 | f'bytes returned: {len(response.text)}') 62 | if not response.ok: 63 | pprint.pprint(response.json()) # show error message 64 | return 65 | 66 | print('Send mail ---------------->', 67 | 'https://graph.microsoft.com/beta/me/microsoft.graph.sendMail') 68 | with open('email.html') as template_file: 69 | template = template_file.read().format(name=display_name, link_url=link_url) 70 | 71 | send_response = send_mail(session=session, 72 | subject='email from Microsoft Graph console app', 73 | recipients=email_to.split(';'), 74 | body=template, 75 | attachments=[profile_pic]) 76 | print(28*' ' + f'') 77 | if not send_response.ok: 78 | pprint.pprint(send_response.json()) # show error message 79 | 80 | if __name__ == '__main__': 81 | GRAPH_SESSION = device_flow_session(config.CLIENT_ID) 82 | if GRAPH_SESSION: 83 | sendmail_sample(GRAPH_SESSION) 84 | --------------------------------------------------------------------------------