Authorization Module
This module provides a flexible authorization system for the SK Agents framework. It implements a factory pattern to dynamically load and manage request authorization mechanisms, with support for pluggable authorization strategies.
Architecture Overview
The authorization module follows a factory pattern with an abstract base class that defines the authorization interface. This design allows for easy extension and customization of authorization mechanisms while maintaining a consistent API.
Files and Classes
__init__.py
Empty initialization file that marks this directory as a Python package.
request_authorizer.py
RequestAuthorizer (Abstract Base Class)
An abstract base class that defines the contract for all authorization implementations.
Purpose: Provides a standardized interface for authorization mechanisms across the application.
Key Method:
authorize_request(auth_header: str) -> str: Abstract method that validates an authorization header and returns a unique user identifier.
Parameters:
auth_header: The value of the 'Authorization' HTTP header (typically "Bearer token" format)
Returns:
- A unique string identifier for the authenticated user (e.g., user ID, username, email)
Raises:
ValueError: For missing, malformed, or invalid authorization headersAuthenticationError: (Optional) For authentication failures in implementations
Usage: All custom authorization implementations must inherit from this class and implement the authorize_request method.
dummy_authorizer.py
DummyAuthorizer (Concrete Implementation)
A simple test/development implementation of the RequestAuthorizer interface.
Purpose: Provides a no-op authorization mechanism for development, testing, or scenarios where authentication is not required.
Behavior:
- Always returns "dummyuser" regardless of the input authorization header
- Does not perform any actual validation or authentication
- Useful for development environments or testing scenarios
Implementation:
Use Cases:
- Local development without authentication setup
- Testing environments
- Placeholder implementation during development
singleton.py
Singleton (Metaclass)
A thread-safe implementation of the Singleton design pattern using a metaclass.
Purpose: Ensures that only one instance of a class exists throughout the application lifecycle while being thread-safe.
Features:
- Thread Safety: Uses
threading.Lock()to prevent race conditions in multi-threaded environments - Instance Management: Maintains a dictionary of class instances (
_instances) - Metaclass Implementation: Implemented as a metaclass (
ABCMetasubclass) for clean integration
Key Components:
_instances: Class-level dictionary storing singleton instances_lock: Threading lock for thread-safe instance creation__call__: Overridden method that controls instance creation
Usage: Classes that need singleton behavior inherit this as their metaclass:
Thread Safety: The implementation ensures that even in multi-threaded environments, only one instance of each class is created.
authorizer_factory.py
AuthorizerFactory (Singleton Factory)
A factory class that dynamically loads and creates authorization implementations based on configuration.
Purpose: Provides a centralized way to create and manage authorization instances while supporting dynamic loading of custom authorization implementations.
Design Pattern: Factory pattern combined with Singleton pattern for application-wide consistency.
Key Features:
- Dynamic Loading: Loads authorization classes from modules specified by file paths in configuration
- Type Safety: Validates that loaded classes are proper
RequestAuthorizersubclasses - Configuration-Driven: Uses environment variables to determine which authorization implementation to use
- Singleton: Ensures consistent authorization behavior across the application
Constructor Parameters:
app_config: AnAppConfiginstance containing application configuration
Key Methods:
get_authorizer() -> RequestAuthorizer
Returns an instance of the configured authorization class.
Returns: A configured RequestAuthorizer implementation
_get_authorizer_config() -> tuple[str, str] (Private)
Retrieves the module and class names from configuration.
Returns: Tuple containing (module_name, class_name)
Raises:
ValueError: If required environment variables are not set
Configuration Requirements:
TA_AUTHORIZER_MODULE: Environment variable specifying the file path to the module containing the authorization class (e.g.,src/sk_agents/authorization/dummy_authorizer.py)TA_AUTHORIZER_CLASS: Environment variable specifying the authorization class name
Error Handling:
ImportError: Raised if the specified module cannot be loaded or the class is not foundTypeError: Raised if the loaded class is not a subclass ofRequestAuthorizerValueError: Raised if required configuration is missing
Usage Example:
# Configuration (environment variables)
TA_AUTHORIZER_MODULE = "src/sk_agents/authorization/dummy_authorizer.py"
TA_AUTHORIZER_CLASS = "DummyAuthorizer"
# Usage
factory = AuthorizerFactory(app_config)
authorizer = factory.get_authorizer()
user_id = await authorizer.authorize_request("Bearer token123")
Configuration
The authorization system is configured through environment variables:
TA_AUTHORIZER_MODULE: Specifies the file path to the Python module containing the authorization implementation (e.g.,src/sk_agents/authorization/dummy_authorizer.py)TA_AUTHORIZER_CLASS: Specifies the class name within the module that implements authorization
Usage Patterns
1. Using the Default (Dummy) Authorization
# Set environment variables
TA_AUTHORIZER_MODULE = "src/sk_agents/authorization/dummy_authorizer.py"
TA_AUTHORIZER_CLASS = "DummyAuthorizer"
# Create factory and get authorizer
factory = AuthorizerFactory(app_config)
authorizer = factory.get_authorizer()
2. Implementing Custom Authorization
# Create custom authorizer
class MyCustomAuthorizer(RequestAuthorizer):
async def authorize_request(self, auth_header: str) -> str:
# Custom validation logic
if not auth_header.startswith("Bearer "):
raise ValueError("Invalid authorization header format")
token = auth_header[7:] # Remove "Bearer " prefix
# Validate token and return user ID
return validate_and_extract_user_id(token)
# Configure to use custom authorizer
TA_AUTHORIZER_MODULE = "my_module/custom_auth.py"
TA_AUTHORIZER_CLASS = "MyCustomAuthorizer"
3. Integration in Web Applications
# In request handlers
async def protected_endpoint(request):
auth_header = request.headers.get("Authorization")
if not auth_header:
raise ValueError("Authorization header required")
factory = AuthorizerFactory(app_config)
authorizer = factory.get_authorizer()
user_id = await authorizer.authorize_request(auth_header)
# Proceed with authorized request
return handle_request_for_user(user_id)
Design Benefits
- Flexibility: Easy to swap authorization mechanisms without code changes
- Extensibility: Simple to add new authorization strategies
- Testability: Dummy implementation available for testing
- Configuration-Driven: No hardcoded authorization logic
- Thread Safety: Singleton implementation ensures consistent behavior
- Type Safety: Factory validates loaded classes at runtime
Dependencies
ska_utils.AppConfig: For configuration managementska_utils.ModuleLoader: For dynamic module loadingsk_agents.configs: For configuration constantsthreading: For thread-safe singleton implementationabc: For abstract base class definition
Thread Safety
The module is designed to be thread-safe:
- The
Singletonmetaclass uses threading locks to prevent race conditions - The
AuthorizerFactoryis a singleton, ensuring consistent authorization across threads - Authorization instances can be safely shared across multiple threads
Error Handling
The module provides comprehensive error handling:
- Configuration Errors: Clear messages for missing environment variables
- Import Errors: Detailed error messages for module loading failures
- Type Errors: Validation that loaded classes implement the correct interface
- Authorization Errors: Proper propagation of authentication failures