mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
- auth.sql: user lookups, counts, register/mirror inserts, profile update - security.sql: access EXISTS, vulnerability list/metrics/status, framework list, latest scan/report, project-id guards - NULL description/service_id rows stay filtered, matching the original scan-error-and-skip behavior - auth_users probe and filtered audit-log queries stay raw: one is a Better Auth table outside the schema, the other is dynamically built - TestSecurityLiveInvariants covers the access predicate, severity ordering, upsert conflict path, and latest-row lookups against live Postgres Authored By: TDvorak <[email protected]>
675 lines
20 KiB
Go
675 lines
20 KiB
Go
package api
|
|
|
|
import (
|
|
"containr/internal/database"
|
|
"containr/internal/database/sqlcdb"
|
|
"containr/internal/security"
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// SecurityHandler handles security-related API endpoints
|
|
type SecurityHandler struct {
|
|
db *database.DB
|
|
scanner *security.Scanner
|
|
complianceManager *security.ComplianceManager
|
|
encryptionManager *security.EncryptionManager
|
|
dataRetentionManager *security.DataRetentionManager
|
|
auditLogger *security.AuditLogger
|
|
}
|
|
|
|
// NewSecurityHandler creates a new security handler
|
|
func NewSecurityHandler(db *database.DB, encryptionKey string) *SecurityHandler {
|
|
encryptionManager, _ := security.NewEncryptionManager(encryptionKey)
|
|
|
|
scanner := security.NewScanner(db)
|
|
// Notify the project owner when a background scan completes — criticals
|
|
// earn a distinct title so they stand out in the notification list.
|
|
scanner.OnScanComplete = func(scan *security.SecurityScan) {
|
|
ownerID, err := sqlcdb.New(db.DB).GetProjectOwnerText(context.Background(), uuid.MustParse(scan.ProjectID))
|
|
if err != nil || ownerID == "" {
|
|
return
|
|
}
|
|
title := "Security scan completed"
|
|
if scan.Summary.Critical > 0 || scan.Summary.High > 0 {
|
|
title = "Security scan found issues"
|
|
}
|
|
insertUserNotification(db, ownerID, "security", title,
|
|
fmt.Sprintf("%s scan finished: %d critical, %d high, %d total findings (score %d/100).",
|
|
scan.ScanType, scan.Summary.Critical, scan.Summary.High, scan.Summary.Total, scan.Summary.Score),
|
|
"project", scan.ProjectID)
|
|
}
|
|
|
|
return &SecurityHandler{
|
|
db: db,
|
|
scanner: scanner,
|
|
complianceManager: security.NewComplianceManager(db),
|
|
encryptionManager: encryptionManager,
|
|
dataRetentionManager: security.NewDataRetentionManager(encryptionManager),
|
|
auditLogger: security.NewAuditLogger(encryptionManager, db),
|
|
}
|
|
}
|
|
|
|
// StartSecurityScan starts a new security scan
|
|
func (sh *SecurityHandler) StartSecurityScan(c *gin.Context) {
|
|
var req struct {
|
|
ProjectID string `json:"project_id" binding:"required"`
|
|
ServiceID string `json:"service_id,omitempty"`
|
|
ScanType string `json:"scan_type" binding:"required,oneof=dependency configuration comprehensive"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
userID, ok := sh.requireProjectAccess(c, req.ProjectID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if req.ServiceID != "" {
|
|
if _, err := uuid.Parse(req.ServiceID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid service ID"})
|
|
return
|
|
}
|
|
|
|
serviceExists, err := sqlcdb.New(sh.db.DB).ServiceExistsInProject(context.Background(), sqlcdb.ServiceExistsInProjectParams{
|
|
ID: uuid.MustParse(req.ServiceID),
|
|
ProjectID: uuid.MustParse(req.ProjectID),
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to validate service"})
|
|
return
|
|
}
|
|
|
|
if !serviceExists {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Service not found in project"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Log audit event
|
|
sh.auditLogger.LogSecurityEvent(userID, req.ProjectID, "security_scan_started", "project",
|
|
map[string]interface{}{
|
|
"project_id": req.ProjectID,
|
|
"service_id": req.ServiceID,
|
|
"scan_type": req.ScanType,
|
|
}, c.ClientIP(), c.GetHeader("User-Agent"), true)
|
|
|
|
scan, err := sh.scanner.StartSecurityScan(req.ProjectID, req.ServiceID, req.ScanType)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to start security scan"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusAccepted, scan)
|
|
}
|
|
|
|
// GetSecurityScan retrieves a security scan
|
|
func (sh *SecurityHandler) GetSecurityScan(c *gin.Context) {
|
|
scanID := firstPathParam(c, "scanId", "id")
|
|
if !sh.requireSecurityScanAccess(c, scanID) {
|
|
return
|
|
}
|
|
|
|
scan, err := sh.scanner.GetSecurityScan(scanID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Security scan not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, scan)
|
|
}
|
|
|
|
// GetProjectSecurityHistory retrieves security scan history for a project
|
|
func (sh *SecurityHandler) GetProjectSecurityHistory(c *gin.Context) {
|
|
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
|
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
|
return
|
|
}
|
|
|
|
limit := 10
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 1000 {
|
|
limit = parsedLimit
|
|
}
|
|
}
|
|
|
|
scans, err := sh.scanner.GetProjectSecurityHistory(projectID, limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get security history"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"scans": scans})
|
|
}
|
|
|
|
// GetVulnerabilities retrieves vulnerabilities for a project
|
|
func (sh *SecurityHandler) GetVulnerabilities(c *gin.Context) {
|
|
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
|
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
|
return
|
|
}
|
|
|
|
// Query vulnerabilities
|
|
rows, err := sqlcdb.New(sh.db.DB).ListVulnerabilities(context.Background(), uuid.MustParse(projectID))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get vulnerabilities"})
|
|
return
|
|
}
|
|
|
|
var vulnerabilities []security.Vulnerability
|
|
for _, row := range rows {
|
|
// NULL description/service_id failed the original scan and dropped
|
|
// the row — keep the same filter.
|
|
if !row.Description.Valid || !row.ServiceID.Valid {
|
|
continue
|
|
}
|
|
vulnerabilities = append(vulnerabilities, security.Vulnerability{
|
|
ID: row.ID.String(),
|
|
Type: row.Type,
|
|
Severity: row.Severity,
|
|
Title: row.Title,
|
|
Description: row.Description.String,
|
|
ServiceID: row.ServiceID.UUID.String(),
|
|
Status: row.Status,
|
|
FoundAt: row.FoundAt,
|
|
ResolvedAt: timePtrNT(row.ResolvedAt),
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"vulnerabilities": vulnerabilities})
|
|
}
|
|
|
|
// UpdateVulnerability updates a vulnerability status
|
|
func (sh *SecurityHandler) UpdateVulnerability(c *gin.Context) {
|
|
vulnID := firstPathParam(c, "vulnId", "id")
|
|
userID, ok := sh.requireVulnerabilityAccess(c, vulnID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Status string `json:"status" binding:"required,oneof=open resolved ignored"`
|
|
Notes string `json:"notes,omitempty"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var resolvedAt *time.Time
|
|
if req.Status == "resolved" {
|
|
now := time.Now()
|
|
resolvedAt = &now
|
|
}
|
|
|
|
err := sqlcdb.New(sh.db.DB).UpdateVulnerabilityStatus(context.Background(), sqlcdb.UpdateVulnerabilityStatusParams{
|
|
Status: req.Status,
|
|
ResolvedAt: ntPtr(resolvedAt),
|
|
ID: uuid.MustParse(vulnID),
|
|
})
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update vulnerability"})
|
|
return
|
|
}
|
|
|
|
// Log audit event
|
|
sh.auditLogger.LogSecurityEvent(userID, vulnID, "vulnerability_updated", "vulnerability",
|
|
map[string]interface{}{
|
|
"vulnerability_id": vulnID,
|
|
"new_status": req.Status,
|
|
"notes": req.Notes,
|
|
}, c.ClientIP(), c.GetHeader("User-Agent"), true)
|
|
|
|
c.JSON(http.StatusOK, gin.H{"status": "updated"})
|
|
}
|
|
|
|
// StartComplianceAssessment starts a new compliance assessment
|
|
func (sh *SecurityHandler) StartComplianceAssessment(c *gin.Context) {
|
|
var req struct {
|
|
ProjectID string `json:"project_id" binding:"required"`
|
|
FrameworkID string `json:"framework_id" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
userID, ok := sh.requireProjectAccess(c, req.ProjectID)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
if _, err := uuid.Parse(req.FrameworkID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid framework ID"})
|
|
return
|
|
}
|
|
|
|
frameworkExists, err := sqlcdb.New(sh.db.DB).ComplianceFrameworkExists(context.Background(), uuid.MustParse(req.FrameworkID))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to validate framework"})
|
|
return
|
|
}
|
|
if !frameworkExists {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Compliance framework not found"})
|
|
return
|
|
}
|
|
|
|
// Log audit event
|
|
sh.auditLogger.LogSecurityEvent(userID, req.ProjectID, "compliance_assessment_started", "project",
|
|
map[string]interface{}{
|
|
"project_id": req.ProjectID,
|
|
"framework_id": req.FrameworkID,
|
|
}, c.ClientIP(), c.GetHeader("User-Agent"), true)
|
|
|
|
report, err := sh.complianceManager.AssessCompliance(req.ProjectID, req.FrameworkID, userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to start compliance assessment"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusAccepted, report)
|
|
}
|
|
|
|
// GetComplianceReport retrieves a compliance report
|
|
func (sh *SecurityHandler) GetComplianceReport(c *gin.Context) {
|
|
reportID := firstPathParam(c, "reportId", "id")
|
|
if !sh.requireComplianceReportAccess(c, reportID) {
|
|
return
|
|
}
|
|
|
|
report, err := sh.complianceManager.GetComplianceReport(reportID)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Compliance report not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, report)
|
|
}
|
|
|
|
// GetComplianceFrameworks retrieves available compliance frameworks
|
|
func (sh *SecurityHandler) GetComplianceFrameworks(c *gin.Context) {
|
|
rows, err := sqlcdb.New(sh.db.DB).ListComplianceFrameworks(context.Background())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get compliance frameworks"})
|
|
return
|
|
}
|
|
|
|
var frameworks []security.ComplianceFramework
|
|
for _, row := range rows {
|
|
if !row.Description.Valid {
|
|
continue
|
|
}
|
|
frameworks = append(frameworks, security.ComplianceFramework{
|
|
ID: row.ID.String(),
|
|
Name: row.Name,
|
|
Description: row.Description.String,
|
|
Version: row.Version,
|
|
Enabled: row.Enabled,
|
|
CreatedAt: row.CreatedAt,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"frameworks": frameworks})
|
|
}
|
|
|
|
// InitializeGDPRFramework initializes the GDPR compliance framework
|
|
func (sh *SecurityHandler) InitializeGDPRFramework(c *gin.Context) {
|
|
userID := c.MustGet("user_id").(string)
|
|
|
|
err := sh.complianceManager.InitializeGDPRFramework()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to initialize GDPR framework"})
|
|
return
|
|
}
|
|
|
|
// Log audit event
|
|
sh.auditLogger.LogSecurityEvent(userID, "", "gdpr_framework_initialized", "compliance",
|
|
map[string]interface{}{}, c.ClientIP(), c.GetHeader("User-Agent"), true)
|
|
|
|
c.JSON(http.StatusOK, gin.H{"status": "initialized"})
|
|
}
|
|
|
|
// GetSecurityMetrics retrieves security metrics for a project
|
|
func (sh *SecurityHandler) GetSecurityMetrics(c *gin.Context) {
|
|
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
|
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
|
return
|
|
}
|
|
|
|
// Get vulnerability counts
|
|
var vulnMetrics struct {
|
|
Total int `json:"total"`
|
|
Critical int `json:"critical"`
|
|
High int `json:"high"`
|
|
Medium int `json:"medium"`
|
|
Low int `json:"low"`
|
|
Open int `json:"open"`
|
|
Resolved int `json:"resolved"`
|
|
}
|
|
|
|
q := sqlcdb.New(sh.db.DB)
|
|
mrow, err := q.GetVulnerabilityMetrics(context.Background(), uuid.MustParse(projectID))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get vulnerability metrics"})
|
|
return
|
|
}
|
|
vulnMetrics.Total = int(mrow.Total)
|
|
vulnMetrics.Critical = int(mrow.Critical)
|
|
vulnMetrics.High = int(mrow.High)
|
|
vulnMetrics.Medium = int(mrow.Medium)
|
|
vulnMetrics.Low = int(mrow.Low)
|
|
vulnMetrics.Open = int(mrow.Open)
|
|
vulnMetrics.Resolved = int(mrow.Resolved)
|
|
|
|
// Get latest scan
|
|
var latestScan struct {
|
|
ID string `json:"id"`
|
|
Score int `json:"score"`
|
|
ScannedAt time.Time `json:"scanned_at"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
srow, err := q.GetLatestSecurityScan(context.Background(), uuid.MustParse(projectID))
|
|
latestScan.ID = srow.ID.String()
|
|
latestScan.Score = int(srow.Score)
|
|
latestScan.ScannedAt = srow.ScannedAt
|
|
latestScan.Status = srow.Status
|
|
|
|
if err == sql.ErrNoRows {
|
|
latestScan = struct {
|
|
ID string `json:"id"`
|
|
Score int `json:"score"`
|
|
ScannedAt time.Time `json:"scanned_at"`
|
|
Status string `json:"status"`
|
|
}{ID: "", Score: 0, ScannedAt: time.Time{}, Status: "never_scanned"}
|
|
} else if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get latest scan"})
|
|
return
|
|
}
|
|
|
|
// Get compliance status
|
|
var complianceStatus struct {
|
|
OverallStatus string `json:"overall_status"`
|
|
Score int `json:"score"`
|
|
LastAssessed *time.Time `json:"last_assessed"`
|
|
}
|
|
|
|
crow, err := q.GetLatestComplianceReport(context.Background(), uuid.MustParse(projectID))
|
|
complianceStatus.OverallStatus = crow.OverallStatus
|
|
complianceStatus.Score = int(crow.Score)
|
|
complianceStatus.LastAssessed = &crow.AssessmentDate
|
|
|
|
if err == sql.ErrNoRows {
|
|
complianceStatus = struct {
|
|
OverallStatus string `json:"overall_status"`
|
|
Score int `json:"score"`
|
|
LastAssessed *time.Time `json:"last_assessed"`
|
|
}{OverallStatus: "not_assessed", Score: 0, LastAssessed: nil}
|
|
} else if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get compliance status"})
|
|
return
|
|
}
|
|
|
|
metrics := gin.H{
|
|
"vulnerabilities": vulnMetrics,
|
|
"latest_scan": latestScan,
|
|
"compliance": complianceStatus,
|
|
"security_score": sh.calculateOverallSecurityScore(struct{ Total, Critical, High, Medium, Low, Open, Resolved int }{
|
|
Total: vulnMetrics.Total, Critical: vulnMetrics.Critical, High: vulnMetrics.High,
|
|
Medium: vulnMetrics.Medium, Low: vulnMetrics.Low, Open: vulnMetrics.Open, Resolved: vulnMetrics.Resolved,
|
|
}, latestScan.Score, complianceStatus.Score),
|
|
}
|
|
|
|
c.JSON(http.StatusOK, metrics)
|
|
}
|
|
|
|
// calculateOverallSecurityScore calculates an overall security score
|
|
func (sh *SecurityHandler) calculateOverallSecurityScore(vulnMetrics struct {
|
|
Total, Critical, High, Medium, Low, Open, Resolved int
|
|
}, scanScore, complianceScore int) int {
|
|
// Weight the different components
|
|
vulnScore := 100
|
|
if vulnMetrics.Total > 0 {
|
|
deduction := (vulnMetrics.Critical * 25) + (vulnMetrics.High * 15) + (vulnMetrics.Medium * 8) + (vulnMetrics.Low * 3)
|
|
vulnScore = max(0, 100-deduction)
|
|
}
|
|
|
|
// Calculate weighted average
|
|
overallScore := (vulnScore*40 + scanScore*30 + complianceScore*30) / 100
|
|
return overallScore
|
|
}
|
|
|
|
// GetAuditLogs retrieves audit logs for security events
|
|
func (sh *SecurityHandler) GetAuditLogs(c *gin.Context) {
|
|
projectID := firstPathParam(c, "projectId", "id", "project_id")
|
|
if _, ok := sh.requireProjectAccess(c, projectID); !ok {
|
|
return
|
|
}
|
|
|
|
limit := 50
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 && parsedLimit <= 1000 {
|
|
limit = parsedLimit
|
|
}
|
|
}
|
|
|
|
offset := 0
|
|
if offsetStr := c.Query("offset"); offsetStr != "" {
|
|
if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
|
|
offset = parsedOffset
|
|
}
|
|
}
|
|
|
|
actionFilter := strings.TrimSpace(c.Query("action"))
|
|
resourceFilter := strings.TrimSpace(c.Query("resource"))
|
|
|
|
conditions := []string{
|
|
`((resource = 'project' AND resource_id::text = $1) OR details->>'project_id' = $1)`,
|
|
}
|
|
args := []interface{}{projectID}
|
|
nextArg := 2
|
|
|
|
if actionFilter != "" {
|
|
conditions = append(conditions, fmt.Sprintf("action = $%d", nextArg))
|
|
args = append(args, actionFilter)
|
|
nextArg++
|
|
}
|
|
if resourceFilter != "" {
|
|
conditions = append(conditions, fmt.Sprintf("resource = $%d", nextArg))
|
|
args = append(args, resourceFilter)
|
|
nextArg++
|
|
}
|
|
|
|
whereClause := strings.Join(conditions, " AND ")
|
|
|
|
var total int
|
|
countQuery := "SELECT COUNT(*) FROM audit_logs WHERE " + whereClause
|
|
if err := sh.db.QueryRow(countQuery, args...).Scan(&total); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count audit logs"})
|
|
return
|
|
}
|
|
|
|
dataQuery := fmt.Sprintf(`
|
|
SELECT
|
|
id,
|
|
COALESCE(user_id::text, ''),
|
|
action,
|
|
resource,
|
|
COALESCE(resource_id::text, ''),
|
|
COALESCE(details::text, '{}'),
|
|
COALESCE(ip_address::text, ''),
|
|
COALESCE(user_agent, ''),
|
|
created_at
|
|
FROM audit_logs
|
|
WHERE %s
|
|
ORDER BY created_at DESC
|
|
LIMIT $%d OFFSET $%d
|
|
`, whereClause, nextArg, nextArg+1)
|
|
dataArgs := append(append([]interface{}{}, args...), limit, offset)
|
|
|
|
rows, err := sh.db.Query(dataQuery, dataArgs...)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch audit logs"})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
logs := make([]gin.H, 0, limit)
|
|
for rows.Next() {
|
|
var (
|
|
id string
|
|
userID string
|
|
action string
|
|
resource string
|
|
resourceID string
|
|
detailsRaw string
|
|
ipAddress string
|
|
userAgent string
|
|
createdAt time.Time
|
|
)
|
|
if err := rows.Scan(&id, &userID, &action, &resource, &resourceID, &detailsRaw, &ipAddress, &userAgent, &createdAt); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to decode audit log row"})
|
|
return
|
|
}
|
|
|
|
var details map[string]interface{}
|
|
if err := json.Unmarshal([]byte(detailsRaw), &details); err != nil {
|
|
details = map[string]interface{}{"raw": detailsRaw}
|
|
}
|
|
|
|
logs = append(logs, gin.H{
|
|
"id": id,
|
|
"timestamp": createdAt,
|
|
"user_id": userID,
|
|
"action": action,
|
|
"resource": resource,
|
|
"resource_id": resourceID,
|
|
"details": details,
|
|
"ip_address": ipAddress,
|
|
"user_agent": userAgent,
|
|
"success": true,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"audit_logs": logs,
|
|
"total": total,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
})
|
|
}
|
|
|
|
func (sh *SecurityHandler) requireProjectAccess(c *gin.Context, projectID string) (string, bool) {
|
|
userID := optionalUserID(c)
|
|
|
|
if _, err := uuid.Parse(projectID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid project ID"})
|
|
return "", false
|
|
}
|
|
|
|
hasAccess, err := sqlcdb.New(sh.db.DB).CheckSecurityProjectAccess(context.Background(), sqlcdb.CheckSecurityProjectAccessParams{
|
|
ID: uuid.MustParse(projectID),
|
|
OwnerID: optionalUserUUID(c),
|
|
Column3: contextIsAdmin(c),
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify project access"})
|
|
return "", false
|
|
}
|
|
|
|
if !hasAccess {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Project not found"})
|
|
return "", false
|
|
}
|
|
|
|
return userID, true
|
|
}
|
|
|
|
func (sh *SecurityHandler) requireSecurityScanAccess(c *gin.Context, scanID string) bool {
|
|
if _, err := uuid.Parse(scanID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid scan ID"})
|
|
return false
|
|
}
|
|
|
|
var projectID string
|
|
pid, err := sqlcdb.New(sh.db.DB).GetSecurityScanProjectID(context.Background(), uuid.MustParse(scanID))
|
|
projectID = pid.String()
|
|
if err == sql.ErrNoRows {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Security scan not found"})
|
|
return false
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify scan access"})
|
|
return false
|
|
}
|
|
|
|
_, ok := sh.requireProjectAccess(c, projectID)
|
|
return ok
|
|
}
|
|
|
|
func (sh *SecurityHandler) requireComplianceReportAccess(c *gin.Context, reportID string) bool {
|
|
if _, err := uuid.Parse(reportID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid report ID"})
|
|
return false
|
|
}
|
|
|
|
var projectID string
|
|
pid, err := sqlcdb.New(sh.db.DB).GetComplianceReportProjectID(context.Background(), uuid.MustParse(reportID))
|
|
projectID = pid.String()
|
|
if err == sql.ErrNoRows {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Compliance report not found"})
|
|
return false
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify report access"})
|
|
return false
|
|
}
|
|
|
|
_, ok := sh.requireProjectAccess(c, projectID)
|
|
return ok
|
|
}
|
|
|
|
func (sh *SecurityHandler) requireVulnerabilityAccess(c *gin.Context, vulnID string) (string, bool) {
|
|
if _, err := uuid.Parse(vulnID); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid vulnerability ID"})
|
|
return "", false
|
|
}
|
|
|
|
var projectID string
|
|
pid, err := sqlcdb.New(sh.db.DB).GetVulnerabilityProjectID(context.Background(), uuid.MustParse(vulnID))
|
|
projectID = pid.String()
|
|
if err == sql.ErrNoRows {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Vulnerability not found"})
|
|
return "", false
|
|
}
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to verify vulnerability access"})
|
|
return "", false
|
|
}
|
|
|
|
return sh.requireProjectAccess(c, projectID)
|
|
}
|
|
|
|
// max helper function
|
|
func max(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|