Determining effective permissions

This is something that I've wondered about here and there over the years, but never really had a good answer to. How is one intended to determine effective permissions for the interactive user at runtime?

There is edsaObjectRightsEffective, but that only tells you effective rights at the object-level, not the attribute level. There is edsvaATLinksEffective, which should work, but that means I would have to loop through all of the templates, calculate/expand group membership for each applied item, and accumulate them all into a table to determine effective rights - a very resource-intensive (aka slow) proposition, I'm sure. Active Roles must have already calculated this, but I cannot seem to figure out how to access this. I tried querying nTSecurityDescriptor and edsaActiveDirectoryACEs, but neither of these are accessible to a delegated user.

Obviously both the MMC and Web interface have some kind of mechanism for making this determination (how else would the product know that the description attribute should be rendered read-only because the interactive user does not have write privileges to it, for example), but I'm too dumb to figure it out.

TL;DR: How do you programmatically determine if the interactive user has privileges to modify the description attribute on a user?

Thanks!

Parents
  • Hi Shawn,

    The script published in the following thread is very useful:

    (+) Access Template Link Reporting - Forum - Active Roles Community - One Identity Community

    You can query a specific delegate, and the output will show the specific ACLs granted to this delegate and the target where the delegate has these permissions applied to:

    Jose

  • Thanks, Jose!

    That is useful, but ultimately I was hoping that I could access something that would straight-up tell me what privileges I have without having to calculate the cumulative total of what all of the templates contain.

    The best that I've come up with is to use edsaObjetRightsEffective to calculate object-level rights, then rely on allowedAttributesEffective to tell me what attributes are writable by the current user. Not quite as nice as having something that would explicitly tell you "You have effective Read to this list of attributes, and Write to these", but it seems like the closes I can get.

    EDIT: Seems that I'm having some site difficulty and it's not allowing me to submit a code block; hopefully the code below doesn't get too mangled.

    ---
    $ObjectPath = "EDMS://CN=myObject,OU=SomeOU,DC=domain,DC=com"

    $adObject = [adsi]$ObjectPath
    $adObject.RefreshCache(@("distinguishedName", "edsaObjectRightsEffective", "allowedAttributesEffective"))

    $rightsMap = [ordered]@{
        EDS_RIGHT_DS_CREATE_CHILD   = 1
        EDS_RIGHT_DS_DELETE_CHILD   = 2
        EDS_RIGHT_ACTRL_DS_LIST     = 4
        EDS_RIGHT_DS_SELF           = 8
        EDS_RIGHT_DS_READ_PROP      = 16
        EDS_RIGHT_DS_WRITE_PROP     = 32
        EDS_RIGHT_DS_DELETE_TREE    = 64
        EDS_RIGHT_DS_LIST_OBJECT    = 128
        EDS_RIGHT_DS_CONTROL_ACCESS = 256
        EDS_RIGHT_EDS_COPY          = 512
        EDS_RIGHT_EDS_MOVE          = 2048
        EDS_RIGHT_EDS_MOVE_TO       = 4096
        EDS_RIGHT_DELETE            = 65536
        EDS_RIGHT_READ_CONTROL      = 131072
        EDS_RIGHT_WRITE_DAC         = 262144
        EDS_RIGHT_WRITE_OWNER       = 524288
    }

    $effectiveRightsValue = [int64]$adObject.Properties["edsaObjectRightsEffective"].Value
    $effectiveRightsNames = foreach ($entry in $rightsMap.GetEnumerator()) {
        if (($effectiveRightsValue -band [int64]$entry.Value) -eq [int64]$entry.Value) {
            $entry.Key
        }
    }

    ("adObject: '$($adObject.distinguishedName)'.") | Write-Host
    ("edsaObjectRightsEffective value: $effectiveRightsValue.") | Write-Host
    if ($effectiveRightsNames) {
        $effectiveRightsNames | ForEach-Object {
            ("`t{0}" -f $_) | Write-Host
        }
    }
    else {
        "`t(none recognized)" | Write-Host
    }

    # allowedAttributesEffective (AD): attributes the caller may write on this object.
    # Infer each listed ldapDisplayName as EDS_RIGHT_DS_WRITE_PROP scoped to that property.
    Write-Host ""
    Write-Host "allowedAttributesEffective (inferred EDS_RIGHT_DS_WRITE_PROP per attribute):"
    try {
        $aaeRaw = $adObject.Properties["allowedAttributesEffective"].Value
        $aaeList = @(
            if ($null -eq $aaeRaw) { }
            elseif ($aaeRaw -is [System.Collections.IEnumerable] -and -not ($aaeRaw -is [string])) {
                foreach ($item in $aaeRaw) { [string]$item }
            }
            else {
                [string]$aaeRaw
            }
        ) | Where-Object { $_ -and $_.Length -gt 0 } | Sort-Object -Unique

        if ($aaeList.Count -eq 0) {
            Write-Host "`t(no attributes listed, or attribute unavailable on this binding)"
        }
        else {
            foreach ($attr in $aaeList) {
                Write-Host ("`tEDS_RIGHT_DS_WRITE_PROP -> '{0}'" -f $attr)
            }
            Write-Host ("`t({0} writable attribute(s))" -f $aaeList.Count)
        }
    }
    catch {
        Write-Host ("`t(error reading allowedAttributesEffective: {0})" -f $_.Exception.Message)
    }

    $null = $adObject.Dispose()

  • Hi Shawn,

    I dug into this on a lab server and can confirm your approach is not just "the closest you can get" — it's actually the correct, product-sanctioned way to do it. You don't need to worry that  allowedAttributesEffective  is showing you the service account's view.

    When you bind through the Active Roles Administration Service (the  EDMS://  provider / a proxy connection), these MS-ADTS constructed attributes are recomputed by Active Roles, for the currently connected user, against that user's virtual (Access Template) delegation — the exact same evaluation the MMC and Web Interface use to decide whether to render a field read-only.

    I verified it by binding as a delegate with zero rights and then escalating delegation via Access Templates, reading the attributes each time while impersonating that delegate:

    • No delegation → the object isn't even visible (Access denied).
    • Read-only AT ("All Objects – Read All Properties") →  edsaObjectRightsEffective  = 144 (List + ReadProp), and  allowedAttributesEffective  comes back empty — i.e. you can read it but write nothing.
    • + "Users – Read/Write General Information" →  allowedAttributesEffective  becomes exactly the ~12 attributes of that property set.
    • + "Users – Read/Write Public Information" →  description  now appears in  allowedAttributesEffective , while an attribute outside those sets ( telephoneNumber ) stays absent.

    For contrast, an ARS admin on the same object saw  edsaObjectRightsEffective  = 987135 and 1065 writable attributes. So the writable set tracks the delegated Access Template property sets attribute-by-attribute — no need to enumerate/expand ATs and group membership yourself. It's a single server-side bind, so it's O(1) rather than the slow accumulation you were dreading.

    Here's a tidied-up version of your script wrapped as a reusable function — returns objects instead of writing to the host, connects via proxy if you're not already connected, and lets you test specific attributes:

    function Get-ARSEffectiveRights {
    [CmdletBinding()]
    param(
    [Parameter(Mandatory)] [string] $Identity, # DN or EDMS:// path
    [string] $Service = 'localhost',
    [string[]] $TestAttribute # optional: check specific attrs
    )
    if (-not (Get-QADService -ErrorAction SilentlyContinue)) {
    Connect-QADService -Service $Service -Proxy | Out-Null
    }
    $o = Get-QADObject $Identity -DontUseDefaultIncludedProperties `
    -IncludedProperties name -ErrorAction Stop
    if (-not $o) { throw "Not visible to the current user (Access denied or no List rights)." }
    $de = $o.DirectoryEntry
    $de.RefreshCache(@('edsaObjectRightsEffective','allowedAttributesEffective'))

    $rights = [int64]$de.Properties['edsaObjectRightsEffective'].Value
    $writable = @($de.Properties['allowedAttributesEffective'] | ForEach-Object { $_ })

    $map = [ordered]@{ CreateChild=1;DeleteChild=2;List=4;Self=8;ReadProp=16;WriteProp=32;
    DeleteTree=64;ListObject=128;ControlAccess=256;Delete=65536;ReadControl=131072;
    WriteDac=262144;WriteOwner=524288 }
    $flags = $map.GetEnumerator() | Where-Object { ($rights -band $_.Value) -eq $_.Value } |
    ForEach-Object { $_.Key }

    [pscustomobject]@{
    DN = $o.DN
    ObjectRightsValue = $rights
    ObjectRights = $flags
    WritableAttributes = $writable | Sort-Object
    WritableCount = $writable.Count
    AttributeCheck = if ($TestAttribute) {
    $TestAttribute | ForEach-Object {
    [pscustomobject]@{ Attribute = $_; Writable = ($writable -contains $_) } }
    } else { $null }
    }
    }

    # e.g.
    Get-ARSEffectiveRights -Identity 'CN=jdoe,OU=Staff,DC=domain,DC=com' `
    -TestAttribute description,telephoneNumber

    Two gotchas worth calling out:

    1. Bind through the Administration Service, never a direct DC. A direct DC bind gives you raw AD rights, not the ARS virtual delegation.
    2. These are constructed attributes, so they won't come back in a normal search — you have to request them explicitly /  RefreshCache  them, which your original script already does correctly.

    One honest limitation:  allowedAttributesEffective  only gives you the writable set. There's no symmetric "effective readable attributes" constructed attribute, so the "you have Read to this list, Write to these" ideal is only half-served natively — for read you're left with the object-level  ReadProp  bit in  edsaObjectRightsEffective  plus the practical "did the attribute return a value when I asked for it" test. But for the write question (your  description  example), this nails it.

    Hope that helps!

Reply
  • Hi Shawn,

    I dug into this on a lab server and can confirm your approach is not just "the closest you can get" — it's actually the correct, product-sanctioned way to do it. You don't need to worry that  allowedAttributesEffective  is showing you the service account's view.

    When you bind through the Active Roles Administration Service (the  EDMS://  provider / a proxy connection), these MS-ADTS constructed attributes are recomputed by Active Roles, for the currently connected user, against that user's virtual (Access Template) delegation — the exact same evaluation the MMC and Web Interface use to decide whether to render a field read-only.

    I verified it by binding as a delegate with zero rights and then escalating delegation via Access Templates, reading the attributes each time while impersonating that delegate:

    • No delegation → the object isn't even visible (Access denied).
    • Read-only AT ("All Objects – Read All Properties") →  edsaObjectRightsEffective  = 144 (List + ReadProp), and  allowedAttributesEffective  comes back empty — i.e. you can read it but write nothing.
    • + "Users – Read/Write General Information" →  allowedAttributesEffective  becomes exactly the ~12 attributes of that property set.
    • + "Users – Read/Write Public Information" →  description  now appears in  allowedAttributesEffective , while an attribute outside those sets ( telephoneNumber ) stays absent.

    For contrast, an ARS admin on the same object saw  edsaObjectRightsEffective  = 987135 and 1065 writable attributes. So the writable set tracks the delegated Access Template property sets attribute-by-attribute — no need to enumerate/expand ATs and group membership yourself. It's a single server-side bind, so it's O(1) rather than the slow accumulation you were dreading.

    Here's a tidied-up version of your script wrapped as a reusable function — returns objects instead of writing to the host, connects via proxy if you're not already connected, and lets you test specific attributes:

    function Get-ARSEffectiveRights {
    [CmdletBinding()]
    param(
    [Parameter(Mandatory)] [string] $Identity, # DN or EDMS:// path
    [string] $Service = 'localhost',
    [string[]] $TestAttribute # optional: check specific attrs
    )
    if (-not (Get-QADService -ErrorAction SilentlyContinue)) {
    Connect-QADService -Service $Service -Proxy | Out-Null
    }
    $o = Get-QADObject $Identity -DontUseDefaultIncludedProperties `
    -IncludedProperties name -ErrorAction Stop
    if (-not $o) { throw "Not visible to the current user (Access denied or no List rights)." }
    $de = $o.DirectoryEntry
    $de.RefreshCache(@('edsaObjectRightsEffective','allowedAttributesEffective'))

    $rights = [int64]$de.Properties['edsaObjectRightsEffective'].Value
    $writable = @($de.Properties['allowedAttributesEffective'] | ForEach-Object { $_ })

    $map = [ordered]@{ CreateChild=1;DeleteChild=2;List=4;Self=8;ReadProp=16;WriteProp=32;
    DeleteTree=64;ListObject=128;ControlAccess=256;Delete=65536;ReadControl=131072;
    WriteDac=262144;WriteOwner=524288 }
    $flags = $map.GetEnumerator() | Where-Object { ($rights -band $_.Value) -eq $_.Value } |
    ForEach-Object { $_.Key }

    [pscustomobject]@{
    DN = $o.DN
    ObjectRightsValue = $rights
    ObjectRights = $flags
    WritableAttributes = $writable | Sort-Object
    WritableCount = $writable.Count
    AttributeCheck = if ($TestAttribute) {
    $TestAttribute | ForEach-Object {
    [pscustomobject]@{ Attribute = $_; Writable = ($writable -contains $_) } }
    } else { $null }
    }
    }

    # e.g.
    Get-ARSEffectiveRights -Identity 'CN=jdoe,OU=Staff,DC=domain,DC=com' `
    -TestAttribute description,telephoneNumber

    Two gotchas worth calling out:

    1. Bind through the Administration Service, never a direct DC. A direct DC bind gives you raw AD rights, not the ARS virtual delegation.
    2. These are constructed attributes, so they won't come back in a normal search — you have to request them explicitly /  RefreshCache  them, which your original script already does correctly.

    One honest limitation:  allowedAttributesEffective  only gives you the writable set. There's no symmetric "effective readable attributes" constructed attribute, so the "you have Read to this list, Write to these" ideal is only half-served natively — for read you're left with the object-level  ReadProp  bit in  edsaObjectRightsEffective  plus the practical "did the attribute return a value when I asked for it" test. But for the write question (your  description  example), this nails it.

    Hope that helps!

Children
No Data