microsoft / botframework-sdk

Bot Framework provides the most comprehensive experience for building conversation applications.
MIT License
7.5k stars 2.45k forks source link

OAuth Provider Model for Azure ARM Template (Azure AD B2C) #6642

Closed jlind0 closed 7 months ago

jlind0 commented 7 months ago

So I am getting very close to being able to deploy my Botnet Framework "filtered channels" with Azure ARM Templates:

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
  "parameters": {
    "botServices_courseware_coach_name": {
      "defaultValue": "courseware-coach",
      "type": "String"
    },
    "userAssignedIdentities_courseware_coach_externalid": {
      "defaultValue": "/subscriptions/a13458f4-4794-420a-8c6f-991cd3965346/resourceGroups/courseware-coach/providers/Microsoft.ManagedIdentity/userAssignedIdentities/courseware-coach",
      "type": "String"
    },
    "botType": {
      "defaultValue": "courseware-coach",
      "type": "String"
    },
    "subject": {
      "defaultValue": "courseware-coach",
      "type": "String"
    },
    "appId": {
      "defaultValue": "courseware-coach",
      "type": "String"
    },
    "clientSecret": {
      "defaultValue": "courseware-coach",
      "type": "String"
    }
  },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.BotService/botServices",
            "apiVersion": "2023-09-15-preview",
            "name": "[parameters('botServices_courseware_coach_name')]",
            "location": "global",
            "sku": {
                "name": "S1"
            },
            "kind": "azurebot",
          "properties": {
            "displayName": "[parameters('botServices_courseware_coach_name')]",
            "iconUrl": "https://docs.botframework.com/static/devportal/client/images/bot-framework-default.png",
            "endpoint": "[concat('https://coursewarecoachbot.azurewebsites.net/api/messages?botType=', parameters('botType'), '&subject=', parameters('subject'))]",
            "msaAppId": "[parameters('appId')]",
            "msaAppTenantId": "88c25c7a-38aa-45d5-bd8d-e939dd68c4f2",
            "msaAppType": "UserAssignedMSI",
            "msaAppMSIResourceId": "[parameters('userAssignedIdentities_courseware_coach_externalid')]",
            "luisAppIds": [],
            "isStreamingSupported": true,
            "schemaTransformationVersion": "1.3",
            "tenantId": "88c25c7a-38aa-45d5-bd8d-e939dd68c4f2",
            "isCmekEnabled": false,
            "disableLocalAuth": false
          }
        },
        {
            "type": "Microsoft.BotService/botServices/channels",
            "apiVersion": "2023-09-15-preview",
            "name": "[concat(parameters('botServices_courseware_coach_name'), '/DirectLineChannel')]",
            "location": "global",
            "dependsOn": [
                "[resourceId('Microsoft.BotService/botServices', parameters('botServices_courseware_coach_name'))]"
            ],
            "properties": {
                "properties": {
                    "sites": [
                        {
                            "siteName": "Default Site",
                            "isEnabled": true,
                            "isV1Enabled": true,
                            "isV3Enabled": true,
                            "isSecureSiteEnabled": false,
                            "isBlockUserUploadEnabled": false
                        }
                    ],
                    "extensionKey1": "hbICYHvpMFw.bwCvSl9fiWBecIvmBDoYMaLKcA0vgOwg_S00vaPrVu8",
                    "extensionKey2": "hbICYHvpMFw.dXQW4hZrjdzDxN0jLGIoQUcLSahT4hy1lrKlrH8mkrU"
                },
                "etag": "W/\"2bb7433d23b1380c59ffbc6041efaf274/22/2024 9:34:35 AM\"",
                "channelName": "DirectLineChannel",
                "location": "global"
            }
        },
        {
            "type": "Microsoft.BotService/botServices/channels",
            "apiVersion": "2023-09-15-preview",
            "name": "[concat(parameters('botServices_courseware_coach_name'), '/WebChatChannel')]",
            "location": "global",
            "dependsOn": [
                "[resourceId('Microsoft.BotService/botServices', parameters('botServices_courseware_coach_name'))]"
            ],
            "properties": {
                "properties": {
                    "sites": [
                        {
                            "siteName": "Default Site",
                            "isEnabled": true,
                            "isWebchatPreviewEnabled": true,
                            "isBlockUserUploadEnabled": false
                        }
                    ]
                },
                "etag": "W/\"28160aeb414ff237c2653db978c8d58f4/22/2024 9:34:35 AM\"",
                "channelName": "WebChatChannel",
                "location": "global"
            }
        },
        {
            "type": "Microsoft.BotService/botServices/connections",
            "apiVersion": "2023-09-15-preview",
            "name": "[concat(parameters('botServices_courseware_coach_name'), '/Azure AD B2C')]",
            "location": "global",
            "dependsOn": [
                "[resourceId('Microsoft.BotService/botServices', parameters('botServices_courseware_coach_name'))]"
            ],
          "properties": {
            "serviceProviderDisplayName": "Azure Active Directory B2C",
            "id": "d86f045f-7ae7-8207-ddae-725dd8c2c35c_b7f05545-56ce-6cd4-e15a",
            "name": "Azure AD B2C",
            "clientId": "4a456ce3-bae5-4911-8042-4a17632644d1",
            "clientSecret": "[parameters('clientSecret')]"
          }
        }
    ]
}

Screenshot 2024-04-22 072015

The question I have is for those attributes what are the corresponding property names in the ARM template?

The following code deploys the ARM, however (obviously) the OAuth Provider is not configured properly.

public async Task<string?> DeployBot(string name, Guid subject, string botType = "coach", CancellationToken token = default)
{
    try
    {
        string clientId = Config["Portal:ClientId"] ?? throw new InvalidDataException();
        string clientSecret = Config["Portal:ClientSecret"] ?? throw new InvalidDataException();
        string tenantId = Config["Portal:TenantId"] ?? throw new InvalidDataException();
        string subscriptionId = Config["Portal:SubscriptionId"] ?? throw new InvalidDataException();
        string resourceGroupName = Config["Portal:ResourceGroupName"] ?? throw new InvalidDataException();
        string templateJson = File.ReadAllText("bot_template.json");
        string parametersJson = File.ReadAllText("bot_parameters.json");
        parametersJson = parametersJson.Replace("{{botType}}", botType);
        parametersJson = parametersJson.Replace("{{name}}", name);
        parametersJson = parametersJson.Replace("{{subject}}", subject.ToString());
        parametersJson = parametersJson.Replace("{{appId}}", Guid.NewGuid().ToString().ToLower());
        var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(clientId, clientSecret, tenantId, AzureEnvironment.AzureGlobalCloud);
        var azure = Microsoft.Azure.Management.Fluent.Azure.Configure().WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic).Authenticate(credentials).WithSubscription(subscriptionId);
        //var parameters = <Dictionary<string, Dictionary<string, object>>>(parametersJson, new JsonSerializerSettings());
        var deployment = await azure.Deployments.Define($"botDeployment{Guid.NewGuid()}")
            .WithExistingResourceGroup(resourceGroupName)
            .WithTemplate(templateJson)
            .WithParameters(JObject.Parse(parametersJson).GetValue("parameters"))
            .WithMode(DeploymentMode.Incremental)
            .CreateAsync();
        return deployment.CorrelationId;
    }
    catch(Exception ex)
    {
        Logger.LogError(ex, ex.Message);
        return null;
    }

}
tracyboehrer commented 7 months ago

@jlind0 While we use ARM templates ourselves, we are not the best source to get help from. We are consumers of it like yourself.