DSCResources/MSFT_AdfsWebApiApplication/MSFT_AdfsWebApiApplication.psm1

<#
    .SYNOPSIS
        DSC module for the Adfs Web API Application resource
 
    .DESCRIPTION
        The AdfsWebApiApplication DSC resource manages Web API Applications within Active Directory Federation
        Services. Web Api Applications are a construct that represents a web API secured by ADFS.
 
        ## Requirements
 
        * Target machine must be running ADFS on Windows Server 2016 or above to use this resource.
 
    .PARAMETER Name
        Key - String
        Specifies a name for the Web API application.
 
    .PARAMETER ApplicationGroupIdentifier
        Required - String
        Specifies the ID of an application group for the Web API application.
 
    .PARAMETER Identifier
        Required - String
        Specifies an identifier for the Web API application.
 
    .PARAMETER AccessControlPolicyName
        Write - String
        Specifies the name of an access control policy.
 
    .PARAMETER AccessControlPolicyParameters
        Write - MSFT_AccessControlPolicyParameters
        Specifies the parameters and their values to pass to the Access Control Policy.
 
    .PARAMETER AdditionalAuthenticationRules
        Write - String
        Specifies additional authentication rules.
 
    .PARAMETER AllowedAuthenticationClassReferences
        Write - String
        Specifies an array of allow authentication class references.
 
    .PARAMETER AllowedClientTypes
        Write - String
        Allowed values: None, Public, Confidential
        Specifies allowed client types.
 
    .PARAMETER AlwaysRequireAuthentication
        Write - Boolean
        Indicates that this Web API application role always requires authentication, even if it previously
        authenticated credentials for access. Specify this parameter to require users to always supply credentials to
        access sensitive resources.
 
    .PARAMETER ClaimsProviderName
        Write - String
        Specifies an array of claims provider names that you can configure for a relying party trust for Home Realm
        Discovery (HRD) scenario.
 
    .PARAMETER DelegationAuthorizationRules
        Write - String
        Specifies delegation authorization rules.
 
    .PARAMETER Description
        Write - String
        Specifies a description for the Web API application.
 
    .PARAMETER ImpersonationAuthorizationRules
        Write - String
        Specifies the impersonation authorization rules.
 
    .PARAMETER IssuanceAuthorizationRules
        Write - String
        Specifies the issuance authorization rules.
 
    .PARAMETER IssuanceTransformRules
        Write - String
        Specifies the issuance transform rules.
 
    .PARAMETER IssueOAuthRefreshTokensTo
        Write - String
        Allowed values: NoDevice, WorkplaceJoinedDevices, AllDevices
        Specifies the refresh token issuance device types.
 
    .PARAMETER NotBeforeSkew
        Write - Sint32
        Specifies the not before skew value.
 
    .PARAMETER RefreshTokenProtectionEnabled
        Write - Boolean
        Indicates whether refresh token protection is enabled.
 
    .PARAMETER RequestMFAFromClaimsProviders
        Write - Boolean
        Indicates that the request MFA from claims providers option is used.
 
    .PARAMETER TokenLifetime
        Write - Sint32
        Specifies the token lifetime.
 
    .PARAMETER Ensure
        Write - String
        Allowed values: Present, Absent
        Specifies whether the Web API application should be present or absent. Default value is 'Present'.
#>


Set-StrictMode -Version 2.0

$script:dscModuleName = 'AdfsDsc'
$script:psModuleName = 'ADFS'
$script:dscResourceName = [System.IO.Path]::GetFileNameWithoutExtension($MyInvocation.MyCommand.Name)

$script:resourceModulePath = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent
$script:modulesFolderPath = Join-Path -Path $script:resourceModulePath -ChildPath 'Modules'

$script:localizationModulePath = Join-Path -Path $script:modulesFolderPath -ChildPath "$($script:DSCModuleName).Common"
Import-Module -Name (Join-Path -Path $script:localizationModulePath -ChildPath "$($script:dscModuleName).Common.psm1")

$script:localizedData = Get-LocalizedData -ResourceName $script:dscResourceName

function Get-TargetResource
{
    <#
    .SYNOPSIS
        Get-TargetResource
 
    .NOTES
        Used Cmdlets/Functions:
 
        Name | Module
        -----------------------------------------|----------------
        Get-AdfsWebApiApplication | Adfs
        Assert-Module | AdfsDsc.Common
        Assert-Command | AdfsDsc.Common
        Assert-AdfsService | AdfsDsc.Common
        ConvertFrom-IssuanceTransformRule | AdfsDsc.Common
        ConvertFrom-AccessControlPolicyParameter | AdfsDsc.Common
    #>


    [CmdletBinding()]
    [OutputType([System.Collections.Hashtable])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ApplicationGroupIdentifier,

        [Parameter(Mandatory = $true)]
        [System.String[]]
        $Identifier
    )

    # Set Verbose and Debug parameters
    $commonParms = @{
        Verbose = $VerbosePreference
        Debug   = $DebugPreference
    }

    Write-Verbose -Message ($script:localizedData.GettingResourceMessage -f $Name)

    # Check of the Resource PowerShell module is installed
    Assert-Module -ModuleName $script:psModuleName

    # Check if the Get-AdfsWebApiApplication command is available
    Assert-Command -Module $script:psModuleName -Command 'Get-AdfsWebApiApplication'

    # Check if the ADFS Service is present and running
    Assert-AdfsService @commonParms

    try
    {
        $targetResource = Get-AdfsWebApiApplication -Name $Name
    }
    catch
    {
        $errorMessage = $script:localizedData.GettingResourceErrorMessage -f $Name
        New-InvalidOperationException -Message $errorMessage -Error $_
    }

    if ($targetResource)
    {
        # Resource is Present
        Write-Debug -Message ($script:localizedData.TargetResourcePresentDebugMessage -f $Name)

        $AccessControlPolicyParameters = ConvertFrom-AccessControlPolicyParameter `
            -Policy $targetResource.AccessControlPolicyParameters @commonParms

        $IssuanceTransformRules = ConvertFrom-IssuanceTransformRule `
            -Rule $targetResource.IssuanceTransformRules @commonParms

        $returnValue = @{
            Name                                 = $targetResource.Name
            ApplicationGroupIdentifier           = $targetResource.ApplicationGroupIdentifier
            Identifier                           = @($targetResource.Identifier)
            AccessControlPolicyName              = $targetResource.AccessControlPolicyName
            AccessControlPolicyParameters        = $AccessControlPolicyParameters
            AdditionalAuthenticationRules        = $targetResource.AdditionalAuthenticationRules
            AlwaysRequireAuthentication          = $targetResource.AlwaysRequireAuthentication
            AllowedClientTypes                   = @($targetResource.AllowedClientTypes.ToString().Split(',').Trim(' '))
            AllowedAuthenticationClassReferences = @($targetResource.AllowedAuthenticationClassReferences)
            ClaimsProviderName                   = @($targetResource.ClaimsProviderName)
            DelegationAuthorizationRules         = $targetResource.DelegationAuthorizationRules
            Description                          = $targetResource.Description
            ImpersonationAuthorizationRules      = $targetResource.ImpersonationAuthorizationRules
            IssuanceAuthorizationRules           = $targetResource.IssuanceAuthorizationRules
            IssuanceTransformRules               = @($IssuanceTransformRules)
            IssueOAuthRefreshTokensTo            = $targetResource.IssueOAuthRefreshTokensTo
            NotBeforeSkew                        = $targetResource.NotBeforeSkew
            RefreshTokenProtectionEnabled        = $targetResource.RefreshTokenProtectionEnabled
            RequestMFAFromClaimsProviders        = $targetResource.RequestMFAFromClaimsProviders
            TokenLifetime                        = $targetResource.TokenLifetime
            Ensure                               = 'Present'
        }
    }
    else
    {
        # Resource is Absent
        Write-Debug -Message ($script:localizedData.TargetResourceAbsentDebugMessage -f $Name)

        $returnValue = @{
            Name                                 = $Name
            ApplicationGroupIdentifier           = $ApplicationGroupIdentifier
            Identifier                           = @($Identifier)
            AccessControlPolicyName              = $null
            AccessControlPolicyParameters        = $null
            AdditionalAuthenticationRules        = $null
            AllowedAuthenticationClassReferences = @()
            AllowedClientTypes                   = @('None')
            AlwaysRequireAuthentication          = $null
            ClaimsProviderName                   = @()
            DelegationAuthorizationRules         = $null
            Description                          = $null
            ImpersonationAuthorizationRules      = $null
            IssuanceAuthorizationRules           = $null
            IssuanceTransformRules               = $null
            IssueOAuthRefreshTokensTo            = 'NoDevice'
            NotBeforeSkew                        = 0
            RefreshTokenProtectionEnabled        = $false
            RequestMFAFromClaimsProviders        = $false
            TokenLifetime                        = 0
            Ensure                               = 'Absent'
        }
    }

    $returnValue
}


function Set-TargetResource
{
    <#
    .SYNOPSIS
        Set-TargetResource
 
    .NOTES
        Used Cmdlets/Functions:
 
        Name | Module
        ---------------------------------------|----------------
        Add-AdfsWebApiApplication | Adfs
        Remove-AdfsWebApiApplication | Adfs
        Set-AdfsWebApiApplication | Adfs
        Compare-IssuanceTransformRule | AdfsDsc.Common
        Compare-AccessControlPolicyParameter | AdfsDsc.Common
        Compare-ResourcePropertyState | AdfsDsc.Common
        ConvertTo-IssuanceTransformRule | AdfsDsc.Common
        ConvertTo-AccessControlPolicyParameter | AdfsDsc.Common
    #>


    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ApplicationGroupIdentifier,

        [Parameter(Mandatory = $true)]
        [System.String[]]
        $Identifier,

        [Parameter()]
        [System.String]
        $AccessControlPolicyName,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance]
        $AccessControlPolicyParameters,

        [Parameter()]
        [System.String]
        $AdditionalAuthenticationRules,

        [Parameter()]
        [System.String[]]
        $AllowedAuthenticationClassReferences,

        [Parameter()]
        [ValidateSet('None', 'Public', 'Confidential')]
        [System.String[]]
        $AllowedClientTypes,

        [Parameter()]
        [System.Boolean]
        $AlwaysRequireAuthentication,

        [Parameter()]
        [System.String[]]
        $ClaimsProviderName,

        [Parameter()]
        [System.String]
        $DelegationAuthorizationRules,

        [Parameter()]
        [System.String]
        $Description,

        [Parameter()]
        [System.String]
        $ImpersonationAuthorizationRules,

        [Parameter()]
        [System.String]
        $IssuanceAuthorizationRules,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $IssuanceTransformRules,

        [Parameter()]
        [ValidateSet('NoDevice', 'WorkplaceJoinedDevices', 'AllDevices')]
        [System.String]
        $IssueOAuthRefreshTokensTo,

        [Parameter()]
        [System.Int32]
        $NotBeforeSkew,

        [Parameter()]
        [System.Boolean]
        $RefreshTokenProtectionEnabled,

        [Parameter()]
        [System.Boolean]
        $RequestMFAFromClaimsProviders,

        [Parameter()]
        [System.Int32]
        $TokenLifetime,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    # Set Verbose and Debug parameters
    $commonParms = @{
        Verbose = $VerbosePreference
        Debug   = $DebugPreference
    }

    # Remove any parameters not used in Splats
    [HashTable]$parameters = $PSBoundParameters
    $parameters.Remove('Ensure')
    $parameters.Remove('Verbose')

    Write-Verbose -Message ($script:localizedData.SettingResourceMessage -f $Name)

    $GetTargetResourceParms = @{
        ApplicationGroupIdentifier = $ApplicationGroupIdentifier
        Name                       = $Name
        Identifier                 = $Identifier
    }
    $targetResource = Get-TargetResource @GetTargetResourceParms

    if ($targetResource.Ensure -eq 'Present')
    {
        # Resource is Present
        Write-Debug -Message ($script:localizedData.TargetResourcePresentDebugMessage -f $Name)

        if ($Ensure -eq 'Present')
        {
            # Resource should be Present
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBePresentDebugMessage -f $Name)

            $propertiesNotInDesiredState = @()

            if ($PSBoundParameters.Keys.Contains('IssuanceTransformRules'))
            {
                $propertiesNotInDesiredState += (
                    Compare-IssuanceTransformRule -CurrentValue $targetResource.IssuanceTransformRules `
                        -DesiredValue $IssuanceTransformRules -ParameterName 'IssuanceTransformRules' `
                        @commonParms | Where-Object -Property InDesiredState -eq $false)
            }

            if ($PSBoundParameters.Keys.Contains('AccessControlPolicyParameters'))
            {
                $propertiesNotInDesiredState += (
                    Compare-AccessControlPolicyParameter -CurrentValue $targetResource.AccessControlPolicyParameters `
                        -DesiredValue $AccessControlPolicyParameters -ParameterName 'AccessControlPolicyParameters' `
                        @commonParms | Where-Object -Property InDesiredState -eq $false)
            }

            $propertiesNotInDesiredState += (
                Compare-ResourcePropertyState -CurrentValues $targetResource -DesiredValues $parameters `
                    -IgnoreProperties 'IssuanceTransformRules', 'AccessControlPolicyParameters' `
                    @commonParms | Where-Object -Property InDesiredState -eq $false)

            if ($propertiesNotInDesiredState |
                Where-Object -Property ParameterName -eq 'ApplicationGroupIdentifier')
            {
                Write-Verbose -Message ($script:localizedData.RemovingResourceMessage -f
                    $Name, $targetResource.ApplicationGroupIdentifier)

                try
                {
                    Remove-AdfsWebApiApplication -TargetName $Name
                }
                catch
                {
                    $errorMessage = $script:localizedData.RemovingResourceErrorMessage -f $Name
                    New-InvalidOperationException -Message $errorMessage -Error $_
                }

                Write-Verbose -Message ($script:localizedData.AddingResourceMessage -f
                    $Name, $ApplicationGroupIdentifier)

                try
                {
                    Add-AdfsWebApiApplication @parameters -Verbose:$false
                }
                catch
                {
                    $errorMessage = $script:localizedData.AddingResourceErrorMessage -f $Name
                    New-InvalidOperationException -Message $errorMessage -Error $_
                }

                break
            }

            $setParameters = @{ }
            foreach ($property in $propertiesNotInDesiredState)
            {
                Write-Verbose -Message ($script:localizedData.SettingResourcePropertyMessage -f
                    $Name, $property.ParameterName, ($property.Expected -join ', '))

                if ($property.ParameterName -eq 'IssuanceTransformRules')
                {
                    # Custom processing for 'IssuanceTransformRules' property
                    $setParameters.Add($property.ParameterName, ($IssuanceTransformRules |
                            ConvertTo-IssuanceTransformRule @commonParms))
                }
                elseif ($property.ParameterName -eq 'AccessControlPolicyParameters')
                {
                    # Custom processing for 'AccessControlPolicyParameters' property
                    $setParameters.Add($property.ParameterName, ($AccessControlPolicyParameters |
                            ConvertTo-AccessControlPolicyParameter @commonParms))
                }
                else
                {
                    $setParameters.add($property.ParameterName, $property.Expected)
                }
            }

            try
            {
                Set-AdfsWebApiApplication -TargetName $Name @setParameters
            }
            catch
            {
                $errorMessage = $script:localizedData.SettingResourceErrorMessage -f $Name
                New-InvalidOperationException -Message $errorMessage -Error $_
            }
        }
        else
        {
            # Resource should be Absent
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBeAbsentDebugMessage -f $Name)

            Write-Verbose -Message ($script:localizedData.RemovingResourceMessage -f
                $Name, $ApplicationGroupIdentifier)

            try
            {
                Remove-AdfsWebApiApplication -TargetName $Name
            }
            catch
            {
                $errorMessage = $script:localizedData.RemovingResourceErrorMessage -f $Name
                New-InvalidOperationException -Message $errorMessage -Error $_
            }
        }
    }
    else
    {
        # Resource is Absent
        Write-Debug -Message ($script:localizedData.TargetResourceAbsentDebugMessage -f $Name)

        if ($Ensure -eq 'Present')
        {
            # Resource should be Present
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBePresentDebugMessage -f $Name)

            if ($parameters.ContainsKey('IssuanceTransformRules'))
            {
                # Custom processing for 'IssuanceTransformRules' property
                $parameters.IssuanceTransformRules = ($parameters.IssuanceTransformRules |
                    ConvertTo-IssuanceTransformRule @commonParms)
            }

            if ($parameters.ContainsKey('AccessControlPolicyParameters'))
            {
                # Custom processing for 'AccessControlPolicyParameters' property
                $parameters.AccessControlPolicyParameters = ($parameters.AccessControlPolicyParameters |
                    ConvertTo-AccessControlPolicyParameter @commonParms)
            }

            Write-Verbose -Message ($script:localizedData.AddingResourceMessage -f
                $Name, $ApplicationGroupIdentifier)

            try
            {
                Add-AdfsWebApiApplication @parameters -Verbose:$false
            }
            catch
            {
                $errorMessage = $script:localizedData.AddingResourceErrorMessage -f $Name
                New-InvalidOperationException -Message $errorMessage -Error $_
            }
        }
        else
        {
            # Resource should be Absent
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBeAbsentDebugMessage -f $Name)

            Write-Verbose -Message ($script:localizedData.ResourceInDesiredStateMessage -f $Name)
        }
    }
}

function Test-TargetResource
{
    <#
    .SYNOPSIS
        Test-TargetResource
 
    .NOTES
        Used Cmdlets/Functions:
 
        Name | Module
        -------------------------------------|------------------
        Compare-IssuanceTransformRule | AdfsDsc.Common
        Compare-AccessControlPolicyParameter | AdfsDsc.Common
        Compare-ResourcePropertyState | AdfsDsc.Common
    #>


    [CmdletBinding()]
    [OutputType([System.Boolean])]
    param
    (
        [Parameter(Mandatory = $true)]
        [System.String]
        $Name,

        [Parameter(Mandatory = $true)]
        [System.String]
        $ApplicationGroupIdentifier,

        [Parameter(Mandatory = $true)]
        [System.String[]]
        $Identifier,

        [Parameter()]
        [System.String]
        $AccessControlPolicyName,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance]
        $AccessControlPolicyParameters,

        [Parameter()]
        [System.String]
        $AdditionalAuthenticationRules,

        [Parameter()]
        [System.String[]]
        $AllowedAuthenticationClassReferences,

        [Parameter()]
        [ValidateSet('None', 'Public', 'Confidential')]
        [System.String[]]
        $AllowedClientTypes,

        [Parameter()]
        [System.Boolean]
        $AlwaysRequireAuthentication,

        [Parameter()]
        [System.String[]]
        $ClaimsProviderName,

        [Parameter()]
        [System.String]
        $DelegationAuthorizationRules,

        [Parameter()]
        [System.String]
        $Description,

        [Parameter()]
        [System.String]
        $ImpersonationAuthorizationRules,

        [Parameter()]
        [System.String]
        $IssuanceAuthorizationRules,

        [Parameter()]
        [Microsoft.Management.Infrastructure.CimInstance[]]
        $IssuanceTransformRules,

        [Parameter()]
        [ValidateSet('NoDevice', 'WorkplaceJoinedDevices', 'AllDevices')]
        [System.String]
        $IssueOAuthRefreshTokensTo,

        [Parameter()]
        [System.Int32]
        $NotBeforeSkew,

        [Parameter()]
        [System.Boolean]
        $RefreshTokenProtectionEnabled,

        [Parameter()]
        [System.Boolean]
        $RequestMFAFromClaimsProviders,

        [Parameter()]
        [System.Int32]
        $TokenLifetime,

        [Parameter()]
        [ValidateSet('Present', 'Absent')]
        [System.String]
        $Ensure = 'Present'
    )

    # Set Verbose and Debug parameters
    $commonParms = @{
        Verbose = $VerbosePreference
        Debug   = $DebugPreference
    }

    Write-Verbose -Message ($script:localizedData.TestingResourceMessage -f $Name)

    $getTargetResourceParms = @{
        Name                       = $Name
        ApplicationGroupIdentifier = $ApplicationGroupIdentifier
        Identifier                 = $Identifier
    }
    $targetResource = Get-TargetResource @getTargetResourceParms

    if ($targetResource.Ensure -eq 'Present')
    {
        # Resource is Present
        Write-Debug -Message ($script:localizedData.TargetResourcePresentDebugMessage -f $Name)

        if ($Ensure -eq 'Present')
        {
            # Resource should be Present
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBePresentDebugMessage -f $Name)

            $propertiesNotInDesiredState = @()

            if ($PSBoundParameters.Keys.Contains('IssuanceTransformRules'))
            {
                $propertiesNotInDesiredState += (
                    Compare-IssuanceTransformRule -CurrentValue $targetResource.IssuanceTransformRules `
                        -DesiredValue $IssuanceTransformRules -ParameterName 'IssuanceTransformRules' `
                        @commonParms | Where-Object -Property InDesiredState -eq $false)
            }

            if ($PSBoundParameters.Keys.Contains('AccessControlPolicyParameters'))
            {
                $propertiesNotInDesiredState += (
                    Compare-AccessControlPolicyParameter -CurrentValue $targetResource.AccessControlPolicyParameters `
                        -DesiredValue $AccessControlPolicyParameters -ParameterName 'AccessControlPolicyParameters' `
                        @commonParms | Where-Object -Property InDesiredState -eq $false)
            }

            $propertiesNotInDesiredState += (
                Compare-ResourcePropertyState -CurrentValues $targetResource -DesiredValues $PSBoundParameters `
                    -IgnoreProperties 'IssuanceTransformRules', 'AccessControlPolicyParameters' `
                    @commonParms | Where-Object -Property InDesiredState -eq $false)

            if ($propertiesNotInDesiredState)
            {
                # Resource is not in desired state
                Write-Verbose -Message ($script:localizedData.ResourceNotInDesiredStateMessage -f $Name)

                $inDesiredState = $false
            }
            else
            {
                # Resource is in desired state
                Write-Verbose -Message ($script:localizedData.ResourceInDesiredStateMessage -f $Name)

                $inDesiredState = $true
            }
        }
        else
        {
            # Resource should be Absent
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBeAbsentDebugMessage -f $Name)

            Write-Verbose -Message ($script:localizedData.ResourceIsPresentButShouldBeAbsentMessage -f $Name)

            $inDesiredState = $false
        }
    }
    else
    {
        # Resource is Absent
        Write-Debug -Message ($script:localizedData.TargetResourceAbsentDebugMessage -f $Name)

        if ($Ensure -eq 'Present')
        {
            # Resource should be Present
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBePresentDebugMessage -f $Name)

            Write-Verbose -Message ($script:localizedData.ResourceIsAbsentButShouldBePresentMessage -f $Name)

            $inDesiredState = $false
        }
        else
        {
            # Resource should be Absent
            Write-Debug -Message ($script:localizedData.TargetResourceShouldBeAbsentDebugMessage -f $Name)

            Write-Verbose -Message ($script:localizedData.ResourceInDesiredStateMessage -f $Name)

            $inDesiredState = $true
        }
    }

    $inDesiredState
}

Export-ModuleMember -Function *-TargetResource