""" Authorization module for environment-based user access control. This module manages user authorization based on Auth0 email addresses and environment settings (dev/qa/prod). """ import csv import os from io import StringIO from typing import Dict, Tuple def _get_csv_path() -> str: """Get the path to the allowed users CSV file.""" return os.path.join(os.path.dirname(__file__), "allowed_auth0_users.csv") def _read_csv_file(csv_path: str) -> str: """ Read CSV file contents. Args: csv_path: Path to CSV file Returns: CSV file contents as string Raises: FileNotFoundError: If file does not exist """ if not os.path.exists(csv_path): raise FileNotFoundError( f"User authorization file not found: {csv_path}. " "Please ensure src/allowed_auth0_users.csv exists." ) with open(csv_path, "r") as file: return file.read() def _parse_csv_content(csv_content: str) -> Dict[str, Dict[str, bool]]: """ Parse CSV content and return user authorization dictionary. Args: csv_content: CSV file contents as string Returns: Dict mapping email addresses to access permissions: { "user@example.com": { "qa_access": True, "prod_access": False } } Raises: ValueError: If CSV format is invalid """ users = {} try: reader = csv.DictReader(StringIO(csv_content)) # Validate required columns required_columns = {"EMAIL", "QA_ACCESS", "PROD_ACCESS"} if not required_columns.issubset(set(reader.fieldnames or [])): raise ValueError( f"CSV must contain columns: {required_columns}. " f"Found: {reader.fieldnames}" ) for row in reader: email = row.get("EMAIL", "").strip().lower() qa_access = row.get("QA_ACCESS", "").strip().lower() == "true" prod_access = row.get("PROD_ACCESS", "").strip().lower() == "true" if email: # Skip empty rows users[email] = {"qa_access": qa_access, "prod_access": prod_access} except csv.Error as e: raise ValueError(f"Error parsing CSV file: {e}") return users def load_allowed_users() -> Dict[str, Dict[str, bool]]: """ Load allowed users from CSV file. Returns: Dict mapping email addresses to access permissions: { "user@example.com": { "qa_access": True, "prod_access": False } } Raises: FileNotFoundError: If allowed_auth0_users.csv is not found ValueError: If CSV format is invalid """ csv_path = _get_csv_path() csv_content = _read_csv_file(csv_path) return _parse_csv_content(csv_content) def is_user_authorized(email: str, environment: str) -> Tuple[bool, str]: """ Check if a user is authorized to access the app in the given environment. Authorization rules: - For production (environment="prod"): User must have PROD_ACCESS=true - For non-production (dev, qa, etc.): User must have QA_ACCESS=true Args: email: User's email address from Auth0 environment: Current environment (dev, qa, prod, etc.) Returns: Tuple of (is_authorized: bool, reason: str) - (True, "Authorized") if user has access - (False, "reason for denial") if user does not have access Examples: >>> is_user_authorized("user@example.com", "dev") (True, "Authorized for dev environment") >>> is_user_authorized("unknown@example.com", "prod") (False, "User not found in authorization list") """ # Normalize inputs email = email.strip().lower() environment = environment.strip().lower() try: users = load_allowed_users() except (FileNotFoundError, ValueError) as e: return False, f"Authorization system error: {str(e)}" # Check if user exists if email not in users: return False, "User not found in authorization list" user_access = users[email] # Check environment-specific access if environment == "prod": if user_access["prod_access"]: return True, "Authorized for production environment" else: return ( False, "User does not have production access. Contact administrator for access.", ) else: # Non-production environments (dev, qa, etc.) if user_access["qa_access"]: return True, f"Authorized for {environment} environment" else: return ( False, "User does not have non-production access. Contact administrator for access.", )