using MarketAlly.AIPlugin; using MarketAlly.AIPlugin.DevOps.Performance; using MarketAlly.AIPlugin.DevOps.Security; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; namespace MarketAlly.AIPlugin.DevOps.Core { public abstract class BaseDevOpsPlugin : IAIPlugin { protected readonly ILogger _logger; protected readonly AnalysisCache _cache; protected readonly AuditLogger _auditLogger; protected readonly RateLimiter _rateLimiter; protected readonly CryptographicValidator _cryptoValidator; protected BaseDevOpsPlugin(ILogger logger = null) { _logger = logger; _cache = new AnalysisCache(logger as ILogger); _auditLogger = new AuditLogger(logger as ILogger); _rateLimiter = new RateLimiter(_auditLogger); _cryptoValidator = new CryptographicValidator(_auditLogger); } public abstract IReadOnlyDictionary SupportedParameters { get; } public async Task ExecuteAsync(IReadOnlyDictionary parameters) { var stopwatch = Stopwatch.StartNew(); var pluginName = GetType().Name; try { // Rate limiting check var clientId = ExtractClientId(parameters); if (!await _rateLimiter.TryExecuteAsync(clientId)) { await _auditLogger.LogSecurityEventAsync(new SecurityAuditEvent { EventType = SecurityEventType.PermissionChecked, Severity = SecuritySeverity.Medium, Source = pluginName, UserId = clientId, Details = "Rate limit exceeded" }); return new AIPluginResult( new InvalidOperationException("Rate limit exceeded. Please try again later."), "Rate limit exceeded" ); } // Log analysis start await _auditLogger.LogSecurityEventAsync(new SecurityAuditEvent { EventType = SecurityEventType.AnalysisStarted, Severity = SecuritySeverity.Low, Source = pluginName, UserId = clientId, Details = "Analysis started" }); // Validate parameters var validationResult = await ValidateParametersAsync(parameters); if (!validationResult.IsValid) { return new AIPluginResult( new ArgumentException($"Parameter validation failed: {string.Join(", ", validationResult.Errors)}"), "Parameter validation failed" ); } // Execute the actual plugin logic var result = await ExecuteInternalAsync(parameters); stopwatch.Stop(); // Log completion var issuesFound = CountIssuesInResult(result); await _auditLogger.LogAnalysisEventAsync(pluginName, GetPrimaryFilePath(parameters), issuesFound, stopwatch.Elapsed); return result; } catch (Exception ex) { stopwatch.Stop(); _logger?.LogError(ex, "Plugin execution failed: {PluginName}", pluginName); await _auditLogger.LogSecurityEventAsync(new SecurityAuditEvent { EventType = SecurityEventType.AnalysisCompleted, Severity = SecuritySeverity.High, Source = pluginName, Details = $"Analysis failed: {ex.Message}", Metadata = new Dictionary { ["error"] = ex.Message, ["executionTimeMs"] = stopwatch.ElapsedMilliseconds } }); return new AIPluginResult(ex, $"Failed to execute {pluginName}"); } } protected abstract Task ExecuteInternalAsync(IReadOnlyDictionary parameters); protected virtual async Task ValidateParametersAsync(IReadOnlyDictionary parameters) { var result = new ValidationResult(); // Basic parameter validation foreach (var supportedParam in SupportedParameters) { if (!parameters.ContainsKey(supportedParam.Key)) { // Check if parameter is required (this would need to be enhanced with attribute checking) continue; } var value = parameters[supportedParam.Key]; if (value != null && !supportedParam.Value.IsAssignableFrom(value.GetType())) { result.AddError(supportedParam.Key, $"Parameter '{supportedParam.Key}' must be of type {supportedParam.Value.Name}", "TYPE_MISMATCH"); } } return await Task.FromResult(result); } protected virtual string ExtractClientId(IReadOnlyDictionary parameters) { // In a real implementation, this would extract from authentication context return Environment.UserName ?? "anonymous"; } protected virtual string GetPrimaryFilePath(IReadOnlyDictionary parameters) { // Try common parameter names for file paths var pathKeys = new[] { "filePath", "path", "dockerfilePath", "pipelinePath", "configDirectory" }; foreach (var key in pathKeys) { if (parameters.TryGetValue(key, out var value)) { return value?.ToString() ?? "unknown"; } } return "unknown"; } protected virtual int CountIssuesInResult(AIPluginResult result) { if (!result.Success || result.Data == null) { return 0; } // This is a simplified implementation - would need to be enhanced // based on the actual result structure of each plugin var data = result.Data; var dataType = data.GetType(); // Try to find properties that might contain issues/violations var issueProperties = new[] { "SecurityIssues", "BestPracticeViolations", "ConfigurationIssues" }; var totalIssues = 0; foreach (var prop in issueProperties) { var property = dataType.GetProperty(prop); if (property?.GetValue(data) is System.Collections.ICollection collection) { totalIssues += collection.Count; } } return totalIssues; } protected async Task GetOrSetCacheAsync(string cacheKey, Func> factory, TimeSpan? expiry = null) where T : class { return await _cache.GetOrSetAsync(cacheKey, factory, expiry); } protected async Task ValidateFileIntegrityAsync(string filePath, string expectedHash = null) { return await _cryptoValidator.ValidateFileIntegrityAsync(filePath, expectedHash); } protected async Task LogSecurityIssueAsync(string issueType, string severity, string details = null) { await _auditLogger.LogSecurityIssueAsync(GetType().Name, GetType().Name, issueType, severity); } protected bool IsFilePathSafe(string filePath) { if (string.IsNullOrWhiteSpace(filePath)) { return false; } try { // Check for path traversal attempts var fullPath = Path.GetFullPath(filePath); var fileName = Path.GetFileName(fullPath); // Basic safety checks if (fileName.StartsWith("..") || filePath.Contains("..") || Path.IsPathRooted(filePath) && !Path.IsPathFullyQualified(filePath)) { return false; } return true; } catch { return false; } } protected async Task EnsureFileExistsAsync(string filePath) { if (!IsFilePathSafe(filePath)) { await LogSecurityIssueAsync("UnsafeFilePath", "High", $"Unsafe file path detected: {filePath}"); return false; } if (!File.Exists(filePath)) { _logger?.LogWarning("File not found: {FilePath}", filePath); return false; } return true; } protected async Task EnsureDirectoryExistsAsync(string directoryPath) { if (!IsFilePathSafe(directoryPath)) { await LogSecurityIssueAsync("UnsafeDirectoryPath", "High", $"Unsafe directory path detected: {directoryPath}"); return false; } if (!Directory.Exists(directoryPath)) { _logger?.LogWarning("Directory not found: {DirectoryPath}", directoryPath); return false; } return true; } public void Dispose() { // Cleanup resources if needed _cache?.Clear(); } } public class ValidationResult { public List Errors { get; } = new(); public List Warnings { get; } = new(); public bool IsValid => !Errors.Any(); public void AddError(string parameter, string message, string errorCode) { Errors.Add(new ValidationError { Parameter = parameter, Message = message, ErrorCode = errorCode }); } public void AddWarning(string parameter, string message, string warningCode) { Warnings.Add(new ValidationWarning { Parameter = parameter, Message = message, WarningCode = warningCode }); } } public class ValidationError { public string Parameter { get; set; } public string Message { get; set; } public string ErrorCode { get; set; } } public class ValidationWarning { public string Parameter { get; set; } public string Message { get; set; } public string WarningCode { get; set; } } }