CatalystDosIdentity.psm1

<#
    .Synopsis
    Attempts to get an access token for given client
 
    .Description
    Takes in a client id and secret with valid scope and hits the identity server provided and returns an access token.
 
    .Parameter identityUrl
    The identity url to get the access token from
 
    .Parameter clientId
    The id of the client the access token is being requested for
 
    .Parameter secret
    The secret of the client the access token is being requested for
 
    .Parameter scopes
    The scope of the access token to be requested
 
    .Example
    Get-AccessToken -identityUrl "https://server/identity" -clientId "sample-client-id" -secret "SECrEtStrING" -scopes "offline_access"
#>

function Get-AccessToken {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $secret,
        [string] $scope
    )
    
    [Uri] $url = "$($identityUrl.OriginalString.TrimEnd("/"))/connect/token"
    $body = @{
        client_id     = $clientId
        grant_type    = "client_credentials"
        scope         = $scope
        client_secret = $secret
    }

    $accessTokenResponse = Invoke-RestMethod -Method Post -Uri $url -Body $body
    return $accessTokenResponse.access_token
}

<#
    .Synopsis
    Attempts to get an access token for the fabric installer client
 
    .Description
    Takes in the identity server url and the installer secret and return the installer access token.
 
    .Parameter identityUrl
    The identity url to get the access token from
 
    .Parameter secret
    The secret of the fabric installer client
 
    .Example
    Get-AccessToken -identityUrl "https://server/identity" -secret "SECrEtStrING"
#>

function Get-FabricInstallerAccessToken {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $secret
    )

    $clientId = "fabric-installer"
    $scope = "fabric/identity.manageresources fabric/authorization.read fabric/authorization.write fabric/authorization.dos.write fabric/authorization.manageclients"
    return Get-AccessToken $identityUrl $clientId $secret $scope
}

<#
    .Synopsis
    Attempts to retrieve information about an existing identity client
 
    .Description
    Takes in the identity server url, the clientid and an access token.
    Returns a client identity object
 
    .Parameter identityUrl
    The base identity url
 
    .Parameter clientId
    the identifier for the client to retrieve
 
    .Parameter accessToken
    an access token previously retrieved from the identity server
 
    .Example
    Get-ClientRegistration -identityUrl "https://server/identity" -clientId "sample-client-id" -accessToken "eyJhbGciO"
#>

function Get-ClientRegistration {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    [Uri]$url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/v1/client/$clientId"

    $headers = @{"Accept" = "application/json"}
    $headers.Add("Authorization", "Bearer $accessToken")

    $clientResponse = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
    return $clientResponse
}

<#
    .Synopsis
    Attempts to register a new identity client
 
    .Description
    Takes in the identity server url, the json client body and an access token.
    Returns a client secret for the new client.
    If the client already exists, this function updates the client attributes and resets the client secret.
 
    .Parameter identityUrl
    The base identity url
 
    .Parameter body
    the json payload of attributes and values for the new client
 
    .Parameter accessToken
    an access token previously retrieved from the identity server
 
    .Example
    New-ClientRegistration -identityUrl "https://server/identity" -body '{"clientId":"fabric-installer", "clientName":"Fabric Installer" ...... }' -accessToken "eyJhbGciO"
#>

function New-ClientRegistration {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] $body,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    [Uri] $url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/client"

    if (!($body -is [string])) {
        $clientObject = $body
        $body = $body | ConvertTo-Json
    }
    else {
        $clientObject = ConvertFrom-Json -InputObject $body
    }

    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }

    # attempt to add
    try {
        $registrationResponse = Invoke-RestMethod -Method Post -Uri $url -Body $body -ContentType "application/json" -Headers $headers
        return $registrationResponse.clientSecret
    }
    catch {
        $exception = $_.Exception
        if ((Assert-WebExceptionType -exception $exception -typeCode 409)) {
            try {
                # client ID already exists, update with PUT
                Invoke-RestMethod -Method Put -Uri "$url/$($clientObject.clientId)" -Body $body -ContentType "application/json" -Headers $headers | out-null

                # Reset client secret
                $apiResponse = Invoke-RestMethod -Method Post -Uri "$url/$($clientObject.clientId)/resetPassword" -ContentType "application/json" -Headers $headers
                return $apiResponse.clientSecret
            }
            catch {
                $error = "Unknown error attempting to update"
                $exception = $_.Exception
                if ($null -ne $exception -and $null -ne $exception.Response) {
                    $error = Get-ErrorFromResponse -response $exception.Response
                }
                throw (New-Object -TypeName "System.Net.WebException" "There was an error updating Client $($clientObject.clientName): $error. Halting installation.", $exception)
            }
        }
        else {
            $error = "Unknown error attempting to post"
            $exception = $_.Exception
            if ($null -ne $exception -and $null -ne $exception.Response) {
                $error = Get-ErrorFromResponse -response $exception.Response
            }
            throw ( New-Object -TypeName "System.Net.WebException" "There was an error registering client $($clientObject.clientName) with Fabric.Identity: $error, halting installation.", $exception)
        }
    }
}

<#
    .Synopsis
    Creates a Client Credentials Identity Client
 
    .Description
    Returns a new hashtable that represents a valid identity client body for use with New-ClientRegistration. Sensible defaults values are included.
 
    .Parameter clientId
    The text identiifier for the client
 
    .Parameter clientName
    The human readable text name for the client
 
    .Parameter allowedScopes
    Array of strings representing the allowed scopes for the client
 
    .Example
    New-ClientCredentialsClientBody -clientId "sample-credential-client" -clientName "Sample Client using Client Credentials" -allowedScopes @("dos/metadata.read")
#>

function New-ClientCredentialsClientBody {
    param(
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $clientName,
        [Parameter(Mandatory = $True)] [AllowEmptyCollection()] [string[]] $allowedScopes
    )

    # parameters
    $newClient = @{}
    $newClient.Add("clientId", $clientId)
    $newClient.Add("clientName", $clientName)
    $newClient.Add("allowedScopes", $allowedScopes)

    $newClient.Add("allowedGrantTypes", @("client_credentials"))

    return $newClient
}

<#
    .Synopsis
    Creates an Implicit Identity Client
 
    .Description
    Retruns a new hashtable that represents a valid identity client body for use with New-ClientRegistration. Sensible defaults values are included.
 
    .Parameter clientId
    The text identiifier for the client
 
    .Parameter clientName
    The human readable text name for the client
 
    .Parameter allowedScopes
    Array of strings representing the allowed scopes for the client
 
    .Parameter allowedCorsOrigins
    Array of strings representing the allowed addresses for Cross-Origin Resource Sharing
 
    .Parameter redirectUris
    Array of strings representing list of uri's that can request a login redirect
 
    .Parameter postLogoutRedirectUris
    Array of strings representing list of uri's that are acceptable navigation after logout
 
    .Example
    New-ImplicitClientBody -clientId "sample-implicit-client" -clientName "Sample Client using Implicit Registration" -allowesScopes @("dos/metadata.read") -allowesCorsOrigins @('http://some.server', 'https://some.other.server') -redirectUris @('https://some.server/loginRedirect') -postLogoutRedirectUris @('https://some.other.server/logoutRedirect')
#>

function New-ImplicitClientBody {
    param(
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $clientName,
        [Parameter(Mandatory = $True)] [AllowEmptyCollection()] [string[]] $allowedScopes,
        [Parameter(Mandatory = $false)] [string[]] $allowedCorsOrigins,
        [Parameter(Mandatory = $false)] [string[]] $redirectUris,
        [Parameter(Mandatory = $false)] [string[]] $postLogoutRedirectUris
    )

    # parameters
    $newClient = @{}
    $newClient.Add("clientId", $clientId)
    $newClient.Add("clientName", $clientName)
    $newClient.Add("allowedScopes", $allowedScopes)
    $newClient.Add("allowedCorsOrigins", $allowedCorsOrigins)
    $newClient.Add("redirectUris", $redirectUris )
    $newClient.Add("postLogoutRedirectUris", $postLogoutRedirectUris)

    $newClient.Add("allowedGrantTypes", @("implicit"))
    $newClient.Add("requireConsent", $false)
    $newClient.Add("allowOfflineAccess", $false)
    $newClient.Add("allowAccessTokensViaBrowser", $true)
    $newClient.Add("enableLocalLogin", $false)
    $newClient.Add("accessTokenLifetime", 1200)

    return $newClient
}

<#
    .Synopsis
    Creates an Hybryd Identity Client
 
    .Description
    Retruns a new hashtable that represents a valid identity client body for use with New-ClientRegistration. Sensible defaults values are included.
 
    .Parameter clientId
    The text identiifier for the client
 
    .Parameter clientName
    The human readable text name for the client
 
    .Parameter allowedScopes
    Array of strings representing the allowed scopes for the client
 
    .Parameter allowedCorsOrigins
    Array of strings representing the allowed addresses for Cross-Origin Resource Sharing
 
    .Parameter redirectUris
    Array of strings representing list of uri's that can request a login redirect
 
    .Parameter postLogoutRedirectUris
    Array of strings representing list of uri's that are acceptable navigation after logout
 
    .Example
    New-HybridClientBody -clientId "sample-hybrid-client" -clientName "Sample Client using Hybrid Registration" -allowesScopes @("dos/metadata.read") -allowesCorsOrigins @('http://some.server', 'https://some.other.server') -redirectUris @('https://some.server/loginRedirect') -postLogoutRedirectUris @('https://some.other.server/logoutRedirect')
#>

function New-HybridClientBody {
    param(
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $clientName,
        [Parameter(Mandatory = $True)] [AllowEmptyCollection()] [string[]] $allowedScopes,
        [Parameter(Mandatory = $false)] [string[]] $allowedCorsOrigins,
        [Parameter(Mandatory = $false)] [string[]] $redirectUris,
        [Parameter(Mandatory = $false)] [string[]] $postLogoutRedirectUris
    )

    # parameters
    $newClient = @{}
    $newClient.Add("clientId", $clientId)
    $newClient.Add("clientName", $clientName)
    $newClient.Add("allowedScopes", $allowedScopes)
    $newClient.Add("allowedCorsOrigins", $allowedCorsOrigins)
    $newClient.Add("redirectUris", $redirectUris )
    $newClient.Add("postLogoutRedirectUris", $postLogoutRedirectUris)

    $newClient.Add("allowedGrantTypes", @("hybrid"))
    $newClient.Add("requireConsent", $false)
    $newClient.Add("allowOfflineAccess", $true)
    $newClient.Add("allowAccessTokensViaBrowser", $false)
    $newClient.Add("enableLocalLogin", $false)

    return $newClient
}

<#
    .Synopsis
    Creates an Hybryd and PKCE Identity Client
 
    .Description
    Retruns a new hashtable that represents a valid identity client body for use with New-ClientRegistration. Sensible defaults values are included.
 
    .Parameter clientId
    The text identiifier for the client
 
    .Parameter clientName
    The human readable text name for the client
 
    .Parameter allowedScopes
    Array of strings representing the allowed scopes for the client
 
    .Parameter redirectUris
    Array of strings representing list of uri's that can request a login redirect
 
    .Example
    New-HybridPkceClientBody -clientId "sample-hybridpkce-client" -clientName "Sample Client using Hybrid and PKCE Registration" -allowesScopes @("dos/metadata.read") -redirectUris @('https://some.server/loginRedirect')
#>

function New-HybridPkceClientBody {
    param(
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $clientName,
        [Parameter(Mandatory = $True)] [AllowEmptyCollection()] [string[]] $allowedScopes,
        [Parameter(Mandatory = $false)] [string[]] $redirectUris
    )

    # parameters
    $newClient = @{}
    $newClient.Add("clientId", $clientId)
    $newClient.Add("clientName", $clientName)
    $newClient.Add("allowedScopes", $allowedScopes)
    $newClient.Add("redirectUris", $redirectUris )

    $newClient.Add("allowedGrantTypes", @("hybrid"))
    $newClient.Add("requireConsent", $false)
    $newClient.Add("requireClientSecret", $false)
    $newClient.Add("allowOfflineAccess", $true)
    $newClient.Add("requirePkce", $true)
    $newClient.Add("updateAccessTokenClaimsOnRefresh", $true)

    return $newClient
}

<#
    .Synopsis
    Attempts to update an identity client
 
    .Description
    Takes in the identity server url, the json client body and an access token.
 
    .Parameter identityUrl
    The identity url to get the access token from
 
    .Parameter body
    the json payload of attributes and values for the new client
 
    .Parameter accessToken
    an access token previously retrieved from the identity server
 
    .Example
    Edit-ClientRegistration -identityUrl "https://server/identity" -body @'{"clientId":"fabric-installer", "clientName":"Fabric Installer" ...... }@' -accessToken "eyJhbGciO"
#>

function Edit-ClientRegistration {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] $body,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    if (!($body -is [string])) {
        $clientObject = $body
        $body = $body | ConvertTo-Json
    }
    else {
        $clientObject = ConvertFrom-Json -InputObject $body
    }

    [Uri] $url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/client"
    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }

    # attempt to PUT
    try {
        $updateReturn = Invoke-RestMethod -Method Put -Uri "$url/$($clientObject.clientId)" -Body $body -ContentType "application/json" -Headers $headers
        return $updateReturn
    }
    catch {
        $error = "Unknown error."
        $exception = $_.Exception
        if ($null -ne $exception -and $null -ne $exception.Response) {
            $error = Get-ErrorFromResponse -response $exception.Response
        }
        throw ( New-Object -TypeName "System.Net.WebException" "There was an error updating client registration $($clientObject.clientName) with Fabric.Identity: $error, halting installation.", $exception)
    }
}

<#
    .Synopsis
    Attempts to update the client secret of an identity client
 
    .Description
    Takes in the identity server url, the json client ID and an access token.
 
    .Parameter identityUrl
    The identity url to get the access token from
 
    .Parameter clientId
    the unique identifier for the client to reset
 
    .Parameter accessToken
    an access token previously retrieved from the identity server
 
    .Example
    Reset-ClientPassword -identityUrl "https://server/identity" -clientId "someClient" -accessToken "eyJhbGciO"
#>

function Reset-ClientPassword {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    [Uri] $url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/client"
    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }

    # attempt to Reset
    try {
        $apiResponse = Invoke-RestMethod -Method Post -Uri "$url/$clientId/resetPassword" -ContentType "application/json" -Headers $headers
        return $apiResponse.clientSecret
    }
    catch {
        $error = "Unknown error."
        $exception = $_.Exception
        if ($null -ne $exception -and $null -ne $exception.Response) {
            $error = Get-ErrorFromResponse -response $exception.Response
        }
        throw ( New-Object -TypeName "System.Net.WebException" "There was an error resetting client secret $clientId with Fabric.Identity: $error, halting installation.", $exception)
    }
}

<#
    .Synopsis
    checks for the existence of a specific identity client
 
    .Description
    Takes in the identity server url, the json client ID and an access token.
 
    .Parameter identityUrl
    The identity url to get the access token from
 
    .Parameter clientId
    the unique identifier for the client to check
 
    .Parameter accessToken
    an access token previously retrieved from the identity server
 
    .Example
    Test-IsClientRegistered -identityUrl "https://server/identity" -clientId "someClient" -accessToken "eyJhbGciO"
#>

function Test-IsClientRegistered {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $clientId,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    [Uri] $url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/client/$clientId"

    $headers = @{"Accept" = "application/json"}
    $headers.Add("Authorization", "Bearer $accessToken")

    try {
        Invoke-RestMethod -Method Get -Uri $url -Headers $headers | Out-Null
        # exception thrown if not found
        return $True
    }
    catch {
        $exception = $_.Exception
        if (Assert-WebExceptionType -exception $exception -typeCode 404) {
            try {
                return $false
            }
            catch {
                $error = "Unknown error."
                $exception = $_.Exception
                if ($null -ne $exception -and $null -ne $exception.Response) {
                    $error = Get-ErrorFromResponse -response $exception.Response
                }
                throw ( New-Object -TypeName "System.Net.WebException" "There was an error looking for client $clientId with Fabric.Identity: $error, halting installation.", $exception)
            }
        }
        else {
            $error = "Unknown error."
            $exception = $_.Exception
            if ($null -ne $exception -and $null -ne $exception.Response) {
                $error = Get-ErrorFromResponse -response $exception.Response
            }
            throw ( New-Object -TypeName "System.Net.WebException" "There was an error looking for client $clientId with Fabric.Identity: $error, halting installation.", $exception)
        }
    }
}

<#
    .Synopsis
    Attempts to retrieve information about an existing identity api
 
    .Description
    Takes in the identity server url the apiname and an access token.
 
    .Parameter identityUrl
    The identity url to get the api registration
 
    .Parameter apiName
    The name of the api
 
    .Parameter accessToken
    An access token previously retrieved from the identity server
 
    .Example
    Get-ApiRegistration -identityUrl "https://server/identity" -apiName "TestAPI" -accessToken "eyJhbGciO"
#>

function Get-ApiRegistration {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $apiName,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    [Uri]$url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/apiresource/$apiName"

    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }

    $clientResponse = Invoke-RestMethod -Method Get -Uri $url -Headers $headers
    return $clientResponse
}

<#
    .Synopsis
    Attempts to register an api with fabric identity
 
    .Description
    Takes in the identity server url the json request body and an access token.
 
    .Parameter identityUrl
    The identity url to post the api registration
 
    .Parameter body
    The json request format for creating the api registration
 
    .Parameter accessToken
    An access token previously retrieved from the identity server
 
    .Example
    New-ApiRegistration -identityUrl "https://server/identity" -body "{"enabled":true, "name": "sample-api", "userClaims":[], "scopes":[]}" -accessToken "eyJhbGciO"
#>

function New-ApiRegistration {
    param(
        [Parameter(Mandatory=$True)] [Uri] $identityUrl,
        [Parameter(Mandatory=$True)] $body,
        [Parameter(Mandatory=$True)] [string] $accessToken
    )

    $url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/apiresource"
    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }

    try{
        if(!($body -is [string])) {
            $body = $body | ConvertTo-Json
        }
        
        $registrationResponse = Invoke-RestMethod -Method Post -Uri $url -body $body -ContentType "application/json" -Headers $headers
        return $registrationResponse.apiSecret
    }
    catch {
        $exception = $_.Exception
        $apiResourceObject = ConvertFrom-Json -InputObject $body
        if ((Assert-WebExceptionType -exception $exception -typeCode 409)) {
            try {
                Invoke-RestMethod -Method Put -Uri "$url/$($apiResourceObject.name)" -Body $body -ContentType "application/json" -Headers $headers | out-null

                # Reset api secret
                $apiResponse = Reset-ApiPassword -identityUrl $($identityUrl.OriginalString) -apiName $($apiResourceObject.name) -accessToken $accessToken
                return $apiResponse
            }catch{
                $error = "Unknown error attempting to post api"
                $exception = $_.Exception
                if ($null -ne $exception -and $null -ne $exception.Response) {
                    $error = Get-ErrorFromResponse -response $exception.Response
                }
                throw (New-Object -TypeName "System.Net.WebException" "There was an error registering api $($apiResourceObject.name): $error. Registration failure.", $exception)
            }
        }
        else {
            $error = "Unknown error attempting to post"
            $exception = $_.Exception
            if ($null -ne $exception -and $null -ne $exception.Response) {
                $error = Get-ErrorFromResponse -response $exception.Response
            }
            throw ( New-Object -TypeName "System.Net.WebException" "There was an error registering api $($apiResourceObject.name) with Fabric.Identity: $error, Registration failure.", $exception)
        }
    }
}

<#
    .Synopsis
    Attempts to create an apiresource object
 
    .Description
    Takes in the apiname userclaims scopes and isenabled.
 
    .Parameter apiName
    The name of the api
     
    .Parameter userClaims
    Array of strings representing the user claims
 
    .Parameter scopes
    Array of Hashtables representing the scopes for the api
     
    .Parameter isEnabled
    If the apiresource is enabled
 
    .Example
    New-ApiRegistrationBody -apiName "this-Api" -userClaims @("name", "email", "role", "groups") -scopes @{"name" = "this-Api"; "displayName" = "This-API"} -isEnabled true
#>

function New-ApiRegistrationBody {
    param(
        [Parameter(Mandatory = $True)] [string] $apiName,
        [string[]] $userClaims,
        [Hashtable[]] $scopes,
        [string] $isEnabled
    )

    $newApiResource = @{}
    $newApiResource.Add("name", $apiName)
    $newApiResource.Add("scopes", $scopes)
    $newApiResource.Add("userClaims", $userClaims)
    $newApiResource.Add("enabled", $isEnabled)

    return $newApiResource
}

<#
    .Synopsis
    Attempts to remove the api object
 
    .Description
    Takes in the identity server url an apiname and an access token.
 
    .Parameter identityUrl
    The identity url to delete the api registration
 
    .Parameter apiName
    The name of the api
 
    .Parameter accessToken
    An access token previously retrieved from the identity server
 
    .Example
    Remove-ApiRegistration -identityUrl "https://server/identity" -apiName "TestAPI" -accessToken "eyJhbGciO"
#>

function Remove-ApiRegistration {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $apiName,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    [Uri]$url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/apiresource/$apiName"

    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }

    try {
        $clientResponse = Invoke-RestMethod -Method Delete -Uri $url -Headers $headers
        return $clientResponse
    }
    catch {
        $exception = $_.Exception
        $error = "Unknown error attempting to delete api"
        if ($null -ne $exception -and $null -ne $exception.Response) {
            $error = Get-ErrorFromResponse -response $exception.Response
        }
        throw ( New-Object -TypeName "System.Net.WebException" "There was an error deleting api $apiName with Fabric.Identity: $error, Removing api registration failure.", $exception)   
    }
}

<#
    .Synopsis
    Attempts to edit an api object with fabric identity
 
    .Description
    Takes in the identity server url the json request body an apiname and an access token.
 
    .Parameter identityUrl
    The identity url to put the api registration
 
    .Parameter body
    The json request format for editing the api registration
 
    .Parameter apiName
    The name of the api
 
    .Parameter accessToken
    An access token previously retrieved from the identity server
 
    .Example
    Edit-ApiRegistration -identityUrl "https://server/identity" -body "{"enabled":true, "name": "sample-api", "userClaims":[], "scopes":[]}" -apiName "TestAPI" -accessToken "eyJhbGciO"
#>

function Edit-ApiRegistration {
    param(
        [Parameter(Mandatory=$True)] [Uri] $identityUrl,
        [Parameter(Mandatory=$True)] $body,
        [Parameter(Mandatory=$True)] [string] $apiName,
        [Parameter(Mandatory=$True)] [string] $accessToken
    )

    $url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/apiresource/$apiName"
    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }
    try{
        if(!($body -is [string])) {
            $body = $body | ConvertTo-Json
        }

        Invoke-RestMethod -Method Put -uri $url -body $body -ContentType "application/json" -Headers $headers | out-null

        # Reset api secret
        $apiResponse = Reset-ApiPassword -identityUrl $($identityUrl.OriginalString) -apiName $apiName -accessToken $accessToken
        return $apiResponse
    }catch{
        $exception = $_.Exception
        $apiResourceObject = ConvertFrom-Json -InputObject $body
        $error = "Unknown error attempting to edit api"
        if ($null -ne $exception -and $null -ne $exception.Response) {
            $error = Get-ErrorFromResponse -response $exception.Response
        }
        throw ( New-Object -TypeName "System.Net.WebException" "There was an error editing api $($apiResourceObject.name) with Fabric.Identity: $error, Editing registration failure.", $exception)
    }
}

<#
    .Synopsis
    Attempts to reset an api object password with fabric identity
 
    .Description
    Takes in the identity server url an apiname and an access token.
 
    .Parameter identityUrl
    The identity url to reset the api password
 
    .Parameter apiName
    The name of the api
 
    .Parameter accessToken
    An access token previously retrieved from the identity server
 
    .Example
    Reset-ApiPassword -identityUrl "https://server/identity" -apiName "TestAPI" -accessToken "eyJhbGciO"
#>

function Reset-ApiPassword {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $apiName,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    [Uri]$url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/apiresource/$apiName/resetPassword"

    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }
    try {
        # Reset api secret
        $apiResponse = Invoke-RestMethod -Method Post -Uri $url -ContentType "application/json" -Headers $headers
        return $apiResponse.apiSecret
    }
    catch {
        $exception = $_.Exception
        $error = "Unknown error attempting to reset api"
        if ($null -ne $exception -and $null -ne $exception.Response) {
            $error = Get-ErrorFromResponse -response $exception.Response
        }
        throw ( New-Object -TypeName "System.Net.WebException" "There was an error resetting api password $apiName with Fabric.Identity: $error, Resetting api password failure.", $exception)
    }
}

<#
    .Synopsis
    Attempts to get an api object registration
 
    .Description
    Takes in the identity server url an apiname and an access token.
 
    .Parameter identityUrl
    The identity url to get the api object
 
    .Parameter apiName
    The name of the api
 
    .Parameter accessToken
    An access token previously retrieved from the identity server
 
    .Example
    Test-IsApiRegistered -identityUrl "https://server/identity" -apiName "TestAPI" -accessToken "eyJhbGciO"
#>

function Test-IsApiRegistered {
    param(
        [Parameter(Mandatory = $True)] [Uri] $identityUrl,
        [Parameter(Mandatory = $True)] [string] $apiName,
        [Parameter(Mandatory = $True)] [string] $accessToken
    )

    $apiExists = $false
    $url = "$($identityUrl.OriginalString.TrimEnd("/"))/api/apiresource/$apiName"

    $headers = @{"Accept" = "application/json"}
    if ($accessToken) {
        $headers.Add("Authorization", "Bearer $accessToken")
    }

    try {
        $getResponse = Invoke-RestMethod -Method Get -Uri $url -ContentType "application/json" -Headers $headers
        $apiExists = $true
        return $apiExists
    }
    catch {
        $exception = $_.Exception
        if ((Assert-WebExceptionType -exception $exception -typeCode 404)) {
            try {
                return $false
            }
            catch {
                $error = "Unknown error looking for api"
                $exception = $_.Exception
                if ($null -ne $exception -and $null -ne $exception.Response) {
                    $error = Get-ErrorFromResponse -response $exception.Response
                }
                throw (New-Object -TypeName "System.Net.WebException" "There was an error looking for api $($apiResourceObject.name): $error. Registration lookup failure.", $exception)
            }
        }
        else {
            $error = "Unknown error looking for api"
            $exception = $_.Exception
            if ($null -ne $exception -and $null -ne $exception.Response) {
                $error = Get-ErrorFromResponse -response $exception.Response
            }
            throw ( New-Object -TypeName "System.Net.WebException" "There was an error looking for api $($apiResourceObject.name) with Fabric.Identity: $error, Registration lookup failure.", $exception)
        }
    }
}

<#
    .Synopsis
    checks the exception for type
 
    .Description
    checks in the exception for the typeCode
    Abstracted to facilitate exception inspection
    returns either $true or $false
 
    .Parameter exception
    an exception or object of some kind thrown
 
    .Parameter typeCode
    an exception code to find
 
    .Example
    Assert-WebExceptionType -exception $exception -typeCode 404
#>

function Assert-WebExceptionType( $exception, $typeCode) {
    if ($null -ne $exception -and $exception.Response.StatusCode.value__ -eq $typeCode) {
        return $true
    }
    else {
        return $false
    }
}

<#
    .Synopsis
    INTERNAL: function to extract exception response messages
 
    .Description
    extracts the exception message if it exists
    Abstracted to facilitate exception inspection
    returns the response body (either an object or a string)
 
    .Parameter response
    the exception response
 
    .Example
    Get-ErrorFromResponse -response $exception.response
#>

function Get-ErrorFromResponse($response) {
    $result = $response.GetResponseStream()
    $reader = New-Object System.IO.StreamReader($result)
    $reader.BaseStream.Position = 0
    $reader.DiscardBufferedData()
    $responseBody = $reader.ReadToEnd();
    return $responseBody
}

Export-ModuleMember -Function Get-AccessToken
Export-ModuleMember -Function Get-FabricInstallerAccessToken
Export-ModuleMember -Function Get-ClientRegistration
Export-ModuleMember -Function New-ClientRegistration
Export-ModuleMember -Function New-ClientCredentialsClientBody
Export-ModuleMember -Function New-ImplicitClientBody
Export-ModuleMember -Function New-HybridClientBody
Export-ModuleMember -Function New-HybridPkceClientBody
Export-ModuleMember -Function Edit-ClientRegistration
Export-ModuleMember -Function Reset-ClientPassword
Export-ModuleMember -Function Test-IsClientRegistered
Export-ModuleMember -Function Get-ApiRegistration
Export-ModuleMember -Function New-ApiRegistration
Export-ModuleMember -Function New-ApiRegistrationBody
Export-ModuleMember -Function Remove-ApiRegistration
Export-ModuleMember -Function Edit-ApiRegistration
Export-ModuleMember -Function Reset-ApiPassword
Export-ModuleMember -Function Test-IsApiRegistered
Export-ModuleMember -Function Assert-WebExceptionType
# SIG # Begin signature block
# MIIcRgYJKoZIhvcNAQcCoIIcNzCCHDMCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA8iMg2rXmZJ3iY
# JB9nRcJnN1DBfaUrqq2kU/OWL6jINKCCCqAwggUwMIIEGKADAgECAhAECRgbX9W7
# ZnVTQ7VvlVAIMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV
# BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0xMzEwMjIxMjAwMDBa
# Fw0yODEwMjIxMjAwMDBaMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy
# dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lD
# ZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQD407Mcfw4Rr2d3B9MLMUkZz9D7RZmxOttE9X/l
# qJ3bMtdx6nadBS63j/qSQ8Cl+YnUNxnXtqrwnIal2CWsDnkoOn7p0WfTxvspJ8fT
# eyOU5JEjlpB3gvmhhCNmElQzUHSxKCa7JGnCwlLyFGeKiUXULaGj6YgsIJWuHEqH
# CN8M9eJNYBi+qsSyrnAxZjNxPqxwoqvOf+l8y5Kh5TsxHM/q8grkV7tKtel05iv+
# bMt+dDk2DZDv5LVOpKnqagqrhPOsZ061xPeM0SAlI+sIZD5SlsHyDxL0xY4PwaLo
# LFH3c7y9hbFig3NBggfkOItqcyDQD2RzPJ6fpjOp/RnfJZPRAgMBAAGjggHNMIIB
# yTASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK
# BggrBgEFBQcDAzB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9v
# Y3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGln
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHow
# eDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJl
# ZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0Rp
# Z2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwA
# AgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAK
# BghghkgBhv1sAzAdBgNVHQ4EFgQUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHwYDVR0j
# BBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQELBQADggEBAD7s
# DVoks/Mi0RXILHwlKXaoHV0cLToaxO8wYdd+C2D9wz0PxK+L/e8q3yBVN7Dh9tGS
# dQ9RtG6ljlriXiSBThCk7j9xjmMOE0ut119EefM2FAaK95xGTlz/kLEbBw6RFfu6
# r7VRwo0kriTGxycqoSkoGjpxKAI8LpGjwCUR4pwUR6F6aGivm6dcIFzZcbEMj7uo
# +MUSaJ/PQMtARKUT8OZkDCUIQjKyNookAv4vcn4c10lFluhZHen6dGRrsutmQ9qz
# sIzV6Q3d9gEgzpkxYz0IGhizgZtPxpMQBvwHgfqL2vmCSfdibqFT+hKUGIUukpHq
# aGxEMrJmoecYpJpkUe8wggVoMIIEUKADAgECAhAKRecO+XBAYPQ5XoaaebXrMA0G
# CSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0
# IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcNMTcwNDEzMDAwMDAw
# WhcNMjAwNDE1MTIwMDAwWjCBpDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcw
# FQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVSGVhbHRoIENhdGFseXN0
# LCBJbmMuMR4wHAYDVQQDExVIZWFsdGggQ2F0YWx5c3QsIEluYy4xLzAtBgkqhkiG
# 9w0BCQEWIGFkbWluaXN0cmF0b3JAaGVhbHRoY2F0YWx5c3QuY29tMIIBIjANBgkq
# hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv8AEfB5imOv8J17fvW8w+WKuE0keRub9
# 1+QzkiI+nSa9y2yADr/ZCEXqxGqDKdg47CjlvpOmKg8K88NPaTPvGN5fm7p7avmn
# Cfp7IGXLGtutZ1RnFW2fYC8+kl86WinKVQ7eHLe7Rsvn9CyurIzttJpJcTikxqrr
# U45yE8Iw/H9ziiwP+grfm8AiGN3C2vuxbhs8YwG2pbbn2aa5hN5q4bbFzoQ4xHGO
# kFiqhRYVyGbVZNeoGTpkf/DNXJh07RuSDdcFXoh7whwwvfXhrk9Z5YzE6GEk2CUF
# adTjqWHuGyfpBpY7bYZ8/mbDTmUqLNeGsTQrVmowv4r+usyK6lz6LwIDAQABo4IB
# xTCCAcEwHwYDVR0jBBgwFoAUWsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYE
# FDCXth9LjWUWNRWEPkEw5VZAVdBSMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAK
# BggrBgEFBQcDAzB3BgNVHR8EcDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2Vy
# dC5jb20vc2hhMi1hc3N1cmVkLWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQu
# ZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3
# BglghkgBhv1sAwEwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
# Y29tL0NQUzAIBgZngQwBBAEwgYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25p
# bmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAkIewxl/k
# WdhH2w7hIW0jT2WXhasjLk/UVeJtON2V7uj6J5/geg9huBlF9UDASBN9Po3sULeE
# /WQ+Lxbd3BDLq+jcENPKdEE7v9NFOCzs142tBJ+tng5uSD4KCG7wStTggI8XElpu
# 0uraecK21bq4T4A2uGXpruEVNdS8DkANh34AwLJWanhaavbqunHZMkjQU0oluktS
# ikJ1BVeyROM0Xh11VBnM5nSftS4c8eC66ZXhsuc268wwzwb3eD81jKwXdli3SrvT
# zFKtAFqzh2/1bVIceq+iT7zketpGuFTg3BOkhbiJhIEjAS9pA3v+tVKrWcdTp/HC
# mT2XH0Xyeg2GhzGCEPwwghD4AgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV
# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0ECEApF
# 5w75cEBg9Dlehpp5teswDQYJYIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIw
# ADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYK
# KwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg3PMNWHRM1nmKfMukWNu9m3o/W0wr
# L3tvB9FAp+N3c38wDQYJKoZIhvcNAQEBBQAEggEApr3IT7KOarJr0Zz9VUZoHFrC
# UzHsTToSbrirR0Weqrp5/OQaxaQaC0/zfzi6rNhA35lxEzMW5tKUd/4Tcgfe2U3h
# 3i/0DpaI9T/Ret1RbeNQslMtpFp9TeRhDpOcZJvPcud/ME6ZO2+0ByDkuFuekIbt
# OSN3cocq5FA1vOZg3RYMNJqcgfBT2qyYNTNklFJ5Dvs4SRmoWpmtsBAXn9497tEm
# 0oJ5TjBV0oNc9E7rF6D060OtmaIdqx+j1I7ilqesPrAl+n2jbG0Fg0aDDS9iyqQV
# Il3BOwLc+wBqJkOBjVlFVqb8dTC8ppKL1sgQpjv4FBfqeIK4RnWIHLXY2Y86X6GC
# Dsgwgg7EBgorBgEEAYI3AwMBMYIOtDCCDrAGCSqGSIb3DQEHAqCCDqEwgg6dAgED
# MQ8wDQYJYIZIAWUDBAIBBQAwdwYLKoZIhvcNAQkQAQSgaARmMGQCAQEGCWCGSAGG
# /WwHATAxMA0GCWCGSAFlAwQCAQUABCBX6Lnwt8JD8HinEV/moh67L6+UR3Xz54rW
# GPdR7AUnwgIQDnkCqVCeXGzsXt8DBfrwBBgPMjAxODA3MjAxNTM0MjBaoIILuzCC
# BoIwggVqoAMCAQICEAnA/EbIBEITtVmLryhPTkEwDQYJKoZIhvcNAQELBQAwcjEL
# MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
# LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElE
# IFRpbWVzdGFtcGluZyBDQTAeFw0xNzAxMDQwMDAwMDBaFw0yODAxMTgwMDAwMDBa
# MEwxCzAJBgNVBAYTAlVTMREwDwYDVQQKEwhEaWdpQ2VydDEqMCgGA1UEAxMhRGln
# aUNlcnQgU0hBMiBUaW1lc3RhbXAgUmVzcG9uZGVyMIIBIjANBgkqhkiG9w0BAQEF
# AAOCAQ8AMIIBCgKCAQEAnpWYajQ7cxuofvzHvilpicdoJkZfPY1ic4eBo6Gc8Ldb
# JDdaktT0Wdd2ieTc1Sfw1Wa8Cu60KzFnrFjFSpFZK0UeCQHWZLNZ7o1mTfsjXswQ
# DQuKZ+9SrqAIkMJS9/WotW6bLHud57U++3jNMlAYv0C1TIy7V/SgTxFFbEJCueWv
# 1t/0p3wKaJYP0l8pV877HTL/9BGhEyL7Esvv11PS65fLoqwbHZ1YIVGCwsLe6is/
# LCKE0EPsOzs/R8T2VtxFN5i0a3S1Wa94V2nIDwkCeN3YU8GZ22DEnequr+B+hkpc
# qVhhqF50igEoaHJOp4adtQJSh3BmSNOO74EkzNzYZQIDAQABo4IDODCCAzQwDgYD
# VR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUH
# AwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCGSAGG/WwHATCCAZIwKAYIKwYBBQUH
# AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwggFkBggrBgEFBQcCAjCC
# AVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0AGgAaQBzACAAQwBlAHIAdABp
# AGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1AHQAZQBzACAAYQBjAGMAZQBw
# AHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABpAGcAaQBDAGUAcgB0ACAAQwBQ
# AC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBlAGwAeQBpAG4AZwAgAFAAYQBy
# AHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBoAGkAYwBoACAAbABpAG0AaQB0
# ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAgAGEAcgBlACAAaQBuAGMAbwBy
# AHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAgAGIAeQAgAHIAZQBmAGUAcgBl
# AG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1UdIwQYMBaAFPS24SAd/imu0uRhpbKi
# JbLIFzVuMB0GA1UdDgQWBBThpzJK7gEhKH1U1fIHkm60Bw89hzBxBgNVHR8EajBo
# MDKgMKAuhixodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLXRz
# LmNybDAyoDCgLoYsaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJl
# ZC10cy5jcmwwgYUGCCsGAQUFBwEBBHkwdzAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tME8GCCsGAQUFBzAChkNodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRTSEEyQXNzdXJlZElEVGltZXN0YW1waW5nQ0EuY3J0
# MA0GCSqGSIb3DQEBCwUAA4IBAQAe8EGCMq7t8bQ1E9xQwtWXriIinQ4OrzPTTP18
# v28BEaeUZSJcxiKhyIlSa5qMc1zZXj8y3hZgTIs2/TGZCr3BhLeNHe+JJhMFVvNH
# zUdbrYSyOK9qI7VF4x6IMkaA0remmSL9wXjP9YvYDIwFCe5E5oDVbXDMn1MeJ90q
# SN7ak2WtbmWjmafCQA5zzFhPj0Uo5byciOYozmBdLSVdi3MupQ1bUdqaTv9QBYko
# 2vJ4u9JYeI1Ep6w6AJF4aYlkBNNdlt8qv/mlTCyT/+aK3YKs8dKzooaawVWJVmpH
# P/rWM5VDNYkFeFo6adoiuARD029oNTZ6FD5F6Zhkhg8TDCZKMIIFMTCCBBmgAwIB
# AgIQCqEl1tYyG35B5AXaNpfCFTANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJV
# UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
# Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTYw
# MTA3MTIwMDAwWhcNMzEwMTA3MTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UE
# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYD
# VQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvdAy7kvNj3/dqbqCmcU5VChX
# tiNKxA4HRTNREH3Q+X1NaH7ntqD0jbOI5Je/YyGQmL8TvFfTw+F+CNZqFAA49y4e
# O+7MpvYyWf5fZT/gm+vjRkcGGlV+Cyd+wKL1oODeIj8O/36V+/OjuiI+GKwR5PCZ
# A207hXwJ0+5dyJoLVOOoCXFr4M8iEA91z3FyTgqt30A6XLdR4aF5FMZNJCMwXbzs
# PGBqrC8HzP3w6kfZiFBe/WZuVmEnKYmEUeaC50ZQ/ZQqLKfkdT66mA+Ef58xFNat
# 1fJky3seBdCEGXIX8RcG7z3N1k3vBkL9olMqT4UdxB08r8/arBD13ays6Vb/kwID
# AQABo4IBzjCCAcowHQYDVR0OBBYEFPS24SAd/imu0uRhpbKiJbLIFzVuMB8GA1Ud
# IwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMBIGA1UdEwEB/wQIMAYBAf8CAQAw
# DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHkGCCsGAQUFBwEB
# BG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsG
# AQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1
# cmVkSURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5k
# aWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRo
# dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0Eu
# Y3JsMFAGA1UdIARJMEcwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRw
# czovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAsGCWCGSAGG/WwHATANBgkqhkiG9w0B
# AQsFAAOCAQEAcZUS6VGHVmnN793afKpjerN4zwY3QITvS4S/ys8DAv3Fp8MOIEIs
# r3fzKx8MIVoqtwU0HWqumfgnoma/Capg33akOpMP+LLR2HwZYuhegiUexLoceywh
# 4tZbLBQ1QwRostt1AuByx5jWPGTlH0gQGF+JOGFNYkYkh2OMkVIsrymJ5Xgf1gsU
# pYDXEkdws3XVk4WTfraSZ/tTYYmo9WuWwPRYaQ18yAGxuSh1t5ljhSKMYcp5lH5Z
# /IwP42+1ASa2bKXuh1Eh5Fhgm7oMLSttosR+u8QlK0cCCHxJrhO24XxCQijGGFbP
# QTS2Zl22dHv1VjMiLyI2skuiSpXY9aaOUjGCAk0wggJJAgEBMIGGMHIxCzAJBgNV
# BAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdp
# Y2VydC5jb20xMTAvBgNVBAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBUaW1l
# c3RhbXBpbmcgQ0ECEAnA/EbIBEITtVmLryhPTkEwDQYJYIZIAWUDBAIBBQCggZgw
# GgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0xODA3
# MjAxNTM0MjBaMC8GCSqGSIb3DQEJBDEiBCBqFNYxqXS0553IHce0ZSI9heO1mNiP
# hWAq+W2MYtDmBjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBRAAZFHXJiJHeuhBK9H
# CRtettTLyzANBgkqhkiG9w0BAQEFAASCAQBsTwSzxWsmq7PQ5sSv9wmuYbeZReYY
# y0aHasWGpcIdxkaUcpxtmGQ8Vym3WmCrEYaipze2hEGbh8tB7nluHgNEO47N2Jls
# F4j58CHVddnZRga8BCrzzDRUmvy5l93EnDkwoybzk2arYk9c6qS4zN6maxk+Cpnw
# gDSpDUK6IzqQKhbmUF88NpBHyyk4gIZ/be0YXEaRHNSiX/5bSrnBMmjnAYf4scBl
# h7yaIZJw+4PldSN9aSgUzHDYZptYsP7zj4C3EZM0mPVaaupTpFSj88GTWJk761OU
# 2HlJ+7zVfPel/EuxXPLKSkpXHg0rXFJghUrgUX+qrIZv3qB/e26EjJPC
# SIG # End signature block