Skip to content

Commit

Permalink
Merge branch 'main' into CalLog_SaveTimeLine
Browse files Browse the repository at this point in the history
  • Loading branch information
dpaulson45 authored Jan 12, 2024
2 parents bc6f3af + b1e882f commit 0993385
Show file tree
Hide file tree
Showing 23 changed files with 1,514 additions and 414 deletions.
4 changes: 3 additions & 1 deletion .build/Build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Set-StrictMode -Version Latest
. $PSScriptRoot\BuildFunctions\Get-ScriptDependencyTree.ps1
. $PSScriptRoot\BuildFunctions\Show-ScriptDependencyTree.ps1

Write-Host "Build process is running on: Windows? $IsWindows - MacOS? $IsMacOS - Linux? $IsLinux"

$repoRoot = Get-Item "$PSScriptRoot\.."

<#
Expand Down Expand Up @@ -139,7 +141,7 @@ if ($nonUnique.Count -gt 0) {

$scriptVersions = @()

$disclaimer = [IO.File]::ReadAllLines("$PSScriptRoot\..\LICENSE")
$disclaimer = [IO.File]::ReadAllLines([IO.Path]::Combine($PSScriptRoot, "..", "LICENSE"))

$documentedScriptFiles | ForEach-Object {
$scriptName = [IO.Path]::GetFileName($_)
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist
.DS_Store
1 change: 1 addition & 0 deletions Admin/Test-ExchangePropertyPermissions/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

$propertySets = @(
[PSCustomObject]@{
Name = "Exchange-Information"
RightsGuid = [Guid]::Parse("1F298A89-DE98-47b8-B5CD-572AD53D267E")
MemberAttributes = New-Object System.Collections.ArrayList
},
[PSCustomObject]@{
Name = "Exchange-Personal-Information"
RightsGuid = [Guid]::Parse("B1B3A417-EC55-4191-B327-B72E33E38AF2")
MemberAttributes = New-Object System.Collections.ArrayList
},
[PSCustomObject]@{
Name = "Personal-Information"
RightsGuid = [Guid]::Parse("77B5B886-944A-11d1-AEBD-0000F80367C1")
MemberAttributes = New-Object System.Collections.ArrayList
},
[PSCustomObject]@{
Name = "Public-Information"
RightsGuid = [Guid]::Parse("E48D0154-BCF8-11D1-8702-00C04FB96050")
MemberAttributes = New-Object System.Collections.ArrayList
}
)

$rootDSE = [ADSI]("LDAP://$([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)/RootDSE")
$schemaContainer = [ADSI]("LDAP://" + $rootDSE.schemaNamingContext)

foreach ($propertySet in $propertySets) {
$rightsGuidByteString = ""
$propertySet.RightsGuid.ToByteArray() | ForEach-Object { $rightsGuidByteString += ("\$($_.ToString("X"))") }
$searcher = New-Object System.DirectoryServices.directorySearcher($schemaContainer, "(&(objectClass=attributeSchema)(attributeSecurityGuid=$rightsGuidByteString))")
$searcher.PageSize = 100
$results = $searcher.FindAll()
foreach ($result in $results) {
[void]$propertySet.MemberAttributes.Add($result.Properties["cn"][0])
}
}

$getPropertySetInfoBuilder = New-Object System.Text.StringBuilder
[void]$getPropertySetInfoBuilder.AppendLine("# Copyright (c) Microsoft Corporation.")
[void]$getPropertySetInfoBuilder.AppendLine("# Licensed under the MIT License.")
[void]$getPropertySetInfoBuilder.AppendLine("")
[void]$getPropertySetInfoBuilder.AppendLine("# This is a generated function. Do not manually modify.")
[void]$getPropertySetInfoBuilder.AppendLine("function Get-PropertySetInfo {")
[void]$getPropertySetInfoBuilder.AppendLine(" [CmdletBinding()]")
[void]$getPropertySetInfoBuilder.AppendLine(" [OutputType([System.Object[]])]")
[void]$getPropertySetInfoBuilder.AppendLine(" param ()")
[void]$getPropertySetInfoBuilder.AppendLine("")
[void]$getPropertySetInfoBuilder.AppendLine(" # cSpell:disable")
[void]$getPropertySetInfoBuilder.AppendLine(" `$propertySetInfo = @(")
for ($i = 0; $i -lt $propertySets.Count; $i++) {
$propertySet = $propertySets[$i]
$memberAttributeString = [string]::Join(", ", ($propertySet.MemberAttributes | ForEach-Object { "`"$_`"" }))
[void]$getPropertySetInfoBuilder.AppendLine(" [PSCustomObject]@{")
[void]$getPropertySetInfoBuilder.AppendLine(" Name = `"$($propertySet.Name)`"")
[void]$getPropertySetInfoBuilder.AppendLine(" RightsGuid = [Guid]::Parse(`"$($propertySet.RightsGuid)`")")
[void]$getPropertySetInfoBuilder.AppendLine(" MemberAttributes = $memberAttributeString")
[void]$getPropertySetInfoBuilder.Append(" }")
if ($i + 1 -lt $propertySets.Count) {
[void]$getPropertySetInfoBuilder.AppendLine(",")
}
}
[void]$getPropertySetInfoBuilder.AppendLine(" )")
[void]$getPropertySetInfoBuilder.AppendLine(" # cSpell:enable")
[void]$getPropertySetInfoBuilder.AppendLine(" `$propertySetInfo")
[void]$getPropertySetInfoBuilder.AppendLine("}")
[void]$getPropertySetInfoBuilder.AppendLine("")

Set-Content $PSScriptRoot\Get-PropertySetInfo.ps1 $getPropertySetInfoBuilder.ToString()
34 changes: 34 additions & 0 deletions Admin/Test-ExchangePropertyPermissions/Get-PropertySetInfo.ps1

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]
$TargetObjectDN,

[Parameter(Mandatory = $true, Position = 1)]
[string]
$ComputerAccountDN,

[Parameter(Mandatory = $false, Position = 2)]
[string]
$DomainController,

[Parameter(Mandatory = $false, Position = 3)]
[switch]
$SaveReport,

[Parameter(Mandatory = $false, Position = 3)]
[switch]
$OutputDebugInfo
)

begin {
. $PSScriptRoot\..\..\Shared\Out-Columns.ps1
. $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ActiveDirectoryAcl.ps1
. $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ExchangeADSplitPermissionsEnabled.ps1
. $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ExchangeOtherWellKnownObjects.ps1
. $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-ObjectTypeDisplayName.ps1
. $PSScriptRoot\..\..\Shared\ActiveDirectoryFunctions\Get-TokenGroupsGlobalAndUniversal.ps1
. $PSScriptRoot\Get-PropertySetInfo.ps1
. $PSScriptRoot\Test-ExchangeSchema.ps1

$requiredWellKnownGroupsInToken = "Exchange Trusted Subsystem", "Exchange Servers"

$report = [PSCustomObject]@{
TargetObjectDN = $TargetObjectDN
ComputerAccountDN = $ComputerAccountDN
DomainController = $DomainController
RequiredInToken = @()
Token = $null
ACL = $null
ProblemsFound = @()
}
}

process {
if (-not (Test-ExchangeSchema)) {
Write-Warning "Schema validation failed. Exiting."
return
}

if (Get-ExchangeADSplitPermissionsEnabled) {
Write-Host "Split permissions is enabled. In this scenario, it is expected that the Exchange server
computer account does not have write permission to many recipient attributes. The script will
report these as problems, although they may be normal for this configuration."
}

$token = Get-TokenGroupsGlobalAndUniversal -DistinguishedName $ComputerAccountDN
$report.Token = $token
Write-Host "Token groups: $ComputerAccountDN"
$token | Out-Columns

$wellKnownObjects = Get-ExchangeOtherWellKnownObjects
foreach ($wellKnownName in $requiredWellKnownGroupsInToken) {
$groupDN = ($wellKnownObjects | Where-Object { $_.WellKnownName -eq $wellKnownName }).DistinguishedName
$objectSidBytes = ([ADSI]("LDAP://$groupDN")).Properties["objectSID"][0]
$objectSid = New-Object System.Security.Principal.SecurityIdentifier($objectSidBytes, 0)
$report.RequiredInToken += [PSCustomObject]@{
WellKnownName = $wellKnownName
DistinguishedName = $groupDN
ObjectSid = $objectSid.ToString()
}

$matchFound = $token | Where-Object { $_.SID -eq $objectSid.ToString() }
if ($null -eq $matchFound) {
$report.ProblemsFound += "The group $wellKnownName is not in the token."
}
}

$params = @{
DistinguishedName = $TargetObjectDN
}

if (-not [string]::IsNullOrEmpty($DomainController)) {
$params.DomainController = $DomainController
}

$acl = Get-ActiveDirectoryAcl @params
$objectTypeCache = @{}
$displayAces = @()
for ($i = 0; $i -lt $acl.Access.Count; $i++) {
$ace = $acl.Access[$i]
if ($ace.ObjectType -ne [Guid]::Empty) {
if ($null -ne $objectTypeCache[$ace.ObjectType]) {
$ace | Add-Member -NotePropertyName ObjectTypeDisplay -NotePropertyValue $objectTypeCache[$ace.ObjectType]
} else {
$objectTypeDisplay = Get-ObjectTypeDisplayName -ObjectType $ace.ObjectType
$objectTypeCache[$ace.ObjectType] = $objectTypeDisplay
$ace | Add-Member -NotePropertyName ObjectTypeDisplay -NotePropertyValue $objectTypeDisplay
}
}

if ($ace.InheritedObjectType -ne [Guid]::Empty) {
if ($null -ne $objectTypeCache[$ace.InheritedObjectType]) {
$ace | Add-Member -NotePropertyName InheritedObjectTypeDisplay -NotePropertyValue $objectTypeCache[$ace.InheritedObjectType]
} else {
$objectTypeDisplay = Get-ObjectTypeDisplayName -ObjectType $ace.InheritedObjectType
$objectTypeCache[$ace.InheritedObjectType] = $objectTypeDisplay
$ace | Add-Member -NotePropertyName InheritedObjectTypeDisplay -NotePropertyValue $objectTypeDisplay
}
}

$ace | Add-Member -MemberType NoteProperty -Name "Index" -Value $i
$displayAces += $ace
}

$report.ACL = $displayAces
Write-Host "ACL: $TargetObjectDN"
$displayAces | Where-Object { $_.PropagationFlags -ne "InheritOnly" } | Out-Columns -Properties Index, IdentityReference, AccessControlType, ActiveDirectoryRights, ObjectTypeDisplay, IsInherited

$propertySetInfo = Get-PropertySetInfo
$attributeCount = $propertySetInfo.MemberAttributes.Count
$progressCount = 0
$sw = New-Object System.Diagnostics.Stopwatch
$sw.Start()
$schemaPath = ([ADSI]("LDAP://$([System.DirectoryServices.ActiveDirectory.Domain]::GetComputerDomain().Name)/RootDSE")).Properties["schemaNamingContext"][0]
$identityReferenceCache = @{}
foreach ($propertySet in $propertySetInfo) {
foreach ($attributeName in $propertySet.MemberAttributes) {
$progressCount++
if ($sw.ElapsedMilliseconds -gt 1000) {
$sw.Restart()
Write-Progress -Activity "Checking permissions" -PercentComplete $((($progressCount * 100) / $attributeCount))
}

$attributeSchemaEntry = [ADSI]("LDAP://CN=$attributeName,$schemaPath")
if ($attributeSchemaEntry.Properties["attributeSecurityGuid"].Count -lt 1) {
# This schema validation failure should be extremely rare, but we have seen a few
# cases in lab/dev/test environments, such as when ADSchemaAnalyzer has been used to
# copy schema between forests.
$report.ProblemsFound += "The attribute $attributeName is not in the $($propertySet.Name) property set."
continue
}

$schemaIdGuid = New-Object Guid(, $attributeSchemaEntry.Properties["schemaIDGuid"][0])

# We need to hit a write allow ACE for a SID in the token on one of the following:
# - The rightsGuid from the property set
# - The schemaIdGuid from the attributeSchemaEntry
# We must hit the allow before we hit a deny on the same thing.

$found = $false
$problemAceIndex = $null
for ($i = 0; $i -lt $displayAces.Count; $i++) {
$ace = $displayAces[$i]
if ($ace.PropagationFlags -eq "InheritOnly") {
continue
}

$sidToFind = $null
if ($null -eq $ace.IdentityReference.SID) {
if ($null -ne $identityReferenceCache[$ace.IdentityReference.Value]) {
$sidToFind = $identityReferenceCache[$ace.IdentityReference.Value]
} else {
$sidToFind = $ace.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
$identityReferenceCache[$ace.IdentityReference.Value] = $sidToFind
}
} else {
$sidToFind = $ace.IdentityReference.SID
}

$matchingSid = $token | Where-Object { $_.SID -eq $sidToFind.ToString() }
if ($null -ne $matchingSid) {
# The ACE affects this token.
# Does it affect this property?
if ($ace.ObjectType -eq $propertySet.RightsGuid -or $ace.ObjectType -eq $schemaIdGuid -or $ace.ObjectType -eq [Guid]::Empty) {
if ($ace.ActiveDirectoryRights -contains "WriteProperty" -or $ace.ActiveDirectoryRights -contains "GenericAll") {
if ($ace.AccessControlType -eq "Allow") {
$found = $true
break
} else {
$problemAceIndex = $i
break
}
}
}
}
}

if (-not $found) {
if ($null -ne $problemAceIndex) {
$report.ProblemsFound += "The property $attributeName is denied Write by ACE $problemAceIndex."
} else {
$report.ProblemsFound += "The property $attributeName is not allowed Write by any ACE."
}
}
}
}

if ($report.ProblemsFound.Count -gt 0) {
foreach ($problem in $report.ProblemsFound) {
Write-Warning $problem
}
} else {
Write-Host "No problems found."
}

if ($SaveReport) {
$reportPath = $PSScriptRoot + "\" + "PermissionReport-$([DateTime]::Now.ToString("yyMMddHHmmss")).xml"
$report | Export-Clixml $reportPath
Write-Host "Report saved to $reportPath"
}

if ($OutputDebugInfo) {
$debugInfo = @{
ACL = $acl
DisplayAces = $displayAces
IdentityReferenceCache = $identityReferenceCache
Token = $token
TargetObjectDN = $TargetObjectDN
Report = $report
}

$debugInfoPath = Join-Path $PSScriptRoot "DebugInfo.xml"
$debugInfo | Export-Clixml -Path $debugInfoPath
Write-Host "Debug info saved to $debugInfoPath"
}
}
Loading

0 comments on commit 0993385

Please sign in to comment.