From efc4ac9a499e674a48819daba5cee4496795e742 Mon Sep 17 00:00:00 2001 From: Nik Charlebois Date: Mon, 7 Oct 2024 20:30:02 -0400 Subject: [PATCH] Added Support for OnPremisesPublishing --- CHANGELOG.md | 1 + .../MSFT_AADApplication.psm1 | 199 +++++++++++++++++- .../MSFT_AADApplication.schema.mof | 53 +++++ 3 files changed, 252 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a42713a14c..0fa9247d31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Initial release. * AADApplication * Fixed an issue trying to retrieve the beta instance. + * Added support for OnPremisesPublishing. * AADDeviceRegistrationPolicy * Initial release. * AADEntitlementManagementSettings diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 index 3101f97fd7..e2834628f8 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.psm1 @@ -88,6 +88,10 @@ function Get-TargetResource [Microsoft.Management.Infrastructure.CimInstance[]] $Permissions, + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $OnPremisesPublishing, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] @@ -274,7 +278,6 @@ function Get-TargetResource $complexApi = $null } - $complexKeyCredentials = @() foreach ($currentkeyCredentials in $AADAppKeyCredentials.keyCredentials) { @@ -379,6 +382,77 @@ function Get-TargetResource $IsFallbackPublicClientValue = $AADApp.IsFallbackPublicClient } + #region OnPremisesPublishing + $onPremisesPublishingValue = @{} + $oppInfo = $null + + try + { + $oppInfo = Invoke-MgGraphRequest -Method GET ` + -Uri "https://graph.microsoft.com/beta/applications/$($AADBetaApp.Id)/onPremisesPublishing" ` + -ErrorAction SilentlyContinue + } + catch + { + Write-Verbose -Message "On-premises publishing is not enabled for App {$($AADBetaApp.DisplayName)}" + } + + if ($null -ne $oppInfo) + { + $onPremisesPublishingValue = @{ + alternateUrl = $oppInfo.alternateUrl + applicationServerTimeout = $oppInfo.applicationServerTimeout + externalAuthenticationType = $oppInfo.externalAuthenticationType + externalUrl = $oppInfo.externalUrl + internalUrl = $oppInfo.internalUrl + isBackendCertificateValidationEnabled = $oppInfo.isBackendCertificateValidationEnabled + isHttpOnlyCookieEnabled = $oppInfo.isHttpOnlyCookieEnabled + isPersistentCookieEnabled = $oppInfo.isPersistentCookieEnabled + isSecureCookieEnabled = $oppInfo.isSecureCookieEnabled + isStateSessionEnabled = $oppInfo.isStateSessionEnabled + isTranslateHostHeaderEnabled = $oppInfo.isTranslateHostHeaderEnabled + isTranslateLinksInBodyEnabled = $oppInfo.isTranslateLinksInBodyEnabled + } + + # onPremisesApplicationSegments + $segmentValues = @() + foreach ($segment in $oppInfo.onPremisesApplicationSegments) + { + $entry = @{ + alternateUrl = $segment.AlternateUrl + externalUrl = $segment.externalUrl + internalUrl = $segment.internalUrl + } + + $corsConfigurationValues = @() + foreach ($cors in $segment.corsConfigurations) + { + $corsEntry = @{ + allowedHeaders = [Array]($cors.allowedHeaders) + allowedMethods = [Array]($cors.allowedMethods) + allowedOrigins = [Array]($cors.allowedOrigins) + maxAgeInSeconds = $cors.maxAgeInSeconds + resource = $cors.resource + } + $corsConfigurationValues += $corsEntry + } + $entry.Add('corsConfigurations', $corsConfigurationValues) + $segmentValues += $entry + } + $onPremisesPublishingValue.Add('onPremisesApplicationSegments', $segmentValues) + + # singleSignOnSettings + $singleSignOnValues = @{ + kerberosSignOnSettings = @{ + kerberosServicePrincipalName = $oppInfo.singleSignOnSettings.kerberosSignOnSettings.kerberosServicePrincipalName + kerberosSignOnMappingAttributeType = $oppInfo.singleSignOnSettings.kerberosSignOnSettings.kerberosSignOnMappingAttributeType + } + singleSignOnMode = $oppInfo.singleSignOnSettings.singleSignOnMode + } + $onPremisesPublishingValue.Add('singleSignOnSettings', $singleSignOnValues) + } + #endregion + $result = @{ DisplayName = $AADApp.DisplayName AvailableToOtherTenants = $AvailableToOtherTenantsValue @@ -401,6 +475,7 @@ function Get-TargetResource PasswordCredentials = $complexPasswordCredentials AppRoles = $complexAppRoles Permissions = $permissionsObj + OnPremisesPublishing = $onPremisesPublishingValue Ensure = 'Present' Credential = $Credential ApplicationId = $ApplicationId @@ -522,6 +597,10 @@ function Set-TargetResource [Microsoft.Management.Infrastructure.CimInstance[]] $Permissions, + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $OnPremisesPublishing, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] @@ -678,6 +757,7 @@ function Set-TargetResource $currentParameters.Remove('ReplyURLs') | Out-Null $currentParameters.Remove('LogoutURL') | Out-Null $currentParameters.Remove('Homepage') | Out-Null + $currentParameters.Remove('OnPremisesPublishing') | Out-Null $keys = (([Hashtable]$currentParameters).clone()).Keys @@ -944,6 +1024,74 @@ function Set-TargetResource Write-Warning -Message "KeyCredentials cannot be updated for AAD Applications with more than one KeyCredentials due to technical limitation of Update-MgApplication Cmdlet. Learn more at: https://learn.microsoft.com/en-us/graph/api/application-addkey" } } + + #region OnPremisesPublishing + if ($null -ne $OnPremisesPublishing) + { + $oppInfo = $OnPremisesPublishing + $onPremisesPublishingValue = @{ + alternateUrl = $oppInfo.alternateUrl + applicationServerTimeout = $oppInfo.applicationServerTimeout + externalAuthenticationType = $oppInfo.externalAuthenticationType + #externalUrl = $oppInfo.externalUrl + internalUrl = $oppInfo.internalUrl + isBackendCertificateValidationEnabled = $oppInfo.isBackendCertificateValidationEnabled + isHttpOnlyCookieEnabled = $oppInfo.isHttpOnlyCookieEnabled + isPersistentCookieEnabled = $oppInfo.isPersistentCookieEnabled + isSecureCookieEnabled = $oppInfo.isSecureCookieEnabled + isStateSessionEnabled = $oppInfo.isStateSessionEnabled + isTranslateHostHeaderEnabled = $oppInfo.isTranslateHostHeaderEnabled + isTranslateLinksInBodyEnabled = $oppInfo.isTranslateLinksInBodyEnabled + } + + # onPremisesApplicationSegments + $segmentValues = @() + foreach ($segment in $oppInfo.onPremisesApplicationSegments) + { + $entry = @{ + alternateUrl = $segment.AlternateUrl + externalUrl = $segment.externalUrl + internalUrl = $segment.internalUrl + } + + $corsConfigurationValues = @() + foreach ($cors in $segment.corsConfigurations) + { + $corsEntry = @{ + allowedHeaders = [Array]($cors.allowedHeaders) + allowedMethods = [Array]($cors.allowedMethods) + allowedOrigins = [Array]($cors.allowedOrigins) + maxAgeInSeconds = $cors.maxAgeInSeconds + resource = $cors.resource + } + $corsConfigurationValues += $corsEntry + } + $entry.Add('corsConfigurations', $corsConfigurationValues) + $segmentValues += $entry + } + $onPremisesPublishingValue.Add('onPremisesApplicationSegments', $segmentValues) + + # singleSignOnSettings + $singleSignOnValues = @{ + kerberosSignOnSettings = @{ + kerberosServicePrincipalName = $oppInfo.singleSignOnSettings.kerberosSignOnSettings.kerberosServicePrincipalName + kerberosSignOnMappingAttributeType = $oppInfo.singleSignOnSettings.kerberosSignOnSettings.kerberosSignOnMappingAttributeType + } + singleSignOnMode = $oppInfo.singleSignOnSettings.singleSignOnMode + } + if ($null -eq $singleSignOnValues.kerberosSignOnSettings.kerberosServicePrincipalName) + { + $singleSignOnValues.Remove('kerberosSignOnSettings') | Out-Null + } + + $onPremisesPublishingValue.Add('singleSignOnSettings', $singleSignOnValues) + $onPremisesPayload = ConvertTo-Json $onPremisesPublishingValue -Depth 10 -Compress + Write-Verbose -Message "Updating the OnPremisesPublishing settings for application {$($currentAADApp.DisplayName)} with payload: $onPremisesPayload" + Invoke-MgGraphRequest -Method 'PATCH' ` + -Uri "https://graph.microsoft.com/beta/applications/$($currentAADApp.Id)/onPremisesPublishing" ` + -Body $onPremisesPayload + } + #endregion } function Test-TargetResource @@ -1036,6 +1184,10 @@ function Test-TargetResource [Microsoft.Management.Infrastructure.CimInstance[]] $Permissions, + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance] + $OnPremisesPublishing, + [Parameter()] [ValidateSet('Present', 'Absent')] [System.String] @@ -1309,6 +1461,47 @@ function Export-TargetResource } } + if ($null -ne $Results.OnPremisesPublishing.singleSignOnSettings) + { + $complexMapping = @( + @{ + Name = 'singleSignOnSettings' + CimInstanceName = 'AADApplicationOnPremisesPublishingSingleSignOnSetting' + IsRequired = $False + }, + @{ + Name = 'onPremisesApplicationSegments' + CimInstanceName = 'AADApplicationOnPremisesPublishingSegment' + IsRequired = $False + }, + @{ + Name = 'kerberosSignOnSettings' + CimInstanceName = 'AADApplicationOnPremisesPublishingSingleSignOnSettingKerberos' + IsRequired = $False + }, + @{ + Name = 'corsConfigurations' + CimInstanceName = 'AADApplicationOnPremisesPublishingSegmentCORS' + IsRequired = $False + } + ) + $complexTypeStringResult = Get-M365DSCDRGComplexTypeToString ` + -ComplexObject $Results.OnPremisesPublishing ` + -CIMInstanceName 'AADApplicationOnPremisesPublishing' ` + -ComplexTypeMapping $complexMapping + if (-not [String]::IsNullOrWhiteSpace($complexTypeStringResult)) + { + $Results.OnPremisesPublishing = $complexTypeStringResult + } + else + { + $Results.Remove('OnPremisesPublishing') | Out-Null + } + } + else + { + $Results.Remove('OnPremisesPublishing') | Out-Null + } if ($null -ne $Results.OptionalClaims) { @@ -1415,6 +1608,10 @@ function Export-TargetResource { $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName "OptionalClaims" -IsCIMArray:$False } + if ($Results.OnPremisesPublishing) + { + $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName "OnPremisesPublishing" -IsCIMArray:$False + } if ($Results.AuthenticationBehaviors) { $currentDSCBlock = Convert-DSCStringParamToVariable -DSCBlock $currentDSCBlock -ParameterName "AuthenticationBehaviors" -IsCIMArray:$False diff --git a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof index c78c43016d..ae3c0625d2 100644 --- a/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof +++ b/Modules/Microsoft365DSC/DSCResources/MSFT_AADApplication/MSFT_AADApplication.schema.mof @@ -1,3 +1,55 @@ +[ClassVersion("1.0.0")] +class MSFT_AADApplicationOnPremisesPublishingSegmentCORS +{ + [Write, Description("The request headers that the origin domain may specify on the CORS request. The wildcard character * indicates that any header beginning with the specified prefix is allowed.")] String allowedHeaders[]; + [Write, Description("The maximum amount of time that a browser should cache the response to the preflight OPTIONS request.")] UInt32 maxAgeInSeconds; + [Write, Description("Resource within the application segment for which CORS permissions are granted. / grants permission for whole app segment.")] String resource; + [Write, Description("The HTTP request methods that the origin domain may use for a CORS request.")] String allowedMethods[]; + [Write, Description("The origin domains that are permitted to make a request against the service via CORS. The origin domain is the domain from which the request originates. The origin must be an exact case-sensitive match with the origin that the user age sends to the service.")] String allowedOrigins[]; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADApplicationOnPremisesPublishingSegment +{ + [Write, Description("If you're configuring a traffic manager in front of multiple App Proxy application segments, contains the user-friendly URL that will point to the traffic manager.")] String alternateUrl; + [Write, Description("CORS Rule definition for a particular application segment."), EmbeddedInstance("MSFT_AADApplicationOnPremisesPublishingSegmentCORS")] String corsConfigurations[]; + [Write, Description("The published external URL for the application segment; for example, https://intranet.contoso.com./")] String externalUrl; + [Write, Description("The internal URL of the application segment; for example, https://intranet/.")] String internalUrl; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADApplicationOnPremisesPublishingSingleSignOnSettingKerberos +{ + [Write, Description("The Internal Application SPN of the application server. This SPN needs to be in the list of services to which the connector can present delegated credentials.")] String kerberosServicePrincipalName; + [Write, Description("The Delegated Login Identity for the connector to use on behalf of your users. For more information, see Working with different on-premises and cloud identities . Possible values are: userPrincipalName, onPremisesUserPrincipalName, userPrincipalUsername, onPremisesUserPrincipalUsername, onPremisesSAMAccountName.")] String kerberosSignOnMappingAttributeType; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADApplicationOnPremisesPublishingSingleSignOnSetting +{ + [Write, Description("The preferred single-sign on mode for the application. Possible values are: none, onPremisesKerberos, aadHeaderBased,pingHeaderBased, oAuthToken.")] String singleSignOnMode; + [Write, Description("The Kerberos Constrained Delegation settings for applications that use Integrated Window Authentication."), EmbeddedInstance("MSFT_AADApplicationOnPremisesPublishingSingleSignOnSettingKerberos")] String kerberosSignOnSettings; +}; + +[ClassVersion("1.0.0")] +class MSFT_AADApplicationOnPremisesPublishing +{ + [Write, Description("If you're configuring a traffic manager in front of multiple App Proxy applications, the alternateUrl is the user-friendly URL that points to the traffic manager.")] String alternateUrl; + [Write, Description("The duration the connector waits for a response from the backend application before closing the connection. Possible values are default, long.")] String applicationServerTimeout; + [Write, Description("Details the pre-authentication setting for the application. Pre-authentication enforces that users must authenticate before accessing the app. Pass through doesn't require authentication. Possible values are: passthru, aadPreAuthentication.")] String externalAuthenticationType; + [Write, Description("The published external url for the application. For example, https://intranet-contoso.msappproxy.net/.")] String externalUrl; + [Write, Description("The internal url of the application. For example, https://intranet/.")] String internalUrl; + [Write, Description("Indicates whether backend SSL certificate validation is enabled for the application. For all new Application Proxy apps, the property is set to true by default. For all existing apps, the property is set to false.")] Boolean isBackendCertificateValidationEnabled; + [Write, Description("Indicates if the HTTPOnly cookie flag should be set in the HTTP response headers. Set this value to true to have Application Proxy cookies include the HTTPOnly flag in the HTTP response headers. If using Remote Desktop Services, set this value to False. Default value is false.")] Boolean isHttpOnlyCookieEnabled; + [Write, Description("Indicates if the Persistent cookie flag should be set in the HTTP response headers. Keep this value set to false. Only use this setting for applications that can't share cookies between processes. For more information about cookie settings, see Cookie settings for accessing on-premises applications in Microsoft Entra ID. Default value is false.")] Boolean isPersistentCookieEnabled; + [Write, Description("Indicates if the Secure cookie flag should be set in the HTTP response headers. Set this value to true to transmit cookies over a secure channel such as an encrypted HTTPS request. Default value is true.")] Boolean isSecureCookieEnabled; + [Write, Description("Indicates whether validation of the state parameter when the client uses the OAuth 2.0 authorization code grant flow is enabled. This setting allows admins to specify whether they want to enable CSRF protection for their apps.")] Boolean isStateSessionEnabled; + [Write, Description("Indicates if the application should translate urls in the response headers. Keep this value as true unless your application required the original host header in the authentication request. Default value is true.")] Boolean isTranslateHostHeaderEnabled; + [Write, Description("Indicates if the application should translate urls in the application body. Keep this value as false unless you have hardcoded HTML links to other on-premises applications and don't use custom domains. For more information, see Link translation with Application Proxy. Default value is false.")] Boolean isTranslateLinksInBodyEnabled; + [Write, Description("Represents the collection of application segments for an on-premises wildcard application that's published through Microsoft Entra application proxy."), EmbeddedInstance("MSFT_AADApplicationOnPremisesPublishingSegment")] String onPremisesApplicationSegments[]; + [Write, Description("Represents the single sign-on configuration for the on-premises application."), EmbeddedInstance("MSFT_AADApplicationOnPremisesPublishingSingleSignOnSetting")] String singleSignOnSettings; +}; + [ClassVersion("1.0.0")] class MSFT_AADApplicationPermission { @@ -102,6 +154,7 @@ class MSFT_AADApplication : OMI_BaseResource [Write, Description("Specifies whether this application is a public client (such as an installed application running on a mobile device). Default is false.")] Boolean PublicClient; [Write, Description("Specifies the URLs that user tokens are sent to for sign in, or the redirect URIs that OAuth 2.0 authorization codes and access tokens are sent to.")] String ReplyURLs[]; [Write, Description("UPN or ObjectID values of the app's owners.")] String Owners[]; + [Write, Description("Represents the set of properties required for configuring Application Proxy for this application. Configuring these properties allows you to publish your on-premises application for secure remote access."), EmbeddedInstance("MSFT_AADApplicationOnPremisesPublishing")] String OnPremisesPublishing; [Write, Description("Specify if the Azure AD App should exist or not."), ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; [Write, Description("Credentials for the Microsoft Graph delegated permissions."), EmbeddedInstance("MSFT_Credential")] string Credential; [Write, Description("Id of the Azure Active Directory application to authenticate with.")] String ApplicationId;