Skip to content

Commit 939cab0

Browse files
mdesmethashhar
authored andcommitted
Use keyring to securely cache the Oauth2 token
1 parent 5681fb7 commit 939cab0

File tree

3 files changed

+41
-2
lines changed

3 files changed

+41
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ the [OAuth2 authentication type](https://trino.io/docs/current/security/oauth2.h
172172

173173
A callback to handle the redirect url can be provided via param `redirect_auth_url_handler` of the `trino.auth.OAuth2Authentication` class. By default, it will try to launch a web browser (`trino.auth.WebBrowserRedirectHandler`) to go through the authentication flow and output the redirect url to stdout (`trino.auth.ConsoleRedirectHandler`). Multiple redirect handlers are combined using the `trino.auth.CompositeRedirectHandler` class.
174174

175-
The OAuth2 token will be cached either per `trino.auth.OAuth2Authentication` instance.
175+
The OAuth2 token will be cached either per `trino.auth.OAuth2Authentication` instance or, when keyring is installed, it will be cached within a secure backend (MacOS keychain, Windows credential locker, etc) under a key including host of the Trino connection. Keyring can be installed using `pip install 'trino[external-authentication-token-cache]'`.
176176

177177
- DBAPI
178178

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727

2828
kerberos_require = ["requests_kerberos"]
2929
sqlalchemy_require = ["sqlalchemy~=1.3"]
30+
external_authentication_token_cache_require = ["keyring"]
3031

32+
# We don't add localstorage_require to all_require as users must explicitly opt in to use keyring.
3133
all_require = kerberos_require + sqlalchemy_require
3234

3335
tests_require = all_require + [
@@ -80,6 +82,7 @@
8082
"kerberos": kerberos_require,
8183
"sqlalchemy": sqlalchemy_require,
8284
"tests": tests_require,
85+
"external-authentication-token-cache": external_authentication_token_cache_require,
8386
},
8487
entry_points={
8588
"sqlalchemy.dialects": [

trino/auth.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from requests import Request
2323
from requests.auth import AuthBase, extract_cookies_to_jar
2424
from requests.utils import parse_dict_header
25+
import importlib
2526

2627
import trino.logging
2728
from trino.client import exceptions
@@ -230,6 +231,40 @@ def store_token_to_cache(self, host: str, token: str) -> None:
230231
self._cache[host] = token
231232

232233

234+
class _OAuth2KeyRingTokenCache(_OAuth2TokenCache):
235+
"""
236+
Keyring Token Cache implementation
237+
"""
238+
239+
def __init__(self):
240+
super().__init__()
241+
try:
242+
self._keyring = importlib.import_module("keyring")
243+
except ImportError:
244+
self._keyring = None
245+
logger.info("keyring module not found. OAuth2 token will not be stored in keyring.")
246+
247+
def is_keyring_available(self) -> bool:
248+
return self._keyring is not None
249+
250+
def get_token_from_cache(self, host: str) -> Optional[str]:
251+
try:
252+
return self._keyring.get_password(host, "token")
253+
except self._keyring.errors.NoKeyringError as e:
254+
raise trino.exceptions.NotSupportedError("Although keyring module is installed no backend has been "
255+
"detected, check https://pypi.org/project/keyring/ for more "
256+
"information.") from e
257+
258+
def store_token_to_cache(self, host: str, token: str) -> None:
259+
try:
260+
# keyring is installed, so we can store the token for reuse within multiple threads
261+
self._keyring.set_password(host, "token", token)
262+
except self._keyring.errors.NoKeyringError as e:
263+
raise trino.exceptions.NotSupportedError("Although keyring module is installed no backend has been "
264+
"detected, check https://pypi.org/project/keyring/ for more "
265+
"information.") from e
266+
267+
233268
class _OAuth2TokenBearer(AuthBase):
234269
"""
235270
Custom implementation of Trino Oauth2 based authorization to get the token
@@ -239,7 +274,8 @@ class _OAuth2TokenBearer(AuthBase):
239274

240275
def __init__(self, redirect_auth_url_handler: Callable[[str], None]):
241276
self._redirect_auth_url = redirect_auth_url_handler
242-
self._token_cache = _OAuth2TokenInMemoryCache()
277+
keyring_cache = _OAuth2KeyRingTokenCache()
278+
self._token_cache = keyring_cache if keyring_cache.is_keyring_available() else _OAuth2TokenInMemoryCache()
243279
self._token_lock = threading.Lock()
244280
self._inside_oauth_attempt_lock = threading.Lock()
245281
self._inside_oauth_attempt_blocker = threading.Event()

0 commit comments

Comments
 (0)