Private/Cloud-ACL-Conversion.ps1

function ConvertTo-ACLUserEncoding {
    <#
    .SYNOPSIS
        Encodes user/group IDs for OpenNebula ACL rules.
     
    .DESCRIPTION
        Converts user IDs, group IDs, or wildcard to the encoded format required by OpenNebula ACLs.
     
    .PARAMETER UserID
        Individual user ID to encode
         
    .PARAMETER GroupID
        Group ID to encode
         
    .PARAMETER All
        Use wildcard (all users)
     
    .EXAMPLE
        ConvertTo-ACLUserEncoding -UserID 4
        Returns: 4294967300
         
    .EXAMPLE
        ConvertTo-ACLUserEncoding -GroupID 1
        Returns: 8589934593
         
    .EXAMPLE
        ConvertTo-ACLUserEncoding -All
        Returns: 17179869184
    #>

    
    [CmdletBinding(DefaultParameterSetName='User')]
    param(
        [Parameter(Mandatory=$true, ParameterSetName='User')]
        [int]$UserID,
        
        [Parameter(Mandatory=$true, ParameterSetName='Group')]
        [int]$GroupID,
        
        [Parameter(Mandatory=$true, ParameterSetName='All')]
        [switch]$All
    )
    
    switch ($PSCmdlet.ParameterSetName) {
        'User' {
            # Individual user: 2^32 + user_id
            return [Math]::Pow(2, 32) + $UserID
        }
        'Group' {
            # Group: 2^33 + group_id
            return [Math]::Pow(2, 33) + $GroupID
        }
        'All' {
            # All users: 2^34
            return [Math]::Pow(2, 34)
        }
    }
}

#function ConvertTo-ACLResourceEncoding {
# <#
# .SYNOPSIS
# Encodes resource types and IDs for OpenNebula ACL rules.
# #>
#
# [CmdletBinding()]
# param(
# [Parameter(Mandatory=$true)]
# [ValidateSet(
# 'VM', 'HOST', 'NET', 'IMAGE', 'USER', 'TEMPLATE', 'GROUP', 'DATASTORE',
# 'CLUSTER', 'DOCUMENT', 'ZONE', 'SECGROUP', 'VDC', 'VROUTER', 'MARKETPLACE',
# 'MARKETPLACEAPP', 'VMGROUP', 'VNTEMPLATE', 'BACKUPJOB'
# )]
# [string[]]$ResourceType,
#
# [Parameter(Mandatory=$false)]
# [int]$ResourceID = -1,
#
# [Parameter(Mandatory=$false)]
# [int]$GroupID = -1
# )
#
# $resourceBits = @{
# 'VM' = [long]0x1000000000 # 2^36
# 'HOST' = [long]0x2000000000 # 2^37
# 'NET' = [long]0x4000000000 # 2^38
# 'IMAGE' = [long]0x8000000000 # 2^39
# 'USER' = [long]0x10000000000 # 2^40
# 'TEMPLATE' = [long]0x20000000000 # 2^41
# 'GROUP' = [long]0x40000000000 # 2^42
# 'DATASTORE' = [long]0x1000000000000 # 2^48
# 'CLUSTER' = [long]0x80000000000 # 2^43
# 'DOCUMENT' = [long]0x100000000000 # 2^44
# 'ZONE' = [long]0x200000000000 # 2^45
# 'SECGROUP' = [long]0x400000000000 # 2^46
# 'VDC' = [long]0x800000000000 # 2^47
# 'VROUTER' = [long]0x2000000000000 # 2^49
# 'MARKETPLACE' = [long]0x4000000000000 # 2^50
# 'MARKETPLACEAPP' = [long]0x8000000000000 # 2^51
# 'VMGROUP' = [long]0x10000000000000 # 2^52
# 'VNTEMPLATE' = [long]0x20000000000000 # 2^53
# 'BACKUPJOB' = [long]0x40000000000000 # 2^54
# }
#
# # Infrastructure types that cannot use @group selector
# $infrastructureTypes = @('HOST', 'GROUP', 'CLUSTER', 'ZONE', 'VDC', 'DATASTORE')
#
# # Check if trying to use group selector with infrastructure types
# if ($GroupID -ge 0) {
# $hasInfraType = $ResourceType | Where-Object { $_ -in $infrastructureTypes }
# if ($hasInfraType) {
# throw "Group(@) selector cannot be applied to infrastructure types: $($hasInfraType -join ', '). Use wildcard (*) instead."
# }
# }
#
# # Calculate resource type mask
# [long]$typeMask = 0
# foreach ($type in $ResourceType) {
# $typeMask = $typeMask -bor $resourceBits[$type]
# }
#
# # Selector encoding - these bits indicate WHICH resources of that type:
# # Bit 32 (0x100000000) = INDIVIDUAL bit - must be set when specifying specific ID
# # Bit 33 (0x200000000) = CLUSTER bit
# # Bit 34 (0x400000000) = ALL bit
# # Bits 0-31 = The actual ID value (when INDIVIDUAL bit is set)
#
# [long]$selector = 0
# if ($ResourceID -ge 0) {
# # Specific resource ID: set INDIVIDUAL bit (bit 32) + add the ID
# $selector = [long]0x100000000 + $ResourceID
# }
# elseif ($GroupID -ge 0) {
# # Group-owned resources: set GROUP bit + add group ID
# # (Note: this is different from INDIVIDUAL bit)
# # Based on OpenNebula source, group ownership uses a different encoding
# $selector = [long]0x100000000 + $GroupID
# }
# else {
# # All resources (*): set ALL bit (bit 34)
# $selector = [long]0x400000000
# }
#
# # Combine type mask with selector
# $encoded = $typeMask -bor $selector
#
# Write-Verbose "Type mask: $typeMask (0x$([Convert]::ToString($typeMask, 16)))"
# Write-Verbose "Selector: $selector (0x$([Convert]::ToString($selector, 16)))"
# Write-Verbose "Combined: $encoded (0x$([Convert]::ToString($encoded, 16)))"
#
# return $encoded
#}
#

function ConvertTo-ACLResourceEncoding2 {
    <#
    .SYNOPSIS
        Encodes resource types and IDs for OpenNebula ACL rules.
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [ValidateSet(
            'VM', 'HOST', 'NET', 'IMAGE', 'USER', 'TEMPLATE', 'GROUP', 'DATASTORE', 
            'CLUSTER', 'DOCUMENT', 'ZONE', 'SECGROUP', 'VDC', 'VROUTER', 'MARKETPLACE', 
            'MARKETPLACEAPP', 'VMGROUP', 'VNTEMPLATE', 'BACKUPJOB'
        )]
        [string[]]$ResourceType,
        
        [Parameter(Mandatory=$false)]
        [int]$ResourceID = -1,
        
        [Parameter(Mandatory=$false)]
        [int]$GroupID = -1
    )
    
    $resourceBits = @{
        'VM'              = [long]0x1000000000     # 2^36
        'HOST'            = [long]0x2000000000     # 2^37
        'NET'             = [long]0x4000000000     # 2^38
        'IMAGE'           = [long]0x8000000000     # 2^39
        'USER'            = [long]0x10000000000    # 2^40
        'TEMPLATE'        = [long]0x20000000000    # 2^41
        'GROUP'           = [long]0x40000000000    # 2^42
        'DATASTORE'       = [long]0x1000000000000  # 2^48
        'CLUSTER'         = [long]0x80000000000    # 2^43
        'DOCUMENT'        = [long]0x100000000000   # 2^44
        'ZONE'            = [long]0x200000000000   # 2^45
        'SECGROUP'        = [long]0x400000000000   # 2^46
        'VDC'             = [long]0x800000000000   # 2^47
        'VROUTER'         = [long]0x2000000000000  # 2^49
        'MARKETPLACE'     = [long]0x4000000000000  # 2^50
        'MARKETPLACEAPP'  = [long]0x8000000000000  # 2^51
        'VMGROUP'         = [long]0x10000000000000 # 2^52
        'VNTEMPLATE'      = [long]0x20000000000000 # 2^53
        'BACKUPJOB'       = [long]0x40000000000000 # 2^54
    }
    
    # Infrastructure types that cannot use @group selector
    $infrastructureTypes = @('HOST', 'GROUP', 'CLUSTER', 'ZONE', 'VDC', 'DATASTORE')
    
    # Check if trying to use group selector with infrastructure types
    if ($GroupID -ge 0) {
        $hasInfraType = $ResourceType | Where-Object { $_ -in $infrastructureTypes }
        if ($hasInfraType) {
            throw "Group(@) selector cannot be applied to infrastructure types: $($hasInfraType -join ', '). Use wildcard (*) instead."
        }
    }
    
    # Calculate resource type mask
    [long]$typeMask = 0
    foreach ($type in $ResourceType) {
        $typeMask = $typeMask -bor $resourceBits[$type]
    }
    
    # Selector encoding - these bits indicate WHICH resources of that type:
    # Bit 32 (0x100000000) = INDIVIDUAL bit (#) - for specific resource ID
    # Bit 33 (0x200000000) = GROUP bit (@) - for group-owned resources ← FIXED!
    # Bit 34 (0x400000000) = ALL bit (*) - for all resources
    # Bits 0-31 = The actual ID value
    
    [long]$selector = 0
    if ($ResourceID -ge 0) {
        # Specific resource ID (#ID): set INDIVIDUAL bit (bit 32) + add the ID
        $selector = [long]0x100000000 + $ResourceID
    }
    elseif ($GroupID -ge 0) {
        # Group-owned resources (@GID): set GROUP bit (bit 33) + add group ID
        $selector = [long]0x200000000 + $GroupID  # ← FIXED! Was 0x100000000
    }
    else {
        # All resources (*): set ALL bit (bit 34)
        $selector = [long]0x400000000
    }
    
    # Combine type mask with selector
    $encoded = $typeMask -bor $selector
    
    Write-Verbose "Type mask: $typeMask (0x$([Convert]::ToString($typeMask, 16)))"
    Write-Verbose "Selector: $selector (0x$([Convert]::ToString($selector, 16)))"
    Write-Verbose "Combined: $encoded (0x$([Convert]::ToString($encoded, 16)))"
    
    return $encoded
}

function ConvertTo-ACLResourceEncoding {
    <#
    .SYNOPSIS
        Encodes resource types and IDs for OpenNebula ACL rules.
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [ValidateSet(
            'VM', 'HOST', 'NET', 'IMAGE', 'USER', 'TEMPLATE', 'GROUP', 'DATASTORE', 
            'CLUSTER', 'DOCUMENT', 'ZONE', 'SECGROUP', 'VDC', 'VROUTER', 'MARKETPLACE', 
            'MARKETPLACEAPP', 'VMGROUP', 'VNTEMPLATE', 'BACKUPJOB'
        )]
        [string[]]$ResourceType,
        
        [Parameter(Mandatory=$false)]
        [int]$ResourceID = -1,
        
        [Parameter(Mandatory=$false)]
        [int]$GroupID = -1
    )
    
    $resourceBits = @{
        'VM'              = [long]0x1000000000     # 2^36
        'HOST'            = [long]0x2000000000     # 2^37
        'NET'             = [long]0x4000000000     # 2^38
        'IMAGE'           = [long]0x8000000000     # 2^39
        'USER'            = [long]0x10000000000    # 2^40
        'TEMPLATE'        = [long]0x20000000000    # 2^41
        'GROUP'           = [long]0x40000000000    # 2^42
        'DATASTORE'       = [long]0x1000000000000  # 2^48
        'CLUSTER'         = [long]0x80000000000    # 2^43
        'DOCUMENT'        = [long]0x100000000000   # 2^44
        'ZONE'            = [long]0x200000000000   # 2^45
        'SECGROUP'        = [long]0x400000000000   # 2^46
        'VDC'             = [long]0x800000000000   # 2^47
        'VROUTER'         = [long]0x2000000000000  # 2^49
        'MARKETPLACE'     = [long]0x4000000000000  # 2^50
        'MARKETPLACEAPP'  = [long]0x8000000000000  # 2^51
        'VMGROUP'         = [long]0x10000000000000 # 2^52
        'VNTEMPLATE'      = [long]0x20000000000000 # 2^53
        'BACKUPJOB'       = [long]0x40000000000000 # 2^54
    }
    
    # Infrastructure types that cannot use @group selector
    $infrastructureTypes = @('HOST', 'GROUP', 'CLUSTER', 'ZONE', 'VDC', 'DATASTORE')
    
    # Check if trying to use group selector with infrastructure types
    if ($GroupID -ge 0) {
        $hasInfraType = $ResourceType | Where-Object { $_ -in $infrastructureTypes }
        if ($hasInfraType) {
            throw "Group(@) selector cannot be applied to infrastructure types: $($hasInfraType -join ', '). Use wildcard (*) instead."
        }
    }
    
    # Calculate resource type mask
    [long]$typeMask = 0
    foreach ($type in $ResourceType) {
        $typeMask = $typeMask -bor $resourceBits[$type]
    }
    
    # Selector encoding - these bits indicate WHICH resources of that type:
    # Bit 32 (0x100000000) = INDIVIDUAL bit (#) - for specific resource ID
    # Bit 33 (0x200000000) = GROUP bit (@) - for group-owned resources ← FIXED!
    # Bit 34 (0x400000000) = ALL bit (*) - for all resources
    # Bits 0-31 = The actual ID value
    
    [long]$selector = 0
    if ($ResourceID -ge 0) {
        # Specific resource ID (#ID): set INDIVIDUAL bit (bit 32) + add the ID
        #$selector = [long]0x100000000 + $ResourceID
        $selector = [long]0x200000000 + $GroupID
    }
    elseif ($GroupID -ge 0) {
        # Group-owned resources (@GID): set GROUP bit (bit 33) + add group ID
        $selector = [long]0x200000000 + $GroupID  # ← FIXED! Was 0x100000000
    }
    else {
        # All resources (*): set ALL bit (bit 34)
        $selector = [long]0x400000000
    }
    
    # Combine type mask with selector
    $encoded = $typeMask -bor $selector
    
    Write-Verbose "Type mask: $typeMask (0x$([Convert]::ToString($typeMask, 16)))"
    Write-Verbose "Selector: $selector (0x$([Convert]::ToString($selector, 16)))"
    Write-Verbose "Combined: $encoded (0x$([Convert]::ToString($encoded, 16)))"
    
    return $encoded
}

function ConvertTo-ACLRightsEncoding {
    <#
    .SYNOPSIS
        Encodes rights for OpenNebula ACL rules.
     
    .DESCRIPTION
        Converts permission rights to the encoded format required by OpenNebula ACLs.
     
    .PARAMETER Rights
        Rights to grant. Valid values: USE, MANAGE, ADMIN, CREATE
     
    .EXAMPLE
        ConvertTo-ACLRightsEncoding -Rights "USE"
        Returns: 1
         
    .EXAMPLE
        ConvertTo-ACLRightsEncoding -Rights "USE","MANAGE"
        Returns: 3
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [ValidateSet('USE', 'MANAGE', 'ADMIN', 'CREATE')]
        [string[]]$Rights
    )
    
    $rightsBits = @{
        'USE'    = 1    # 2^0
        'MANAGE' = 2    # 2^1
        'ADMIN'  = 4    # 2^2
        'CREATE' = 8    # 2^3
    }
    
    $encoded = 0
    foreach ($right in $Rights) {
        $encoded = $encoded -bor $rightsBits[$right]
    }
    
    return $encoded
}

function ConvertTo-ACLZoneEncoding {
    <#
    .SYNOPSIS
        Encodes zone for OpenNebula ACL rules.
     
    .DESCRIPTION
        Converts zone ID or wildcard to the encoded format required by OpenNebula ACLs.
     
    .PARAMETER ZoneID
        Specific zone ID
         
    .PARAMETER All
        Use wildcard (all zones)
     
    .EXAMPLE
        ConvertTo-ACLZoneEncoding -ZoneID 0
        Returns: 4294967296
         
    .EXAMPLE
        ConvertTo-ACLZoneEncoding -All
        Returns: 17179869184
    #>

    
    [CmdletBinding(DefaultParameterSetName='Zone')]
    param(
        [Parameter(Mandatory=$true, ParameterSetName='Zone')]
        [int]$ZoneID,
        
        [Parameter(Mandatory=$true, ParameterSetName='All')]
        [switch]$All
    )
    
    if ($PSCmdlet.ParameterSetName -eq 'All') {
        # All zones: 2^34
        return [Math]::Pow(2, 34)
    }
    else {
        # Specific zone: 2^32 + zone_id
        return [Math]::Pow(2, 32) + $ZoneID
    }
}

function ConvertFrom-ACLEncoding {
    <#
    .SYNOPSIS
        Decodes an ACL rule to human-readable format.
     
    .DESCRIPTION
        Converts encoded ACL values back to readable format for verification.
     
    .PARAMETER ACL
        ACL object from Get-CloudACL
     
    .EXAMPLE
        Get-CloudACL -ID 5 | ConvertFrom-ACLEncoding
    #>

    
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [object]$ACL
    )
    
    process {
        # This just returns the string representation that's already decoded
        [PSCustomObject]@{
            ID     = $ACL.id
            Rule   = $ACL.string
            User   = $ACL.user
            Resource = $ACL.resource
            Rights = $ACL.rights
            Zone   = $ACL.zone
        }
    }
}