Source code for argus_api.helpers.authentication
import getpass
import hmac
from time import time
from hashlib import sha256
from base64 import urlsafe_b64encode, b64decode
from urllib.parse import urlparse
from requests import post
from argus_api import __version__
from argus_cli.settings import settings
[docs]def csrf_token_for(url: str, key: str) -> str:
"""Generates a CSRF token for an API URL
:param url: URL endpoint
:param key: User's session key
:returns: CSRF token
"""
# Parse the URL into its components
u = urlparse(url)
# Create the original path, i.e https://domain.tld/path
path = "%s://%s/%s" % (u.scheme, u.netloc, u.path)
# Decode the session key
session_key = b64decode(key)
canonical_string = """{"alg": "HS256", "typ": "JWT"}.{"url": "%s", "timestamp": "%s"}""" % (path, int(round(time()*1000)))
# Sign the URL, timestamp,
signature = hmac.new(session_key, msg=canonical_string.encode("utf-8"), digestmod=sha256).digest()
token = "%s.%s" % (canonical_string, urlsafe_b64encode(signature))
return token
[docs]def with_api_key(api_key: str) -> dict:
"""Authenticates the user towards the API with an API key
:param api_key: User's API key
:return: Authentication headers
"""
# TODO: Check if the API Key is valid for the user
return {
"Argus-API-Key": api_key
}
[docs]def with_credentials(username: str = "", password: str = "", method: str = "ldap", base_url: str = None) -> callable:
"""Authenticates the user with the API via TOTP, Radius or LDAP, and returns a function
that creates the correct headers with CSRF token for a given URL, using the user's
sessionKey and cookie.
:param base_url: API URL
:param method: Authentication method
:param username: The user's username
:param password: The user's password
:return: Authentication data
"""
base_url = base_url or settings["api"]["api_url"]
auth_url = "%s/authentication/v1/%s/authenticate" % (base_url, method)
parameters = {
"userName": username,
"password": password
}
if not username:
parameters["userName"] = input("Username")
if not password:
parameters["password"] = getpass.getpass("Enter %spassword:" % ("AD3 " if method == "ldap" else ""))
if method == "totp":
parameters["tokenCode"] = input("Enter TOTP token code:")
elif method == "radius":
parameters["tokenCode"] = input("Enter Radius token code:")
parameters["mode"] = "AUTHENTICATION"
elif method not in ("ldap", "password"):
raise ValueError("Invalid authentication method: %s" % method)
response = post(auth_url, json=parameters)
# Accept both 200 and 201 response
if response.status_code in (200, 201):
authentication_data = response.json()["data"]
def authenticate(url: str) -> dict:
"""Authenticator function, generates a CSRF token for the given URL
:param str url: URL to create CSRF token for
:returns: Headers to pass to a request
"""
return {
"Cookie": "argus_username=%s; argus_cookie=%s;" % (username, authentication_data["cookie"]),
"Argus-CSRF-Token": csrf_token_for(url, authentication_data["sessionKey"])
}
return authenticate
else:
print("Authentication failed %d" % response.status_code)
# Return empty headers — failed authentication does not need to raise
# any further errors.
return lambda x: {}
[docs]def with_authentication(mode: str = "ldap", base_url: str = None, username: str = "", api_key: str = "") -> callable:
"""Creates a decorator that can be used by plugins to have authentication handled for them,
by calling with_credentials or with_api_key and then passing the authentication headers
to the decorated function as `authentication`.
Generated API methods accept `authentication` parameter, so after decorating a function
with this, the authentication argument can be passed directly to any API method.
:param str mode: LDAP, Radius, Password or API key
:returns: decorator function
"""
base_url = base_url or settings["api"]["api_url"]
def decorator(function):
"""Authenticate with {mode}""".format(mode=mode)
def authenticate(*args, **kwargs):
"""Returns a callable that can be used to create authentication headers for a URL,
for LDAP, TOTP, Password or API key authentication. This will be passed in to the function
as authentication=function, and can be passed to any API call
"""
if mode.lower() not in ('apikey', 'api_key'):
kwargs.update({ "authentication": with_credentials(method=mode, username=username, base_url=base_url) })
else:
kwargs.update({
"authentication": lambda url: with_api_key(api_key) if api_key else input("Enter Argus API key")
})
return function(*args, **kwargs)
return authenticate
return decorator