2020-09-25 21:58:58 +00:00
|
|
|
"""OAuth Clients"""
|
2020-09-25 23:26:06 +00:00
|
|
|
from typing import Any, Dict, Optional
|
2020-09-25 21:58:58 +00:00
|
|
|
from urllib.parse import urlencode
|
|
|
|
|
|
|
|
from django.http import HttpRequest
|
|
|
|
from requests import Session
|
|
|
|
from requests.exceptions import RequestException
|
2020-09-25 22:34:57 +00:00
|
|
|
from requests.models import Response
|
2020-09-25 21:58:58 +00:00
|
|
|
from structlog import get_logger
|
|
|
|
|
|
|
|
from passbook import __version__
|
2020-09-25 22:34:57 +00:00
|
|
|
from passbook.sources.oauth.models import OAuthSource
|
2020-09-25 21:58:58 +00:00
|
|
|
|
|
|
|
LOGGER = get_logger()
|
|
|
|
|
|
|
|
|
|
|
|
class BaseOAuthClient:
|
|
|
|
"""Base OAuth Client"""
|
|
|
|
|
|
|
|
session: Session
|
2020-09-25 22:34:57 +00:00
|
|
|
|
2020-09-25 23:26:06 +00:00
|
|
|
source: OAuthSource
|
2020-09-25 22:34:57 +00:00
|
|
|
request: HttpRequest
|
2020-09-25 23:26:06 +00:00
|
|
|
|
2020-09-25 22:34:57 +00:00
|
|
|
callback: Optional[str]
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
self, source: OAuthSource, request: HttpRequest, callback: Optional[str] = None
|
|
|
|
):
|
2020-09-25 21:58:58 +00:00
|
|
|
self.source = source
|
|
|
|
self.session = Session()
|
2020-09-25 22:34:57 +00:00
|
|
|
self.request = request
|
|
|
|
self.callback = callback
|
|
|
|
self.session.headers.update({"User-Agent": f"passbook {__version__}"})
|
2020-09-25 21:58:58 +00:00
|
|
|
|
2020-09-25 22:34:57 +00:00
|
|
|
def get_access_token(self, **request_kwargs) -> Optional[Dict[str, Any]]:
|
2020-09-25 21:58:58 +00:00
|
|
|
"Fetch access token from callback request."
|
|
|
|
raise NotImplementedError("Defined in a sub-class") # pragma: no cover
|
|
|
|
|
|
|
|
def get_profile_info(self, token: Dict[str, str]) -> Optional[Dict[str, Any]]:
|
|
|
|
"Fetch user profile information."
|
|
|
|
try:
|
2020-09-26 12:00:48 +00:00
|
|
|
response = self.do_request("get", self.source.profile_url, token=token)
|
2020-09-25 21:58:58 +00:00
|
|
|
response.raise_for_status()
|
|
|
|
except RequestException as exc:
|
|
|
|
LOGGER.warning("Unable to fetch user profile", exc=exc)
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return response.json()
|
|
|
|
|
2020-09-25 22:34:57 +00:00
|
|
|
def get_redirect_args(self) -> Dict[str, str]:
|
2020-09-25 21:58:58 +00:00
|
|
|
"Get request parameters for redirect url."
|
|
|
|
raise NotImplementedError("Defined in a sub-class") # pragma: no cover
|
|
|
|
|
2020-09-25 22:34:57 +00:00
|
|
|
def get_redirect_url(self, parameters=None):
|
2020-09-25 21:58:58 +00:00
|
|
|
"Build authentication redirect url."
|
2020-09-25 22:34:57 +00:00
|
|
|
args = self.get_redirect_args()
|
2020-09-25 21:58:58 +00:00
|
|
|
additional = parameters or {}
|
|
|
|
args.update(additional)
|
|
|
|
params = urlencode(args)
|
|
|
|
LOGGER.info("redirect args", **args)
|
2020-09-25 22:34:57 +00:00
|
|
|
return f"{self.source.authorization_url}?{params}"
|
2020-09-25 21:58:58 +00:00
|
|
|
|
2020-09-25 23:26:06 +00:00
|
|
|
def parse_raw_token(self, raw_token: str) -> Dict[str, Any]:
|
2020-09-25 21:58:58 +00:00
|
|
|
"Parse token and secret from raw token response."
|
|
|
|
raise NotImplementedError("Defined in a sub-class") # pragma: no cover
|
|
|
|
|
2020-09-25 22:34:57 +00:00
|
|
|
def do_request(self, method: str, url: str, **kwargs) -> Response:
|
|
|
|
"""Wrapper around self.session.request, which can add special headers"""
|
|
|
|
return self.session.request(method, url, **kwargs)
|
|
|
|
|
2020-09-25 21:58:58 +00:00
|
|
|
@property
|
2020-09-25 22:34:57 +00:00
|
|
|
def session_key(self) -> str:
|
2020-09-25 21:58:58 +00:00
|
|
|
"""Return Session Key"""
|
|
|
|
raise NotImplementedError("Defined in a sub-class") # pragma: no cover
|