MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.DevOps/Core/BaseDevOpsPlugin.cs

315 lines
11 KiB
C#
Executable File

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<AnalysisCache>);
_auditLogger = new AuditLogger(logger as ILogger<AuditLogger>);
_rateLimiter = new RateLimiter(_auditLogger);
_cryptoValidator = new CryptographicValidator(_auditLogger);
}
public abstract IReadOnlyDictionary<string, Type> SupportedParameters { get; }
public async Task<AIPluginResult> ExecuteAsync(IReadOnlyDictionary<string, object> 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<string, object>
{
["error"] = ex.Message,
["executionTimeMs"] = stopwatch.ElapsedMilliseconds
}
});
return new AIPluginResult(ex, $"Failed to execute {pluginName}");
}
}
protected abstract Task<AIPluginResult> ExecuteInternalAsync(IReadOnlyDictionary<string, object> parameters);
protected virtual async Task<ValidationResult> ValidateParametersAsync(IReadOnlyDictionary<string, object> 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<string, object> parameters)
{
// In a real implementation, this would extract from authentication context
return Environment.UserName ?? "anonymous";
}
protected virtual string GetPrimaryFilePath(IReadOnlyDictionary<string, object> 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<T> GetOrSetCacheAsync<T>(string cacheKey, Func<Task<T>> factory, TimeSpan? expiry = null) where T : class
{
return await _cache.GetOrSetAsync(cacheKey, factory, expiry);
}
protected async Task<bool> 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<bool> 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<bool> 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<ValidationError> Errors { get; } = new();
public List<ValidationWarning> 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; }
}
}