"""This module implements the auth layer.""" # Third-party imports from requests import auth # Local imports from uplink import utils from uplink.compat import abc __all__ = [ "ApiTokenHeader", "ApiTokenParam", "BasicAuth", "BearerToken", "MultiAuth", "ProxyAuth", ] def get_auth(auth_object=None): if auth_object is None: return utils.no_op if isinstance(auth_object, abc.Iterable): return BasicAuth(*auth_object) if callable(auth_object): return auth_object raise ValueError(f"Invalid authentication strategy: {auth_object}") class ApiTokenParam: """ Authorizes requests using a token or key in a query parameter. Users may use this directly, or API library authors may subclass this to predefine the query parameter name to use. If supplying query parameter name on a subclass, define the `_param` property or attribute and override `__init__()` without using `super()`. Examples: Direct use: ```python token_param = ApiTokenParam(QUERY_PARAM_NAME, TOKEN) api_consumer = SomeApiConsumerClass(BASE_URL, auth=token_param) ``` Subclass in API library: ```python class ExampleApiTokenParam(ApiTokenParam): _param = "api-token" def __init__(self, token): self._param_value = token # using the subclass token_param = ExampleApiTokenParam(TOKEN) api_consumer = SomeApiConsumerClass(BASE_URL, auth=token_param) ``` """ def __init__(self, param, token): self._param = param self._param_value = token def __call__(self, request_builder): request_builder.info["params"][self._param] = self._param_value class ApiTokenHeader: """ Authorizes requests using a token or key in a header. Users should subclass this class to define which header is the token header. The subclass may also, optionally, define a token prefix (such as in BearerToken) Users may use this directly, or API library authors may subclass this to predefine the header name or a prefix to use. If supplying header name or prefix in a subclass, define the `_header` and/or `_prefix` properties or attributes and override `__init__()` without using `super()`. Examples: Direct use: ```python token_header = ApiTokenHeader(HEADER_NAME, TOKEN) api_consumer = SomeApiConsumerClass(BASE_URL, auth=token_header) ``` Subclass in API library with a prefix: ```python class ExampleApiTokenHeader(ApiTokenHeader): _header = "X-Api-Token" def __init__(self, token): self._token = token # using the subclass token_header = ExampleApiTokenHeader(TOKEN) api_consumer = SomeApiConsumerClass(BASE_URL, auth=token_header) ``` Subclass in API library with a prefix: ```python class ExampleApiTokenHeader(ApiTokenHeader): _header = "X-App-Id" _prefix = "APP" def __init__(self, token): self._token = token # using the subclass token_header = ExampleApiTokenHeader(TOKEN) api_consumer = SomeApiConsumerClass(BASE_URL, auth=token_header) ``` """ _header = None _prefix = None def __init__(self, header, token, prefix=None): self._header = header self._prefix = prefix self._token = token @property def _header_value(self): if self._prefix: return f"{self._prefix} {self._token}" return self._token def __call__(self, request_builder): request_builder.info["headers"][self._header] = self._header_value class BasicAuth(ApiTokenHeader): """ Authorizes requests using HTTP Basic Authentication. There are two ways to use BasicAuth with a Consumer: ```python # implicit BasicAuth github = Github(BASE_URL, auth=(USER, PASS)) # explicit BasicAuth github = GitHub(BASE_URL, auth=BasicAuth(USER, PASS)) ``` """ _header = "Authorization" def __init__(self, username, password): self._username = username self._password = password @property def _header_value(self): return auth._basic_auth_str(self._username, self._password) class ProxyAuth(BasicAuth): """ Authorizes requests with an intermediate HTTP proxy. If both API auth and intermediate Proxy auth are required, wrap ProxyAuth in MultiAuth: ```python # only ProxyAuth github = Github(BASE_URL, auth=ProxyAuth(PROXYUSER, PROXYPASS)) # both BasicAuth and ProxyAuth auth_methods = MultiAuth( BasicAuth(USER, PASS), ProxyAuth(PROXYUSER, PROXYPASS) ) github = GitHub(BASE_URL, auth=auth_methods) ``` """ _header = "Proxy-Authorization" class BearerToken(ApiTokenHeader): """ Authorizes requests using a Bearer Token. ```python token = something_like_oauth_that_returns_a_token() bearer = BearerToken(token) api_consumer = SomeApiConsumerClass(BASE_URL, auth=bearer) ``` """ _header = "Authorization" _prefix = "Bearer" def __init__(self, token): self._token = token class MultiAuth: """ Authorizes requests using multiple auth methods at the same time. This is useful for API users to supply both API credentials and intermediary credentials (such as for a proxy). ```python auth_methods = MultiAuth( BasicAuth(USER, PASS), ProxyAuth(PROXY_USER, PROXY_PASS) ) api_consumer = SomeApiConsumerClass(BASE_URL, auth=auth_methods) ``` This may also be used if an API requires multiple Auth Tokens. ```python auth_methods = MultiAuth( BearerToken(API_TOKEN), ApiTokenParam(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE), ApiTokenHeader(API_HEADER_NAME, API_TOKEN_2) ) api_consumer = SomeApiConsumerClass(BASE_URL, auth=auth_methods) ``` API library authors may find it more helpful to treat MultiAuth as a list using `append` or `extend` to add aditional auth methods. ```python auth_methods = MultiAuth() auth_methods.append(BearerToken(API_TOKEN)) auth_methods.extend([ ApiTokenParam(QUERY_PARAMETER_NAME, QUERY_PARAMETER_VALUE), ApiTokenHeader(API_HEADER_NAME, API_TOKEN_2) ]) api_consumer = SomeApiConsumerClass(BASE_URL, auth=auth_methods) # looping over contained auth methods is also supported for method in auth_methods: print(method.__class__.__name__) ``` """ def __init__(self, *auth_methods): self._auth_methods = [get_auth(auth_method) for auth_method in auth_methods] def __call__(self, request_builder): for auth_method in self._auth_methods: auth_method(request_builder) def __getitem__(self, index): return self._auth_methods[index] def __len__(self): return len(self._auth_methods) def append(self, auth_method): self._auth_methods.append(get_auth(auth_method)) def extend(self, auth_methods): self._auth_methods.extend( [get_auth(auth_method) for auth_method in auth_methods] )