diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb19259c2..f5a0b0d71c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,10 @@ # UNRELEASED -* EXOMailboxIRMAccess - * Initial Release. * AADPasswordRuleSettings * Initial release +* ADOSecurityPolicy + * Initial release. * AzureSubscription * Initial Release. * EXOArcConfig @@ -14,6 +14,8 @@ * Initial Release. * EXOMailboxCalendarConfiguration * Initial Release. +* EXOMailboxIRMAccess + * Initial Release. * EXOManagementScope * Initial Release. * EXOManagementScope @@ -43,7 +45,7 @@ * DEPENDENCIES * Added dependencies on Az.Accounts, Az.Resources and Az.SecurityInsights * Updated DSCParser to version 2.0.0.9. - * Updated MSCloudLoginAssistant to version 1.1.24. + * Updated MSCloudLoginAssistant to version 1.1.25. # 1.24.904.1 diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/MSFT_ADOSecurityPolicy.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/MSFT_ADOSecurityPolicy.psm1 new file mode 100644 index 0000000000..e6c10774cd --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/MSFT_ADOSecurityPolicy.psm1 @@ -0,0 +1,534 @@ +function Get-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $OrganizationName, + + [Parameter()] + [System.Boolean] + $DisallowAadGuestUserAccess, + + [Parameter()] + [System.Boolean] + $DisallowOAuthAuthentication, + + [Parameter()] + [System.Boolean] + $DisallowSecureShell, + + [Parameter()] + [System.Boolean] + $LogAuditEvents, + + [Parameter()] + [System.Boolean] + $AllowAnonymousAccess, + + [Parameter()] + [System.Boolean] + $ArtifactsExternalPackageProtectionToken, + + [Parameter()] + [System.Boolean] + $EnforceAADConditionalAccess, + + [Parameter()] + [System.Boolean] + $AllowTeamAdminsInvitationsAccessToken, + + [Parameter()] + [System.Boolean] + $AllowRequestAccessToken, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [Parameter()] + [System.String] + $CertificateThumbprint, + + [Parameter()] + [Switch] + $ManagedIdentity, + + [Parameter()] + [System.String[]] + $AccessTokens + ) + New-M365DSCConnection -Workload 'AzureDevOPS' ` + -InboundParameters $PSBoundParameters | Out-Null + + #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 + try + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.DisallowAadGuestUserAccess?defaultValue" + $DisallowAadGuestUserAccessValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.DisallowOAuthAuthentication?defaultValue" + $DisallowOAuthAuthenticationValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.DisallowSecureShell?defaultValue" + $DisallowSecureShellValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.LogAuditEvents?defaultValue" + $LogAuditEventsValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.AllowAnonymousAccess?defaultValue" + $AllowAnonymousAccessValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.ArtifactsExternalPackageProtectionToken?defaultValue" + $ArtifactsExternalPackageProtectionTokenValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.EnforceAADConditionalAccess?defaultValue" + $EnforceAADConditionalAccessValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.AllowTeamAdminsInvitationsAccessToken?defaultValue" + $AllowTeamAdminsInvitationsAccessTokenValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.AllowRequestAccessToken?defaultValue" + $AllowRequestAccessTokenValue = (Invoke-M365DSCAzureDevOPSWebRequest -uri $uri).Value + + $results = @{ + OrganizationName = $OrganizationName + DisallowAadGuestUserAccess = [Boolean]::Parse($DisallowAadGuestUserAccessValue) + DisallowOAuthAuthentication = [Boolean]::Parse($DisallowOAuthAuthenticationValue) + DisallowSecureShell = [Boolean]::Parse($DisallowSecureShellValue) + LogAuditEvents = [Boolean]::Parse($LogAuditEventsValue) + AllowAnonymousAccess = [Boolean]::Parse($AllowAnonymousAccessValue) + ArtifactsExternalPackageProtectionToken = [Boolean]::Parse($ArtifactsExternalPackageProtectionTokenValue) + EnforceAADConditionalAccess = [Boolean]::Parse($EnforceAADConditionalAccessValue) + AllowTeamAdminsInvitationsAccessToken = [Boolean]::Parse($AllowTeamAdminsInvitationsAccessTokenValue) + AllowRequestAccessToken = [Boolean]::Parse($AllowRequestAccessTokenValue) + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + CertificateThumbprint = $CertificateThumbprint + ManagedIdentity = $ManagedIdentity.IsPresent + AccessTokens = $AccessTokens + } + return [System.Collections.Hashtable] $results + } + catch + { + Write-Verbose -Message $_ + New-M365DSCLogEntry -Message 'Error retrieving data:' ` + -Exception $_ ` + -Source $($MyInvocation.MyCommand.Source) ` + -TenantId $TenantId ` + -Credential $Credential + + return $nullResult + } +} + +function Set-TargetResource +{ + [CmdletBinding()] + param + ( + + [Parameter(Mandatory = $true)] + [System.String] + $OrganizationName, + + [Parameter()] + [System.Boolean] + $DisallowAadGuestUserAccess, + + [Parameter()] + [System.Boolean] + $DisallowOAuthAuthentication, + + [Parameter()] + [System.Boolean] + $DisallowSecureShell, + + [Parameter()] + [System.Boolean] + $LogAuditEvents, + + [Parameter()] + [System.Boolean] + $AllowAnonymousAccess, + + [Parameter()] + [System.Boolean] + $ArtifactsExternalPackageProtectionToken, + + [Parameter()] + [System.Boolean] + $EnforceAADConditionalAccess, + + [Parameter()] + [System.Boolean] + $AllowTeamAdminsInvitationsAccessToken, + + [Parameter()] + [System.Boolean] + $AllowRequestAccessToken, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [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 + + New-M365DSCConnection -Workload 'AzureDevOPS' ` + -InboundParameters $PSBoundParameters | Out-Null + + if ($PSBoundParameters.ContainsKey('DisallowAadGuestUserAccess')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.DisallowAadGuestUserAccess?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($DisallowAadGuestUserAccess.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating DisallowAadGuestUserAccess policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('DisallowOAuthAuthentication')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.DisallowOAuthAuthentication?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($DisallowOAuthAuthentication.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating DisallowOAuthAuthentication policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('DisallowSecureShell')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.DisallowSecureShell?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($DisallowSecureShell.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating DisallowSecureShell policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('LogAuditEvents')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.LogAuditEvents?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($LogAuditEvents.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating LogAuditEvents policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('AllowAnonymousAccess')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.AllowAnonymousAccess?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($AllowAnonymousAccess.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating AllowAnonymousAccess policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('ArtifactsExternalPackageProtectionToken')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.ArtifactsExternalPackageProtectionToken?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($ArtifactsExternalPackageProtectionToken.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating ArtifactsExternalPackageProtectionToken policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('EnforceAADConditionalAccess')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.EnforceAADConditionalAccess?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($EnforceAADConditionalAccess.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating EnforceAADConditionalAccess policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('AllowTeamAdminsInvitationsAccessToken')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.AllowTeamAdminsInvitationsAccessToken?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($AllowTeamAdminsInvitationsAccessToken.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating AllowTeamAdminsInvitationsAccessToken policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } + + if ($PSBoundParameters.ContainsKey('AllowRequestAccessToken')) + { + $uri = "https://dev.azure.com/$($OrganizationName)/_apis/OrganizationPolicy/Policies/Policy.AllowRequestAccessToken?api-version=5.0-preview" + $body = "[{`"from`":`"`",`"op`":2,`"path`":`"/Value`",`"value`":`"$($AllowRequestAccessToken.ToString().ToLower())`"}]" + Write-Verbose -Message "Updating AllowRequestAccessToken policy with values: $($body)" + + Invoke-M365DSCAzureDevOPSWebRequest -uri $uri -Method 'PATCH' -Body $body + } +} + +function Test-TargetResource +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $OrganizationName, + + [Parameter()] + [System.Boolean] + $DisallowAadGuestUserAccess, + + [Parameter()] + [System.Boolean] + $DisallowOAuthAuthentication, + + [Parameter()] + [System.Boolean] + $DisallowSecureShell, + + [Parameter()] + [System.Boolean] + $LogAuditEvents, + + [Parameter()] + [System.Boolean] + $AllowAnonymousAccess, + + [Parameter()] + [System.Boolean] + $ArtifactsExternalPackageProtectionToken, + + [Parameter()] + [System.Boolean] + $EnforceAADConditionalAccess, + + [Parameter()] + [System.Boolean] + $AllowTeamAdminsInvitationsAccessToken, + + [Parameter()] + [System.Boolean] + $AllowRequestAccessToken, + + [Parameter()] + [System.Management.Automation.PSCredential] + $Credential, + + [Parameter()] + [System.String] + $ApplicationId, + + [Parameter()] + [System.String] + $TenantId, + + [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 + + $CurrentValues = Get-TargetResource @PSBoundParameters + $ValuesToCheck = ([Hashtable]$PSBoundParameters).Clone() + + Write-Verbose -Message "Current Values: $(Convert-M365DscHashtableToString -Hashtable $CurrentValues)" + Write-Verbose -Message "Target Values: $(Convert-M365DscHashtableToString -Hashtable $ValuesToCheck)" + + $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 'AzureDevOPS' ` + -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 + { + $Script:ExportMode = $true + + $profile = Invoke-M365DSCAzureDevOPSWebRequest -Uri 'https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=5.1' + $accounts = Invoke-M365DSCAzureDevOPSWebRequest -Uri "https://app.vssps.visualstudio.com/_apis/accounts?api-version=7.1-preview.1&memberId=$($profile.id)" + + $i = 1 + $dscContent = '' + if ($accounts.Length -eq 0) + { + Write-Host $Global:M365DSCEmojiGreenCheckMark + } + else + { + Write-Host "`r`n" -NoNewline + } + foreach ($account in $accounts) + { + $organization = $account.Value.accountName + if ($null -ne $Global:M365DSCExportResourceInstancesCount) + { + $Global:M365DSCExportResourceInstancesCount++ + } + + $displayedKey = $organization + Write-Host " |---[$i/$($accounts.Count)] $displayedKey" -NoNewline + $params = @{ + OrganizationName = $organization + Credential = $Credential + ApplicationId = $ApplicationId + TenantId = $TenantId + 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_ADOSecurityPolicy/MSFT_ADOSecurityPolicy.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/MSFT_ADOSecurityPolicy.schema.mof new file mode 100644 index 0000000000..51e606ca11 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/MSFT_ADOSecurityPolicy.schema.mof @@ -0,0 +1,20 @@ +[ClassVersion("1.0.0.0"), FriendlyName("ADOSecurityPolicy")] +class MSFT_ADOSecurityPolicy : OMI_BaseResource +{ + [Key, Description("The name of the Azure DevOPS Organization.")] String OrganizationName; + [Write, Description("Controls the external guest access.")] Boolean DisallowAadGuestUserAccess; + [Write, Description("Controls the Third-party application access via OAuth.")] Boolean DisallowOAuthAuthentication; + [Write, Description("Controls SSH Authentication.")] Boolean DisallowSecureShell; + [Write, Description("Controls Log Audit Events.")] Boolean LogAuditEvents; + [Write, Description("Controls the Allow public projects setting.")] Boolean AllowAnonymousAccess; + [Write, Description("Controls the Additional protections when using public package registries setting.")] Boolean ArtifactsExternalPackageProtectionToken; + [Write, Description("Controls the Enable IP Conditional Access policy validation setting.")] Boolean EnforceAADConditionalAccess; + [Write, Description("Controls the Allow team and project administrators to invite new user setting.")] Boolean AllowTeamAdminsInvitationsAccessToken; + [Write, Description("Controls the Request access setting.")] Boolean AllowRequestAccessToken; + [Write, Description("Credentials of the workload's 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("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_ADOSecurityPolicy/readme.md b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/readme.md new file mode 100644 index 0000000000..46a7607129 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/readme.md @@ -0,0 +1,6 @@ + +# ADOSecurityPolicy + +## Description + +Configures Azure DevOPS Security Policies. diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/settings.json b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/settings.json new file mode 100644 index 0000000000..0a92ea98a7 --- /dev/null +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_ADOSecurityPolicy/settings.json @@ -0,0 +1,20 @@ +{ + "resourceName": "ADOSecurityPolicy", + "description": "Configures Azure DevOPS Security Policies.", + "roles": { + "read": [], + "update": [] + }, + "permissions": { + "graph": { + "delegated": { + "read": [], + "update": [] + }, + "application": { + "read": [], + "update": [] + } + } + } +} diff --git a/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 b/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 index afd204cd31..1b2afb7550 100644 --- a/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 +++ b/Modules/Microsoft365DSC/Dependencies/Manifest.psd1 @@ -102,7 +102,7 @@ }, @{ ModuleName = "MSCloudLoginAssistant" - RequiredVersion = "1.1.24" + RequiredVersion = "1.1.25" }, @{ ModuleName = 'PnP.PowerShell' diff --git a/Modules/Microsoft365DSC/Examples/Resources/ADOSecurityPolicy/2-Update.ps1 b/Modules/Microsoft365DSC/Examples/Resources/ADOSecurityPolicy/2-Update.ps1 new file mode 100644 index 0000000000..d226a0df30 --- /dev/null +++ b/Modules/Microsoft365DSC/Examples/Resources/ADOSecurityPolicy/2-Update.ps1 @@ -0,0 +1,41 @@ +<# +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 + { + ADOSecurityPolicy "ADOPolicy" + { + AllowAnonymousAccess = $True; + AllowRequestAccessToken = $False; + AllowTeamAdminsInvitationsAccessToken = $True; + ApplicationId = $ApplicationId; + ArtifactsExternalPackageProtectionToken = $False; + CertificateThumbprint = $CertificateThumbprint; + DisallowAadGuestUserAccess = $True; + DisallowOAuthAuthentication = $True; + DisallowSecureShell = $False; + EnforceAADConditionalAccess = $False; + LogAuditEvents = $True; + OrganizationName = "O365DSC-Dev"; + TenantId = $TenantId; + } + } +} diff --git a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 index 829a2cef89..0d8b221bdd 100644 --- a/Modules/Microsoft365DSC/Microsoft365DSC.psd1 +++ b/Modules/Microsoft365DSC/Microsoft365DSC.psd1 @@ -79,6 +79,7 @@ 'Modules/M365DSCDRGUtil.psm1', 'Modules/EncodingHelpers/M365DSCEmojis.psm1', 'Modules/EncodingHelpers/M365DSCStringEncoding.psm1', + 'Modules/WorkloadHelpers/M365DSCAzureDevOPSHelper.psm1', 'Modules/WorkloadHelpers/M365DSCFabricHelper.psm1', 'Modules/M365DSCConfigurationHelper.psm1' ) diff --git a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 index 9397159c76..b498f41a3f 100644 --- a/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 +++ b/Modules/Microsoft365DSC/Modules/M365DSCUtil.psm1 @@ -6,7 +6,8 @@ $Global:SessionSecurityCompliance = $null #region Extraction Modes $Global:DefaultComponents = @('SPOApp', 'SPOSiteDesign') -$Global:FullComponents = @('AADGroup', 'AADServicePrincipal', 'EXOCalendarProcessing', 'EXODistributionGroup', 'EXOMailboxAutoReplyConfiguration', ` +$Global:FullComponents = @('AADGroup', 'AADServicePrincipal', 'ADOSecurityPolicy', 'AzureSubscription','FabricAdminTenantSettings', ` + 'EXOCalendarProcessing', 'EXODistributionGroup', 'EXOMailboxAutoReplyConfiguration', ` 'EXOMailboxPermission','EXOMailboxCalendarFolder','EXOMailboxSettings', 'EXOManagementRole', 'O365Group', 'AADUser', ` 'PlannerPlan', 'PlannerBucket', 'PlannerTask', 'PPPowerAppsEnvironment', 'PPTenantSettings', ` 'SPOSiteAuditSettings', 'SPOSiteGroup', 'SPOSite', 'SPOUserProfileProperty', 'SPOPropertyBag', 'TeamsTeam', 'TeamsChannel', ` @@ -1771,7 +1772,6 @@ function New-M365DSCConnection [System.Boolean] $SkipModuleReload = $false ) - $verbosepreference = 'Continue' $Global:MaximumFunctionCount = 32767 if ($Workload -eq 'MicrosoftTeams') { @@ -3697,6 +3697,10 @@ function Get-M365DSCExportContentForResource { $primaryKey = $Results.WorkspaceName } + elseif ($Keys.Contains('OrganizationName')) + { + $primaryKey = $Results.OrganizationName + } if ([String]::IsNullOrEmpty($primaryKey) -and ` -not $Keys.Contains('IsSingleInstance')) diff --git a/Modules/Microsoft365DSC/Modules/WorkloadHelpers/M365DSCAzureDevOPSHelper.psm1 b/Modules/Microsoft365DSC/Modules/WorkloadHelpers/M365DSCAzureDevOPSHelper.psm1 new file mode 100644 index 0000000000..19f3105cf7 --- /dev/null +++ b/Modules/Microsoft365DSC/Modules/WorkloadHelpers/M365DSCAzureDevOPSHelper.psm1 @@ -0,0 +1,43 @@ +function Invoke-M365DSCAzureDevOPSWebRequest +{ + [OutputType([PSCustomObject])] + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [System.String] + $Uri, + + [Parameter()] + [System.String] + $Method = 'GET', + + [Parameter()] + [System.String] + $Body + ) + + $headers = @{ + Authorization = $global:MsCloudLoginConnectionProfile.AzureDevOPS.AccessToken + 'Content-Type' = 'application/json-patch+json' + } + + $params = @{ + Headers = $headers + Uri = $Uri + Method = $Method + + } + + if ($Method -ne 'GET') + { + $params.Add('Body', $Body) + } + + $response = Invoke-WebRequest @params -UseBasicParsing + $result = $null + if (-not [System.String]::IsNullOrEmpty($response.Content)) + { + $result = ConvertFrom-Json $response.Content + } + return $result +} diff --git a/Tests/Unit/Microsoft365DSC/Microsoft365DSC.ADOSecurityPolicy.Tests.ps1 b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.ADOSecurityPolicy.Tests.ps1 new file mode 100644 index 0000000000..1c3c5a591c --- /dev/null +++ b/Tests/Unit/Microsoft365DSC/Microsoft365DSC.ADOSecurityPolicy.Tests.ps1 @@ -0,0 +1,127 @@ +[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 + } + + Context -Name "The instance exists and values are already in the desired state" -Fixture { + BeforeAll { + $testParams = @{ + LogAuditEvents = $True; + OrganizationName = "MyOrg"; + Credential = $Credential; + } + + Mock -CommandName Invoke-M365DSCAzureDevOPSWebRequest -MockWith { + return @{ + Value = $true + } + } + } + + 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" -Fixture { + BeforeAll { + $testParams = @{ + LogAuditEvents = $True; + OrganizationName = "MyOrg"; + Credential = $Credential; + } + + Mock -CommandName Invoke-M365DSCAzureDevOPSWebRequest -MockWith { + return @{ + Value = $false + } + } + } + + It 'Should return false from the Test method' { + Test-TargetResource @testParams | Should -Be $false + } + } + + Context -Name 'ReverseDSC Tests' -Fixture { + BeforeAll { + $Global:CurrentModeIsExport = $true + $Global:PartialExportFileName = "$(New-Guid).partial.ps1" + $testParams = @{ + Credential = $Credential; + } + + $Script:callCount = 0 + Mock -CommandName Invoke-M365DSCAzureDevOPSWebRequest -MockWith { + if ($Script:callCount -eq 0) + { + $Script:callCount++ + return @{ + id = '12345-12345-12345-12345' + } + } + elseif ($Script:callCount -eq 1) + { + $Script:callCount++ + return @{ + value = @{ + accountName = 'TestOrg' + } + } + } + else + { + return @{ + Value = $true + } + } + } + } + It 'Should Reverse Engineer resource from the Export method' { + $result = Export-TargetResource @testParams + $result | Should -Not -BeNullOrEmpty + } + } + } +} + +Invoke-Command -ScriptBlock $Global:DscHelper.CleanupScript -NoNewScope