Skip to content

Commit 31eac06

Browse files
committed
Readme improvements; minor lint fixes
1 parent e4dbd64 commit 31eac06

File tree

3 files changed

+28
-14
lines changed

3 files changed

+28
-14
lines changed

README.md

+14-11
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The proxy works in the background with a menu bar/taskbar helper or as a headles
1010

1111
### Example use-cases<a id="example-use-cases"></a>
1212
- You need to use an Office 365 email account, but don't get on with Outlook.
13-
The email client you like doesn't support OAuth 2.0, which became mandatory [in January 2023](https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-deprecation-in-exchange-online-september/ba-p/3609437) ([September 2025 for SMTP](https://techcommunity.microsoft.com/t5/exchange-team-blog/exchange-online-to-retire-basic-auth-for-client-submission-smtp/ba-p/4114750)).
13+
The email client you like doesn't support OAuth 2.0, which became mandatory [in January 2023](https://techcommunity.microsoft.com/t5/exchange-team-blog/basic-authentication-deprecation-in-exchange-online-september/ba-p/3609437) ([September 2024 for free Hotmail/Outlook accounts](https://support.microsoft.com/en-us/office/modern-authentication-methods-now-needed-to-continue-syncing-outlook-email-in-non-microsoft-email-apps-c5d65390-9676-4763-b41f-d7986499a90d); [September 2025 for O365 SMTP](https://techcommunity.microsoft.com/t5/exchange-team-blog/exchange-online-to-retire-basic-auth-for-client-submission-smtp/ba-p/4114750)).
1414
- You used to use Gmail via IMAP/POP/SMTP with your raw account credentials (i.e., your real password), but cannot do this now that Google has disabled this method, and don't want to use an [App Password](https://support.google.com/accounts/answer/185833) (or cannot enable this option).
1515
- You have an account already set up in an email client, and you need to switch it to OAuth 2.0 authentication.
1616
You can edit the server details, but the client forces you to delete and re-add the account to enable OAuth 2.0, and you don't want to do this.
@@ -32,7 +32,7 @@ Begin by downloading the proxy via one of the following methods:
3232
</ol>
3333

3434
Next, edit the sample `emailproxy.config` file to add configuration details for each email server and account that you want to use with the proxy.
35-
[Guidance and example account configurations](https://github.com/simonrob/email-oauth2-proxy/blob/main/emailproxy.config) are provided for Office 365, Gmail and several other providers, though you will need to insert your own client credentials for each one (see the [client credentials documentation](#oauth-20-client-credentials) for guidance).
35+
[Guidance and example account configurations](https://github.com/simonrob/email-oauth2-proxy/blob/main/emailproxy.config) are provided for Office 365, Gmail and several other providers, though you will need to insert your own client credentials for each one (see the [client credentials documentation](#oauth-20-client-credentials) below for guidance).
3636
You can remove details from the sample configuration file for services you don't use, or add additional ones for any other OAuth 2.0-authenticated IMAP/POP/SMTP servers you would like to use with the proxy.
3737

3838
You can now start the proxy: depending on which installation option you chose, either launch the application or use the appropriate run command listed above.
@@ -71,7 +71,8 @@ The [sample configuration file](https://github.com/simonrob/email-oauth2-proxy/b
7171

7272
- Office 365: register a new [Microsoft identity application](https://learn.microsoft.com/entra/identity-platform/quickstart-register-app)
7373
- Gmail / Google Workspace: register a [Google API desktop app client](https://developers.google.com/identity/protocols/oauth2/native-app)
74-
- AOL and Yahoo Mail (and subproviders such as AT&T) are not currently allowing new client registrations with the OAuth email scope – the only option here is to reuse the credentials from an existing client that does have this permission.
74+
- Outlook / Hotmail (free accounts): because you are not the administrator for these Microsoft-operated domains, the only option is to reuse an existing client ID – see, for example, [Thunderbird](https://blog.thunderbird.net/2023/01/important-message-for-microsoft-office-365-enterprise-users/), or the links above
75+
- AOL and Yahoo Mail (and subproviders such as AT&T) are not currently allowing new client registrations with the OAuth email scope – the only option here is to reuse the credentials from an existing client that does have this permission
7576

7677
The proxy supports [Google Cloud service accounts](https://cloud.google.com/iam/docs/service-account-overview) for access to Google Workspace Gmail.
7778
It also supports the [client credentials grant (CCG)](https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow) and [resource owner password credentials grant (ROPCG)](https://learn.microsoft.com/entra/identity-platform/v2-oauth-ropc) OAuth 2.0 flows, and [certificate credentials (JWT)](https://learn.microsoft.com/entra/identity-platform/certificate-credentials).
@@ -134,8 +135,8 @@ See the [optional arguments and configuration](#optional-arguments-and-configura
134135

135136
If your network requires connections to use an existing proxy, you can instruct the script to use this by setting the [proxy handler](https://docs.python.org/3/library/urllib.request.html#urllib.request.ProxyHandler) environment variable `https_proxy` (and/or `http_proxy`) – for example, `https_proxy=localhost python -m emailproxy`.
136137

137-
After installing its requirements, the proxy script can be packaged as a single self-contained executable using [pyinstaller](https://pyinstaller.org/) if desired: `pyinstaller --onefile emailproxy.py`.
138-
If you are using the GUI version of the proxy, you may need to add `--hidden-import timeago.locales.en_short` until [this `timeago` issue](https://github.com/hustcc/timeago/issues/40) is resolved.
138+
After installing its requirements, the proxy script can be packaged as a single self-contained executable using [Nuitka](https://nuitka.net/) (`nuitka --standalone --macos-create-app-bundle emailproxy.py`) or [pyinstaller](https://pyinstaller.org/) (`pyinstaller --onefile emailproxy.py`).
139+
If you are using pyinstaller and the GUI version of the proxy, you may need to add `--hidden-import timeago.locales.en_short` until [this `timeago` issue](https://github.com/hustcc/timeago/issues/40) is resolved.
139140

140141
Python 3.7 or later is required to run the proxy.
141142
The [python2 branch](https://github.com/simonrob/email-oauth2-proxy/tree/python2) provides minimal compatibility with python 2.7, but with a limited feature set, and no ongoing maintenance.
@@ -151,7 +152,7 @@ The method to achieve this differs depending on whether you are using macOS, Win
151152

152153
On macOS, the file `~/Library/LaunchAgents/ac.robinson.email-oauth2-proxy.plist` is used to configure automatic starting of the proxy.
153154
If you stop the proxy's service (i.e., `Quit Email OAuth 2.0 Proxy` from the menu bar), you can restart it using `launchctl start ac.robinson.email-oauth2-proxy` from a terminal.
154-
You can stop, disable or remove the service from your startup items either via the menu bar icon option, or using `launchctl unload [plist path]`.
155+
You can stop, disable or remove the service from your startup items either via the menu bar icon option, or using `launchctl unload `_`[plist path]`_.
155156
If you edit the plist file manually, make sure you `unload` and then `load` it to update the system with your changes.
156157
If the `Start at login` option appears not to be working for you on macOS, see the [known issues section](#known-issues) for potential solutions.
157158

@@ -189,7 +190,7 @@ The easiest approach here is to use [OpenSSL](https://www.openssl.org/): `openss
189190
If you are having trouble actually connecting to the proxy, it is always worth double-checking the `local_address` values that you are using.
190191
The [sample configuration file](https://github.com/simonrob/email-oauth2-proxy/blob/main/emailproxy.config) sets this parameter to `127.0.0.1` for all servers.
191192
If you remove this value and do not provide your own, the proxy defaults to `::` – in most cases this resolves to `localhost` for both IPv4 and IPv6 configurations, but it is possible that this differs depending on your environment.
192-
If you are unable to connect to the proxy from your client, it is always worth first specifying this value explicitly – see the [sample configuration file](https://github.com/simonrob/email-oauth2-proxy/blob/main/emailproxy.config) for further details about how to do this.
193+
If you are unable to connect to the proxy from your email client, first try specifying this value explicitly – see the [sample configuration file](https://github.com/simonrob/email-oauth2-proxy/blob/main/emailproxy.config) for further details about how to do this.
193194
Please try setting and connecting to both IPv4 (i.e., `127.0.0.1`) and IPv6 (i.e., `::1`) loopback addresses before reporting any connection issues with the proxy.
194195

195196
### Dependencies and setup<a id="dependencies-and-setup"></a>
@@ -202,7 +203,7 @@ This is caused by missing dependencies for [pystray](https://github.com/moses-pa
202203
See the [pywebview dependencies](https://pywebview.flowrl.com/guide/installation.html#dependencies) and [pystray FAQ](https://pystray.readthedocs.io/en/latest/faq.html) pages and [existing](https://github.com/simonrob/email-oauth2-proxy/issues/1#issuecomment-831746642) [closed issues](https://github.com/simonrob/email-oauth2-proxy/issues/136#issuecomment-1430417456) in this repository for a summary and suggestions about how to resolve this.
203204

204205
A similar issue may occur on Windows with the [pythonnet](https://github.com/pythonnet/pythonnet) package, which is required by [pywebview](https://github.com/r0x0r/pywebview).
205-
If you are unable to resolve this by following the [pythonnet installation instructions](https://github.com/pythonnet/pythonnet/wiki/Installation), you may find that installing a [prebuilt wheel](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pythonnet) helps fix the issue.
206+
The [pythonnet installation instructions](https://github.com/pythonnet/pythonnet/wiki/Installation) may offer alternative ways to install this package if the default installation fails.
206207
Note that the public releases of pythonnet can take some time to be compatible with the latest major python release, so it can be worth using a slightly older version of python, or a pre-release version of pythonnet.
207208

208209
### Known issues<a id="known-issues"></a>
@@ -219,19 +220,21 @@ Once this has been approved, the proxy's menu bar icon will appear as normal.
219220
In some cases — particularly when running the proxy in a virtual environment, or using the built-in macOS python, rather than the python.org version, or installations managed by, e.g., homebrew, pyenv, etc. — the permission prompt does not appear.
220221
If this happens it is worth first trying to `unload` and then `load` the service via `launchctl`.
221222
If this still does not cause the prompt to appear, the only currently-known resolution is to run the proxy outside of a virtual environment and manually grant Full Disk Access to your python executable via the privacy settings in the macOS System Preferences.
222-
You may also need to edit the proxy's launch agent plist file, which is found at the location given in the command above, to set the path to your python executable – it must be the real path rather than a symlink (the `readlink` command can help here).
223+
You may also need to edit the proxy's launch agent plist file, which is found at the location given [in the command above](#starting-the-proxy-automatically), to set the path to your python executable – it must be the real path rather than a symlink (the `readlink` command can help here).
223224
Fortunately this is a one-time fix, and once the proxy loads successfully via this method you will not need to adjust its startup configuration again (except perhaps when upgrading to a newer major macOS version, in which case just repeat the procedure).
224225

225226
### Other problems<a id="other-problems"></a>
226227
Please feel free to [open an issue](https://github.com/simonrob/email-oauth2-proxy/issues) reporting any bugs you find, or [submit a pull request](https://github.com/simonrob/email-oauth2-proxy/pulls) to help improve this tool.
227228

228229

229230
## Advanced features<a id="advanced-features"></a>
230-
The [plugins variant](https://github.com/simonrob/email-oauth2-proxy/tree/plugins) has an additional feature that enables the use of separate scripts to modify IMAP/POP/SMTP commands when they are received from the client or server before passing through to the other side of the connection.
231+
The [plugins variant of the proxy](https://github.com/simonrob/email-oauth2-proxy/tree/plugins) has an additional feature that enables the use of separate scripts to modify IMAP/POP/SMTP commands when they are received from the client or server before passing through to the other side of the connection.
231232
This allows a wide range of additional capabilities or triggers to be added the proxy.
233+
232234
For example, the [IMAPIgnoreSentMessageUpload plugin](https://github.com/simonrob/email-oauth2-proxy/blob/plugins/plugins/IMAPIgnoreSentMessageUpload.py) intercepts any client commands to add emails to the IMAP sent messages mailbox, which resolves message duplication issues for servers that automatically do this when emails are received via SMTP (e.g., Office 365, Gmail, etc.).
233-
The [IMAPCleanO365ATPLinks plugin](https://github.com/simonrob/email-oauth2-proxy/blob/plugins/plugins/IMAPCleanO365ATPLinks.py) restores "Safe Links" modified by Microsoft Defender for Office 365 to their original URLs.
235+
The [IMAPCleanO365ATPLinks plugin](https://github.com/simonrob/email-oauth2-proxy/blob/plugins/plugins/IMAPCleanO365ATPLinks.py) restores "Safe Links" modified by Microsoft Defender for Office 365 to their original URLs, while the [IMAPRegexContentReplacer plugin](https://github.com/simonrob/email-oauth2-proxy/blob/plugins/plugins/IMAPRegexContentReplacer.py) lets you match and remove/replace any content in the message.
234236
The [SMTPBlackHole plugin](https://github.com/simonrob/email-oauth2-proxy/blob/plugins/plugins/SMTPBlackHole.py) gives the impression emails are being sent but actually silently discards them, which is useful for testing email sending tools.
237+
235238
See the [documentation and examples](https://github.com/simonrob/email-oauth2-proxy/tree/plugins/plugins) for further details, additional sample plugins and setup instructions.
236239

237240

emailproxy.config

+9-2
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,13 @@ redirect_uri = http://localhost
199199
client_id = *** your client id here ***
200200
client_secret = *** your client secret here ***
201201

202+
203+
permission_url = https://login.microsoftonline.com/common/oauth2/v2.0/authorize
204+
token_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
205+
oauth2_scope = https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access
206+
client_id = *** your client id here - note that as you are not the administrator of Hotmail.com / Outlook.com, you will need to reuse an existing client ID (see the proxy's readme) ***
207+
redirect_uri = https://localhost
208+
202209
203210
permission_url = https://accounts.google.com/o/oauth2/auth
204211
token_url = https://oauth2.googleapis.com/token
@@ -212,15 +219,15 @@ permission_url = https://api.login.yahoo.com/oauth2/request_auth
212219
token_url = https://api.login.yahoo.com/oauth2/get_token
213220
oauth2_scope = mail-w
214221
redirect_uri = http://localhost
215-
client_id = *** your client id here ***
222+
client_id = *** your client id here - note that as new client registrations are not permitted for Yahoo, you will need to reuse an existing client ID (see the proxy's readme) ***
216223
client_secret = *** your client secret here ***
217224

218225
219226
permission_url = https://api.login.aol.com/oauth2/request_auth
220227
token_url = https://api.login.aol.com/oauth2/get_token
221228
oauth2_scope = mail-w
222229
redirect_uri = http://localhost
223-
client_id = *** your client id here ***
230+
client_id = *** your client id here - note that as new client registrations are not permitted for AOL, you will need to reuse an existing client ID (see the proxy's readme) ***
224231
client_secret = *** your client secret here ***
225232

226233

emailproxy.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
__author__ = 'Simon Robinson'
77
__copyright__ = 'Copyright (c) 2024 Simon Robinson'
88
__license__ = 'Apache 2.0'
9-
__version__ = '2024-10-04' # ISO 8601 (YYYY-MM-DD)
9+
__version__ = '2024-10-21' # ISO 8601 (YYYY-MM-DD)
1010
__package_version__ = '.'.join([str(int(i)) for i in __version__.split('-')]) # for pyproject.toml usage only
1111

1212
import abc
@@ -1105,6 +1105,7 @@ def get_oauth2_authorisation_code(permission_url, redirect_uri, redirect_listen_
11051105
time.sleep(1)
11061106

11071107
@staticmethod
1108+
# pylint: disable-next=too-many-positional-arguments
11081109
def get_oauth2_authorisation_tokens(token_url, redirect_uri, client_id, client_secret, jwt_client_assertion,
11091110
authorisation_code, oauth2_scope, oauth2_flow, username, password):
11101111
"""Requests OAuth 2.0 access and refresh tokens from token_url using the given client_id, client_secret,
@@ -1182,6 +1183,7 @@ def get_service_account_authorisation_token(key_type, key_path_or_contents, oaut
11821183
return {'access_token': credentials.token, 'expires_in': int(credentials.expiry.timestamp() - time.time())}
11831184

11841185
@staticmethod
1186+
# pylint: disable-next=too-many-positional-arguments
11851187
def refresh_oauth2_access_token(token_url, client_id, client_secret, jwt_client_assertion, username, refresh_token):
11861188
"""Obtains a new access token from token_url using the given client_id, client_secret and refresh token,
11871189
returning a dict with 'access_token', 'expires_in', and 'refresh_token' on success; exception on failure"""
@@ -1385,6 +1387,7 @@ class OAuth2ClientConnection(SSLAsyncoreDispatcher):
13851387
"""The base client-side connection that is subclassed to handle IMAP/POP/SMTP client interaction (note that there
13861388
is some protocol-specific code in here, but it is not essential, and only used to avoid logging credentials)"""
13871389

1390+
# pylint: disable-next=too-many-positional-arguments
13881391
def __init__(self, proxy_type, connection_socket, socket_map, proxy_parent, custom_configuration):
13891392
SSLAsyncoreDispatcher.__init__(self, connection_socket=connection_socket, socket_map=socket_map)
13901393
self.receive_buffer = b''
@@ -1829,6 +1832,7 @@ def send_authentication_request(self):
18291832
class OAuth2ServerConnection(SSLAsyncoreDispatcher):
18301833
"""The base server-side connection that is subclassed to handle IMAP/POP/SMTP server interaction"""
18311834

1835+
# pylint: disable-next=too-many-positional-arguments
18321836
def __init__(self, proxy_type, connection_socket, socket_map, proxy_parent, custom_configuration):
18331837
SSLAsyncoreDispatcher.__init__(self, socket_map=socket_map) # note: establish connection later due to STARTTLS
18341838
self.receive_buffer = b''

0 commit comments

Comments
 (0)