Skip to content

Commit

Permalink
Get oauth2.py working again.
Browse files Browse the repository at this point in the history
This updates for Python 3 and deals with the
OAuth2 OOB flow not being a thing anymore.

While I'm here, use SSL (properly) when testing
IMAP and SMTP authentication.
  • Loading branch information
junyer committed Jul 24, 2023
1 parent f283352 commit 944cffd
Showing 1 changed file with 36 additions and 28 deletions.
64 changes: 36 additions & 28 deletions python/oauth2.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
See https://developers.google.com/identity/protocols/OAuth2 for instructions on
registering and for documentation of the APIs invoked by this code.
NOTE: The OAuth2 OOB flow isn't a thing anymore. You will need to set the
application type to "Web application" and then add https://oauth2.dance/ as an
authorised redirect URI. This is necessary for seeing the authorisation code on
a page in your browser.
This script has 3 modes of operation.
1. The first mode is used to generate and authorize an OAuth2 token, the
Expand Down Expand Up @@ -63,15 +68,17 @@
import base64
import imaplib
import json
from optparse import OptionParser
import optparse
import smtplib
import ssl
import sys
import urllib
import urllib.parse
import urllib.request


def SetupOptionParser():
# Usage message is the module's docstring.
parser = OptionParser(usage=__doc__)
parser = optparse.OptionParser(usage=__doc__)
parser.add_option('--generate_oauth2_token',
action='store_true',
dest='generate_oauth2_token',
Expand Down Expand Up @@ -126,8 +133,8 @@ def SetupOptionParser():
GOOGLE_ACCOUNTS_BASE_URL = 'https://accounts.google.com'


# Hardcoded dummy redirect URI for non-web apps.
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
# Hardcoded redirect URI.
REDIRECT_URI = 'https://oauth2.dance/'


def AccountsUrl(command):
Expand All @@ -144,12 +151,12 @@ def AccountsUrl(command):

def UrlEscape(text):
# See OAUTH 5.1 for a definition of which characters need to be escaped.
return urllib.quote(text, safe='~-._')
return urllib.parse.quote(text, safe='~-._')


def UrlUnescape(text):
# See OAUTH 5.1 for a definition of which characters need to be escaped.
return urllib.unquote(text)
return urllib.parse.unquote(text)


def FormatUrlParams(params):
Expand All @@ -162,7 +169,7 @@ def FormatUrlParams(params):
A URL query string version of the given parameters.
"""
param_fragments = []
for param in sorted(params.iteritems(), key=lambda x: x[0]):
for param in sorted(params.items(), key=lambda x: x[0]):
param_fragments.append('%s=%s' % (param[0], UrlEscape(param[1])))
return '&'.join(param_fragments)

Expand All @@ -184,6 +191,8 @@ def GeneratePermissionUrl(client_id, scope='https://mail.google.com/'):
params['redirect_uri'] = REDIRECT_URI
params['scope'] = scope
params['response_type'] = 'code'
params['access_type'] = 'offline'
params['prompt'] = 'consent'
return '%s?%s' % (AccountsUrl('o/oauth2/auth'),
FormatUrlParams(params))

Expand Down Expand Up @@ -211,7 +220,7 @@ def AuthorizeTokens(client_id, client_secret, authorization_code):
params['grant_type'] = 'authorization_code'
request_url = AccountsUrl('o/oauth2/token')

response = urllib.urlopen(request_url, urllib.urlencode(params)).read()
response = urllib.request.urlopen(request_url, urllib.parse.urlencode(params).encode('utf-8')).read()
return json.loads(response)


Expand All @@ -235,7 +244,7 @@ def RefreshToken(client_id, client_secret, refresh_token):
params['grant_type'] = 'refresh_token'
request_url = AccountsUrl('o/oauth2/token')

response = urllib.urlopen(request_url, urllib.urlencode(params)).read()
response = urllib.request.urlopen(request_url, urllib.parse.urlencode(params).encode('utf-8')).read()
return json.loads(response)


Expand All @@ -254,7 +263,7 @@ def GenerateOAuth2String(username, access_token, base64_encode=True):
"""
auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)
if base64_encode:
auth_string = base64.b64encode(auth_string)
auth_string = base64.b64encode(auth_string.encode('utf-8'))
return auth_string


Expand All @@ -269,7 +278,7 @@ def TestImapAuthentication(user, auth_string):
Must not be base64-encoded, since imaplib does its own base64-encoding.
"""
print
imap_conn = imaplib.IMAP4_SSL('imap.gmail.com')
imap_conn = imaplib.IMAP4_SSL('imap.gmail.com', ssl_context=ssl.create_default_context())
imap_conn.debug = 4
imap_conn.authenticate('XOAUTH2', lambda x: auth_string)
imap_conn.select('INBOX')
Expand All @@ -284,17 +293,16 @@ def TestSmtpAuthentication(user, auth_string):
GenerateOAuth2String.
"""
print
smtp_conn = smtplib.SMTP('smtp.gmail.com', 587)
smtp_conn = smtplib.SMTP_SSL('smtp.gmail.com', context=ssl.create_default_context())
smtp_conn.set_debuglevel(True)
smtp_conn.ehlo('test')
smtp_conn.starttls()
smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string))
smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string.encode('utf-8')).decode('utf-8'))


def RequireOptions(options, *args):
missing = [arg for arg in args if getattr(options, arg) is None]
if missing:
print 'Missing options: %s' % ' '.join(missing)
print('Missing options: %s' % ' '.join(missing))
sys.exit(-1)


Expand All @@ -306,27 +314,27 @@ def main(argv):
response = RefreshToken(options.client_id, options.client_secret,
options.refresh_token)
if options.quiet:
print response['access_token']
print(response['access_token'])
else:
print 'Access Token: %s' % response['access_token']
print 'Access Token Expiration Seconds: %s' % response['expires_in']
print('Access Token: %s' % response['access_token'])
print('Access Token Expiration Seconds: %s' % response['expires_in'])
elif options.generate_oauth2_string:
RequireOptions(options, 'user', 'access_token')
oauth2_string = GenerateOAuth2String(options.user, options.access_token)
if options.quiet:
print oauth2_string
print(oauth2_string)
else:
print 'OAuth2 argument:\n' + oauth2_string
print('OAuth2 argument:\n' + oauth2_string.decode('utf-8'))
elif options.generate_oauth2_token:
RequireOptions(options, 'client_id', 'client_secret')
print 'To authorize token, visit this url and follow the directions:'
print ' %s' % GeneratePermissionUrl(options.client_id, options.scope)
authorization_code = raw_input('Enter verification code: ')
print('To authorize token, visit this url and follow the directions:')
print(' %s' % GeneratePermissionUrl(options.client_id, options.scope))
authorization_code = input('Enter verification code: ')
response = AuthorizeTokens(options.client_id, options.client_secret,
authorization_code)
print 'Refresh Token: %s' % response['refresh_token']
print 'Access Token: %s' % response['access_token']
print 'Access Token Expiration Seconds: %s' % response['expires_in']
print('Refresh Token: %s' % response['refresh_token'])
print('Access Token: %s' % response['access_token'])
print('Access Token Expiration Seconds: %s' % response['expires_in'])
elif options.test_imap_authentication:
RequireOptions(options, 'user', 'access_token')
TestImapAuthentication(options.user,
Expand All @@ -339,7 +347,7 @@ def main(argv):
base64_encode=False))
else:
options_parser.print_help()
print 'Nothing to do, exiting.'
print('Nothing to do, exiting.')
return


Expand Down

0 comments on commit 944cffd

Please sign in to comment.