Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 64 additions & 19 deletions oauth2_clientmanager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ def __init__(self, registration: Dict[str, Sequence[str]],
self.authurl: Optional[str] = None
self.authurl_lock = threading.Lock()

self.authtoken: Optional[str] = None
self.authtoken_lock = threading.Lock()

self._server: Optional[_ThreadingHTTPServerWithContext] = None
self._server_thread: Optional[threading.Thread] = None

Expand Down Expand Up @@ -423,6 +426,35 @@ def _wait_for_authurl_on_stdin(self) -> None:
if self.authurl:
break

@staticmethod
def _print_authtoken_prompt() -> None:
print('Please enter the token your browser gave you: ', end='', flush=True)

@staticmethod
def validate_authtoken(url: str) -> bool:
"""Validate that a token could potentially be an authtoken"""
return True

def _wait_for_authtoken_on_stdin(self) -> None:
self._print_authtoken_prompt()
while True:
(readers, _, _) = select.select([sys.stdin], [], [], 0.5)
with self.authtoken_lock:
if not self.authtoken and readers:
try:
token = sys.stdin.readline()
if self.validate_authtoken(token):
self.authtoken = token
else:
print("Error: No authcode provided.")
self._print_authtoken_prompt()
continue
except KeyboardInterrupt:
break
elif self.authtoken:
print("(not necessary any longer)\nResponse provided by browser session.\n")
if self.authtoken:
break

@classmethod
def from_new_authorization(cls, registration: Dict[str, Sequence[str]], client: Dict[str, str], port: int = 0) -> 'OAuth2ClientManager':
Expand All @@ -433,6 +465,8 @@ def from_new_authorization(cls, registration: Dict[str, Sequence[str]], client:
return obj

def _new_authorization(self, port: int = 0) -> None:
if not 'auth_method' in self._registration.keys():
self._registration['auth_method'] = 'url'
redirect_uri = self._registration['redirect_uri']
if 'http://localhost' in redirect_uri:
self._setup_redirect_listener(port)
Expand All @@ -453,25 +487,36 @@ def _new_authorization(self, port: int = 0) -> None:
if self._server:
self._start_server()
self._inform_user_of_listener()
self._wait_for_authurl_on_stdin()
if self._server:
self._stop_server()

if not self.authurl:
raise RuntimeError("Stopped waiting for authurl but none found.")

# oauthlib expects redirects to be https -- no need for localhost
if 'http://localhost' in self.authurl:
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

# o365 requires the 'offline_access' scope in the request to issue
# refresh tokens but strips it in the response. oauthlib views that
# as a changed scope event that is handled as an error unless relaxed.
os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1'
self.token = self.session.fetch_token(self._registration['token_endpoint'],
authorization_response=self.authurl,
include_client_id=True,
**self.client, code_verifier=verifier)
if self._registration['auth_method'] == 'url':
self._wait_for_authurl_on_stdin()
if self._server:
self._stop_server()

if not self.authurl:
raise RuntimeError("Stopped waiting for authurl but none found.")

# oauthlib expects redirects to be https -- no need for localhost
if 'http://localhost' in self.authurl:
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

# o365 requires the 'offline_access' scope in the request to issue
# refresh tokens but strips it in the response. oauthlib views that
# as a changed scope event that is handled as an error unless relaxed.
os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1'
self.token = self.session.fetch_token(self._registration['token_endpoint'],
authorization_response=self.authurl,
include_client_id=True,
**self.client, code_verifier=verifier)
elif self._registration['auth_method'] == 'token':
self._wait_for_authtoken_on_stdin()
if self._server:
self._stop_server()
if not self.authtoken:
raise RuntimeError("Stopped waiting for authtoken but none found.")
self.token = self.session.fetch_token(self._registration['token_endpoint'],
code=self.authtoken,
include_client_id=True,
**self.client, code_verifier=verifier)

def refresh_token(self) -> None:
log.info("Starting token refresh")
Expand Down
3 changes: 3 additions & 0 deletions scripts/oauth2-clientd
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ registrations: Dict[str, Dict[str, Sequence[str]]] = {
'smtp_endpoint': 'smtp.gmail.com',
'sasl_method': 'OAUTHBEARER',
'scope': 'https://mail.google.com/',
'auth_method': 'token',
},
'microsoft': {
'authorize_endpoint': 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
Expand All @@ -65,11 +66,13 @@ registrations: Dict[str, Dict[str, Sequence[str]]] = {
'scope': ('offline_access https://outlook.office.com/IMAP.AccessAsUser.All',
'https://outlook.office.com/POP.AccessAsUser.All',
'https://outlook.office.com/SMTP.Send'),
'auth_method': 'url',
},
'suse-o365': {
'inherits' : 'microsoft',
'client_id' : '3ce62cca-417a-462c-bbe5-03d1888daf53',
'tenant' : 'mysuse.onmicrosoft.com',
'auth_method': 'url',
'client_secret' : ''
}
}
Expand Down