From ebdbb6a0207966d232ddde21a6c5869c83ab4820 Mon Sep 17 00:00:00 2001 From: "Ritik Mittal (from Dev Box)" Date: Thu, 3 Oct 2024 16:40:12 +0530 Subject: [PATCH 1/8] Added AADAuthenticationRequirements resource for Per-User MFA Requirements monitoring --- CHANGELOG.md | 2 + .../MSFT_AADAuthenticationRequirement.psm1 | 381 ++++++++++++++++++ ...FT_AADAuthenticationRequirement.schema.mof | 14 + .../readme.md | 6 + .../settings.json | 20 + .../AADAuthenticationRequirement/2-Update.ps1 | 34 ++ ...DSC.AADAuthenticationRequirement.Tests.ps1 | 160 ++++++++ 7 files changed, 617 insertions(+) create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/readme.md create mode 100644 Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json create mode 100644 Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 create mode 100644 Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2196377267..ed8f6aa7ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ * Added ReportSuspiciousActivitySettings * AADAuthenticationMethodPolicyHardware * Initial release. +* AADAuthenticationRequirement + * Initial release. * AADEntitlementManagementSettings * Initial release. * AADFeatureRolloutPolicy diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 new file mode 100644 index 0000000000..392a3e0f88 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 @@ -0,0 +1,381 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter()] + [ValidateSet('enabled', 'disabled')] + [System.String] + $PerUserMfaState, + + [Parameter(Mandatory = $true)] + [System.String] + $Id, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + try + { + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $nullResult = $PSBoundParameters + + $getValue = $null + $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/users/$Id/authentication/requirements" + $getValue = Invoke-MgGraphRequest -Method Get -Uri $url + + if ($null -eq $getValue) + { + Write-Verbose -Message "Could not find an Azure AD Authentication Requirement for user with id {$Id}" + return $nullResult + } + + Write-Verbose -Message "An Azure AD Authentication Method Policy Requirement for a user with Id {$Id} was found." + + $results = @{ + PerUserMfaState = $getValue.perUserMfaState + Id = $Id + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + ApplicationSecret = $ApplicationSecret + CertificateThumbprint = $CertificateThumbprint + Managedidentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + + return [System.Collections.Hashtable] $results + } + catch + { + New-M365DSCLogEntry -Message 'Error retrieving data:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return $nullResult + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateSet('enabled', 'disabled')] + [System.String] + $PerUserMfaState, + + [Parameter(Mandatory = $true)] + [System.String] + $Id, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + $currentInstance = Get-TargetResource @PSBoundParameters + $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/users/$Id/authentication/requirements" + + $params = @{} + if ($PerUserMfaState -eq 'enabled' -and $currentInstance.PerUserMfaState -eq 'disabled') + { + $params = @{ + "perUserMfaState" = "enabled" + } + elseif ($PerUserMfaState -eq 'disabled' -and $currentInstance.PerUserMfaState -eq 'enabled') + { + $params = @{ + "perUserMfaState" = "disabled" + } + } + + $jsonParams = $params | ConvertTo-Json + + Invoke-MgGraphRequest -Method PATCH -Uri $url -Body $jsonParams +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateSet('enabled', 'disabled')] + [System.String] + $PerUserMfaState, + + [Parameter(Mandatory = $true)] + [System.String] + $Id, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + Write-Verbose -Message "Testing configuration of the Azure AD Authentication Requirement for a user with Id {$Id}" + + $CurrentValues = Get-TargetResource @PSBoundParameters + $ValuesToCheck = ([Hashtable]$PSBoundParameters).clone() + + $testResult = $true + + $CurrentValues.remove('Id') | Out-Null + $ValuesToCheck.remove('Id') | Out-Null + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $ValuesToCheck)" + + if ($testResult) + { + $testResult = Test-M365DSCParameterState -CurrentValues $CurrentValues ` + -Source $($MyInvocation.MyCommand.Source) ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck $ValuesToCheck.Keys + } + + Write-Verbose -Message "Test-TargetResource returned $testResult" + + return $testResult +} + +function Export-TargetResource +{ + [CmdletBinding()] + [OutputType([System.String])] + param + ( + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.Management.Automation.PSCredential] + $ApplicationSecret, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + + $ConnectionMode = New-M365DSCConnection -Workload 'MicrosoftGraph' ` + -InboundParameters $PSBoundParameters + + #Ensure the proper dependencies are installed in the current environment. + Confirm-M365DSCDependencies + + #region Telemetry + $ResourceName = $MyInvocation.MyCommand.ModuleName.Replace('MSFT_', '') + $CommandName = $MyInvocation.MyCommand + $data = Format-M365DSCTelemetryParameters -ResourceName $ResourceName ` + -CommandName $CommandName ` + -Parameters $PSBoundParameters + Add-M365DSCTelemetryEvent -Data $data + #endregion + + try + { + [array]$getValue = Get-MgUser -ErrorAction Stop | Where-Object -FilterScript {$null -ne $_.Id} + + $i = 1 + $dscContent = '' + if ($getValue.Length -eq 0) + { + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + else + { + Write-Host "`r`n" -NoNewline + } + foreach ($config in $getValue) + { + if ($null -ne $Global:M365DSCExportResourceInstancesCount) + { + $Global:M365DSCExportResourceInstancesCount++ + } + + $displayedKey = $config.Id + if (-not [String]::IsNullOrEmpty($config.DisplayName)) + { + $displayedKey = $config.DisplayName + } + + Write-Host " |---[$i/$($getValue.Count)] $displayedKey" -NoNewline + $params = @{ + Id = $config.Id + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + ApplicationSecret = $ApplicationSecret + CertificateThumbprint = $CertificateThumbprint + Managedidentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + + $Results = Get-TargetResource @Params + $Results = Update-M365DSCExportAuthenticationResults -ConnectionMode $ConnectionMode ` + -Results $Results + + $currentDSCBlock = Get-M365DSCExportContentForResource -ResourceName $ResourceName ` + -ConnectionMode $ConnectionMode ` + -ModulePath $PSScriptRoot ` + -Results $Results ` + -Credential $Credential + + $dscContent += $currentDSCBlock + Save-M365DSCPartialExport -Content $currentDSCBlock ` + -FileName $Global:PartialExportFileName + $i++ + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + return $dscContent + } + catch + { + Write-Host $Global:M365DSCEmojiRedX + + New-M365DSCLogEntry -Message 'Error during Export:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return '' + } +} + +Export-ModuleMember -Function *-TargetResource diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof new file mode 100644 index 0000000000..186479b5a4 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof @@ -0,0 +1,14 @@ +[ClassVersion("1.0.0.0"), FriendlyName("AADAuthenticationRequirement")] +class MSFT_AADAuthenticationRequirement : OMI_BaseResource +{ + [Write, Description("The state of the MFA enablement for the user. Possible values are: enabled, disabled."), ValueMap{"enabled","disabled"}, Values{"enabled","disabled"}] String PerUserMfaState; + [Key, Description("The unique identifier for an entity. Read-only.")] String Id; + + [Write, Description("Credentials of the Admin"), EmbeddedInstance("MSFT_Credential")] string Credential; + [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; + [Write, Description("Id of the Azure Active Directory tenant used for authentication.")] String TenantId; + [Write, Description("Secret of the Azure Active Directory tenant used for authentication."), EmbeddedInstance("MSFT_Credential")] String ApplicationSecret; + [Write, Description("Thumbprint of the Azure Active Directory application's authentication certificate to use for authentication.")] String CertificateThumbprint; + [Write, Description("Managed ID being used for authentication.")] Boolean ManagedIdentity; + [Write, Description("Access token used for authentication.")] String AccessTokens[]; +}; diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/readme.md new file mode 100644 index 0000000000..8495f479ee --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/readme.md @@ -0,0 +1,6 @@ + +# AADAuthenticationRequirement + +## Description + +Azure AD Authentication Requirement Resource to set up Per-User MFA settings diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json new file mode 100644 index 0000000000..65c0642985 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json @@ -0,0 +1,20 @@ +{ + "resourceName": "AADAuthenticationRequirement", + "description": "This resource configures Azure AD Authentication MFA Requirements for a user.", + "roles": { + "read": [], + "update": [] + }, + "permissions": { + "graph": { + "delegated": { + "read": [], + "update": [] + }, + "application": { + "read": [], + "update": [] + } + } + } +} diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 new file mode 100644 index 0000000000..807138beab --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 @@ -0,0 +1,34 @@ +<# +This example is used to test new resources and showcase the usage of new resources being worked on. +It is not meant to use as a production baseline. +#> + +Configuration Example +{ + param( + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint + ) + Import-DscResource -ModuleName Microsoft365DSC + + Node localhost + { + AADAuthenticationRequirement "AADAuthenticationRequirement-98ceffcc-7c54-4227-8844-835af5a023ce" + { + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = "disabled" + } + } +} diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 new file mode 100644 index 0000000000..25f990dd66 --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 @@ -0,0 +1,160 @@ +[CmdletBinding()] +param( +) +$M365DSCTestFolder = Join-Path -Path $PSScriptRoot ` + -ChildPath '..\..\Unit' ` + -Resolve +$CmdletModule = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Microsoft365.psm1' ` + -Resolve) +$GenericStubPath = (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\Stubs\Generic.psm1' ` + -Resolve) +Import-Module -Name (Join-Path -Path $M365DSCTestFolder ` + -ChildPath '\UnitTestHelper.psm1' ` + -Resolve) + +$CurrentScriptPath = $PSCommandPath.Split('\') +$CurrentScriptName = $CurrentScriptPath[$CurrentScriptPath.Length -1] +$ResourceName = $CurrentScriptName.Split('.')[1] +$Global:DscHelper = New-M365DscUnitTestHelper -StubModule $CmdletModule ` + -DscResource $ResourceName -GenericStubModule $GenericStubPath + +Describe -Name $Global:DscHelper.DescribeHeader -Fixture { + InModuleScope -ModuleName $Global:DscHelper.ModuleName -ScriptBlock { + Invoke-Command -ScriptBlock $Global:DscHelper.InitializeScript -NoNewScope + BeforeAll { + + $secpasswd = ConvertTo-SecureString (New-Guid | Out-String) -AsPlainText -Force + $Credential = New-Object System.Management.Automation.PSCredential ('tenantadmin@mydomain.com', $secpasswd) + + Mock -CommandName Confirm-M365DSCDependencies -MockWith { + } + + Mock -CommandName New-M365DSCConnection -MockWith { + return "Credentials" + } + + # Mock Write-Host to hide output during the tests + Mock -CommandName Write-Host -MockWith { + } + $Script:exportedInstances =$null + $Script:ExportMode = $false + } + # Test contexts + Context -Name "The instance exists and values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = 'Enabled' + Credential = $Credential; + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + return @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = 'Enabled' + Credential = $Credential; + } + } + } + + It 'Should return true from the Test method' { + Test-TargetResource @testParams | Should -Be $true + } + } + + Context -Name "The instance exists and values are NOT in the desired state - Enable" -Fixture { + BeforeAll { + $testParams = @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = 'Enabled' + Credential = $Credential; + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + return @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = 'Disabled' + Credential = $Credential; + } + } + } + + It 'Should return Values from the Get method' { + (Get-TargetResource @testParams).PerUserMfaState | Should -Be 'Disabled' + } + + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should call the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName Invoke-MgGraphRequest -Exactly 1 -ParameterFilter { $Method -eq 'PATCH' } + } + } + + Context -Name "The instance exists and values are NOT in the desired state - Disable" -Fixture { + BeforeAll { + $testParams = @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = 'Disabled' + Credential = $Credential; + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + return @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = 'Enabled' + Credential = $Credential; + } + } + } + + It 'Should return Values from the Get method' { + (Get-TargetResource @testParams).PerUserMfaState | Should -Be 'Enabled' + } + + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + + It 'Should call the Set method' { + Set-TargetResource @testParams + Should -Invoke -CommandName Invoke-MgGraphRequest -Exactly 1 -ParameterFilter { $Method -eq 'PATCH' } + } + } + + Context -Name 'ReverseDSC Tests' -Fixture { + BeforeAll { + $Global:CurrentModeIsExport = $true + $Global:PartialExportFileName = "$(New-Guid).partial.ps1" + $testParams = @{ + Credential = $Credential; + } + + Mock -CommandName Invoke-MgGraphRequest -MockWith { + return @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = 'Enabled' + Credential = $Credential; + } + } + + Mock -CommandName Get-MgUser -MockWith { + return @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + Credential = $Credential; + } + } + } + It 'Should Reverse Engineer resource from the Export method' { + $result = Export-TargetResource @testParams + $result | Should -Not -BeNullOrEmpty + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope From d2aad00f9b69faa0740ae3503e4082cabfb3431b Mon Sep 17 00:00:00 2001 From: NikCharlebois Date: Thu, 3 Oct 2024 11:11:32 +0000 Subject: [PATCH 2/8] Updated {Update} AAD Integration Tests --- .../M365DSCIntegration.AAD.Update.Tests.ps1 | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 b/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 index c9363e1d89..5ded12c7de 100644 --- a/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 +++ b/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 @@ -408,6 +408,14 @@ ); State = "enabled"; } + AADAuthenticationRequirement 'AADAuthenticationRequirement-98ceffcc-7c54-4227-8844-835af5a023ce' + { + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + PerUserMfaState = "disabled" + } AADAuthenticationStrengthPolicy 'AADAuthenticationStrengthPolicy-Example' { AllowedCombinations = @("windowsHelloForBusiness","fido2","deviceBasedPush"); # Updated Property From 8e9263bb50897c39b3997d486693debe53a5f59e Mon Sep 17 00:00:00 2001 From: NikCharlebois Date: Thu, 3 Oct 2024 11:13:17 +0000 Subject: [PATCH 3/8] Updated Schema Definition --- Modules/Microsoft365DSC/SchemaDefinition.json | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Modules/Microsoft365DSC/SchemaDefinition.json b/Modules/Microsoft365DSC/SchemaDefinition.json index 08ad6e8253..eed76accce 100644 --- a/Modules/Microsoft365DSC/SchemaDefinition.json +++ b/Modules/Microsoft365DSC/SchemaDefinition.json @@ -2044,6 +2044,56 @@ } ] }, + { + "ClassName": "MSFT_AADAuthenticationRequirement", + "Parameters": [ + { + "CIMType": "String", + "Name": "PerUserMfaState", + "Option": "Write" + }, + { + "CIMType": "String", + "Name": "Id", + "Option": "Key" + }, + { + "CIMType": "MSFT_Credential", + "Name": "Credential", + "Option": "Write" + }, + { + "CIMType": "String", + "Name": "ApplicationId", + "Option": "Write" + }, + { + "CIMType": "String", + "Name": "TenantId", + "Option": "Write" + }, + { + "CIMType": "MSFT_Credential", + "Name": "ApplicationSecret", + "Option": "Write" + }, + { + "CIMType": "String", + "Name": "CertificateThumbprint", + "Option": "Write" + }, + { + "CIMType": "Boolean", + "Name": "ManagedIdentity", + "Option": "Write" + }, + { + "CIMType": "String[]", + "Name": "AccessTokens", + "Option": "Write" + } + ] + }, { "ClassName": "MSFT_AADAuthenticationStrengthPolicy", "Parameters": [ From 826ad2d1119c24713a3e040a2ca8c179f7d481c7 Mon Sep 17 00:00:00 2001 From: "Ritik Mittal (from Dev Box)" Date: Thu, 3 Oct 2024 16:58:14 +0530 Subject: [PATCH 4/8] Added permissions --- .../settings.json | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json index 65c0642985..e56d74c0d4 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/settings.json @@ -8,12 +8,32 @@ "permissions": { "graph": { "delegated": { - "read": [], - "update": [] + "read": + [ + { + "name": "UserAuthenticationMethod.Read.All" + } + ], + "update": + [ + { + "name": "UserAuthenticationMethod.ReadWrite.All" + } + ] }, "application": { - "read": [], - "update": [] + "read": + [ + { + "name": "UserAuthenticationMethod.Read.All" + } + ], + "update": + [ + { + "name": "UserAuthenticationMethod.ReadWrite.All" + } + ] } } } From 62dcb23528956a32799cb9fccaa8da767a024f7b Mon Sep 17 00:00:00 2001 From: "Ritik Mittal (from Dev Box)" Date: Fri, 4 Oct 2024 11:46:09 +0530 Subject: [PATCH 5/8] Changed key from id to upn --- .../MSFT_AADAuthenticationRequirement.psm1 | 21 ++++++++++--------- ...FT_AADAuthenticationRequirement.schema.mof | 2 +- .../AADAuthenticationRequirement/2-Update.ps1 | 6 +++--- .../Microsoft365DSC/Modules/M365DSCUtil.psm1 | 4 ++++ ...DSC.AADAuthenticationRequirement.Tests.ps1 | 16 +++++++------- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 index 392a3e0f88..d78d5bfbf1 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.psm1 @@ -11,7 +11,7 @@ function Get-TargetResource [Parameter(Mandatory = $true)] [System.String] - $Id, + $UserPrincipalName, [Parameter()] [System.Management.Automation.PSCredential] @@ -62,20 +62,20 @@ function Get-TargetResource $nullResult = $PSBoundParameters $getValue = $null - $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/users/$Id/authentication/requirements" + $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/users/$UserPrincipalName/authentication/requirements" $getValue = Invoke-MgGraphRequest -Method Get -Uri $url if ($null -eq $getValue) { - Write-Verbose -Message "Could not find an Azure AD Authentication Requirement for user with id {$Id}" + Write-Verbose -Message "Could not find an Azure AD Authentication Requirement for user with UPN {$UserPrincipalName}" return $nullResult } - Write-Verbose -Message "An Azure AD Authentication Method Policy Requirement for a user with Id {$Id} was found." + Write-Verbose -Message "An Azure AD Authentication Method Policy Requirement for a user with UPN {$UserPrincipalName} was found." $results = @{ PerUserMfaState = $getValue.perUserMfaState - Id = $Id + UserPrincipalName = $UserPrincipalName Credential = $Credential ApplicationId = $ApplicationId TenantId = $TenantId @@ -111,7 +111,7 @@ function Set-TargetResource [Parameter(Mandatory = $true)] [System.String] - $Id, + $UserPrincipalName, [Parameter()] [System.Management.Automation.PSCredential] @@ -155,13 +155,14 @@ function Set-TargetResource #endregion $currentInstance = Get-TargetResource @PSBoundParameters - $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/users/$Id/authentication/requirements" + $url = $Global:MSCloudLoginConnectionProfile.MicrosoftGraph.ResourceUrl + "beta/users/$UserPrincipalName/authentication/requirements" $params = @{} if ($PerUserMfaState -eq 'enabled' -and $currentInstance.PerUserMfaState -eq 'disabled') { $params = @{ "perUserMfaState" = "enabled" + } } elseif ($PerUserMfaState -eq 'disabled' -and $currentInstance.PerUserMfaState -eq 'enabled') { @@ -188,7 +189,7 @@ function Test-TargetResource [Parameter(Mandatory = $true)] [System.String] - $Id, + $UserPrincipalName, [Parameter()] [System.Management.Automation.PSCredential] @@ -231,7 +232,7 @@ function Test-TargetResource Add-M365DSCTelemetryEvent -Data $data #endregion - Write-Verbose -Message "Testing configuration of the Azure AD Authentication Requirement for a user with Id {$Id}" + Write-Verbose -Message "Testing configuration of the Azure AD Authentication Requirement for a user with UPN {$UserPrincipalName}" $CurrentValues = Get-TargetResource @PSBoundParameters $ValuesToCheck = ([Hashtable]$PSBoundParameters).clone() @@ -336,7 +337,7 @@ function Export-TargetResource Write-Host " |---[$i/$($getValue.Count)] $displayedKey" -NoNewline $params = @{ - Id = $config.Id + UserPrincipalName = $config.UserPrincipalName Credential = $Credential ApplicationId = $ApplicationId TenantId = $TenantId diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof index 186479b5a4..f1182e0ff3 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADAuthenticationRequirement/MSFT_AADAuthenticationRequirement.schema.mof @@ -2,7 +2,7 @@ class MSFT_AADAuthenticationRequirement : OMI_BaseResource { [Write, Description("The state of the MFA enablement for the user. Possible values are: enabled, disabled."), ValueMap{"enabled","disabled"}, Values{"enabled","disabled"}] String PerUserMfaState; - [Key, Description("The unique identifier for an entity. Read-only.")] String Id; + [Key, Description("The unique identifier for an entity. Read-only.")] String UserPrincipalName; [Write, Description("Credentials of the Admin"), EmbeddedInstance("MSFT_Credential")] string Credential; [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId; diff --git a/Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 index 807138beab..ec3edca920 100644 --- a/Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 +++ b/Modules/Microsoft365DSC/Examples/Resources/AADAuthenticationRequirement/2-Update.ps1 @@ -22,13 +22,13 @@ Configuration Example Node localhost { - AADAuthenticationRequirement "AADAuthenticationRequirement-98ceffcc-7c54-4227-8844-835af5a023ce" + AADAuthenticationRequirement "AADAuthenticationRequirement-TestMailbox109@xtasdftestorg.onmicrosoft.com" { ApplicationId = $ApplicationId TenantId = $TenantId CertificateThumbprint = $CertificateThumbprint - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" - PerUserMfaState = "disabled" + PerUserMfaState = "disabled"; + UserPrincipalName = "TestMailbox109@$OrganizationName"; } } } diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index d8e020b073..148c450c1a 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -3825,6 +3825,10 @@ function Get-M365DSCExportContentForResource { $primaryKey = $Results.DomainName } + elseif ($Keys.Contains('UserPrincipalName')) + { + $primaryKey = $Results.UserPrincipalName + } if ([String]::IsNullOrEmpty($primaryKey) -and ` -not $Keys.Contains('IsSingleInstance')) diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 index 25f990dd66..0b80d7243d 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 @@ -45,14 +45,14 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "The instance exists and values are already in the desired state" -Fixture { BeforeAll { $testParams = @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" PerUserMfaState = 'Enabled' Credential = $Credential; } Mock -CommandName Invoke-MgGraphRequest -MockWith { return @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" PerUserMfaState = 'Enabled' Credential = $Credential; } @@ -67,14 +67,14 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "The instance exists and values are NOT in the desired state - Enable" -Fixture { BeforeAll { $testParams = @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" PerUserMfaState = 'Enabled' Credential = $Credential; } Mock -CommandName Invoke-MgGraphRequest -MockWith { return @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" PerUserMfaState = 'Disabled' Credential = $Credential; } @@ -98,14 +98,14 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Context -Name "The instance exists and values are NOT in the desired state - Disable" -Fixture { BeforeAll { $testParams = @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" PerUserMfaState = 'Disabled' Credential = $Credential; } Mock -CommandName Invoke-MgGraphRequest -MockWith { return @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" PerUserMfaState = 'Enabled' Credential = $Credential; } @@ -136,7 +136,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Mock -CommandName Invoke-MgGraphRequest -MockWith { return @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" PerUserMfaState = 'Enabled' Credential = $Credential; } @@ -144,7 +144,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Mock -CommandName Get-MgUser -MockWith { return @{ - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" + UserPrincipalName = "user@test.com" Credential = $Credential; } } From 5f2d0ad6eca721414cacd1444bc0ba0bbf4de7b8 Mon Sep 17 00:00:00 2001 From: NikCharlebois Date: Fri, 4 Oct 2024 06:17:24 +0000 Subject: [PATCH 6/8] Updated {Update} AAD Integration Tests --- .../Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 b/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 index 5ded12c7de..e28b8587c9 100644 --- a/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 +++ b/Tests/Integration/Microsoft365DSC/M365DSCIntegration.AAD.Update.Tests.ps1 @@ -408,13 +408,13 @@ ); State = "enabled"; } - AADAuthenticationRequirement 'AADAuthenticationRequirement-98ceffcc-7c54-4227-8844-835af5a023ce' + AADAuthenticationRequirement 'AADAuthenticationRequirement-TestMailbox109@xtasdftestorg.onmicrosoft.com' { ApplicationId = $ApplicationId TenantId = $TenantId CertificateThumbprint = $CertificateThumbprint - Id = "98ceffcc-7c54-4227-8844-835af5a023ce" - PerUserMfaState = "disabled" + PerUserMfaState = "disabled"; + UserPrincipalName = "TestMailbox109@$OrganizationName"; } AADAuthenticationStrengthPolicy 'AADAuthenticationStrengthPolicy-Example' { From dabd354272e2b31d867fa0c23543c3eb5fd176af Mon Sep 17 00:00:00 2001 From: "Ritik Mittal (from Dev Box)" Date: Fri, 4 Oct 2024 11:48:28 +0530 Subject: [PATCH 7/8] Fixed UT --- .../Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 index 0b80d7243d..04b2234a09 100644 --- a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.AADAuthenticationRequirement.Tests.ps1 @@ -144,6 +144,7 @@ Describe -Name $Global:DscHelper.DescribeHeader -Fixture { Mock -CommandName Get-MgUser -MockWith { return @{ + Id = "98ceffcc-7c54-4227-8844-835af5a023ce" UserPrincipalName = "user@test.com" Credential = $Credential; } From 84c6043a43ecf359e61cbcb3477d6745044383e6 Mon Sep 17 00:00:00 2001 From: NikCharlebois Date: Fri, 4 Oct 2024 06:19:01 +0000 Subject: [PATCH 8/8] Updated Schema Definition --- Modules/Microsoft365DSC/SchemaDefinition.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Microsoft365DSC/SchemaDefinition.json b/Modules/Microsoft365DSC/SchemaDefinition.json index eed76accce..95916364b2 100644 --- a/Modules/Microsoft365DSC/SchemaDefinition.json +++ b/Modules/Microsoft365DSC/SchemaDefinition.json @@ -2054,7 +2054,7 @@ }, { "CIMType": "String", - "Name": "Id", + "Name": "UserPrincipalName", "Option": "Key" }, {