Skip to content

Commit 3b6f180

Browse files
committed
Merge branch 'client_credentials_password'
2 parents 5610650 + 963ec4b commit 3b6f180

File tree

2 files changed

+24
-12
lines changed

2 files changed

+24
-12
lines changed

emailproxy.config

+13-7
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ local_address = 127.0.0.1
8383
[Account setup]
8484
documentation = Accounts are specified using your email address as the section heading (e.g., [[email protected]],
8585
etc, below). Account usernames (i.e., email addresses) must be unique - only one entry per account is permitted.
86-
Each account section must provide values for `permission_url`, `token_url`, `oauth2_scope` and `redirect_uri`. If
87-
you are adding an account for a service other than the examples shown below then the provider's documentation should
88-
provide these details.
86+
Each account section must provide values for at least `token_url`, `oauth2_scope` and `client_id`. Depending on the
87+
OAuth 2.0 flow you are using, other values may also be required (see examples below). If you are adding an account
88+
for a service other than the examples shown below then the provider's documentation should provide these details.
8989

9090
You will also need to add your own `client_id` and `client_secret` values as indicated below. These can either be
9191
reused from an existing source (such as another email client that supports OAuth 2.0), or you can register and use
@@ -228,7 +228,6 @@ documentation = *** note: this is an advanced O365 account example; in most case
228228
token_url = https://login.microsoftonline.com/*** your tenant id here ***/oauth2/v2.0/token
229229
oauth2_scope = https://outlook.office365.com/.default
230230
oauth2_flow = client_credentials
231-
redirect_uri = http://localhost
232231
client_id = *** your client id here ***
233232
client_secret = *** your client secret here ***
234233

@@ -237,7 +236,6 @@ documentation = *** note: this is an advanced O365 account example; in most case
237236
token_url = https://login.microsoftonline.com/*** your tenant id here ***/oauth2/v2.0/token
238237
oauth2_scope = https://outlook.office365.com/IMAP.AccessAsUser.All https://outlook.office365.com/POP.AccessAsUser.All https://outlook.office365.com/SMTP.Send offline_access
239238
oauth2_flow = password
240-
redirect_uri = http://localhost
241239
client_id = *** your client id here ***
242240
client_secret = *** your client secret here ***
243241

@@ -246,7 +244,6 @@ documentation = *** note: this is an advanced Google account example; in most ca
246244
token_url = https://oauth2.googleapis.com/token
247245
oauth2_scope = https://mail.google.com/
248246
oauth2_flow = service_account
249-
redirect_uri = http://localhost
250247
client_id = file
251248
client_secret = *** your /path/to/service-account-key.json here ***
252249

@@ -255,7 +252,6 @@ documentation = *** note: this is an advanced Google account example; in most ca
255252
token_url = https://oauth2.googleapis.com/token
256253
oauth2_scope = https://mail.google.com/
257254
oauth2_flow = service_account
258-
redirect_uri = http://localhost
259255
client_id = key
260256
client_secret = *** your pasted service account JSON key file contents here,
261257
making sure to indent all lines by at least one space ***
@@ -292,6 +288,15 @@ documentation = The parameters below control advanced options for the proxy. In
292288
using catch-all accounts or the proxy's `--cache-store` parameter you must manually remove unencrypted secrets from
293289
the local configuration file after the encrypted secret has been created (i.e., this will not be automatic).
294290

291+
- use_login_password_as_client_credentials_secret (default = False): When using the O365 client credentials grant
292+
(CCG) flow, rather than encrypting the client secret (see above), the proxy can be instructed to use the given
293+
IMAP/POP/SMTP login password as the client secret. This approach removes the risk of storing the unencrypted client
294+
secret in the proxy's configuration file, and also means there is no risk of unauthorised account access when using
295+
the O365 CCG flow in conjunction with the proxy's catch-all mode (see below). To enable this option, set
296+
`use_login_password_as_client_credentials_secret` to True. Note that if a `client_secret` value is present in your
297+
account's configuration entry, that value will be used instead of the given IMAP/POP/SMTP login password even if
298+
this option is enabled. To avoid this, remove the entire `client_secret` line from the configuration entry.
299+
295300
- allow_catch_all_accounts (default = False): The default behaviour of the proxy is to require a full separate
296301
configuration file entry for each account. However, when proxying multiple accounts from the same domain it can be
297302
cumbersome to have to create multiple near-identical configuration profiles. To simplify this the proxy supports
@@ -308,4 +313,5 @@ documentation = The parameters below control advanced options for the proxy. In
308313
[emailproxy]
309314
delete_account_token_on_password_error = True
310315
encrypt_client_secret_on_first_use = False
316+
use_login_password_as_client_credentials_secret = False
311317
allow_catch_all_accounts = False

emailproxy.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -725,13 +725,12 @@ def get_oauth2_credentials(username, password, reload_remote_accounts=True):
725725
jwt_certificate_path = AppConfig.get_option_with_catch_all_fallback(config, username, 'jwt_certificate_path')
726726
jwt_key_path = AppConfig.get_option_with_catch_all_fallback(config, username, 'jwt_key_path')
727727

728-
# note that we don't require permission_url here because it is not needed for the client credentials grant flow,
729-
# and likewise for client_secret here because it can be optional for Office 365 configurations
730-
if not (token_url and oauth2_scope and redirect_uri and client_id):
728+
# because the proxy supports a wide range of OAuth 2.0 flows, in addition to the token_url we only mandate the
729+
# core parameters that are required by all methods: oauth2_scope and client_id
730+
if not (token_url and oauth2_scope and client_id):
731731
Log.error('Proxy config file entry incomplete for account', username, '- aborting login')
732732
return (False, '%s: Incomplete config file entry found for account %s - please make sure all required '
733-
'fields are added (permission_url, token_url, oauth2_scope, redirect_uri, client_id '
734-
'and client_secret)' % (APP_NAME, username))
733+
'fields are added (at least token_url, oauth2_scope and client_id)' % (APP_NAME, username))
735734

736735
# while not technically forbidden (RFC 6749, A.1 and A.2), it is highly unlikely the example value is valid
737736
example_client_value = '*** your'
@@ -1125,12 +1124,19 @@ def get_oauth2_authorisation_tokens(token_url, redirect_uri, client_id, client_s
11251124
params['client_assertion_type'] = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
11261125
params['client_assertion'] = jwt_client_assertion
11271126

1127+
# CCG flow can fall back to the login password as the client secret (see GitHub #271 discussion)
1128+
elif oauth2_flow == 'client_credentials' and AppConfig.get_global(
1129+
'use_login_password_as_client_credentials_secret', fallback=False):
1130+
params['client_secret'] = password
1131+
11281132
if oauth2_flow != 'authorization_code':
11291133
del params['code'] # CCG/ROPCG flows have no code, but we need the scope and (for ROPCG) username+password
11301134
params['scope'] = oauth2_scope
11311135
if oauth2_flow == 'password':
11321136
params['username'] = username
11331137
params['password'] = password
1138+
if not redirect_uri:
1139+
del params['redirect_uri'] # redirect_uri is not typically required in non-code flows; remove if empty
11341140
try:
11351141
response = urllib.request.urlopen(
11361142
urllib.request.Request(token_url, data=urllib.parse.urlencode(params).encode('utf-8'),

0 commit comments

Comments
 (0)