Skip to content

Instantly share code, notes, and snippets.

@pcf000
Created September 5, 2022 18:42
Show Gist options
  • Save pcf000/54a65a224d18b3010fe733a465faea24 to your computer and use it in GitHub Desktop.
Save pcf000/54a65a224d18b3010fe733a465faea24 to your computer and use it in GitHub Desktop.
#!/usr/local/bin/python3
# This script is a refinement of what oauth2token provides, specific to
# my work situation. Normally, one could start with oauth2create and then
# use oauth2get indefinitely, with the latter getting a token either
# directly or after using the refresh token. However, my workplace has
# it set up to not have a refresh token, so I have this script try "get"
# and call "create" automatically if it fails.
#
# Start with "pip install oauth2token".
import argparse
import os
import sys
from contextlib import contextmanager
from oauth2token.token_mgmt import get_token,create_user_credentials
from google.auth.exceptions import RefreshError
# Requirements. This is a fleshing-out of the instructions in oauth2token's
# PKG-INFO
#
# 1. pip install oauth2token
#
# 2. Create $XDG_CONFIG_HOME/.config/oauth2token/office365/config.json and
# $XDG_CONFIG_HOME/.config/oauth2token/office365/scopes.json. They're
# essentially boilerplate. The client_id and client_secret below are
# from Thunderbird; see
# https://github.com/mozilla/releases-comm-central/blob/master/mailnews/base/src/OAuth2Providers.jsm
#
# config.json:
#
# {
# "installed": {
# "client_id": "08162f7c-0fd2-4200-a84a-f25a4db0b584",
# "client_secret":"TxRBilcHdC6WGBee]fs?QR:SJ8nI[g82",
# "redirect_uris": ["http://localhost", "urn:ietf:wg:oauth:2.0:oob"],
# "auth_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
# "token_uri": "https://login.microsoftonline.com/common/oauth2/v2.0/token"
# }
# }
#
# scopes.json
#
# ["https://outlook.office.com/POP.AccessAsUser.All",
# "https://outlook.office.com/SMTP.Send",
# "https://outlook.office.com/IMAP.AccessAsUser.All"]
# These two functions are used to redirect stdout to /dev/null, since work's
# arrangement makes me call create_user_credentials often, and that prints
# something, and I don't want that because I'm also printing the eventual
# token for capture by the caller.
def fileno(file_or_fd):
fd = getattr(file_or_fd, 'fileno', lambda: file_or_fd)()
if not isinstance(fd, int):
raise ValueError("Expected a file (`.fileno()`) or a file descriptor")
return fd
@contextmanager
def stdout_redirected(to=os.devnull, stdout=None):
if stdout is None:
stdout = sys.stdout
stdout_fd = fileno(stdout)
# copy stdout_fd before it is overwritten
#NOTE: `copied` is inheritable on Windows when duplicating a standard stream
with os.fdopen(os.dup(stdout_fd), 'wb') as copied:
stdout.flush() # flush library buffers that dup2 knows nothing about
try:
os.dup2(fileno(to), stdout_fd) # $ exec >&to
except ValueError: # filename
with open(to, 'wb') as to_file:
os.dup2(to_file.fileno(), stdout_fd) # $ exec > to
try:
yield stdout # allow code to be run with the redirected stdout
finally:
# restore stdout to its previous value
#NOTE: dup2 makes stdout_fd inheritable unconditionally
stdout.flush()
os.dup2(copied.fileno(), stdout_fd) # $ exec >&copied
# The actual work of getting or creating the token. I use it as
#
# PassCmd "/home/ME/bin/oauth2getcreate office365 ME@example.com"
# AuthMechs XOAUTH2
#
# where I arbitrarily named the app "office365" and our logins require
# user@host. oauth2getcreate prints the token and exits.
#
# I use mbsync, which unfortunately required me to build it from source,
# and also build cyrus-sasl-xoauth2 from source.
parser = argparse.ArgumentParser()
parser.add_argument('app')
parser.add_argument('user', default=os.getenv('USER'))
args = parser.parse_args()
try:
print (get_token(user=args.user, app=args.app))
except (RefreshError, FileNotFoundError):
with stdout_redirected(os.devnull):
create_user_credentials(user=args.user, app=args.app)
print (get_token(user=args.user, app=args.app))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment