cWAP.psm1

enum Ensure {
    Absent
    Present
}

enum ExternalPreauthentication {
    PassThrough
    ADFS
}

enum BackendServerAuthenticationModeValues {
    NoAuthentication
    IntegratedWindowsAuthentication
}

enum BackendServerCertificateValidationValues {
    None
    ValidateCertificate
}

enum ClientCertificateAuthenticationBindingModeValues {
    None
    ValidateCertificate
}

enum UserIdleTimeoutActionValues {
    Signout 
    Reauthenticate
}

Function Test-sslBinding {
    [CmdletBinding()]
    param(
        [String]
        $bindingName,
        [String]
        $certificateThumbprint,
        [UInt16]
        $port
    )

    $binding = $null
    $lineNum = 0
    $certificates = @()
    netsh http show sslcert | ForEach-Object {
        $lineNum ++
        
        if( -not ($_.Trim()) -and $binding )
        {
            $certificates += $binding
            $binding = $null
        }
        
        if( $_ -notmatch '^ (.*)\s+: (.*)$' )
        {
            return
        }

        $name = $matches[1].Trim()
        $value = $matches[2].Trim()

        if( $name -eq 'IP:port'  -or $name -eq 'Hostname:port' )
        {
            $binding = @{}
            $name = "IPPort"
            if( $value -notmatch '^(.*):(\d+)$' )
            {
                Write-Verbose 'Invalid IP address/port in netsh output: {0}.' -f $value
            }
            else
            {
                $binding['IPAddress'] = $matches[1]
                $binding['Port'] = $matches[2]
            }                
        }
        if( $value -eq '(null)' )
        {
            $value = $null
        }
        elseif( $value -eq 'Enabled' )
        {
            $value = $true
        }
        elseif( $value -eq 'Disabled' )
        {
            $value = $false
        }
        
        $binding[$name] = $value
    }

    $checkBinding = $false
    $certificates | ForEach-Object{
        if(($_.Port -eq $port) -and ($_.Hash -eq $certificateThumbprint) -and ($_.IPAddress -eq $bindingName)){
            $checkBinding = $true
        }
    }

    return $checkBinding
}

[DscResource()]
class cWAPWebsite {
    <#
    The Ensure property is used to determine if the Web Application Proxy website should be present or not absent.
    #>

    [DscProperty(Mandatory)]
    [Ensure] $Ensure

    <#
    The DisplayName property is the name of the website.
    #>

    [DscProperty(Mandatory)]
    [string] $DisplayName

    <#
    The BackendServerUrl property is the internal url of the application to be published.
    #>

    [DscProperty(key)]
    [string] $BackendServerUrl

    <#
    The ExternalCertificateThumbprint property is the thumbprint of the certificate, located in the local computer's certificate store, that will be bound to the
    website's external url. The cn of the certificate should match the ExternalUrl.
    #>

    [DscProperty(Mandatory)]
    [string] $ExternalCertificateThumbprint

    <#
    The ExternalUrl property is the external url of the application, that is the url users will access.
    #>

    [DscProperty(Mandatory)]
    [string] $ExternalUrl

    <#
    The ExternalPreauthentication property define the website preauthentication mode. Possible values are: PassThrogh, ADFS
    #>

    [DscProperty()]
    [ExternalPreauthentication] $ExternalPreauthentication = "ADFS"
    
    <#
    The ADFSRelyingPartyName property define the ADFS relying party name of the application. It is required only if ExternalPreauthentication is ADFS
    #>

    [DscProperty()]
    [string] $ADFSRelyingPartyName

    <#
    The BackendServerAuthenticationSPN propery define the SPN of the relying party ID. It is required case of not claims aware relying party.
    #>

    [DscProperty()]
    [string] $BackendServerAuthenticationSPN

    <#
    Specifies the authentication method that Web Application Proxy uses when it contacts the backend server. The acceptable values for this parameter are: NoAuthentication and IntegratedWindowsAuthentication.
    #>

    [DscProperty()]
    [BackendServerAuthenticationModeValues]$BackendServerAuthenticationMode

    <#
    Specifies whether Web Application Proxy validates the certificate that the backend server presents.
    #>

    [DscProperty()]
    [BackendServerCertificateValidationValues]$BackendServerCertificateValidation

    <#
    Specifies whether Web Application Proxy verifies whether the certificate that authenticates the federation server authenticates future requests.
    #>

    [DscProperty()]
    [ClientCertificateAuthenticationBindingModeValues]$ClientCertificateAuthenticationBindingMode

    <#
    Indicates that this cmdlet disables the use of the HttpOnly flag when Web Application Proxy sets the access cookie. The access cookie provides single sign-on access to an application.
    #>

    [DscProperty()]
    [bool]$DisableHttpOnlyCookieProtection

    <#
    Indicates that Web Application Proxy does not translate HTTP host headers from public host headers to internal host headers when it forwards the request to the published application.
    #>

    [DscProperty()]
    [bool]$DisableTranslateUrlInRequestHeaders

    <#
    Indicates that Web Application Proxy does not translate internal host names to public host names in Content-Location, Location, and Set-Cookie response headers in redirect responses.
    #>

    [DscProperty()]
    [bool]$DisableTranslateUrlInResponseHeaders

    <#
    Indicates that this cmdlet enables HTTP redirect for Web Application Proxy.
    #>

    [DscProperty()]
    [bool]$EnableHTTPRedirect

    <#
    Indicates whether to enable sign out for Web Application Proxy.
    #>

    [DscProperty()]
    [bool]$EnableSignOut

    <#
    Specifies the length of time, in seconds, until Web Application Proxy closes incomplete HTTP transactions.
    #>

    [DscProperty()]
    [int]$InactiveTransactionsTimeoutSec

    <#
    Specifies the expiration time, in seconds, for persistent access cookies.
    #>

    [DscProperty()]
    [int]$PersistentAccessCookieExpirationTimeSec

    <#
    Indicates whether to enable sign out for Web Application Proxy.
    #>

    [DscProperty()]
    [bool]$UseOAuthAuthentication


    [cWAPWebsite] Get() {

        Write-Verbose -Message 'Starting retrieving configuration for website'

        try {
            Get-WebApplicationProxyApplication $this.DisplayName -ErrorAction Stop
        }
        catch {
            Write-Verbose -Message ('Error occurred while retrieving website configuration')
        }

        Write-Verbose -Message 'Finished retrieving configuration for website'
        return $this
    }

    [System.Boolean] Test() {
        # Assume compliance by default
        $Compliant = $true


        Write-Verbose -Message 'Testing for presence of WAP website'

        try {
            $Properties = Get-WebApplicationProxyApplication $this.DisplayName -ErrorAction Stop
        }
        catch {
            $Compliant = $false
            return $Compliant
        }
        

        if ($this.Ensure -eq 'Present') {
            Write-Verbose -Message 'Checking for configuration of WAP website.'
            if($Properties -eq $null){
                $Compliant = $false
            }
            else{
                if ($this.DisplayName -ne $Properties.Name){
                    $Compliant = $false
                }
    
                if($this.BackendServerUrl -ne $Properties.BackendServerUrl){
                    $Compliant = $false
                }
    
                if($this.ExternalCertificateThumbprint -ne $Properties.ExternalCertificateThumbprint){
                    $Compliant = $false
                }
                
                if($this.ExternalUrl -ne $Properties.ExternalUrl){
                    $Compliant = $false
                }
    
                if($this.ExternalPreauthentication -ne $Properties.ExternalPreauthentication){
                    $Compliant = $false
                }
    
                if($Properties.ExternalPreauthentication -eq "ADFS"){
                    if($this.ADFSRelyingPartyName -ne $Properties.ADFSRelyingPartyName){
                        $Compliant = $false
                    }
                }
    
                if($Properties.BackendServerAuthenticationMode -eq "IntegratedWindowsAuthentication"){
                    if($this.BackendServerAuthenticationSPN -ne $Properties.BackendServerAuthenticationSPN){
                        $Compliant = $false
                    }
                }   

                if($this.BackendServerAuthenticationMode -ne $Properties.BackendServerAuthenticationMode){
                    $Compliant = $false
                }
            
                if($this.BackendServerCertificateValidation -ne $Properties.BackendServerCertificateValidation){
                    $Compliant = $false
                }
             
                if($this.ClientCertificateAuthenticationBindingMode -ne $Properties.ClientCertificateAuthenticationBindingMode){
                    $Compliant = $false
                }
             
                if($this.DisableHttpOnlyCookieProtection -ne $Properties.DisableHttpOnlyCookieProtection){
                    $Compliant = $false
                }
                  
                if($this.DisableTranslateUrlInRequestHeaders -ne $Properties.DisableTranslateUrlInRequestHeaders){
                    $Compliant = $false
                } 
            
                if($this.DisableTranslateUrlInResponseHeaders -ne $Properties.DisableTranslateUrlInResponseHeaders){
                    $Compliant = $false
                } 
            
                if($this.EnableHTTPRedirect -ne $Properties.EnableHTTPRedirect){
                    $Compliant = $false
                } 
                
                if($this.EnableSignOut -ne $Properties.EnableSignOut){
                    $Compliant = $false
                } 
                
                if($this.InactiveTransactionsTimeoutSec -ne $Properties.InactiveTransactionsTimeoutSec){
                    $Compliant = $false
                } 
            
                if($this.PersistentAccessCookieExpirationTimeSec -ne $Properties.PersistentAccessCookieExpirationTimeSec){
                    $Compliant = $false
                } 
            
                if($this.UseOAuthAuthentication -ne $Properties.UseOAuthAuthentication){
                    $Compliant = $false
                } 

                Write-Verbose -Message "Current WAP configuration $Properties" 
            }
            
            if(!$compliant){
                Write-Verbose -Message 'WAP website does not match the desired state.'         
            }
        }

        if ($this.Ensure -eq 'Absent') {
            Write-Verbose -Message 'Checking for absence of WAP website.'
            if ($Properties) {
                Write-Verbose -Message
                $Compliant = $false
            }
        }

        if($Compliant){
            $Compliant = Test-sslBinding -bindingName $this.DisplayName -certificateThumbprint $Properties.ExternalCertificateThumbprint -port 443
        }

        if($Compliant){
            Write-Verbose -Message 'Compliance status: true'
        }
        else{
            Write-Verbose -Message 'Compliance status: false'
        }
        
        return $Compliant
    }

    [void] Set() {
        
        try {
            $WapWebsite = Get-webapplicationproxyapplication $this.DisplayName -ErrorAction stop
        }
        catch {
            $WapWebsite = $false
        }

        ### If WAP website shoud be present, then go ahead and create it.
        if ($this.Ensure -eq [Ensure]::Present) {
            $WapWebsiteInfo = @{
                Name                                        = $this.DisplayName
                ExternalUrl                                 = $this.ExternalUrl
                ExternalCertificateThumbprint               = $this.ExternalCertificateThumbprint
                BackendServerUrl                            = $this.BackendServerUrl
                ExternalPreauthentication                   = $this.ExternalPreauthentication
                EnableHTTPRedirect                          = $this.EnableHTTPRedirect
                UseOAuthAuthentication                      = $this.UseOAuthAuthentication
                BackendServerAuthenticationMode             = $this.BackendServerAuthenticationMode
                BackendServerCertificateValidation          = $this.BackendServerCertificateValidation
                ClientCertificateAuthenticationBindingMode  = $this.ClientCertificateAuthenticationBindingMode
                DisableHttpOnlyCookieProtection             = $this.DisableHttpOnlyCookieProtection
                DisableTranslateUrlInRequestHeaders         = $this.DisableTranslateUrlInRequestHeaders
                DisableTranslateUrlInResponseHeaders        = $this.DisableTranslateUrlInResponseHeaders
                EnableSignOut                               = $this.EnableSignOut
                InactiveTransactionsTimeoutSec              = $this.InactiveTransactionsTimeoutSec
                PersistentAccessCookieExpirationTimeSec     = $this.PersistentAccessCookieExpirationTimeSec
            }


            $recreate = $false

            if ($this.ExternalPreauthentication -eq "ADFS") {
                if($this.ADFSRelyingPartyName -and ($this.ADFSRelyingPartyName -eq $WapWebsite.ADFSRelyingPartyName)){
                    $WapWebsiteInfo.Add('ADFSRelyingPartyName', $this.ADFSRelyingPartyName)
                    $recreate = $true
                }
            }
            elseif ($this.BackendServerAuthenticationSPN) {
                $WapWebsiteInfo.Add('BackendServerAuthenticationSPN', $this.BackendServerAuthenticationSPN)
            }

            if (!$WapWebsite) {
                Write-Verbose -Message 'Creating WAP website'
                Add-WebApplicationProxyApplication @WapWebsiteInfo
            }

            if ($WapWebsite -and !$recreate) {
                Write-Verbose -Message 'Editing WAP website'
                Set-WebApplicationProxyApplication @WapWebsiteInfo
            }
            else{
                Write-Verbose -Message 'Removing and recreating WAP website'
                Remove-WebApplicationProxyApplication -name $this.DisplayName
                Add-WebApplicationProxyApplication @WapWebsiteInfo
            }
        }

        if ($this.Ensure -eq [Ensure]::Absent) {
            if($WapWebsite){
                Remove-WebApplicationProxyApplication -name $this.DisplayName
            }
        }

        return
    }
}
#endregion


[DscResource()]
class cWAPConfiguration
{
    ### Determines whether or not the WAP Config should exist.
    [DscProperty()]
    [Ensure] $Ensure

    <#
    The FederationServiceName property is the name of the Active Directory Federation Services (ADFS) service. For example: adfs-service.contoso.com.
    #>

    [DscProperty(key)]
    [string] $FederationServiceName

    <#
    The Credential property is a PSCredential that represents the username/password of an Active Directory user account that is a member of
    the Domain Administrators security group. This account will be used to add a new proxy to Active Directory Federation Services (ADFS).
    #>

    [DscProperty(Mandatory)]
    [pscredential] $Credential

    <#
    The CertificateThumbprint property is the thumbprint of the certificate, located in the local computer's certificate store, that will be bound to the
    Active Directory Federation Service (ADFS) farm.
    #>

    [DscProperty(Mandatory)]
    [string] $CertificateThumbprint

    <#
    Specifies the DNS name and port number of an HTTP proxy that this federation server proxy uses to obtain access to the federation service. Specify the value for this parameter in the following format: FQDN:PortNumber.
    Only configurable at installation time.
    #>

    [DscProperty()]
    [string] $ForwardProxy = ""

    <#
    Specifies the HTTPS port for the Web Application Proxy server. The default value is 443.
    Only configurable at installation time.
    #>

    [DscProperty()]
    [int] $HttpsPort = 443

    <#
    Specifies the port for the TLS client. Web Application Proxy uses this port for user certificate authentication. The default value is 49443.
    Only configurable at installation time.
    #>

    [DscProperty()]
    [int] $TlsClientPort = 49443

    <#
    Define the ADFS Token acceptance duration in seconds
    #>

    [DscProperty()]
    [int] $ADFSTokenAcceptanceDurationSec

    <#
    Define the user idle timeout in second
    #>

    [DscProperty()]
    [int] $UserIdleTimeoutSec

    <#
    Define the User idle timeout action. Accepted values are: Signout, Reauthenticate
    #>

    [DscProperty()]
    [UserIdleTimeoutActionValues] $UserIdleTimeoutAction

    [cWAPConfiguration] Get()
    {
        Write-Verbose -Message 'Starting retrieving Web Applucation Proxy configuration.'

        try {
            $cWAPConfiguration=Get-WebApplicationProxyConfiguration -ErrorAction Stop
        }
        catch {
            Write-Verbose -Message ('Error occurred while retrieving Web Application Proxy configuration')
        }

        Write-Verbose -Message 'Finished retrieving Web Applucation Proxy configuration.'
        return $this

    }

    [void] Set()
    {
        ### If WAP shoud be present, then go ahead and configure it.
        if ($this.Ensure -eq [Ensure]::Present) {
            try{
                $WapConfiguration = Get-WebApplicationProxyConfiguration -ErrorAction Stop
            }
            catch {
                $WapConfiguration = $false
            }

            if (!$WapConfiguration) {
                Write-Verbose -Message 'Configuring Web Application Proxy.'
                $WapSettings = @{
                    FederationServiceTrustCredential    = $this.Credential
                    CertificateThumbprint               = $this.CertificateThumbprint
                    FederationServiceName               = $this.FederationServiceName
                    ForwardProxy                        = $this.ForwardProxy
                    HttpsPort                           = $this.HttpsPort
                    TlsClientPort                       = $this.TlsClientPort
                }

                if($this.ForwardProxy -ne $null){
                    $WapSettings.Add('ForwardProxy', $this.ForwardProxy)
                }

                if($this.HttpsPort -ne $null){
                    $WapSettings.Add('HttpsPort', $this.HttpsPort)
                }
                
                if($this.TlsClientPort -ne $null){
                    $WapSettings.Add('TlsClientPort', $this.TlsClientPort)
                }

                Install-WebApplicationProxy @WapSettings

                if($this.ADFSTokenAcceptanceDurationSec -ne $null -or $this.UserIdleTimeoutSec -ne $null -or $this.UserIdleTimeoutAction -ne $null){
                    
                    if($this.ADFSTokenAcceptanceDurationSec -ne $null){
                        $WapSettings.Add('ADFSTokenAcceptanceDurationSec', $this.ADFSTokenAcceptanceDurationSec)
                    }
                    
                    if($this.UserIdleTimeoutSec -ne $null){
                        $WapSettings.Add('UserIdleTimeoutSec', $this.UserIdleTimeoutSec)
                    }

                    if($this.UserIdleTimeoutAction -ne $null){
                        $WapSettings.Add('UserIdleTimeoutAction', $this.UserIdleTimeoutAction)
                    }

                    Set-WebApplicationProxyConfiguration @WapSettings
                }
            }

            if ($WapConfiguration) {
                #Check certificate configuration
                $updateCertificates = $false
                try{
                    $certificates = Get-WebApplicationProxySslCertificate
                    $certificates | ForEach-Object {
                        if($_.CertificateHash -ne $this.CertificateThumbprint){
                            $updateCertificates = $true
                        }
                    }
                }
                catch{
                    $updateCertificates = $true
                }
               

                if($updateCertificates){
                    Set-WebApplicationProxySslCertificate -Thumbprint $this.CertificateThumbprint
                }

                if($this.ADFSTokenAcceptanceDurationSec -ne $null -or $this.UserIdleTimeoutSec -ne $null -or $this.UserIdleTimeoutAction -ne $null){
                    $WapSettings = @{}

                    if($this.ADFSTokenAcceptanceDurationSec -ne $null){
                        $WapSettings.Add('ADFSTokenAcceptanceDurationSec', $this.ADFSTokenAcceptanceDurationSec)
                    }
                    
                    if($this.UserIdleTimeoutSec -ne $null){
                        $WapSettings.Add('UserIdleTimeoutSec', $this.UserIdleTimeoutSec)
                    }

                    if($this.UserIdleTimeoutAction -ne $null){
                        $WapSettings.Add('UserIdleTimeoutAction', $this.UserIdleTimeoutAction)
                    }

                    Set-WebApplicationProxyConfiguration @WapSettings
                }
            }
        }

        if ($this.Ensure -eq [Ensure]::Absent) {
            # It is not actually possible to unconfigure WAP, so we do nothing

        }

        return
    }

    [bool] Test()
    {
        # Assume compliance by default
        $Compliant = $true


        Write-Verbose -Message 'Testing for presence of Web Application Proxy.'

        try {
            $WapConfiguration = Get-WebApplicationProxyConfiguration -ErrorAction Stop
        }
        catch {
            $Compliant = $false
            return $Compliant
        }

        if ($this.Ensure -eq 'Present') {
            Write-Verbose -Message 'Checking for correct ADFS service configuration.'
            
            if (-not($WapConfiguration.ADFSUrl.ToLower() -contains $this.FederationServiceName.ToLower())) {
                Write-Verbose -Message 'ADFS Service Name doesn''t match the desired state.'
                $Compliant = $false
            }

            try{
                $certificates = Get-WebApplicationProxySslCertificate
                $certificates | ForEach-Object {
                    if($_.CertificateHash -ne $this.CertificateThumbprint){
                        $Compliant = $false
                    }
                }
            }
            catch{
                $Compliant = $false
            }

            if($this.ADFSTokenAcceptanceDurationSec -ne $WapConfiguration.ADFSTokenAcceptanceDurationSec){
                $Compliant = $false
            }
            
            if($this.UserIdleTimeoutSec -ne $WapConfiguration.UserIdleTimeoutSec){
                $Compliant = $false
            }

            if($this.UserIdleTimeoutAction -ne $WapConfiguration.UserIdleTimeoutAction){
                $Compliant = $false
            }

        }

        if ($this.Ensure -eq 'Absent') {
            Write-Verbose -Message 'Checking for absence of WAP Configuration.'
            if ($WapConfiguration) {
                Write-Verbose -Message
                $Compliant = $false
            }
        }

        return $Compliant
    }

}