Skip to main content
Glama
btp_utils.py8.25 kB
""" BTP (Business Technology Platform) utilities for SAP authentication and configuration. Provides functionality to parse BTP service keys and generate .env files. """ import json import os import requests from typing import Dict, Any, Optional from urllib.parse import urljoin class BtpServiceKey: """Represents a BTP service key with authentication capabilities.""" def __init__(self, service_key_data: Dict[str, Any]): """ Initialize BTP service key from parsed JSON data. Args: service_key_data: Dictionary containing service key information """ self.data = service_key_data self.credentials = service_key_data.get("credentials", {}) @classmethod def from_json_file(cls, file_path: str) -> "BtpServiceKey": """Load service key from JSON file.""" with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) return cls(data) @classmethod def from_json_string(cls, json_string: str) -> "BtpServiceKey": """Load service key from JSON string.""" data = json.loads(json_string) return cls(data) @property def abap_endpoint(self) -> str: """Get the ABAP system endpoint URL.""" # Support both service key format and destination format if "credentials" in self.data: # Standard BTP service key format return self.credentials.get("url", "") else: # Destination format (like sk.json) return self.data.get("url", "") or self.data.get("endpoints", {}).get("abap", "") @property def oauth_url(self) -> str: """Get the OAuth2 token endpoint URL.""" # Support both service key format and destination format if "credentials" in self.data: # Standard BTP service key format return self.credentials.get("uaa", {}).get("url", "") else: # Destination format (like sk.json) return self.data.get("uaa", {}).get("url", "") @property def client_id(self) -> str: """Get the OAuth2 client ID.""" # Support both service key format and destination format if "credentials" in self.data: # Standard BTP service key format return self.credentials.get("uaa", {}).get("clientid", "") else: # Destination format (like sk.json) return self.data.get("uaa", {}).get("clientid", "") @property def client_secret(self) -> str: """Get the OAuth2 client secret.""" # Support both service key format and destination format if "credentials" in self.data: # Standard BTP service key format return self.credentials.get("uaa", {}).get("clientsecret", "") else: # Destination format (like sk.json) return self.data.get("uaa", {}).get("clientsecret", "") @property def username(self) -> Optional[str]: """Get username if available in credentials.""" return self.credentials.get("username") @property def password(self) -> Optional[str]: """Get password if available in credentials.""" return self.credentials.get("password") class BtpTokenManager: """Manages JWT token authentication for BTP services.""" def __init__(self, service_key: BtpServiceKey, username: str, password: str): """ Initialize token manager with service key and user credentials. Args: service_key: BTP service key instance username: BTP user username password: BTP user password """ self.service_key = service_key self.username = username self.password = password self._token = None self._token_expiry = None def get_token(self, force_refresh: bool = False) -> str: """ Get a valid JWT token, refreshing if necessary. Args: force_refresh: Force token refresh even if current token is valid Returns: Valid JWT token string """ if force_refresh or not self._is_token_valid(): self._refresh_token() return self._token def _is_token_valid(self) -> bool: """Check if current token is valid and not expired.""" if not self._token: return False # For simplicity, we'll refresh tokens proactively # In production, you'd decode the JWT and check expiry return False def _refresh_token(self) -> None: """Refresh the JWT token using OAuth2 flow.""" token_url = urljoin(self.service_key.oauth_url, "/oauth/token") auth = (self.service_key.client_id, self.service_key.client_secret) data = { "grant_type": "password", "username": self.username, "password": self.password } response = requests.post(token_url, auth=auth, data=data) response.raise_for_status() token_data = response.json() self._token = token_data["access_token"] def generate_env_from_service_key( service_key: BtpServiceKey, username: str, password: str, output_file: str, use_jwt: bool = True, verify_ssl: bool = True, timeout: int = 30 ) -> None: """ Generate a .env file from BTP service key. Args: service_key: BTP service key instance username: BTP username for authentication password: BTP password for authentication output_file: Path to output .env file use_jwt: Whether to use JWT authentication (True) or basic auth (False) verify_ssl: Whether to verify SSL certificates timeout: Request timeout in seconds """ env_content = [] # Basic connection settings env_content.append(f"SAP_URL={service_key.abap_endpoint}") env_content.append(f"SAP_VERIFY_SSL={str(verify_ssl).lower()}") env_content.append(f"SAP_TIMEOUT={timeout}") if use_jwt: # JWT authentication setup env_content.append("SAP_AUTH_TYPE=jwt") # Get JWT token try: token_manager = BtpTokenManager(service_key, username, password) jwt_token = token_manager.get_token() env_content.append(f"SAP_JWT_TOKEN={jwt_token}") except Exception as e: print(f"Warning: Could not obtain JWT token: {e}") print("Falling back to basic authentication configuration...") use_jwt = False if not use_jwt: # Basic authentication setup env_content.append("SAP_AUTH_TYPE=basic") env_content.append(f"SAP_USER={username}") env_content.append(f"SAP_PASS={password}") # Note: SAP_CLIENT would need to be provided separately for on-premise systems env_content.append("# SAP_CLIENT=100 # Add client number for on-premise systems") # Additional BTP-specific settings env_content.extend([ "", "# BTP Service Key Information (for reference)", f"# OAuth URL: {service_key.oauth_url}", f"# Client ID: {service_key.client_id}", "# Client Secret: [REDACTED]", "" ]) # Write to file with open(output_file, 'w', encoding='utf-8') as f: f.write('\n'.join(env_content)) print(f"Generated .env file: {output_file}") print(f"Authentication type: {'JWT' if use_jwt else 'Basic'}") def parse_service_key_file(file_path: str) -> BtpServiceKey: """ Parse a BTP service key file and return a BtpServiceKey instance. Args: file_path: Path to the service key JSON file Returns: BtpServiceKey instance """ return BtpServiceKey.from_json_file(file_path) def parse_service_key_string(json_string: str) -> BtpServiceKey: """ Parse a BTP service key JSON string and return a BtpServiceKey instance. Args: json_string: Service key as JSON string Returns: BtpServiceKey instance """ return BtpServiceKey.from_json_string(json_string)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/YahorNovik/mcp-adt'

If you have feedback or need assistance with the MCP directory API, please join our Discord server