diff --git a/samples/smartonfhir/docs/ad-apps/fhir-resource-app-registration.md b/samples/smartonfhir/docs/ad-apps/fhir-resource-app-registration.md index be14458d..9a05c8b8 100644 --- a/samples/smartonfhir/docs/ad-apps/fhir-resource-app-registration.md +++ b/samples/smartonfhir/docs/ad-apps/fhir-resource-app-registration.md @@ -4,25 +4,28 @@ This application registration is used to customize the access token sent to the ## Deployment (manual) +1. Find your Primary Domain in Azure Active Directory + - Open Azure AD in the Azure Portal + - Note your `Primary Domain` in the Overview blade of Azure AD. 1. Create a FHIR Resource Application Registration - Go to `App Registrations` - Create a new application. It's easiest if this matches the name of your Azure Developer CLI environment. - Click `Register` (ignore redirect URI). +1. Set the application URL + - Go to `Expose an API` blade. + - Set the application URL to https://.. + - For example `https://my-app-1.mytenant.onmicrosoft.com`. + - Save the `Application URL` for later. +1. Add all the applicable FHIR Scopes. + - Go to the Manifest blade for your application. + - Change `acceptMappedClaims` from null to true. + - Copy the `appRoles` JSON element from [fhir-app-manifest.json](./fhir-app-manifest.json) to the `appRoles` JSON element in your application manifest. + - Copy the `oauth2Permissions` JSON element from [fhir-app-manifest.json](./fhir-app-manifest.json) to the `oauth2Permissions` JSON element in your application manifest. 1. Inform your Azure Developer CLI environment of this application with: ``` + azd env set FhirAudience azd env set FhirResourceAppId ``` -1. Run below command to configure a FHIR Resource Application Registration. - - Windows: - ```powershell - powershell ./scripts/Configure-FhirResourceAppRegistration.ps1 - ``` - - Mac/Linux - ```bash - pwsh ./scripts/Configure-FhirResourceAppRegistration.ps1 - ``` 1. Create a Microsoft Graph Directory Extension to hold the `fhirUser` information for users. Windows: diff --git a/samples/smartonfhir/docs/deployment.md b/samples/smartonfhir/docs/deployment.md index a0268102..f6985613 100644 --- a/samples/smartonfhir/docs/deployment.md +++ b/samples/smartonfhir/docs/deployment.md @@ -43,7 +43,11 @@ Next you will need to clone this repository and prepare your environment for dep azd env set ApiPublisherEmail "Your Email" ``` 1. Finally, deploy your environment by running azd. This command will provision infrastructure and deploy code. It will take about an hour. - - You will need to provide `subscription name` and `location` during exection of this command where all the required resource will get deployed. + - During the execution of this command, you will need to select `subscription name` and `location` from the drop down to specify where all resources will get deployed. + - To create a new resource group for SMART on FHIR resources deployment, leave the `existingResourceGroupName` parameter blank; otherwise, enter the name of an existing resource group where you want to deploy all of your SMART on FHIR resources. + - If you want to create a new FHIR server instance, leave the `fhirid` parameter blank. Otherwise, provide the FHIR instance id of existing FHIR server instance in case you want to use existing FHIR server instance. + - To get the FHIR instance Id go to your fhir service and in left menu click on properties and Copy the Id field. + - Multiple SMART on FHIR apps can not be deployed in same resource group and it's expected to have FHIR server instance and SMART on FHIR resources deployed in same resource group. - You can continue the setup below. ``` azd up diff --git a/samples/smartonfhir/infra/core/fhir.bicep b/samples/smartonfhir/infra/core/fhir.bicep index acd64fbd..5bc630bd 100644 --- a/samples/smartonfhir/infra/core/fhir.bicep +++ b/samples/smartonfhir/infra/core/fhir.bicep @@ -6,6 +6,7 @@ param tenantId string param location string param audience string = '' param appTags object = {} +param fhirInstanceResourceGroup string var loginURL = environment().authentication.loginEndpoint var authority = '${loginURL}${tenantId}' @@ -19,6 +20,7 @@ resource healthWorkspace 'Microsoft.HealthcareApis/workspaces@2021-06-01-preview resource healthWorkspaceExisting 'Microsoft.HealthcareApis/workspaces@2021-06-01-preview' existing = if (!createWorkspace) { name: workspaceName + scope: resourceGroup(fhirInstanceResourceGroup) } var newOrExistingWorkspaceName = createWorkspace ? healthWorkspace.name : healthWorkspaceExisting.name @@ -44,6 +46,7 @@ resource fhir 'Microsoft.HealthcareApis/workspaces/fhirservices@2021-06-01-previ resource fhirExisting 'Microsoft.HealthcareApis/workspaces/fhirservices@2021-06-01-preview' existing = if (!createFhirService) { name: '${newOrExistingWorkspaceName}/${fhirServiceName}' + scope: resourceGroup(fhirInstanceResourceGroup) } output fhirId string = createFhirService ? fhir.id : fhirExisting.id diff --git a/samples/smartonfhir/infra/main.bicep b/samples/smartonfhir/infra/main.bicep index 015083eb..a377575d 100644 --- a/samples/smartonfhir/infra/main.bicep +++ b/samples/smartonfhir/infra/main.bicep @@ -36,21 +36,15 @@ param FhirAudience string // start optional configuration parameters -@description('Do you want to create a new Azure Health Data Services workspace or use an existing one?') -param createWorkspace bool = true - -@description('Do you want to create a new FHIR Service or use an existing one?') -param createFhirService bool = true - -@description('Name of Azure Health Data Services workspace to deploy or use. Leave blank for default.') -param workspaceName string = '' - -@description('Name of the FHIR service to deloy or use. Leave blank for default.') -param fhirServiceName string = '' +@description('Name of your existing resource group (leave blank to create a new one)') +param existingResourceGroupName string @description('Name of the Log Analytics workspace to deploy or use. Leave blank to skip deployment') param logAnalyticsName string = '' +@description('Id of the FHIR Service to load resources into. (Get it from your fhir service properties)') +param fhirid string + // end optional configuration parameters var nameClean = replace(name, '-', '') @@ -66,24 +60,35 @@ var tenantId = subscription().tenantId // Add any extra principals that need to be able to access the Key Vault var fhirSMARTPrincipals = [] var fhirContributorPrincipals = [ principalId ] - +var createResourceGroup = empty(existingResourceGroupName) ? true : false +var createWorkspace = empty(fhirid) ? true : false +var createFhirService = empty(fhirid) ? true : false @description('Resource group to deploy sample in.') -resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { +resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = if (createResourceGroup) { name: '${name}-rg' location: location tags: appTags } -var workspaceNameResolved = length(workspaceName) > 0 ? workspaceName : '${replace(nameCleanShort, '-', '')}health' -var fhirNameResolved = length(fhirServiceName) > 0 ? workspaceName : 'fhirdata' +resource existingResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' existing = if (!createResourceGroup) { + name: existingResourceGroupName +} + var fhirUrl = 'https://${workspaceNameResolved}-${fhirNameResolved}.fhir.azurehealthcareapis.com' +var newOrExistingResourceGroupName = createResourceGroup ? rg.name : existingResourceGroup.name +var fhirResourceIdSplit = split(fhirid,'/') +var fhirserviceRg = empty(fhirid) ? '' : fhirResourceIdSplit[4] +var workspaceNameResolved = empty(fhirid) ? '${replace(nameCleanShort, '-', '')}health' : fhirResourceIdSplit[8] +var fhirNameResolved = empty(fhirid) ? 'fhirdata' : fhirResourceIdSplit[10] +var fhirInstanceResourceGroup = empty(fhirid) ? newOrExistingResourceGroupName : fhirserviceRg @description('Deploy Azure Health Data Services and FHIR service') module fhir 'core/fhir.bicep'= { name: 'azure-health-data-services' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { + fhirInstanceResourceGroup: fhirInstanceResourceGroup createWorkspace: createWorkspace createFhirService: createFhirService workspaceName: workspaceNameResolved @@ -103,7 +108,7 @@ var logAnalyticsNameResolved = length(logAnalyticsName) > 0 ? logAnalyticsName : @description('Deploy monitoring and logging') module monitoring 'core/monitoring.bicep'= { name: 'monitoringDeploy' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { logAnalyticsName: logAnalyticsNameResolved appInsightsName: appInsightsName @@ -115,7 +120,7 @@ module monitoring 'core/monitoring.bicep'= { @description('Deploy base resources needed for function app based custoom operations.') module functionBase 'core/functionHost.bicep' = { name: 'functionBaseDeploy' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { appTags: appTags location: location @@ -127,7 +132,7 @@ module functionBase 'core/functionHost.bicep' = { @description('Deploy Redis Cache for use as External Cache for APIM') module redis './core/redisCache.bicep'= { name: 'redisCacheDeploy' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { apiManagementServiceName: apimName location: location @@ -137,7 +142,7 @@ module redis './core/redisCache.bicep'= { @description('Azure Health Data Services Toolkit auth custom operation function app') module authCustomOperation './app/authCustomOperation.bicep' = { name: 'authCustomOperationDeploy' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { name: name location: location @@ -158,9 +163,9 @@ module authCustomOperation './app/authCustomOperation.bicep' = { } @description('Setup identity connection between FHIR and the given contributors') -module fhirContributorIdentities './core/identity.bicep' = [for principalId in fhirContributorPrincipals: { +module fhirContributorIdentities './core/identity.bicep' = [for principalId in fhirContributorPrincipals: if(createResourceGroup && empty(fhirid)) { name: 'fhirIdentity-${principalId}-fhirContrib' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { fhirId: fhir.outputs.fhirId principalId: principalId @@ -172,7 +177,7 @@ module fhirContributorIdentities './core/identity.bicep' = [for principalId in @description('Setup identity connection between FHIR and the given SMART users') module fhirSMARTIdentities './core/identity.bicep' = [for principalId in fhirSMARTPrincipals: { name: 'fhirIdentity-${principalId}-fhirSmart' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { fhirId: fhir.outputs.fhirId principalId: principalId @@ -186,7 +191,7 @@ var apimName = '${name}-apim' @description('Deploy Azure API Management for the FHIR gateway') module apim './core/apiManagement.bicep'= { name: 'apiManagementDeploy' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { apiManagementServiceName: apimName publisherEmail: ApiPublisherEmail @@ -202,7 +207,7 @@ module apim './core/apiManagement.bicep'= { @description('Link Redis Cache to APIM') module redisApimLink './core/apiManagement/redisExternalCache.bicep'= { name: 'apimRedisLinkDeploy' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { apiManagementServiceName: apimName redisApiVersion: redis.outputs.redisApiVersion @@ -215,7 +220,7 @@ var authorizeStaticWebAppName = '${name}-contextswa' @description('Static web app for SMART Context UI') module contextStaticWebApp './app/contextApp.bicep' = { name: 'staticWebAppDeploy' - scope: rg + scope: resourceGroup(newOrExistingResourceGroupName) params: { staticWebAppName: authorizeStaticWebAppName location: location @@ -234,10 +239,9 @@ output ExportStorageAccountUrl string = 'https://${functionBase.outputs.storageA output ApiManagementHostName string = apim.outputs.apimHostName output ContextAppClientId string = ContextAppClientId output CacheConnectionString string = authCustomOperation.outputs.cacheConnectionString - output AzureAuthCustomOperationManagedIdentityId string = authCustomOperation.outputs.functionAppPrincipalId - output REACT_APP_AAD_APP_CLIENT_ID string = ContextAppClientId output REACT_APP_AAD_APP_TENANT_ID string = tenantId output REACT_APP_API_BASE_URL string = 'https://${apim.outputs.apimHostName}' output REACT_APP_FHIR_RESOURCE_AUDIENCE string = FhirAudience +output AZURE_RESOURCE_GROUP string = newOrExistingResourceGroupName diff --git a/samples/smartonfhir/infra/main.parameters.json b/samples/smartonfhir/infra/main.parameters.json index d9f67b7d..68b67536 100644 --- a/samples/smartonfhir/infra/main.parameters.json +++ b/samples/smartonfhir/infra/main.parameters.json @@ -1,27 +1,33 @@ { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "name": { - "value": "${AZURE_ENV_NAME}" - }, - "location": { - "value": "${AZURE_LOCATION}" - }, - "principalId": { - "value": "${AZURE_PRINCIPAL_ID}" - }, - "ApiPublisherName": { - "value": "${ApiPublisherName=Placeholder Name}" - }, - "ApiPublisherEmail": { - "value": "${ApiPublisherEmail=youremail@onmicrosoft.com}" - }, - "FhirAudience": { - "value": "${FhirAudience}" - }, - "ContextAppClientId": { - "value": "${ContextAppClientId}" - } + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "ApiPublisherEmail": { + "value": "${ApiPublisherEmail=youremail@onmicrosoft.com}" + }, + "ApiPublisherName": { + "value": "${ApiPublisherName=Placeholder Name}" + }, + "ContextAppClientId": { + "value": "${ContextAppClientId}" + }, + "existingresourcegroupname": { + "value": "" + }, + "FhirAudience": { + "value": "${FhirAudience}" + }, + "fhirId": { + "value": "" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "name": { + "value": "${AZURE_ENV_NAME}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" } + } } \ No newline at end of file diff --git a/samples/smartonfhir/scripts/manifest-json-contents/app-roles.json b/samples/smartonfhir/scripts/manifest-json-contents/app-roles.json index 3e4fae4d..f38c1572 100644 --- a/samples/smartonfhir/scripts/manifest-json-contents/app-roles.json +++ b/samples/smartonfhir/scripts/manifest-json-contents/app-roles.json @@ -7,7 +7,6 @@ "displayName": "user.all.all", "id": "5a03b38d-4081-4265-9558-aab5f2b18b8b", "isEnabled": true, - "lang": null, "origin": "Application", "value": "user.all.all" }, @@ -19,7 +18,6 @@ "displayName": "user.all.read", "id": "5a03b38d-4081-4265-9558-aab5f2a18b8b", "isEnabled": true, - "lang": null, "origin": "Application", "value": "user.all.read" }, @@ -31,7 +29,6 @@ "displayName": "system.all.read", "id": "446c1a33-8f34-43b3-855c-db21aab2889f", "isEnabled": true, - "lang": null, "origin": "Application", "value": "system.all.read" }