mirror of
https://github.com/vale981/outlook-oauth-hack
synced 2025-03-04 08:31:38 -05:00
Initial commit
This commit is contained in:
commit
0f42bc7759
4 changed files with 77 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
__pycache__
|
||||
refresh_token
|
37
README.md
Normal file
37
README.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Using OfflineIMAP with M365
|
||||
|
||||
This instruction describes how [OfflineIMAP](https://www.offlineimap.org/) can be used with an IMAP-enabled Exchange Online (M365) environment using OAuth2. Note that the tenant must be configured to support IMAP over 'modern authentication' and that it requires app consent to be granted for a mail app for which you have the client ID and secret.
|
||||
|
||||
A variation of the below should work with any OAuth-enabled mail client or script.
|
||||
|
||||
## Step 1: get a client ID/secret
|
||||
In order to connect to Azure AD for authentication, you need a client ID and secret (an "app registration" in AAD). Confusingly, the client secret doesn't actually need to be a secret (a client app like a mail client can't keep secrets, after all). You can your own app registration, or use an existing one such as Thunderbird's, which is [publicly available](https://hg.mozilla.org/comm-central/file/tip/mailnews/base/src/OAuth2Providers.jsm) (see the `login.microsoft.com` section). Whatever client ID you use, it will need to have been granted the `IMAP.AccessAsUser.All` permission in your M365 tenant.
|
||||
|
||||
## Step 2: get a token
|
||||
Since OfflineIMAP doesn't support an interactive flow for getting a token, you need to do this step yourself. You can use `get_token.py` for this purpose, which uses Microsoft's [MSAL](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-overview) wrapper library to perform the OAuth2 flow:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/UvA-FNWI/M365-IMAP
|
||||
cd M365-IMAP
|
||||
pip install msal
|
||||
# add the client ID and secret from step 1 to config.py
|
||||
python3 get_token.py
|
||||
```
|
||||
|
||||
Follow the instructions to obtain a `refresh_token` file containing an AAD refresh token. Note that the token allows access to your full mailbox (in combination with the client 'secret') and hence should be stored securely.
|
||||
|
||||
## Step 3: configure OfflineIMAP
|
||||
Edit your `.offlineimaprc` file so that your remote repository section looks like this:
|
||||
|
||||
```toml
|
||||
[Repository Remote]
|
||||
type = IMAP
|
||||
sslcacertfile = <path to CA certificates>
|
||||
remotehost = outlook.office365.com
|
||||
remoteuser = <your M365 email address>
|
||||
auth_mechanisms = XOAUTH2
|
||||
oauth2_request_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
|
||||
oauth2_client_id = <the client ID from step 1>
|
||||
oauth2_client_secret = <the client secret from step 1>
|
||||
oauth2_refresh_token = <the contents of the refresh_token file from step 2>
|
||||
```
|
4
config.py
Normal file
4
config.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
ClientId = ""
|
||||
ClientSecret = ""
|
||||
Scopes = ['https://outlook.office365.com/IMAP.AccessAsUser.All']
|
||||
OutputFileName = "refresh_token"
|
34
get_token.py
Normal file
34
get_token.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from msal import ConfidentialClientApplication, SerializableTokenCache
|
||||
import config
|
||||
import sys
|
||||
|
||||
redirect_uri = "http://localhost"
|
||||
|
||||
# We use the cache to extract the refresh token
|
||||
cache = SerializableTokenCache()
|
||||
app = ConfidentialClientApplication(config.ClientId, client_credential=config.ClientSecret, token_cache=cache)
|
||||
|
||||
url = app.get_authorization_request_url(config.Scopes, redirect_uri=redirect_uri)
|
||||
|
||||
print('Navigate to the following url in a web browser:')
|
||||
print(url)
|
||||
|
||||
print()
|
||||
|
||||
print('After login, you will be redirected to a blank (or error) page with a url containing an access code. Paste the url below.')
|
||||
resp = input('Response url: ')
|
||||
|
||||
i = resp.find('code') + 5
|
||||
code = resp[i : resp.find('&', i)] if i > 4 else resp
|
||||
|
||||
token = app.acquire_token_by_authorization_code(code, config.Scopes, redirect_uri=redirect_uri)
|
||||
|
||||
print()
|
||||
|
||||
if 'error' in token:
|
||||
print(token)
|
||||
sys.exit("Failed to get access token")
|
||||
|
||||
print(f'Access token acquired, writing to file {config.OutputFileName}')
|
||||
with open(config.OutputFileName, 'w') as f:
|
||||
f.write(cache.find('RefreshToken')[0]['secret'])
|
Loading…
Add table
Reference in a new issue