Private/ESCalatorIssue.ps1

class ESCalatorIssue {
    <#
        .SYNOPSIS
        Represents a security issue identified in Active Directory Certificate Services (AD CS) environments.
 
        .DESCRIPTION
        This class provides a standardized structure for security issues found by ESCalator vulnerability
        scanning functions like Find-ESC4Issue and Find-ESC5Issue. All Issue objects share the same properties
        and methods for consistent analysis and processing.
 
        .NOTES
        This class is used internally by ESCalator functions to ensure consistent Issue object structure.
        All properties are validated during construction to maintain data integrity.
    #>


    # Core identification properties
    [string]$Forest
    [string]$Name
    [string]$DistinguishedName
    [string]$IdentityReference
    [string]$IdentityReferenceSID
    [string]$ActiveDirectoryRights
    [string]$Technique
    [string]$Subtype
    [string]$Issue
    [string]$Severity

    # Optional properties for detailed analysis
    [string]$ObjectType
    [System.DirectoryServices.DirectoryEntry]$DirectoryEntry

    # Group expansion properties (null for original issues)
    [string]$ExpandedFromGroup
    [string]$ExpandedFromGroupSID
    [string]$MemberType

    # Constructor for original issues (from Find-ESC4Issue, Find-ESC5Issue)
    ESCalatorIssue(
        [string]$Forest,
        [string]$Name,
        [string]$DistinguishedName,
        [string]$IdentityReference,
        [string]$IdentityReferenceSID,
        [string]$ActiveDirectoryRights,
        [string]$Technique,
        [string]$Subtype,
        [string]$Issue,
        [string]$Severity,
        [string]$ObjectType,
        [System.DirectoryServices.DirectoryEntry]$DirectoryEntry
    ) {
        $this.Forest = $Forest
        $this.Name = $Name
        $this.DistinguishedName = $DistinguishedName
        $this.IdentityReference = $IdentityReference
        $this.IdentityReferenceSID = $IdentityReferenceSID
        $this.ActiveDirectoryRights = $ActiveDirectoryRights
        $this.Technique = $Technique
        $this.Subtype = $Subtype
        $this.Issue = $Issue
        $this.Severity = $Severity
        $this.ObjectType = $ObjectType
        $this.DirectoryEntry = $DirectoryEntry
        
        # Set group expansion properties to null for original issues
        $this.ExpandedFromGroup = $null
        $this.ExpandedFromGroupSID = $null
        $this.MemberType = $null
    }

    # Constructor for expanded issues (from group membership expansion)
    ESCalatorIssue(
        [string]$Forest,
        [string]$Name,
        [string]$DistinguishedName,
        [string]$IdentityReference,
        [string]$IdentityReferenceSID,
        [string]$ActiveDirectoryRights,
        [string]$Technique,
        [string]$Subtype,
        [string]$Issue,
        [string]$Severity,
        [string]$ObjectType,
        [System.DirectoryServices.DirectoryEntry]$DirectoryEntry,
        [string]$ExpandedFromGroup,
        [string]$ExpandedFromGroupSID,
        [string]$MemberType
    ) {
        $this.Forest = $Forest
        $this.Name = $Name
        $this.DistinguishedName = $DistinguishedName
        $this.IdentityReference = $IdentityReference
        $this.IdentityReferenceSID = $IdentityReferenceSID
        $this.ActiveDirectoryRights = $ActiveDirectoryRights
        $this.Technique = $Technique
        $this.Subtype = $Subtype
        $this.Issue = $Issue
        $this.Severity = $Severity
        $this.ObjectType = $ObjectType
        $this.DirectoryEntry = $DirectoryEntry
        $this.ExpandedFromGroup = $ExpandedFromGroup
        $this.ExpandedFromGroupSID = $ExpandedFromGroupSID
        $this.MemberType = $MemberType
    }

    # Static method to create original issue
    static [ESCalatorIssue] CreateOriginalIssue(
        [string]$Forest,
        [string]$Name,
        [string]$DistinguishedName,
        [string]$IdentityReference,
        [string]$IdentityReferenceSID,
        [string]$ActiveDirectoryRights,
        [string]$Technique,
        [string]$Subtype,
        [string]$Issue,
        [string]$Severity,
        [string]$ObjectType,
        [System.DirectoryServices.DirectoryEntry]$DirectoryEntry
    ) {
        return [ESCalatorIssue]::new(
            $Forest, $Name, $DistinguishedName, $IdentityReference, $IdentityReferenceSID,
            $ActiveDirectoryRights, $Technique, $Subtype, $Issue, $Severity, $ObjectType, $DirectoryEntry
        )
    }

    # Static method to create expanded issue
    static [ESCalatorIssue] CreateExpandedIssue(
        [string]$Forest,
        [string]$Name,
        [string]$DistinguishedName,
        [string]$IdentityReference,
        [string]$IdentityReferenceSID,
        [string]$ActiveDirectoryRights,
        [string]$Technique,
        [string]$Subtype,
        [string]$Issue,
        [string]$Severity,
        [string]$ObjectType,
        [System.DirectoryServices.DirectoryEntry]$DirectoryEntry,
        [string]$ExpandedFromGroup,
        [string]$ExpandedFromGroupSID,
        [string]$MemberType
    ) {
        return [ESCalatorIssue]::new(
            $Forest, $Name, $DistinguishedName, $IdentityReference, $IdentityReferenceSID,
            $ActiveDirectoryRights, $Technique, $Subtype, $Issue, $Severity, $ObjectType, $DirectoryEntry,
            $ExpandedFromGroup, $ExpandedFromGroupSID, $MemberType
        )
    }

    # Method to check if this is an expanded issue
    [bool] IsExpanded() {
        return -not [string]::IsNullOrEmpty($this.ExpandedFromGroup)
    }

    # Method to get a summary of the issue
    [PSCustomObject] GetSummary() {
        return [PSCustomObject]@{
            Technique = $this.Technique
            Subtype = $this.Subtype
            Object = $this.Name
            Principal = $this.IdentityReference
            Rights = $this.ActiveDirectoryRights
            Severity = $this.Severity
            IsExpanded = $this.IsExpanded()
            ExpandedFrom = $this.ExpandedFromGroup
        }
    }

    # Method to convert to hashtable (for compatibility with existing code)
    [hashtable] ToHashTable() {
        return @{
            Forest = $this.Forest
            Name = $this.Name
            DistinguishedName = $this.DistinguishedName
            IdentityReference = $this.IdentityReference
            IdentityReferenceSID = $this.IdentityReferenceSID
            ActiveDirectoryRights = $this.ActiveDirectoryRights
            Technique = $this.Technique
            Subtype = $this.Subtype
            Issue = $this.Issue
            Severity = $this.Severity
            ObjectType = $this.ObjectType
            DirectoryEntry = $this.DirectoryEntry
            ExpandedFromGroup = $this.ExpandedFromGroup
            ExpandedFromGroupSID = $this.ExpandedFromGroupSID
            MemberType = $this.MemberType
        }
    }

    # Method to convert to PSCustomObject (for compatibility with existing code)
    [PSCustomObject] ToPSCustomObject() {
        return [PSCustomObject]@{
            Forest = $this.Forest
            Name = $this.Name
            DistinguishedName = $this.DistinguishedName
            IdentityReference = $this.IdentityReference
            IdentityReferenceSID = $this.IdentityReferenceSID
            ActiveDirectoryRights = $this.ActiveDirectoryRights
            Technique = $this.Technique
            Subtype = $this.Subtype
            Issue = $this.Issue
            Severity = $this.Severity
            ObjectType = $this.ObjectType
            DirectoryEntry = $this.DirectoryEntry
            ExpandedFromGroup = $this.ExpandedFromGroup
            ExpandedFromGroupSID = $this.ExpandedFromGroupSID
            MemberType = $this.MemberType
        }
    }

    # Override ToString for better display
    [string] ToString() {
        $expandedInfo = if ($this.IsExpanded()) { " (expanded from $($this.ExpandedFromGroup))" } else { "" }
        return "$($this.Technique)-$($this.Subtype): $($this.IdentityReference) -> $($this.Name)$expandedInfo"
    }

    # Method to validate the issue object
    [bool] IsValid() {
        $requiredFields = @('Forest', 'Name', 'DistinguishedName', 'IdentityReference', 
                           'IdentityReferenceSID', 'ActiveDirectoryRights', 'Technique', 'Subtype', 'Issue', 'Severity')
        
        foreach ($field in $requiredFields) {
            if ([string]::IsNullOrEmpty($this.$field)) {
                Write-Warning "ESCalatorIssue validation failed: $field is null or empty"
                return $false
            }
        }
        
        # Validate technique is known
        $validTechniques = @('ESC4', 'ESC5', 'ESC1', 'ESC2', 'ESC3', 'ESC6', 'ESC7', 'ESC8', 'ESC9', 'ESC10', 'ESC11', 'ESC13', 'ESC15', 'ESC16')
        if ($this.Technique -notin $validTechniques) {
            Write-Warning "ESCalatorIssue validation failed: Unknown technique '$($this.Technique)'"
            return $false
        }
        
        # Validate severity is known
        $validSeverities = @('Critical', 'High', 'Medium', 'Low')
        if ($this.Severity -notin $validSeverities) {
            Write-Warning "ESCalatorIssue validation failed: Unknown severity '$($this.Severity)'"
            return $false
        }
        
        return $true
    }

    # Method to create a copy of the issue with modifications
    [ESCalatorIssue] CreateCopy([hashtable]$modifications = @{}) {
        $props = $this.ToHashTable()
        
        # Apply modifications
        foreach ($key in $modifications.Keys) {
            if ($props.ContainsKey($key)) {
                $props[$key] = $modifications[$key]
            }
        }
        
        return [ESCalatorIssue]::new(
            $props.Forest, $props.Name, $props.DistinguishedName, $props.IdentityReference,
            $props.IdentityReferenceSID, $props.ActiveDirectoryRights, $props.Technique,
            $props.Subtype, $props.Issue, $props.Severity, $props.ObjectType, $props.DirectoryEntry,
            $props.ExpandedFromGroup, $props.ExpandedFromGroupSID, $props.MemberType
        )
    }
}