MicrosoftDocs / azure-docs

Open source documentation of Microsoft Azure
https://docs.microsoft.com/azure
Creative Commons Attribution 4.0 International
10.24k stars 21.4k forks source link

Upload AD B2C custom policies PowerShell script combined with the AD B2C VS Code extension environment variables #107093

Closed Jeroen-VdB closed 1 year ago

Jeroen-VdB commented 1 year ago

I extended the upload PowerShell script to work with the environment appsettings.json file from the AD B2C VS Code extension.

I based the environment variable replacing on the PolicyBuild.ts file from the extension repo.

[Cmdletbinding()]
Param(
    [Parameter(Mandatory = $true)][string]$ClientID,
    [Parameter(Mandatory = $true)][string]$ClientSecret,
    [Parameter(Mandatory = $true)][string]$Folder,
    [Parameter(Mandatory = $true)][string]$Environment
)

try {
    # Check if settings file exists
    $settingsPath = $Folder + 'appsettings.json'
    $settingsFileExists = Test-Path -Path $settingsPath -PathType Leaf

    if ($settingsFileExists) {
        $settingsList = Get-Content $settingsPath -Raw | ConvertFrom-Json
        $settings = $settingsList.Environments | Where-Object -FilterScript {$_.Name -eq $Environment}
        $TenantName = $settings.Tenant
        $TenantId = $settings.PolicySettings.TenantId
        Write-Host "Initializing custom policy upload for tenant" $TenantName "(tenantId:" $TenantId ")"

        $body = @{grant_type = "client_credentials"; scope = "https://graph.microsoft.com/.default"; client_id = $ClientID; client_secret = $ClientSecret }

        $response = Invoke-RestMethod -Uri https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token -Method Post -Body $body
        $token = $response.access_token

        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("Content-Type", 'application/xml')
        $headers.Add("Authorization", 'Bearer ' + $token)

        # Get the list of files to upload
        $filesArray = Get-ChildItem -Path $Folder *.xml -Name

        Foreach ($file in $filesArray) {

            $filePath = $Folder + $file.Trim()

            # Check if file exists
            $FileExists = Test-Path -Path $filePath -PathType Leaf

            if ($FileExists) {
                $policycontent = Get-Content $filePath -Encoding UTF8

                # Get the policy name from the XML document
                $match = Select-String -InputObject $policycontent  -Pattern '(?<=\bPolicyId=")[^"]*'

                If ($match.matches.groups.count -ge 1) {
                    $PolicyId = $match.matches.groups[0].value

                    # Replace template variables from appsettings.json.
                    # SOURCE: https://github.com/azure-ad-b2c/vscode-extension/blob/master/src/PolicyBuild.ts
                    Write-Host "Replacing" $Environment "template variables for" $PolicyId "policy..."

                    $policycontent = $policycontent.Replace("{Settings:Tenant}", $settings.Tenant)
                    $policycontent = $policycontent.Replace("{Settings:Environment}", $settings.Name)

                    $settings.PolicySettings.PSObject.Properties| ForEach-Object {
                        $settingsName = $_.Name
                        $policycontent = $policycontent.Replace("{Settings:$settingsName}", $_.Value)
                    }

                    Write-Host "Replaced" $Environment "template variables for" $PolicyId "policy."

                    # Upload the policy
                    Write-Host "Uploading the" $PolicyId "policy..."

                    $graphuri = 'https://graph.microsoft.com/beta/trustframework/policies/' + $PolicyId + '/$value'
                    $content = [System.Text.Encoding]::UTF8.GetBytes($policycontent)
                    $response = Invoke-RestMethod -Uri $graphuri -Method Put -Body $content -Headers $headers -ContentType "application/xml; charset=utf-8"

                    Write-Host "Policy" $PolicyId "uploaded successfully."
                }
            }
            else {
                $warning = "File " + $filePath + " couldn't be not found."
                Write-Warning -Message $warning
            }
        }
    }
    else {
        $warning = "File " + $settingsPath + " couldn't be not found."
        Write-Warning -Message $warning
    }
}
catch {
    Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__

    $_

    $streamReader = [System.IO.StreamReader]::new($_.Exception.Response.GetResponseStream())
    $streamReader.BaseStream.Position = 0
    $streamReader.DiscardBufferedData()
    $errResp = $streamReader.ReadToEnd()
    $streamReader.Close()

    $ErrResp

    exit 1
}

exit 0

An example for the content of appsettings.json:

{
    "PoliciesFolder": ".",
    "EnvironmentsFolder": "environments",
    "Environments": [
        {
            "Name": "dev",
            "Production": false,
            "Tenant": "mydevtenant.onmicrosoft.com",
            "PolicySettings" : {
                "TenantId": "12345",
                "B2CExtensionAppClientId": 12345",
                "B2CExtensionAppObjectId": "12345",
                "AppInsightsInstrumentationKey": "1234",
                "DeploymentMode": "Development",
                "DeveloperMode": "true"
            }
        }
    ]
}

It will replace defined variables in a custom policy, like:

<?xml version="1.0" encoding="utf-8" ?>
<TrustFrameworkPolicy 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns="http://schemas.microsoft.com/online/cpim/schemas/2013/06" 
  PolicySchemaVersion="0.3.0.0" 
  TenantId="{Settings:Tenant}" 
  PolicyId="B2C_1A_TrustFrameworkExtensions" 
  PublicPolicyUri="http://{Settings:Tenant}/B2C_1A_TrustFrameworkExtensions"
  DeploymentMode="{Settings:DeploymentMode}">

  <BasePolicy>
    <TenantId>{Settings:Tenant}</TenantId>
    <PolicyId>B2C_1A_TrustFrameworkBase</PolicyId>
  </BasePolicy>
  <BuildingBlocks>

  </BuildingBlocks>

  <ClaimsProviders>

    <!-- CUSTOM: Identity Provider -->
    <ClaimsProvider>
      <DisplayName>MyCustomIdentityProvider</DisplayName>
      <TechnicalProfiles>
        <TechnicalProfile Id="Custom-OIDC">
          <DisplayName>MyCustomIdentityProvider</DisplayName>
          <Protocol Name="OpenIdConnect" />
          <Metadata>
            <Item Key="METADATA">{Settings:MyCustomIdentityProviderMetaData}</Item>
            <Item Key="client_id">{Settings:MyCustomIdentityProviderClientId}</Item>
            <Item Key="response_types">code</Item>
            <Item Key="response_mode">query</Item>
            <Item Key="UsePolicyInRedirectUri">false</Item>
            <Item Key="HttpBinding">POST</Item>
          </Metadata>
...

This way you can use the PowerShell script in an Azure Pipeline as follows:

- pwsh: $(System.DefaultWorkingDirectory)/UploadB2CCustomPolicies.ps1 -ClientID $(clientId) -ClientSecret $(clientSecret) -Folder $(System.DefaultWorkingDirectory)/ -Environment $(environment)
- displayName: Upload B2C custom policies

Just wanted to share this code. Hope this might be of any use. Cheers!


Document Details

Do not edit this section. It is required for learn.microsoft.com ➟ GitHub issue linking.

Naveenommi-MSFT commented 1 year ago

@Jeroen-VdB Thanks for your feedback! We will investigate and update as appropriate.

Naveenommi-MSFT commented 1 year ago

Hi @Jeroen-VdB Thanks for your contribution. Please add your ideas in below link, so our production team can review it and update the same. Ideas · Community (azure.com)