#!/usr/bin/env python3 # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """ Token Authentication HTTP Handlers Provides HTTP endpoints for token management including creation, revocation, listing, and statistics. Used for administrative token management in HTTP mode. """ import json from typing import Dict, Any from starlette.requests import Request from starlette.responses import JSONResponse, HTMLResponse from ..utils.logger import get_logger from ..utils.security import SecurityLevel from ..utils.config import DatabaseConfig from .token_security_middleware import TokenSecurityMiddleware class TokenHandlers: """Token Authentication HTTP Handlers""" def __init__(self, security_manager, config=None): self.security_manager = security_manager self.logger = get_logger(__name__) # Initialize security middleware if config is provided if config: self.security_middleware = TokenSecurityMiddleware(config) else: self.security_middleware = None self.logger.warning("Token handlers initialized without security middleware - access control disabled") async def handle_create_token(self, request: Request) -> JSONResponse: """Handle token creation request""" # Apply security checks if self.security_middleware: security_response = await self.security_middleware.check_token_management_access(request) if security_response: return security_response try: # Check if token manager is available if not self.security_manager.auth_provider.token_manager: return JSONResponse({ "error": "Token authentication is not enabled" }, status_code=503) # Parse request data if request.method == "GET": # GET request with query parameters query_params = dict(request.query_params) token_id = query_params.get("token_id") expires_hours_str = query_params.get("expires_hours") description = query_params.get("description", "") custom_token = query_params.get("custom_token") # Database configuration from query params db_config = None if query_params.get("db_host"): db_config = DatabaseConfig( host=query_params.get("db_host", "localhost"), port=int(query_params.get("db_port", "9030")), user=query_params.get("db_user", "root"), password=query_params.get("db_password", ""), database=query_params.get("db_database", "information_schema"), fe_http_port=int(query_params.get("db_fe_http_port", "8030")) ) else: # POST request with JSON body try: body = await request.json() except: return JSONResponse({ "error": "Invalid JSON body" }, status_code=400) token_id = body.get("token_id") expires_hours_str = body.get("expires_hours") description = body.get("description", "") custom_token = body.get("custom_token") # Database configuration from JSON body db_config = None if body.get("database_config"): db_data = body["database_config"] try: db_config = DatabaseConfig( host=db_data.get("host", "localhost"), port=int(db_data.get("port", 9030)), user=db_data.get("user", "root"), password=db_data.get("password", ""), database=db_data.get("database", "information_schema"), fe_http_port=int(db_data.get("fe_http_port", 8030)) ) except (ValueError, TypeError) as e: return JSONResponse({ "error": f"Invalid database configuration: {str(e)}" }, status_code=400) # Validate required fields if not token_id: return JSONResponse({ "error": "token_id is required" }, status_code=400) # Parse expires_hours expires_hours = None if expires_hours_str: try: expires_hours = int(expires_hours_str) except ValueError: return JSONResponse({ "error": "expires_hours must be an integer" }, status_code=400) # Create token using the actual API try: token = await self.security_manager.create_token( token_id=token_id, expires_hours=expires_hours, description=description, custom_token=custom_token, database_config=db_config ) return JSONResponse({ "success": True, "token_id": token_id, "token": token, "expires_hours": expires_hours, "description": description, "message": "Token created successfully" }) except Exception as e: self.logger.error(f"Token creation failed: {e}") return JSONResponse({ "error": f"Token creation failed: {str(e)}" }, status_code=400) except Exception as e: self.logger.error(f"Error in handle_create_token: {e}") return JSONResponse({ "error": f"Internal server error: {str(e)}" }, status_code=500) async def handle_revoke_token(self, request: Request) -> JSONResponse: """Handle token revocation request""" # Apply security checks if self.security_middleware: security_response = await self.security_middleware.check_token_management_access(request) if security_response: return security_response try: # Check if token manager is available if not self.security_manager.auth_provider.token_manager: return JSONResponse({ "error": "Token authentication is not enabled" }, status_code=503) # Get token_id from query parameters or path token_id = request.query_params.get("token_id") if not token_id and request.method == "DELETE": # Try to get from path: /token/revoke/{token_id} path_parts = str(request.url.path).split("/") if len(path_parts) >= 4: token_id = path_parts[-1] if not token_id: return JSONResponse({ "error": "token_id is required" }, status_code=400) # Revoke token success = await self.security_manager.revoke_token(token_id) if success: return JSONResponse({ "success": True, "token_id": token_id, "message": "Token revoked successfully" }) else: return JSONResponse({ "success": False, "token_id": token_id, "message": "Token not found or already revoked" }, status_code=404) except Exception as e: self.logger.error(f"Error in handle_revoke_token: {e}") return JSONResponse({ "error": f"Internal server error: {str(e)}" }, status_code=500) async def handle_list_tokens(self, request: Request) -> JSONResponse: """Handle token listing request""" # Apply security checks if self.security_middleware: security_response = await self.security_middleware.check_token_management_access(request) if security_response: return security_response try: # Check if token manager is available if not self.security_manager.auth_provider.token_manager: return JSONResponse({ "error": "Token authentication is not enabled" }, status_code=503) # Get tokens list tokens = await self.security_manager.list_tokens() return JSONResponse({ "success": True, "count": len(tokens), "tokens": tokens }) except Exception as e: self.logger.error(f"Error in handle_list_tokens: {e}") return JSONResponse({ "error": f"Internal server error: {str(e)}" }, status_code=500) async def handle_token_stats(self, request: Request) -> JSONResponse: """Handle token statistics request""" # Apply security checks if self.security_middleware: security_response = await self.security_middleware.check_token_management_access(request) if security_response: return security_response try: # Check if token manager is available if not self.security_manager.auth_provider.token_manager: return JSONResponse({ "error": "Token authentication is not enabled" }, status_code=503) # Get token statistics stats = self.security_manager.get_token_stats() return JSONResponse({ "success": True, "stats": stats }) except Exception as e: self.logger.error(f"Error in handle_token_stats: {e}") return JSONResponse({ "error": f"Internal server error: {str(e)}" }, status_code=500) async def handle_cleanup_tokens(self, request: Request) -> JSONResponse: """Handle expired tokens cleanup request""" # Apply security checks if self.security_middleware: security_response = await self.security_middleware.check_token_management_access(request) if security_response: return security_response try: # Check if token manager is available if not self.security_manager.auth_provider.token_manager: return JSONResponse({ "error": "Token authentication is not enabled" }, status_code=503) # Cleanup expired tokens cleaned_count = await self.security_manager.cleanup_expired_tokens() return JSONResponse({ "success": True, "cleaned_count": cleaned_count, "message": f"Cleaned up {cleaned_count} expired tokens" }) except Exception as e: self.logger.error(f"Error in handle_cleanup_tokens: {e}") return JSONResponse({ "error": f"Internal server error: {str(e)}" }, status_code=500) async def handle_management_page(self, request: Request) -> HTMLResponse: """Handle token management demo page""" # Apply security checks if self.security_middleware: security_response = await self.security_middleware.check_token_management_access(request) if security_response: # Convert JSON response to HTML for demo page error_data = security_response.body.decode('utf-8') if hasattr(security_response, 'body') else '{"error": "Access denied"}' try: error_info = json.loads(error_data) except: error_info = {"error": "Access denied"} error_html = f""" Access Denied - Token Management

🔐 Token Management - Access Denied

Access Denied

Error: {error_info.get('error', 'Access denied')}

Message: {error_info.get('message', 'Token management access is restricted')}

{'

Your IP: ' + str(error_info.get('client_ip', 'Unknown')) + '

' if 'client_ip' in error_info else ''}

🛡️ Security Information

Token management endpoints are protected by the following security measures:

If you need access, please:

  1. Access from the server host (127.0.0.1)
  2. Ensure HTTP token management is enabled in configuration
  3. Provide valid admin authentication
""" return HTMLResponse(error_html, status_code=security_response.status_code) try: # Check if token manager is available if not self.security_manager.auth_provider.token_manager: html_content = """ Token Management - Not Available

Token Management

Token authentication is not enabled on this server.
""" return HTMLResponse(html_content) # Get current stats for demo stats = self.security_manager.get_token_stats() html_content = f""" Doris MCP Server - Token Management

🔐 Doris MCP Server - Token Management

📊 Token Statistics

{stats.get('total_tokens', 0)}
Total Tokens
{stats.get('active_tokens', 0)}
Active Tokens
{stats.get('expired_tokens', 0)}
Expired Tokens

Token Expiry: {'Enabled' if stats.get('expiry_enabled') else 'Disabled'}

Default Expiry: {stats.get('default_expiry_hours', 0)} hours

➕ Create New Token

If not provided, a secure token will be generated automatically

🗄️ Database Configuration (Optional)

Configure database connection for this token. Leave empty to use system defaults.

📋 Token Management

Revoke Token

🔧 API Endpoints

Use these endpoints for programmatic token management:

""" return HTMLResponse(html_content) except Exception as e: self.logger.error(f"Error in handle_demo_page: {e}") error_html = f""" Token Management Error

Token Management Error

Error loading token management page: {str(e)}

""" return HTMLResponse(error_html, status_code=500)