Source code for intranet.apps.auth.backends

import enum
import logging

import pam
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
from prometheus_client import Counter, Summary

logger = logging.getLogger(__name__)

pam_authenticate = Summary("intranet_pam_authenticate", "PAM authentication requests")
pam_authenticate_failures = Counter("intranet_pam_authenticate_failures", "Number of failed PAM authentication attempts")
pam_authenticate_post_failures = Counter(
    "intranet_pam_authenticate_post_failures",
    "Number of PAM authentication attempts that failed even though a ticket was successfully obtained (for example, if the user object does not "
    "exist)",
)


[docs]class PamAuthenticationResult(enum.Enum): FAILURE = 0 # Authentication failed SUCCESS = 1 # Authentication succeeded EXPIRED = -1 # Password expired; needs reset
[docs]class PamAuthenticationBackend: """Authenticate using PAM. This is the default authentication backend. """
[docs] @staticmethod def pam_auth_timeout_handle(username, realm): """Check if the user exists before we throw an error.""" try: u = get_user_model().objects.get(username__iexact=username) except get_user_model().DoesNotExist: logger.warning("pam_auth timed out for %s@%s (invalid user)", username, realm) return logger.critical( "pam_auth timed out for %s", realm, extra={ "stack": True, "data": {"username": username}, "sentry.interfaces.User": {"id": u.id, "username": username, "ip_address": "127.0.0.1"}, }, )
[docs] @staticmethod def pam_auth(username, password): """Attempts to authenticate a user against PAM. Args: username The username. password The password. Returns: Boolean indicating success or failure of PAM authentication """ # We should not try to authenticate with an empty password if password == "": return PamAuthenticationResult.FAILURE, False realm = settings.CSL_REALM pam_authenticator = pam.pam() full_username = f"{username}@{realm}" result = pam_authenticator.authenticate(full_username, password) if result: result = PamAuthenticationResult.SUCCESS logger.debug("PAM authorized %s@%s", username, realm) else: logger.debug("PAM failed to authorize %s", username) result = PamAuthenticationResult.FAILURE if "authentication token is no longer valid" in pam_authenticator.reason.lower(): result = PamAuthenticationResult.EXPIRED logger.debug("Password for %s@%s expired, needs reset", username, realm) if "timeout" in pam_authenticator.reason.lower(): PamAuthenticationBackend.pam_auth_timeout_handle(username, realm) return result
[docs] @pam_authenticate.time() def authenticate(self, request, username=None, password=None): """Authenticate a username-password pair. Creates a new user if one is not already in the database. Args: username The username of the `User` to authenticate. password The password of the `User` to authenticate. Returns: `User` """ if not isinstance(username, str): return None # remove all non-alphanumerics username = username.lower() result = self.pam_auth(username, password) if result == PamAuthenticationResult.SUCCESS: logger.debug("Authentication successful") try: user = get_user_model().objects.get(username__iexact=username) except get_user_model().DoesNotExist: pam_authenticate_failures.inc() pam_authenticate_post_failures.inc() return None return user elif result == PamAuthenticationResult.EXPIRED: user, _ = get_user_model().objects.get_or_create(username="RESET_PASSWORD", user_type="service", id=999999) return user else: pam_authenticate_failures.inc() return None
[docs] def get_user(self, user_id): """Returns a user, given his or her user id. Required for a custom authentication backend. Args: user_id The user id of the user to fetch. Returns: User or None """ try: return get_user_model().objects.get(id=user_id) except get_user_model().DoesNotExist: return None
[docs]class MasterPasswordAuthenticationBackend: """Authenticate as any user against a master password whose hash is in secret.py."""
[docs] def authenticate(self, request, username=None, password=None): """Authenticate a username-password pair. Creates a new user if one is not already in the database. Args: username The username of the `User` to authenticate. password The master password. Returns: `User` """ if not hasattr(settings, "MASTER_PASSWORD"): logging.debug("Master password not set.") return None if check_password(password, settings.MASTER_PASSWORD): try: user = get_user_model().objects.get(username__iexact=username) except get_user_model().DoesNotExist: if settings.MASTER_NOTIFY: logger.critical("Master password authentication FAILED due to invalid username %s", username) logger.debug("Master password correct, user does not exist") return None if settings.MASTER_NOTIFY: logger.critical("Master password authentication SUCCEEDED with username %s", username) logger.debug("Authentication with master password successful") return user logger.debug("Master password authentication failed") return None
[docs] def get_user(self, user_id): """Returns a user, given his or her user id. Required for a custom authentication backend. Args: user_id The user id of the user to fetch. Returns: User or None """ try: return get_user_model().objects.get(id=user_id) except get_user_model().DoesNotExist: return None