Merge pull request #11 from 1iveowl/main

New deployment paradigm extended to Permissions API, Admin-API and Sign-up Administration Web App
This commit is contained in:
1iveowl 2023-02-06 09:27:45 +01:00 коммит произвёл GitHub
Родитель 29c43b01b1 0ee0672db6
Коммит 58f4bcd4f7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
192 изменённых файлов: 4862 добавлений и 2811 удалений

67
.github/workflows/admin-service-api-deploy.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,67 @@
---
name: ASDK Administration Service API - Deploy to Azure Web Services
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
env:
AZURE_WEBAPP_NAME: 'admin-api-asdk-test-fd4k' # set this to your application's name
AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: 7.x.x
PROJECT_DIR: ./src/Saas.Admin/Saas.Admin.Service
PROJECT_PATH: ./src/Saas.Admin/Saas.Admin.Service/Saas.Admin.Service.csproj
OUTPUT_PATH: ./publish/admin-api
BUILD_CONFIGURATION: Release # setting the configuration manager build configuration value for our workflow.
APP_NAME: admin-api
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
#azure login
- uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# checkout the repo specifying the branch name in 'ref:'
- uses: actions/checkout@v3
with:
ref: main #IMPORTANT we're checking out and deploying the 'main' branch here
token: ${{ secrets.GITHUB_TOKEN }}
# Setup .NET Core SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
# Run dotnet build and publish
- name: dotnet build and publish
run: |
dotnet restore ${{ env.PROJECT_DIR }}
dotnet build ${{ env.PROJECT_PATH }} \
--configuration ${{ env.BUILD_CONFIGURATION }}
dotnet publish ${{ env.PROJECT_PATH }} \
--configuration ${{ env.BUILD_CONFIGURATION }} \
--output '${{ env.OUTPUT_PATH }}/{{ env.AZURE_WEBAPP_NAME }}'
# Deploy to Azure Web apps
- name: Run Azure webapp deploy action using publish profile credentials
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }} # Replace with your app name
package: ${{ env.OUTPUT_PATH }}/{{ env.AZURE_WEBAPP_NAME }}
# slot-name: 'PermissionsApi-Staging'
# Azure logout
- name: logout
run: |
az logout

12
.github/workflows/permissions-api-deploy.yml поставляемый
Просмотреть файл

@ -9,13 +9,15 @@ permissions:
contents: read
env:
AZURE_WEBAPP_NAME: 'api-permission-asdk-test-b3yf' # set this to your application's name
AZURE_WEBAPP_NAME: 'api-permission-asdk-test-fd4k' # set this to your application's name
AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: 7.x.x
PROJECT_DIR: ./src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1
PROJECT_PATH: ./src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Saas.Permissions.Service.csproj
OUTPUT_PATH: ./publish
OUTPUT_PATH: ./publish/api-permission
BUILD_CONFIGURATION: Release # setting the configuration manager build configuration value for our workflow.
APP_NAME: permissions-api
jobs:
build-and-deploy:
runs-on: ubuntu-latest
@ -30,7 +32,7 @@ jobs:
# checkout the repo specifying the branch name in 'ref:'
- uses: actions/checkout@v3
with:
ref: main #IMPORTAT we're checking out and deploying the 'main' branch here
ref: main #IMPORTANT we're checking out and deploying the 'main' branch here
token: ${{ secrets.GITHUB_TOKEN }}
# Setup .NET Core SDK
@ -45,10 +47,10 @@ jobs:
dotnet restore ${{ env.PROJECT_DIR }}
dotnet build ${{ env.PROJECT_PATH }} \
--configuration Release
--configuration ${{ env.BUILD_CONFIGURATION }}
dotnet publish ${{ env.PROJECT_PATH }} \
--configuration Release \
--configuration ${{ env.BUILD_CONFIGURATION }} \
--output '${{ env.OUTPUT_PATH }}/{{ env.AZURE_WEBAPP_NAME }}'
# Deploy to Azure Web apps

67
.github/workflows/signup-administration-deploy.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,67 @@
---
name: ASDK Sign-up Administration Web App - Deploy to Azure Web Services
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
env:
AZURE_WEBAPP_NAME: 'signupadmin-app-asdk-test-fd4k' # set this to your application's name
AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: 7.x.x
PROJECT_DIR: ./src/Saas.SignupAdministration/Saas.SignupAdministration.Web
PROJECT_PATH: ./src/Saas.SignupAdministration/Saas.SignupAdministration.Web/Saas.SignupAdministration.Web.csproj
OUTPUT_PATH: ./publish/signupadmin
BUILD_CONFIGURATION: Release # setting the configuration manager build configuration value for our workflow.
APP_NAME: signupadmin-app
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
#azure login
- uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# checkout the repo specifying the branch name in 'ref:'
- uses: actions/checkout@v3
with:
ref: main #IMPORTANT we're checking out and deploying the 'main' branch here
token: ${{ secrets.GITHUB_TOKEN }}
# Setup .NET Core SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
# Run dotnet build and publish
- name: dotnet build and publish
run: |
dotnet restore ${{ env.PROJECT_DIR }}
dotnet build ${{ env.PROJECT_PATH }} \
--configuration ${{ env.BUILD_CONFIGURATION }}
dotnet publish ${{ env.PROJECT_PATH }} \
--configuration ${{ env.BUILD_CONFIGURATION }} \
--output '${{ env.OUTPUT_PATH }}/{{ env.AZURE_WEBAPP_NAME }}'
# Deploy to Azure Web apps
- name: Run Azure webapp deploy action using publish profile credentials
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }} # Replace with your app name
package: ${{ env.OUTPUT_PATH }}/{{ env.AZURE_WEBAPP_NAME }}
# slot-name: 'PermissionsApi-Staging'
# Azure logout
- name: logout
run: |
az logout

Просмотреть файл

@ -1,78 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "src\TestUtilities\TestUtilities.csproj", "{02F6CFF4-70ED-4C9D-AF21-4EE3AD668AA4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.SignupAdministration.Web", "src\Saas.SignupAdministration\Saas.SignupAdministration.Web\Saas.SignupAdministration.Web.csproj", "{71694EE0-1D10-4DC3-B4EA-30C45A9FF31F}"
EndProject
Project("{151D2E53-A2C4-4D7D-83FE-D05416EBD58E}") = "Saas.SignupAdministration.Web.Deployment", "src\Saas.SignupAdministration\Saas.SignupAdministration.Web.Deployment\Saas.SignupAdministration.Web.Deployment.deployproj", "{6A0836ED-483E-4F7E-8248-AC00FB52E778}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Admin.Service.Tests", "src\Saas.Admin\Saas.Admin.Service.Tests\Saas.Admin.Service.Tests.csproj", "{7AAD364B-DEFB-4C66-AEBA-D0A10DBDE45B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Admin.Service", "src\Saas.Admin\Saas.Admin.Service\Saas.Admin.Service.csproj", "{B75AFCC1-A92D-4122-9FB2-6C478C67132F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7037F896-6D8A-49AD-964C-6A0FA9AE09DC}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Application.Web", "src\Saas.Application\Saas.Application.Web\Saas.Application.Web.csproj", "{29BBEAD7-1043-41EC-A89D-766E1402B701}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.AspNetCore.Authorization", "src\Saas.Authorization\Saas.AspNetCore.Authorization\Saas.AspNetCore.Authorization.csproj", "{6B9F75FC-4739-4CA1-9347-2F4092A794B1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Permissions.Service", "src\Saas.Identity\Saas.Permissions\Saas.Permissions.Service\Saas.Permissions.Service.csproj", "{E8F9B31E-E2E7-45F7-AE9B-68409630C82E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.AspNetCore.Authorization.Tests", "src\Saas.Authorization\Saas.AspNetCore.Authorization.Tests\Saas.AspNetCore.Authorization.Tests.csproj", "{AEDE788C-35EF-4C03-AA2F-1D1D787001FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{02F6CFF4-70ED-4C9D-AF21-4EE3AD668AA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{02F6CFF4-70ED-4C9D-AF21-4EE3AD668AA4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02F6CFF4-70ED-4C9D-AF21-4EE3AD668AA4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02F6CFF4-70ED-4C9D-AF21-4EE3AD668AA4}.Release|Any CPU.Build.0 = Release|Any CPU
{71694EE0-1D10-4DC3-B4EA-30C45A9FF31F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71694EE0-1D10-4DC3-B4EA-30C45A9FF31F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71694EE0-1D10-4DC3-B4EA-30C45A9FF31F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71694EE0-1D10-4DC3-B4EA-30C45A9FF31F}.Release|Any CPU.Build.0 = Release|Any CPU
{6A0836ED-483E-4F7E-8248-AC00FB52E778}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A0836ED-483E-4F7E-8248-AC00FB52E778}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A0836ED-483E-4F7E-8248-AC00FB52E778}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A0836ED-483E-4F7E-8248-AC00FB52E778}.Release|Any CPU.Build.0 = Release|Any CPU
{7AAD364B-DEFB-4C66-AEBA-D0A10DBDE45B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7AAD364B-DEFB-4C66-AEBA-D0A10DBDE45B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7AAD364B-DEFB-4C66-AEBA-D0A10DBDE45B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7AAD364B-DEFB-4C66-AEBA-D0A10DBDE45B}.Release|Any CPU.Build.0 = Release|Any CPU
{B75AFCC1-A92D-4122-9FB2-6C478C67132F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B75AFCC1-A92D-4122-9FB2-6C478C67132F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B75AFCC1-A92D-4122-9FB2-6C478C67132F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B75AFCC1-A92D-4122-9FB2-6C478C67132F}.Release|Any CPU.Build.0 = Release|Any CPU
{29BBEAD7-1043-41EC-A89D-766E1402B701}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29BBEAD7-1043-41EC-A89D-766E1402B701}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29BBEAD7-1043-41EC-A89D-766E1402B701}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29BBEAD7-1043-41EC-A89D-766E1402B701}.Release|Any CPU.Build.0 = Release|Any CPU
{6B9F75FC-4739-4CA1-9347-2F4092A794B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B9F75FC-4739-4CA1-9347-2F4092A794B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B9F75FC-4739-4CA1-9347-2F4092A794B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B9F75FC-4739-4CA1-9347-2F4092A794B1}.Release|Any CPU.Build.0 = Release|Any CPU
{E8F9B31E-E2E7-45F7-AE9B-68409630C82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8F9B31E-E2E7-45F7-AE9B-68409630C82E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8F9B31E-E2E7-45F7-AE9B-68409630C82E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8F9B31E-E2E7-45F7-AE9B-68409630C82E}.Release|Any CPU.Build.0 = Release|Any CPU
{AEDE788C-35EF-4C03-AA2F-1D1D787001FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEDE788C-35EF-4C03-AA2F-1D1D787001FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEDE788C-35EF-4C03-AA2F-1D1D787001FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEDE788C-35EF-4C03-AA2F-1D1D787001FA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FD825DD0-F9E6-41F6-AA7B-33A4ECC441F2}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -1,51 +0,0 @@
version: "3.8"
services:
asdk-admin:
image: asdk-admin
container_name: asdk-admin
build:
context: ./src/
dockerfile: ./Saas.Admin/Saas.Admin.Service/Dockerfile
expose:
- "80"
ports:
- "8080:80"
asdk-web:
image: asdk-web
container_name: asdk-web
build:
context: ./src/
dockerfile: ./Saas.Application/Saas.Application.Web/Dockerfile
expose:
- "80"
ports:
- "8081:80"
asdk-signup:
image: asdk-signup
container_name: asdk-signup
build:
context: ./src/
dockerfile: ./Saas.SignupAdministration/Saas.SignupAdministration.Web/Dockerfile
expose:
- "80"
ports:
- "8082:80"
asdk-permissions:
image: asdk-permissions
container_name: asdk-permissions
build:
context: ./src/
dockerfile: ./Saas.Identity/Saas.Permissions/Saas.Permissions.Service/Dockerfile
expose:
- "80"
ports:
- "8083:80"
asdk-identity-setup:
image: asdk-identity-setup
container_name: asdk-identity-setup
build:
context: ./src/Saas.Identity
dockerfile: ./Saas.IdentityProvider/scripts/Dockerfile

Просмотреть файл

@ -1,120 +0,0 @@
#Requires -Version 3.0
Param(
[string] [Parameter(Mandatory=$true)] $ResourceGroupLocation,
[string] $ResourceGroupName = 'Saas.Admin',
[switch] $UploadArtifacts,
[string] $StorageAccountName,
[string] $StorageContainerName = $ResourceGroupName.ToLowerInvariant() + '-stageartifacts',
[string] $TemplateFile = 'azuredeploy.json',
[string] $TemplateParametersFile = 'azuredeploy.parameters.json',
[string] $ArtifactStagingDirectory = '.',
[string] $DSCSourceFolder = 'DSC',
[switch] $ValidateOnly
)
try {
[Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0')
} catch { }
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 3
function Format-ValidationOutput {
param ($ValidationOutput, [int] $Depth = 0)
Set-StrictMode -Off
return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) })
}
$OptionalParameters = New-Object -TypeName Hashtable
$TemplateFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateFile))
$TemplateParametersFile = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $TemplateParametersFile))
if ($UploadArtifacts) {
# Convert relative paths to absolute paths if needed
$ArtifactStagingDirectory = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $ArtifactStagingDirectory))
$DSCSourceFolder = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, $DSCSourceFolder))
# Parse the parameter file and update the values of artifacts location and artifacts location SAS token if they are present
$JsonParameters = Get-Content $TemplateParametersFile -Raw | ConvertFrom-Json
if (($JsonParameters | Get-Member -Type NoteProperty 'parameters') -ne $null) {
$JsonParameters = $JsonParameters.parameters
}
$ArtifactsLocationName = '_artifactsLocation'
$ArtifactsLocationSasTokenName = '_artifactsLocationSasToken'
$OptionalParameters[$ArtifactsLocationName] = $JsonParameters | Select -Expand $ArtifactsLocationName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
$OptionalParameters[$ArtifactsLocationSasTokenName] = $JsonParameters | Select -Expand $ArtifactsLocationSasTokenName -ErrorAction Ignore | Select -Expand 'value' -ErrorAction Ignore
# Create DSC configuration archive
if (Test-Path $DSCSourceFolder) {
$DSCSourceFilePaths = @(Get-ChildItem $DSCSourceFolder -File -Filter '*.ps1' | ForEach-Object -Process {$_.FullName})
foreach ($DSCSourceFilePath in $DSCSourceFilePaths) {
$DSCArchiveFilePath = $DSCSourceFilePath.Substring(0, $DSCSourceFilePath.Length - 4) + '.zip'
Publish-AzureRmVMDscConfiguration $DSCSourceFilePath -OutputArchivePath $DSCArchiveFilePath -Force -Verbose
}
}
# Create a storage account name if none was provided
if ($StorageAccountName -eq '') {
$StorageAccountName = 'stage' + ((Get-AzureRmContext).Subscription.SubscriptionId).Replace('-', '').substring(0, 19)
}
$StorageAccount = (Get-AzureRmStorageAccount | Where-Object{$_.StorageAccountName -eq $StorageAccountName})
# Create the storage account if it doesn't already exist
if ($StorageAccount -eq $null) {
$StorageResourceGroupName = 'ARM_Deploy_Staging'
New-AzureRmResourceGroup -Location "$ResourceGroupLocation" -Name $StorageResourceGroupName -Force
$StorageAccount = New-AzureRmStorageAccount -StorageAccountName $StorageAccountName -Type 'Standard_LRS' -ResourceGroupName $StorageResourceGroupName -Location "$ResourceGroupLocation"
}
# Generate the value for artifacts location if it is not provided in the parameter file
if ($OptionalParameters[$ArtifactsLocationName] -eq $null) {
$OptionalParameters[$ArtifactsLocationName] = $StorageAccount.Context.BlobEndPoint + $StorageContainerName + '/'
}
# Copy files from the local storage staging location to the storage account container
New-AzureStorageContainer -Name $StorageContainerName -Context $StorageAccount.Context -ErrorAction SilentlyContinue *>&1
$ArtifactFilePaths = Get-ChildItem $ArtifactStagingDirectory -Recurse -File | ForEach-Object -Process {$_.FullName}
foreach ($SourcePath in $ArtifactFilePaths) {
Set-AzureStorageBlobContent -File $SourcePath -Blob $SourcePath.Substring($ArtifactStagingDirectory.length + 1) `
-Container $StorageContainerName -Context $StorageAccount.Context -Force
}
# Generate a 4 hour SAS token for the artifacts location if one was not provided in the parameters file
if ($OptionalParameters[$ArtifactsLocationSasTokenName] -eq $null) {
$OptionalParameters[$ArtifactsLocationSasTokenName] = ConvertTo-SecureString -AsPlainText -Force `
(New-AzureStorageContainerSASToken -Container $StorageContainerName -Context $StorageAccount.Context -Permission r -ExpiryTime (Get-Date).AddHours(4))
}
}
# Create the resource group only when it doesn't already exist
if ((Get-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -ErrorAction SilentlyContinue) -eq $null) {
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force -ErrorAction Stop
}
if ($ValidateOnly) {
$ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
@OptionalParameters)
if ($ErrorMessages) {
Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.'
}
else {
Write-Output '', 'Template is valid.'
}
}
else {
New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) `
-ResourceGroupName $ResourceGroupName `
-TemplateFile $TemplateFile `
-TemplateParameterFile $TemplateParametersFile `
@OptionalParameters `
-Force -Verbose `
-ErrorVariable ErrorMessages
if ($ErrorMessages) {
Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") })
}
}

Просмотреть файл

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<OutputPath>bin\$(Configuration)\</OutputPath>
<DebugSymbols>false</DebugSymbols>
<SkipCopyBuildProduct>true</SkipCopyBuildProduct>
<AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>
<TargetRuntime>None</TargetRuntime>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
<BaseIntermediateOutputPath Condition=" !HasTrailingSlash('$(BaseIntermediateOutputPath)') ">$(BaseIntermediateOutputPath)\</BaseIntermediateOutputPath>
<IntermediateOutputPath>$(BaseIntermediateOutputPath)$(Configuration)\</IntermediateOutputPath>
<ProjectReferencesOutputPath Condition=" '$(ProjectReferencesOutputPath)' == '' ">$(IntermediateOutputPath)ProjectReferences</ProjectReferencesOutputPath>
<ProjectReferencesOutputPath Condition=" !HasTrailingSlash('$(ProjectReferencesOutputPath)') ">$(ProjectReferencesOutputPath)\</ProjectReferencesOutputPath>
<StageArtifacts Condition=" '$(StageArtifacts)' == '' ">true</StageArtifacts>
</PropertyGroup>
<PropertyGroup>
<DefineCommonItemSchemas>false</DefineCommonItemSchemas>
<DefineCommonCapabilities>false</DefineCommonCapabilities>
</PropertyGroup>
<ProjectExtensions>
<ProjectCapabilities>
<DeploymentProject />
</ProjectCapabilities>
</ProjectExtensions>
<ItemDefinitionGroup>
<Content>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None>
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<ProjectReference>
<Private>false</Private>
<Targets>Build</Targets>
</ProjectReference>
</ItemDefinitionGroup>
<Target Name="CreateManifestResourceNames" />
<PropertyGroup>
<StageArtifactsDependsOn>
_GetDeploymentProjectContent;
_CalculateContentOutputRelativePaths;
_GetReferencedProjectsOutput;
_CalculateArtifactStagingDirectory;
_CopyOutputToArtifactStagingDirectory;
</StageArtifactsDependsOn>
</PropertyGroup>
<Target Name="_CopyOutputToArtifactStagingDirectory">
<Copy SourceFiles="@(DeploymentProjectContentOutput)" DestinationFiles="$(ArtifactStagingDirectory)\$(MSBuildProjectName)%(RelativePath)" />
<Copy SourceFiles="@(BuildProjectReferencesOutput)" DestinationFiles="$(ArtifactStagingDirectory)\$(MSBuildProjectName)\%(ProjectName)\%(RecursiveDir)%(FileName)%(Extension)" />
</Target>
<Target Name="_GetDeploymentProjectContent">
<MSBuild Projects="$(MSBuildProjectFile)" Targets="ContentFilesProjectOutputGroup">
<Output TaskParameter="TargetOutputs" ItemName="DeploymentProjectContentOutput" />
</MSBuild>
</Target>
<Target Name="_GetReferencedProjectsOutput">
<PropertyGroup>
<MsBuildProperties>Configuration=$(Configuration);Platform=$(Platform)</MsBuildProperties>
</PropertyGroup>
<MSBuild Projects="@(ProjectReference)"
BuildInParallel="$(BuildInParallel)"
Properties="$(MsBuildProperties)"
Targets="%(ProjectReference.Targets)" />
<ItemGroup>
<BuildProjectReferencesOutput Include="%(ProjectReference.IncludeFilePath)">
<ProjectName>$([System.IO.Path]::GetFileNameWithoutExtension('%(ProjectReference.Identity)'))</ProjectName>
</BuildProjectReferencesOutput>
</ItemGroup>
</Target>
<Target Name="_CalculateArtifactStagingDirectory" Condition=" '$(ArtifactStagingDirectory)'=='' ">
<PropertyGroup>
<ArtifactStagingDirectory Condition=" '$(OutDir)'!='' ">$(OutDir)</ArtifactStagingDirectory>
<ArtifactStagingDirectory Condition=" '$(ArtifactStagingDirectory)'=='' ">$(OutputPath)</ArtifactStagingDirectory>
<ArtifactStagingDirectory Condition=" !HasTrailingSlash('$(ArtifactStagingDirectory)') ">$(ArtifactStagingDirectory)\</ArtifactStagingDirectory>
<ArtifactStagingDirectory>$(ArtifactStagingDirectory)staging\</ArtifactStagingDirectory>
<ArtifactStagingDirectory Condition=" '$(Build_StagingDirectory)'!='' AND '$(TF_Build)'=='True' ">$(Build_StagingDirectory)</ArtifactStagingDirectory>
</PropertyGroup>
</Target>
<!-- Appends each of the deployment project's content output files with metadata indicating its relative path from the deployment project's folder. -->
<Target Name="_CalculateContentOutputRelativePaths"
Outputs="%(DeploymentProjectContentOutput.Identity)">
<PropertyGroup>
<_OriginalIdentity>%(DeploymentProjectContentOutput.Identity)</_OriginalIdentity>
<_RelativePath>$(_OriginalIdentity.Replace('$(MSBuildProjectDirectory)', ''))</_RelativePath>
</PropertyGroup>
<ItemGroup>
<DeploymentProjectContentOutput>
<RelativePath>$(_RelativePath)</RelativePath>
</DeploymentProjectContentOutput>
</ItemGroup>
</Target>
<Target Name="CoreCompile" />
<PropertyGroup>
<StageArtifactsAfterTargets Condition=" '$(StageArtifacts)' == 'true' ">
PrepareForRun
</StageArtifactsAfterTargets>
</PropertyGroup>
<Target Name="StageArtifacts" DependsOnTargets="$(StageArtifactsDependsOn)" AfterTargets="$(StageArtifactsAfterTargets)"/>
<!-- Custom target to clean up local deployment staging files -->
<Target Name="DeleteBinObjFolders" BeforeTargets="Clean">
<RemoveDir Directories="$(OutputPath)" />
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
</Target>
</Project>

Просмотреть файл

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|AnyCPU">
<Configuration>Debug</Configuration>
<Platform>AnyCPU</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|AnyCPU">
<Configuration>Release</Configuration>
<Platform>AnyCPU</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>d52c84e3-a85c-4bde-9550-35e6c075381c</ProjectGuid>
</PropertyGroup>
<PropertyGroup>
<PrepareForBuildDependsOn>
</PrepareForBuildDependsOn>
</PropertyGroup>
<Import Condition=" Exists('Deployment.targets') " Project="Deployment.targets" />
<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
<!-- vertag<:>start tokens<:>maj.min -->
<Import Condition=" Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Deployment\1.1\DeploymentProject.targets') " Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Deployment\1.1\DeploymentProject.targets" />
<!-- vertag<:>end -->
<ItemGroup>
<Content Include="azuredeploy.json" />
<Content Include="azuredeploy.parameters.json" />
<None Include="Deployment.targets">
<Visible>False</Visible>
</None>
<Content Include="Deploy-AzureResourceGroup.ps1" />
</ItemGroup>
<Target Name="GetReferenceAssemblyPaths" />
</Project>

Просмотреть файл

@ -1,161 +0,0 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"SaasProviderName": {
"type": "string",
"defaultValue": "contoso"
},
"SaasEnvironment": {
"type": "string",
"defaultValue": "dev",
"allowedValues": [
"dev",
"staging",
"test",
"prod"
]
},
"SaasLocation": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for the Cosmos DB account."
}
},
"SaasInstanceNumber": {
"type": "string",
"defaultValue": "001"
},
"CosmosDbEndpoint": {
"type": "string"
},
"CosmosDbAccountKey": {
"type": "string",
"metadata": {
"description": "The account key output of CosmosDB"
}
},
"CosmosDbAccountName": {
"type": "string",
"defaultValue": "[concat('cosmos-', parameters('SaasProviderName'), '-', parameters('SaasEnvironment'), '-', parameters('SaasInstanceNumber'))]",
"metadata": {
"description": "Cosmos DB account name"
}
},
"CosmosDbDatabaseName": {
"type": "string",
"defaultValue": "azuresaas",
"metadata": {
"description": "The name for the Core (SQL) database"
}
},
"CosmosDbConnectionString": {
"type": "string"
},
"IdentityDbConnectionString": {
"type": "string"
},
"CatalogDbConnectionString": {
"type": "string"
}
},
"variables": {
"appServicePlanName": "[concat('app-', parameters('SaasProviderName'), '-admin-', parameters('SaasEnvironment'), '-', parameters('SaasInstanceNumber'))]",
"adminWebAppName": "[concat('app-', parameters('SaasProviderName'), '-admin-', parameters('SaasEnvironment'), '-', parameters('SaasInstanceNumber'))]"
},
"resources": [
{
"name": "[variables('appServicePlanName')]",
"type": "Microsoft.Web/serverfarms",
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"sku": {
"name": "F1"
},
"dependsOn": [],
"tags": {
"displayName": "SaaS Provider App Service Plan"
},
"properties": {
"name": "[variables('appServicePlanName')]",
"numberOfWorkers": 1
},
"resources": [
{
"name": "[variables('adminWebAppName')]",
"type": "Microsoft.Web/sites",
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"dependsOn": [
"[concat('Microsoft.Web/serverFarms/', variables('appServicePlanName'))]"
],
"tags": {
"displayName": "SaaS Administration Web App"
},
"properties": {
"name": "[variables('adminWebAppName')]",
"serverFarmId": "[resourceId(resourceGroup().name, 'Microsoft.Web/serverFarms', variables('appServicePlanName'))]",
"siteConfig": {
"netFrameworkVersion": "v6.0",
"appSettings": [
{
"name": "ASPNETCORE_ENVIRONMENT",
"value": "Production"
},
{
"name": "AppSettings:CosmosDb:Account",
"value": "[parameters('CosmosDbEndpoint')]"
},
{
"name": "AppSettings:CosmosDb:Key",
"value": "[parameters('CosmosDbAccountKey')]"
},
{
"name": "AppSettings:CosmosDb:DatabaseName",
"value": "[parameters('CosmosDbDatabaseName')]"
},
{
"name": "AppSettings:CosmosDb:ContainerName",
"value": "OnboardingFlow"
}
],
"connectionStrings": [
{
"name": "CosmosDb",
"connectionString": "[parameters('CosmosDbConnectionString')]",
"type": "Custom"
},
{
"name": "IdentityDbConnection",
"connectionString": "[parameters('IdentityDbConnectionString')]",
"type": "SQLAzure"
},
{
"name": "CatalogDbConnection",
"connectionString": "[parameters('CatalogDbConnectionString')]",
"type": "SQLAzure"
}
]
}
},
"resources": [
{
"name": "MSDeploy",
"type": "extensions",
"location": "[resourceGroup().location]",
"apiVersion": "2015-08-01",
"dependsOn": [ "[resourceId('Microsoft.Web/sites', variables('adminWebAppName'))]" ],
"tags": { "displayName": "Deploy" },
"properties": {
"packageUri": "https://stsaasdev001.blob.core.windows.net/artifacts/saas-admin/Saas.Admin.Service.zip?sv=2020-04-08&st=2021-06-07T19%3A23%3A20Z&se=2022-06-08T19%3A23%3A00Z&sr=c&sp=rl&sig=kNf0qwTfaCJg02xYeUHlfmHOJvI1bGU1HftjUJ5hl5o%3D"
}
}
]
}
]
}
],
"outputs": {
}
}

Просмотреть файл

@ -1,6 +0,0 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
}
}

Просмотреть файл

@ -1,15 +1,12 @@
namespace Saas.Admin.Service.Tests
namespace Saas.Admin.Service.Tests;
public class NewTenantRequestTests
{
using Xunit;
public class NewTenantRequestTests
[Theory, AutoDataNSubstitute]
public void All_Values_Are_Copied_To_Tenant(NewTenantRequest tenantRequest)
{
[Theory, AutoDataNSubstitute]
public void All_Values_Are_Copied_To_Tenant(NewTenantRequest tenantRequest)
{
Tenant tenant = tenantRequest.ToTenant();
Tenant tenant = tenantRequest.ToTenant();
AssertAdditions.AllPropertiesAreEqual(tenant, tenantRequest, nameof(tenant.ConcurrencyToken), nameof(tenant.CreatedTime), nameof(tenant.Id));
}
AssertAdditions.AllPropertiesAreEqual(tenant, tenantRequest, nameof(tenant.ConcurrencyToken), nameof(tenant.CreatedTime), nameof(tenant.Id));
}
}
}

Просмотреть файл

@ -1,21 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.5" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.1.2">
<PackageReference Include="coverlet.collector" Version="3.2.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>

Просмотреть файл

@ -1,109 +1,98 @@
namespace Saas.Admin.Service.Tests
using System.Collections.Generic;
using System.Linq;
using AutoFixture.Xunit2;
using Microsoft.EntityFrameworkCore;
using Saas.Admin.Service.Exceptions;
using Saas.Admin.Service.Services;
namespace Saas.Admin.Service.Tests;
public class TenantServiceTests
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoFixture.Xunit2;
using Microsoft.EntityFrameworkCore;
using Saas.Admin.Service.Data;
using Saas.Admin.Service.Exceptions;
using Saas.Admin.Service.Services;
using TestUtilities;
using Xunit;
public class TenantServiceTests
[Theory, AutoDataNSubstitute]
public async Task Will_Not_Return_Null_When_No_Tenants(TenantService tenantService)
{
[Theory, AutoDataNSubstitute]
public async Task Will_Not_Return_Null_When_No_Tenants(TenantService tenantService)
{
IEnumerable<TenantDTO>? result = await tenantService.GetAllTenantsAsync();
Assert.NotNull(result);
Assert.Empty(result.ToList());
}
[Theory, AutoDataNSubstitute]
public async Task Will_throw_if_tenenent_Not_Found([Frozen] TenantsContext tenantsContext, TenantService tenantService, Guid tenantId)
{
Assert.Null(await tenantsContext.Tenants.FindAsync(tenantId));
await Assert.ThrowsAsync<ItemNotFoundExcepton>(() => tenantService.GetTenantAsync(tenantId));
}
[Theory, AutoDataNSubstitute]
public async Task Will_throw_if_tenenent_Not_Found2([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] tenants)
{
await tenantsContext.Tenants.AddRangeAsync(tenants);
Guid id = tenants[^1].Id;
TenantDTO? tenant = await tenantService.GetTenantAsync(id);
AssertAdditions.AllPropertiesAreEqual(tenant, tenants[^1], nameof(tenant.CreatedTime), nameof(tenant.Version));
await Assert.ThrowsAsync<ItemNotFoundExcepton>(() => tenantService.GetTenantAsync(Guid.NewGuid()));
}
[Theory, AutoDataNSubstitute]
public async Task can_find_tenent([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] tenants)
{
await tenantsContext.Tenants.AddRangeAsync(tenants);
Guid id = tenants[^1].Id;
TenantDTO? tenant = await tenantService.GetTenantAsync(id);
AssertAdditions.AllPropertiesAreEqual(tenant, tenants[^1], nameof(tenant.CreatedTime), nameof(tenant.Version));
}
[Theory, AutoDataNSubstitute]
public async Task Can_update_tenant([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] originalTenants, TenantDTO updatedDto)
{
await tenantsContext.Tenants.AddRangeAsync(originalTenants);
await tenantsContext.SaveChangesAsync();
Tenant tenant = await tenantsContext.Tenants.FirstAsync();
DateTime originalCreated = tenant.CreatedTime!.Value;
updatedDto.Id = tenant.Id;
updatedDto.Version = null;
TenantDTO? updatedTenant = await tenantService.UpdateTenantAsync(updatedDto);
AssertAdditions.AllPropertiesAreEqual(updatedDto, updatedTenant, nameof(updatedTenant.Version), nameof(updatedTenant.CreatedTime));
Assert.Equal(originalCreated, updatedTenant.CreatedTime);
}
[Theory, AutoDataNSubstitute]
public async Task Can_delete_tenant([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] originalTenants)
{
await tenantsContext.Tenants.AddRangeAsync(originalTenants);
await tenantsContext.SaveChangesAsync();
Guid idToDelete = originalTenants[0].Id;
Assert.NotNull(await tenantService.GetTenantAsync(idToDelete));
await tenantService.DeleteTenantAsync(idToDelete);
await Assert.ThrowsAsync<ItemNotFoundExcepton>(() => tenantService.GetTenantAsync(idToDelete));
}
[Theory, AutoDataNSubstitute]
public async Task Check_For_Existing_Route([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] originalTenants, string notInDBRoute)
{
await tenantsContext.Tenants.AddRangeAsync(originalTenants);
await tenantsContext.SaveChangesAsync();
bool exists = await tenantService.CheckPathExists(originalTenants[0].Route);
Assert.True(exists);
bool notExists = await tenantService.CheckPathExists(notInDBRoute);
Assert.False(notExists);
}
IEnumerable<TenantDTO>? result = await tenantService.GetAllTenantsAsync();
Assert.NotNull(result);
Assert.Empty(result.ToList());
}
}
[Theory, AutoDataNSubstitute]
public async Task Will_throw_if_tenenent_Not_Found([Frozen] TenantsContext tenantsContext, TenantService tenantService, Guid tenantId)
{
Assert.Null(await tenantsContext.Tenants.FindAsync(tenantId));
await Assert.ThrowsAsync<ItemNotFoundExcepton>(() => tenantService.GetTenantAsync(tenantId));
}
[Theory, AutoDataNSubstitute]
public async Task Will_throw_if_tenenent_Not_Found2([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] tenants)
{
await tenantsContext.Tenants.AddRangeAsync(tenants);
Guid id = tenants[^1].Id;
TenantDTO? tenant = await tenantService.GetTenantAsync(id);
AssertAdditions.AllPropertiesAreEqual(tenant, tenants[^1], nameof(tenant.CreatedTime), nameof(tenant.Version));
await Assert.ThrowsAsync<ItemNotFoundExcepton>(() => tenantService.GetTenantAsync(Guid.NewGuid()));
}
[Theory, AutoDataNSubstitute]
public async Task can_find_tenent([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] tenants)
{
await tenantsContext.Tenants.AddRangeAsync(tenants);
Guid id = tenants[^1].Id;
TenantDTO? tenant = await tenantService.GetTenantAsync(id);
AssertAdditions.AllPropertiesAreEqual(tenant, tenants[^1], nameof(tenant.CreatedTime), nameof(tenant.Version));
}
[Theory, AutoDataNSubstitute]
public async Task Can_update_tenant([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] originalTenants, TenantDTO updatedDto)
{
await tenantsContext.Tenants.AddRangeAsync(originalTenants);
await tenantsContext.SaveChangesAsync();
Tenant tenant = await tenantsContext.Tenants.FirstAsync();
DateTime originalCreated = tenant.CreatedTime!.Value;
updatedDto.Id = tenant.Id;
updatedDto.Version = null;
TenantDTO? updatedTenant = await tenantService.UpdateTenantAsync(updatedDto);
AssertAdditions.AllPropertiesAreEqual(updatedDto, updatedTenant, nameof(updatedTenant.Version), nameof(updatedTenant.CreatedTime));
Assert.Equal(originalCreated, updatedTenant.CreatedTime);
}
[Theory, AutoDataNSubstitute]
public async Task Can_delete_tenant([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] originalTenants)
{
await tenantsContext.Tenants.AddRangeAsync(originalTenants);
await tenantsContext.SaveChangesAsync();
Guid idToDelete = originalTenants[0].Id;
Assert.NotNull(await tenantService.GetTenantAsync(idToDelete));
await tenantService.DeleteTenantAsync(idToDelete);
await Assert.ThrowsAsync<ItemNotFoundExcepton>(() => tenantService.GetTenantAsync(idToDelete));
}
[Theory, AutoDataNSubstitute]
public async Task Check_For_Existing_Route([Frozen] TenantsContext tenantsContext, TenantService tenantService, Tenant[] originalTenants, string notInDBRoute)
{
await tenantsContext.Tenants.AddRangeAsync(originalTenants);
await tenantsContext.SaveChangesAsync();
bool exists = await tenantService.CheckPathExists(originalTenants[0].Route);
Assert.True(exists);
bool notExists = await tenantService.CheckPathExists(notInDBRoute);
Assert.False(notExists);
}
}

Просмотреть файл

@ -26,7 +26,9 @@ public class TenantDTO
ProductTierId = tenant.ProductTierId;
CategoryId = tenant.CategoryId;
Version = tenant.ConcurrencyToken != null ? Convert.ToBase64String(tenant.ConcurrencyToken) : null;
Version = tenant.ConcurrencyToken is not null
? Convert.ToBase64String(tenant.ConcurrencyToken)
: null;
}
public Tenant ToTenant()

Просмотреть файл

@ -1,37 +1,78 @@
using System.Security.Cryptography.X509Certificates;
using Azure.Identity;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Microsoft.Extensions.Options;
using Saas.Admin.Service;
using Saas.Admin.Service.Data;
using Saas.Admin.Service.Utilities;
using Saas.AspNetCore.Authorization.AuthHandlers;
using Saas.AspNetCore.Authorization.ClaimTransformers;
using Saas.Shared.Options;
using Saas.Swagger;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
X509Certificate2 permissionsApiCertificate;
string projectName = Assembly.GetCallingAssembly().GetName().Name
?? throw new NullReferenceException("Project name cannot be null.");
if (builder.Environment.IsProduction())
var logger = LoggerFactory.Create(config => config.AddConsole()).CreateLogger(projectName);
logger.LogInformation("001");
/* IMPORTANT
In the configuration pattern used here, we're seeking to minimize the use of appsettings.json,
as well as eliminate the need for storing local secrets.
Instead we're utilizing the Azure App Configuration service for storing settings and the Azure Key Vault to store secrets.
Azure App Configuration still hold references to the secret, but not the secret themselves.
This approach is more secure and allows us to have a single source of truth
for all settings and secrets.
The settings and secrets are provisioned by the deployment script made available for deploying this service.
Please see the readme for the project for details.
For local development, please see the ASDK Permission Service readme.md for more
on how to set up and run this service in a local development environment - i.e., a local dev machine.
*/
if (builder.Environment.IsDevelopment())
{
// Get Secrets From Azure Key Vault if in production. If not in production, secrets are automatically loaded in from the .NET secrets manager
// https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0
builder.Configuration.AddAzureKeyVault(
new Uri(builder.Configuration["KeyVault:Url"]),
new DefaultAzureCredential(),
new CustomPrefixKeyVaultSecretManager("admin"));
InitializeDevEnvironment();
}
else
{
InitializeProdEnvironment();
}
builder.Services.Configure<AzureB2CAdminApiOptions>(
builder.Configuration.GetRequiredSection(AzureB2CAdminApiOptions.SectionName));
builder.Services.Configure<ClaimToRoleTransformerOptions>(
builder.Configuration.GetRequiredSection(ClaimToRoleTransformerOptions.SectionName));
builder.Services.Configure<AzureB2CPermissionsApiOptions>(
builder.Configuration.GetRequiredSection(AzureB2CPermissionsApiOptions.SectionName));
builder.Services.Configure<PermissionsApiOptions>(
builder.Configuration.GetRequiredSection(PermissionsApiOptions.SectionName));
builder.Services.Configure<SqlOptions>(
builder.Configuration.GetRequiredSection(SqlOptions.SectionName));
// Using Entity Framework for accessing permission data stored in the Permissions Db.
builder.Services.AddDbContext<TenantsContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("TenantsContext")));
{
var sqlConnectionString = builder.Configuration.GetRequiredSection(SqlOptions.SectionName)
.Get<SqlOptions>()?.SQLConnectionString
?? throw new NullReferenceException("SQL Connection string cannot be null.");
});
// Add authentication for incoming requests
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration, "AzureAdB2C");
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration, AzureB2CAdminApiOptions.SectionName);
builder.Services.AddTransient<IClaimsTransformation, ClaimToRoleTransformer>();
builder.Services.AddClaimToRoleTransformer(builder.Configuration, "ClaimToRoleTransformer");
builder.Services.AddRouteBasedRoleHandler("tenantId");
builder.Services.AddRouteBasedRoleHandler("userId");
@ -39,7 +80,6 @@ builder.Services.AddRouteBasedRoleHandler("userId");
builder.Services.AddAuthorization(options =>
{
options.AddPolicy(AppConstants.Policies.Authenticated, policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
@ -93,26 +133,25 @@ builder.Services.AddAuthorization(options =>
});
builder.Services.AddControllers();
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<IPermissionService, PermissionService>();
builder.Services.AddHttpClient<IPermissionServiceClient, PermissionServiceClient>()
.ConfigureHttpClient(options =>
.ConfigureHttpClient((serviceProvider, client) =>
{
options.BaseAddress = new Uri(builder.Configuration["PermissionsApi:BaseUrl"]);
options.DefaultRequestHeaders.Add("x-api-key", builder.Configuration["PermissionsApi:ApiKey"]);
});
using var scope = serviceProvider.CreateScope();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
string? xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
var baseUrl = scope.ServiceProvider.GetRequiredService<IOptions<AzureB2CPermissionsApiOptions>>().Value.BaseUrl
?? throw new NullReferenceException("Permissions Base Url cannot be null");
var apiKey = scope.ServiceProvider.GetRequiredService<IOptions<PermissionsApiOptions>>().Value.ApiKey
?? throw new NullReferenceException("Permissions Base Api Key cannot be null");
client.BaseAddress = new Uri(baseUrl);
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
});
var app = builder.Build();
@ -134,4 +173,78 @@ app.UseAuthorization();
app.MapControllers();
app.Run();
app.Run();
/*---------------
local methods
----------------*/
void InitializeDevEnvironment()
{
// IMPORTANT
// The current version.
// Must correspond exactly to the version string of our deployment as specificed in the deployment config.json.
var version = "ver0.8.0";
logger.LogInformation("Version: {version}", version);
logger.LogInformation($"Is Development.");
// For local development, use the Secret Manager feature of .NET to store a connection string
// and likewise for storing a secret for the permission-api app.
// https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-7.0&tabs=windows
var appConfigurationconnectionString = builder.Configuration.GetConnectionString("AppConfig")
?? throw new NullReferenceException("App config missing.");
// Use the connection string to access Azure App Configuration to get access to app settings stored there.
// To gain access to Azure Key Vault use 'Azure Cli: az login' to log into Azure.
// This login on will also now provide valid access tokens to the local development environment.
// For more details and the option to chain and combine multiple credential options with `ChainedTokenCredential`
// please see: https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme?view=azure-dotnet#define-a-custom-authentication-flow-with-chainedtokencredential
AzureCliCredential credential = new();
builder.Configuration.AddAzureAppConfiguration(options =>
options.Connect(appConfigurationconnectionString)
.ConfigureKeyVault(kv => kv.SetCredential(new ChainedTokenCredential(credential)))
.Select(KeyFilter.Any, version)); // <-- Important: since we're using labels in our Azure App Configuration store
// Configuring Swagger.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
// Enabling to option for add the 'x-api-key' header to swagger UI.
builder.Services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new() { Title = "Admin API", Version = "v1.1" });
option.OperationFilter<SwagCustomHeaderFilter>();
});
}
void InitializeProdEnvironment()
{
// For procution environment, we'll configured Managed Identities for managing access Azure App Services
// and Key Vault. The Azure App Services endpoint is stored in an environment variable for the web app.
var version = builder.Configuration.GetRequiredSection("Version")?.Value
?? throw new NullReferenceException("The Version value cannot be found. Has the 'Version' environment variable been set correctly for the Web App?");
logger.LogInformation("Version: {version}", version);
logger.LogInformation($"Is Production.");
var appConfigurationEndpoint = builder.Configuration.GetRequiredSection("AppConfiguration:Endpoint")?.Value
?? throw new NullReferenceException("The Azure App Configuration Endpoint cannot be found. Has the endpoint environment variable been set correctly for the Web App?");
// Get the ClientId of the UserAssignedIdentity
// If we don't set this ClientID in the ManagedIdentityCredential constructor, it doesn't know it should use the user assigned managed id.
var managedIdentityClientId = builder.Configuration.GetRequiredSection("UserAssignedManagedIdentityClientId")?.Value
?? throw new NullReferenceException("The Environment Variable 'UserAssignedManagedIdentityClientId' cannot be null. Check the App Service Configuration.");
ManagedIdentityCredential userAssignedManagedCredentials = new(managedIdentityClientId);
builder.Configuration.AddAzureAppConfiguration(options =>
options.Connect(new Uri(appConfigurationEndpoint), userAssignedManagedCredentials)
.ConfigureKeyVault(kv => kv.SetCredential(userAssignedManagedCredentials))
.Select(KeyFilter.Any, version)); // <-- Important since we're using labels in our Azure App Configuration store
}

Просмотреть файл

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-Saas.Admin.Service-5358E0C3-EA51-44EA-B381-CA2F9D9710D3</UserSecretsId>
@ -11,20 +11,22 @@
<ItemGroup>
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
<PackageReference Include="Azure.Identity" Version="1.6.0" />
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.3.0" />
<PackageReference Include="Azure.Identity" Version="1.8.1" />
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.4.0" />
<PackageReference Include="Dawn.Guard" Version="1.12.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.5">
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.21.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureAppConfiguration" Version="5.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Identity.Web" Version="1.24.1" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="1.24.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.1" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.25.10" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="1.25.10" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
</ItemGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
@ -32,6 +34,7 @@
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Saas.Authorization\Saas.AspNetCore.Authorization\Saas.AspNetCore.Authorization.csproj" />
<ProjectReference Include="..\..\Saas.Lib\Saas.Authorization\Saas.AspNetCore.Authorization\Saas.AspNetCore.Authorization.csproj" />
<ProjectReference Include="..\..\Saas.Lib\Saas.Shared\Saas.Shared.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -1,8 +1,4 @@
{
"AzureAdB2C": {
"SignedOutCallbackPath": "/signout/B2C_1A_SIGNUP_SIGNIN",
"SignUpSignInPolicyId": "B2C_1A_SIGNUP_SIGNIN"
},
{
"Logging": {
"LogLevel": {
"Default": "Information",
@ -13,9 +9,9 @@
"Url": "",
"PermissionsApiCertName": "devenvcert"
},
"PermissionsApi": {
"BaseUrl": "https://localhost:7023"
},
//"PermissionsApi": {
// "BaseUrl": "https://localhost:7023"
//},
"ClaimToRoleTransformer": {
"SourceClaimType": "permissions", //Name of the claim custom roles are in
"RoleClaimtype": "MyCustomRoles", //Type of the claim to use in the new Identity (works along side of built in)

Просмотреть файл

@ -1,22 +1,23 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
# Visual Studio Version 17
VisualStudioVersion = 17.4.33213.308
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Saas.Admin.Service", "Saas.Admin.Service\Saas.Admin.Service.csproj", "{A6134452-BA8E-4C84-8879-C8EEF82ED5C9}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Admin.Service", "Saas.Admin.Service\Saas.Admin.Service.csproj", "{A6134452-BA8E-4C84-8879-C8EEF82ED5C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Saas.Admin.Service.Tests", "Saas.Admin.Service.Tests\Saas.Admin.Service.Tests.csproj", "{5974515A-43DF-43E7-A1E6-FC97E47E480F}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Admin.Service.Tests", "Saas.Admin.Service.Tests\Saas.Admin.Service.Tests.csproj", "{5974515A-43DF-43E7-A1E6-FC97E47E480F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtilities", "..\TestUtilities\TestUtilities.csproj", "{5A220537-38DA-495B-8CCF-723FA65310F1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\TestUtilities\TestUtilities.csproj", "{5A220537-38DA-495B-8CCF-723FA65310F1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.AspNetCore.Authorization", "..\Saas.Lib\Saas.Authorization\Saas.AspNetCore.Authorization\Saas.AspNetCore.Authorization.csproj", "{9B4C4680-3BBA-419B-AF79-CB80E1422B1A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Shared", "..\Saas.Lib\Saas.Shared\Saas.Shared.csproj", "{E584B588-FBB6-4D30-9EA2-6B6E531F82EC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A6134452-BA8E-4C84-8879-C8EEF82ED5C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6134452-BA8E-4C84-8879-C8EEF82ED5C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
@ -30,5 +31,19 @@ Global
{5A220537-38DA-495B-8CCF-723FA65310F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A220537-38DA-495B-8CCF-723FA65310F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A220537-38DA-495B-8CCF-723FA65310F1}.Release|Any CPU.Build.0 = Release|Any CPU
{9B4C4680-3BBA-419B-AF79-CB80E1422B1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9B4C4680-3BBA-419B-AF79-CB80E1422B1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9B4C4680-3BBA-419B-AF79-CB80E1422B1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9B4C4680-3BBA-419B-AF79-CB80E1422B1A}.Release|Any CPU.Build.0 = Release|Any CPU
{E584B588-FBB6-4D30-9EA2-6B6E531F82EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E584B588-FBB6-4D30-9EA2-6B6E531F82EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E584B588-FBB6-4D30-9EA2-6B6E531F82EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E584B588-FBB6-4D30-9EA2-6B6E531F82EC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {503B7AA4-0DAC-4A3D-95CE-B1D2DCAE55B0}
EndGlobalSection
EndGlobal

5
src/Saas.Admin/deployment/act/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
# nektos/act
.secret
.secrets
secret
secrets

Просмотреть файл

@ -0,0 +1,25 @@
#!/usr/bin/env bash
# repo base
repo_base="$( git rev-parse --show-toplevel )"
REPO_BASE="${repo_base}"
host_deployment_dir="${repo_base}/src/Saas.Admin/deployment"
container_deployment_dir="/asdk/src/Saas.Admin/deployment"
# running the './act/script/clean-credentials' script using our ASDK deployment script container - i.e., not the act container
docker run \
--interactive \
--tty \
--rm \
--volume "${host_deployment_dir}":"${container_deployment_dir}":ro \
--volume "${REPO_BASE}/src/Saas.Lib/Deployment.Script.Modules/":/asdk/src/Saas.Lib/Deployment.Script.Modules:ro \
--volume "${REPO_BASE}/src/Saas.Identity/Saas.IdentityProvider/deployment/config/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/config:ro \
--volume "${REPO_BASE}/.git/":/asdk/.git:ro \
--volume "${HOME}/.azure/":/asdk/.azure:ro \
--volume "${HOME}/asdk/act/.secret":/asdk/act/.secret \
--env "ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE=/asdk/src/Saas.Admin/deployment" \
"${DEPLOYMENT_CONTAINER_NAME}" \
bash /asdk/src/Saas.Lib/Deployment.Script.Modules/clean-credentials.sh
./setup.sh -s

Просмотреть файл

@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -u -e -o pipefail
# shellcheck disable=SC1091
{
source "./../constants.sh"
source "$SHARED_MODULE_DIR/config-module.sh"
}
repo_base="$(git rev-parse --show-toplevel)"
REPO_BASE="${repo_base}"
host_act_secrets_dir="${HOME}/asdk/act/.secret"
host_deployment_dir="${repo_base}/src/Saas.Admin/deployment"
container_deployment_dir="/asdk/src/Saas.Admin/deployment"
# running the './act/script/patch-app-name.sh' script using our ASDK deployment script container - i.e., not the act container
docker run \
--interactive \
--tty \
--rm \
--volume "${host_deployment_dir}":"${container_deployment_dir}":ro \
--volume "${host_deployment_dir}/act/workflows/":"${container_deployment_dir}/act/workflows" \
--volume "${REPO_BASE}/src/Saas.Lib/Deployment.Script.Modules/":/asdk/src/Saas.Lib/Deployment.Script.Modules:ro \
--volume "${REPO_BASE}/src/Saas.Identity/Saas.IdentityProvider/deployment/config/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/config:ro \
--volume "${REPO_BASE}/.git/":/asdk/.git:ro \
--volume "${HOME}/.azure/":/asdk/.azure:ro \
--volume "${host_act_secrets_dir}":/asdk/act/.secret \
--env "ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE"="${container_deployment_dir}" \
"${DEPLOYMENT_CONTAINER_NAME}" \
bash /asdk/src/Saas.Lib/Deployment.Script.Modules/deploy-debug.sh
# --artifact-server-path=./src/publish \
# run act container to run github action locally, using local workflow file and local code base.
gh act workflow_dispatch \
--rm \
--bind \
--pull=false \
--secret-file "${host_act_secrets_dir}/secret" \
--directory "${REPO_BASE}" \
--workflows "${ACT_LOCAL_WORKFLOW_DEBUG_FILE}" \
--platform "ubuntu-latest=${ACT_CONTAINER_NAME}"

Просмотреть файл

@ -0,0 +1,9 @@
# Saving Time Running Local GitHub Actions
GitHub actions are terrific for CI/CD automated deployment. It the *inner loop* for getting GitHub actions right can be a tedious affair - i.e., having to commit, push and run when testing and troubleshoot.
Luckily, there a solution for this called [act](https://github.com/nektos/act). Act lets you run the a GitHub running locally in a container that mimics what is running in GitHub. You still have to commit your latest code to GitHub, as act will pull it from there when it runs. However, you don't have to commit and push every time you make a change to the GitHub action workflow. This last part can save a lot of time and avoid all this *testing*, *wip* etc. commit and pushes to you main branch. It also allows you to have a slightly different `workflow.yml` file that pulls from for instance your dev branch rather than your main branch.
... bla. bla.

Просмотреть файл

@ -0,0 +1,39 @@
#!/usr/bin/env bash
skip_docker_build=false
force_update=false
while getopts 'sf' flag; do
case "${flag}" in
s) skip_docker_build=true ;;
f) force_update=true ;;
*) skip_docker_build=false ;;
esac
done
# shellcheck disable=SC1091
source "../constants.sh"
echo "Setting up the SaaS Admin Service API Act deployment environment."
echo "Settings execute permissions on necessary scripts files."
sudo mkdir -p "${ACT_SECRETS_DIR}"
sudo chmod +x ${ACT_DIR}/*.sh
sudo chmod +x ${SCRIPT_DIR}/*.sh >/dev/null 2>&1
sudo touch ${ACT_SECRETS_FILE}
sudo chown "${USER}" ${ACT_SECRETS_FILE}
sudo touch ${ACT_SECRETS_FILE_RG}
sudo chown "${USER}" ${ACT_SECRETS_FILE_RG}
if [ "${skip_docker_build}" = false ]; then
echo "Building the deployment container."
if [[ "${force_update}" == false ]]; then
"${ACT_CONTAINER_DIR}"/build.sh -n "${ACT_CONTAINER_NAME}"
else
"${ACT_CONTAINER_DIR}"/build.sh -n "${ACT_CONTAINER_NAME}" -f
fi
fi
echo "SaaS SaaS Admin Service API Act environment setup complete. You can now run the local deployment script using the command './deploy.sh'."

Просмотреть файл

@ -0,0 +1,84 @@
---
name: ASDK Administration Service API - Deploy to Azure Web Services
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
env:
APP_NAME: admin-api
AZURE_WEBAPP_NAME: admin-api-asdk-test-fd4k # set this to your application's name
AZURE_WEBAPP_PACKAGE_PATH: . # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: 7.x.x
PROJECT_DIR: ./src/Saas.Admin/Saas.Admin.Service
PROJECT_PATH: ${{ env.PROJECT_DIR }}/Saas.Admin.Service.csproj
PUBLISH_PATH: ./publish
OUTPUT_PATH: ${{ env.PUBLISH_PATH }}/${{ env.APP_NAME }}/package
SYMBOLS_PATH: ${{ env.PUBLISH_PATH }}/symbols
BUILD_CONFIGURATION: Debug # setting the configuration manager build configuration value for our workflow.
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
##################################################################
# this section is specifically changed for for local deployment. #
##################################################################
# Azure login
- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
# checkout the _local_ repository
- name: Checkout
uses: actions/checkout@v3
##################################################################
# end of local deployment specific section. #
##################################################################
# Setup .NET Core SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
# Run dotnet build and publish
- name: dotnet build and publish
run: |
dotnet restore ${{ env.PROJECT_DIR }}
dotnet build ${{ env.PROJECT_PATH }} \
--configuration ${{ env.BUILD_CONFIGURATION }}
dotnet publish ${{ env.PROJECT_PATH }} \
--configuration ${{ env.BUILD_CONFIGURATION }} \
--output ${{ env.OUTPUT_PATH }}
# Deploy to Azure Web apps
- name: Run Azure webapp deploy action using publish profile credentials
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }} # Replace with your app name
package: ${{ env.OUTPUT_PATH }}
######################
# *** Debug only *** #
######################
# Copy symbols files (*.pdb)) to local publish folder # rm -rf ${{ env.OUTPUT_PATH }}/${{ env.AZURE_WEBAPP_NAME }}
- name: copy symbols files (*.pdb)) to local publish folder
run: |
mkdir -p ${{ env.PUBLISH_PATH }}/symbols
echo "Copying symbols files to '${{ env.SYMBOLS_PATH }}'"
cp -r ${{ env.OUTPUT_PATH }}/*.pdb ${{ env.SYMBOLS_PATH }}
######################
# *** End *** #
######################
# Azure logout
- name: logout
run: |
az logout

2
src/Saas.Admin/deployment/bicep/Parameters/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
*-parameters.json
identity-foundation-outputs.json

Просмотреть файл

@ -0,0 +1,5 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {}
}

Просмотреть файл

@ -0,0 +1,51 @@
@description('The SaaS Signup Administration web site name.')
param adminapi string
@description('Version')
param version string
@description('Environment')
@allowed([
'Development'
'Staging'
'Production'
])
param environment string
@description('The App Service Plan ID.')
param appServicePlanName string
@description('The Uri of the Key Vault.')
param keyVaultUri string
@description('The location for all resources.')
param location string
@description('Azure App Configuration User Assigned Identity Name.')
param userAssignedIdentityName string
@description('The name of the Azure App Configuration.')
param appConfigurationName string
@description('The name of the Log Analytics Workspace used by Application Insigths.')
param logAnalyticsWorkspaceName string
@description('The name of Application Insights.')
param applicationInsightsName string
module signupAdministrationWebApp './../../../Saas.Lib/Saas.Bicep.Module/appServiceModuleWithObservability.bicep' = {
name: 'AdminServiceApi'
params: {
appServiceName: adminapi
version: version
environment: environment
appServicePlanName: appServicePlanName
keyVaultUri: keyVaultUri
location: location
userAssignedIdentityName: userAssignedIdentityName
appConfigurationName: appConfigurationName
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
applicationInsightsName: applicationInsightsName
}
}

Просмотреть файл

@ -0,0 +1,159 @@
@description('Version')
param version string
@description('The name of the key vault')
param keyVaultName string
@description('Azure B2C Domain Name.')
param azureB2CDomain string
@description('Azure B2C Tenant Id.')
param azureB2cTenantId string
@description('Azure AD Instance')
param azureAdInstance string
@description('The Azure B2C Signed Out Call Back Path.')
param signedOutCallBackPath string
@description('The Azure B2C Sign up/in Policy Id.')
param signUpSignInPolicyId string
@description('The Azure B2C Permissions API base Url.')
param baseUrl string
@description('The Client Id found on registered Permissions API app page.')
param clientId string
@description('User Identity Name')
param userAssignedIdentityName string
@description('App Configuration Name')
param appConfigurationName string
@description('Indicates the Authentication type for new identity')
param authenticationType string
@description('Type of the claim to use in the new Identity, works alongside built-in')
param roleClaimType string
@description('Name of the claim custom roles are in')
param sourceClaimType string
@description('Application ID URI for the exposed Admin API scopes.')
param applicationIdUri string
@description('Admin API scopes.')
param adminApiScopes string
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' existing = {
name: userAssignedIdentityName
}
// Create object with array of objects containing the kayname and value to be stored in Azure App Configuration store.
var azureB2CKeyName = 'AzureB2C'
var adminApiKeyName = 'AdminApi'
var claimToRoleTransformerKeyName = 'ClaimToRoleTransformer'
var appConfigStore = {
appConfigurationName: appConfigurationName
keyVaultName: keyVaultName
userAssignedIdentityName: userAssignedIdentity.name
label: version
entries: [
{
key: '${adminApiKeyName}:${azureB2CKeyName}:BaseUrl'
value: baseUrl
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:${azureB2CKeyName}:ClientId'
value: clientId
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:${azureB2CKeyName}:TenantId'
value: azureB2cTenantId
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:${azureB2CKeyName}:Domain'
value: azureB2CDomain
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:${azureB2CKeyName}:Instance'
value: azureAdInstance
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:${azureB2CKeyName}:Audience'
value: clientId
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:${azureB2CKeyName}:SignedOutCallbackPath'
value: signedOutCallBackPath
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:${azureB2CKeyName}:SignUpSignInPolicyId'
value: signUpSignInPolicyId
isSecret: false
contentType: 'text/plain'
}
{
key: '${claimToRoleTransformerKeyName}:AuthenticationType'
value: authenticationType
isSecret: false
contentType: 'text/plain'
}
{
key: '${claimToRoleTransformerKeyName}:RoleClaimType'
value: roleClaimType
isSecret: false
contentType: 'text/plain'
}
{
key: '${claimToRoleTransformerKeyName}:SourceClaimType'
value: sourceClaimType
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:ApplicationIdUri'
value: applicationIdUri
isSecret: false
contentType: 'text/plain'
}
{
key: '${adminApiKeyName}:Scopes'
value: ' ${string(adminApiScopes)}' // notice the space before the string, this is a necessary hack. https://github.com/Azure/bicep/issues/6167
isSecret: false
contentType: 'application/json'
}
]
}
// Adding App Configuration entries
module appConfigurationSettings './../../../Saas.Lib/Saas.Bicep.Module/addConfigEntry.bicep' = [ for entry in appConfigStore.entries: {
name: replace('Entry-${entry.key}', ':', '-')
params: {
appConfigurationName: appConfigStore.appConfigurationName
userAssignedIdentityName: appConfigStore.userAssignedIdentityName
keyVaultName: keyVaultName
value: entry.value
contentType: entry.contentType
keyName: entry.key
label: appConfigStore.label
isSecret: entry.isSecret
}
}]

Просмотреть файл

@ -0,0 +1,22 @@
#!/usr/bin/env bash
force_update=false
while getopts f flag
do
case "${flag}" in
f) force_update=true;;
*) force_update=false;;
esac
done
repo_base="$( git rev-parse --show-toplevel )"
docker_file_folder="${repo_base}/src/Saas.lib/Deployment.Container"
# redirect to build.sh in the Deployment.Container folder
if [[ "${force_update}" == false ]]; then
"${docker_file_folder}/build.sh"
else
"${docker_file_folder}/build.sh" -f
fi

Просмотреть файл

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# disable unused variable warning https://www.shellcheck.net/wiki/SC2034
# shellcheck disable=SC2034
# app naming
APP_NAME="admin-api"
APP_DEPLOYMENT_NAME="adminServiceApi"
# repo base
repo_base="$(git rev-parse --show-toplevel)"
REPO_BASE="${repo_base}"
# project base directory
BASE_DIR="${REPO_BASE}/src/Saas.Admin/deployment"
# local script directory
SCRIPT_DIR="${BASE_DIR}/script"
#local log directory
LOG_FILE_DIR="${BASE_DIR}/log"
# act directory
ACT_DIR="${BASE_DIR}/act"
# GitHub workflows
WORKFLOW_BASE="${REPO_BASE}/.github/workflows"
GITHUB_ACTION_WORKFLOW_FILE="${WORKFLOW_BASE}/admin-service-api-deploy.yml"
ACT_LOCAL_WORKFLOW_DEBUG_FILE="${ACT_DIR}/workflows/admin-service-api-deploy-debug.yml"
# global script directory
SHARED_MODULE_DIR="${REPO_BASE}/src/Saas.Lib/Deployment.Script.Modules"
# adding app service global constants
source "${SHARED_MODULE_DIR}/app-service-constants.sh"

Просмотреть файл

@ -0,0 +1,29 @@
#!/usr/bin/env bash
source "./constants.sh"
repo_base="$(git rev-parse --show-toplevel)"
host_act_secrets_dir="${HOME}/asdk/act/.secret"
host_deployment_dir="${repo_base}/src/Saas.Admin/deployment"
container_deployment_dir="/asdk/src/Saas.Admin/deployment"
# using volumes '--volume' to mount only the needed directories to the container.
# using ':ro' to make scrip directories etc. read-only. Only config and log directories are writable.
docker run \
--interactive \
--tty \
--rm \
--volume "${host_deployment_dir}":"${container_deployment_dir}":ro \
--volume "${host_deployment_dir}/log":"${container_deployment_dir}/log" \
--volume "${host_deployment_dir}/Bicep/Parameters":"${container_deployment_dir}"/Bicep/Parameters \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/deployment/config/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/config:ro \
--volume "${repo_base}/src/Saas.Lib/Deployment.Script.Modules/":/asdk/src/Saas.Lib/Deployment.Script.Modules:ro \
--volume "${repo_base}/src/Saas.Lib/Saas.Bicep.Module":/asdk/src/Saas.Lib/Saas.Bicep.Module:ro \
--volume "${repo_base}/.github/workflows":/asdk/.github/workflows \
--volume "${repo_base}/.git/":/asdk/.git:ro \
--volume "${HOME}/.azure/":/asdk/.azure:ro \
--volume "${host_act_secrets_dir}":/asdk/act/.secret \
--env "ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE"="${container_deployment_dir}" \
"${DEPLOYMENT_CONTAINER_NAME}" \
bash ${container_deployment_dir}/start.sh

Просмотреть файл

@ -0,0 +1,132 @@
#!/usr/bin/env python3
import json
import sys
import re
def get_b2c_value(
config: dict,
key: str,
keyName: str) -> 'dict[str, dict[str, str]]':
value = config['azureb2c'][key]
return {
keyName: {
'value': value
}
}
def get_claimTransformer_value(
config: dict,
key: str,
keyName: str) -> 'dict[str, dict[str, str]]':
value = config['claimToRoleTransformer'][key]
return {
keyName: {
'value': value
}
}
def get_deploy_b2c_value(
config: dict,
key: str,
keyName: str) -> 'dict[str, dict[str, str]]':
value = config['deployment']['azureb2c'][key]
return {
keyName: {
'value': value
}
}
def get_app_value(
config: dict,
app_name: str,
key: str,
keyName: str) -> 'dict[str, dict[str, str]]':
for app in config['appRegistrations']:
if app['name'] == app_name:
return {
keyName: {
'value': app[key]
}
}
def get_app_scopes(
config: dict,
app_name: str,
keyName: str) -> 'dict[str, dict[str, str]]':
scopes = []
for app in config['appRegistrations']:
if app['name'] == app_name:
for scope in app['scopes']:
scopes.append(scope['name'])
return {
keyName: {
'value': json.dumps(scopes)
}
}
def get_output_value(outputs: dict, output_name: str) -> 'dict[str, dict[str, str]]':
item = outputs[output_name]
if item : return {
output_name : {
'value': item['value']
}
}
def patch_paramenters_file(
app_name: str,
identity_outputs: str,
paramenter_file: str,
config_file: str) -> None:
with open(config_file, 'r') as f:
config = json.load(f)
with open(identity_outputs, 'r') as f:
identity_outputs = json.load(f)
with open(paramenter_file, 'r') as f:
parameters = json.load(f)
parameters['parameters'].update(get_output_value(identity_outputs, 'version'))
parameters['parameters'].update(get_output_value(identity_outputs, 'keyVaultName'))
parameters['parameters'].update(get_deploy_b2c_value(config, 'domainName', 'azureB2CDomain'))
parameters['parameters'].update(get_deploy_b2c_value(config, 'tenantId', 'azureB2cTenantId'))
parameters['parameters'].update(get_deploy_b2c_value(config, 'instance', 'azureAdInstance'))
parameters['parameters'].update(get_b2c_value(config, 'signedOutCallBackPath', 'signedOutCallBackPath'))
parameters['parameters'].update(get_b2c_value(config, 'signUpSignInPolicyId', 'signUpSignInPolicyId'))
parameters['parameters'].update(get_app_value(config, app_name, 'appId', 'clientId'))
parameters['parameters'].update(get_app_value(config, app_name, 'baseUrl', 'baseUrl'))
parameters['parameters'].update(get_app_value(config, app_name, 'applicationIdUri', 'applicationIdUri'))
parameters['parameters'].update(get_app_scopes(config, app_name, 'adminApiScopes'))
parameters['parameters'].update(get_output_value(identity_outputs, 'userAssignedIdentityName'))
parameters['parameters'].update(get_output_value(identity_outputs, 'appConfigurationName'))
parameters['parameters'].update(get_claimTransformer_value(config, 'authenticationType', 'authenticationType'))
parameters['parameters'].update(get_claimTransformer_value(config, 'roleClaimType', 'roleClaimType'))
parameters['parameters'].update(get_claimTransformer_value(config, 'sourceClaimType', 'sourceClaimType'))
with open(paramenter_file, 'w') as f:
f.write(json.dumps(parameters, indent=4))
# Main entry point for the script
if __name__ == "__main__":
app_name = sys.argv[1]
identity_outputs = sys.argv[2]
paramenter_file = sys.argv[3]
config_file = sys.argv[4]
patch_paramenters_file(app_name, identity_outputs, paramenter_file, config_file)

Просмотреть файл

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# shellcheck disable=SC1091
source "./constants.sh"
echo "Setting up the deployment environment."
echo "Settings execute permissions on necessary scripts files."
(
sudo chmod +x ./*.sh
sudo chmod +x ./script/*.sh >/dev/null 2>&1
sudo chmod +x ./script/*.py
) ||
{
echo "Failed to set execute permissions on the necessary scripts."
exit 1
}
repo_base="$(git rev-parse --show-toplevel)" ||
{
echo "Failed to get the root of the repository."
exit 1
}
docker_file_folder="${repo_base}/src/Saas.lib/Deployment.Container"
# redirect to build.sh in the Deployment.Container folder
sudo chmod +x "${docker_file_folder}/build.sh" ||
{
echo "Failed to set execute permissions on the 'build.sh' script."
exit 1
}
echo "Building the deployment container."
./build.sh ||
{
echo "Failed to build the deployment container. Please ensure that Docker is installed and running."
exit 1
}
(
echo "Setting up log folder..."
mkdir -p "$LOG_FILE_DIR"
sudo chown "${USER}" "$LOG_FILE_DIR"
) ||
{
echo "Failed to set up log folder."
exit 1
}
echo
echo "Setup complete. You can now run the deployment script using the command './run.sh'."

Просмотреть файл

@ -0,0 +1,63 @@
#!/usr/bin/env bash
# if not running in a container
if ! [ -f /.dockerenv ]; then
echo "Running outside of a container us not supported. Please run the script using './run.sh'."
exit 0
fi
# shellcheck disable=SC1091
{
source "${ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE}/constants.sh"
source "$SHARED_MODULE_DIR/config-module.sh"
source "$SHARED_MODULE_DIR/log-module.sh"
source "$SHARED_MODULE_DIR/user-module.sh"
}
# set bash options to exit on unset variables and errors (exit 1) including pipefail
set -u -e -o pipefail
if ! [[ -f $CONFIG_FILE ]]; then
echo "The ASDK Identity Foundation has not completed or 'config.json' file from it's deployment is missing. Please run the Identity Foundation deployment script first."
exit 0
fi
# get now date and time for backup file name
now=$(date '+%Y-%m-%d--%H-%M-%S')
# set run time for deployment script instance
export ASDK_DEPLOYMENT_SCRIPT_RUN_TIME="${now}"
# using the az cli settings and cache from the host machine
initialize-az-cli "$HOME/.azure"
echo "Provisioning the SaaS Administration Service API..." |
log-output \
--level info \
--header "SaaS Administration Service API"
"${SHARED_MODULE_DIR}/"deploy-app-service.sh
"${SHARED_MODULE_DIR}/"deploy-config-entries.sh
echo "Patching '${APP_NAME}' GitHub Action workflow file." |
log-output \
--level info \
--header "SaaS Administration Service API"
"${SHARED_MODULE_DIR}/patch-github-workflow.py" \
"${APP_NAME}" \
"${CONFIG_FILE}" \
"${GITHUB_ACTION_WORKFLOW_FILE}" ||
echo "Failed to patch ${APP_NAME} GitHub Action workflow file" |
log-output \
--level error \
--header "Critical Error" ||
exit 1
git_repo_origin="$(git config --get remote.origin.url)"
echo "'${APP_NAME}' is ready to be deployed. You have two options:"
echo " a) To deploy to production, use the GitHub Action: ${git_repo_origin::-4}/actions"
echo
echo " b) To deploy for live debugging in Azure; navigate to the act directory ('cd act') and run './setup.sh' and then run './deploy.sh' to deploy for remote debugging."

Просмотреть файл

@ -1,76 +1,64 @@
# Saas.Admin.Service
# SaaS Admin Service API
The SaaS Admin Service is an API that is reponsible for tenant management operations. Within this folder, you will find 3 sections:
The SaaS Admin Service is an API that is reponsible for tenant management operations.
1. Saas.Admin.Service - The .NET Web API project containing the code for the API
This project hosts a service API which serves as a gateway to administrate the SaaS ecosystem of Tenants.
2. Saas.Admin.Service.Deployment - The bicep module for deploying the infrastructure required to host the API in Azure
## Overview
3. Saas.Admin.Service.Tests - Unit tests for the service
Within this folder you will find two subfolders:
## 1. Module Overview
- **Saas.Permissions.Service** - the C# project for the API
- **deployment** - a set of tools for deploying the API for production
- The sub-subfolder **[act](./deployment/act)** is for deploying the API for remote debugging
- Saas.Admin.Service.Tests - Unit tests for the API.
This project hosts a service api which serves as a gateway to administrate the SaaS ecosystem of Tenants. It is fully self-contained such that it includes complete copies of all necessary classes for operation. Since it contains no direct references to the other projects, it can be extracted to launch in isolation. However, keep in mind that some functionality within the API does have [dependencies](https://azure.github.io/azure-saas/components/admin-service/#dependencies) on other services.
## Dependencies
The service depends on:
- The **Identity Foundation** that was deployed a spart of the Identity Foundation and on the [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/use-the-api).
- The **[SaaS Permissions Services API](./../Saas.Identity/Saas.Permissions/readme.md)**.
- The [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview).
For a complete overview, please see the [SaaS.Admin.Service](https://azure.github.io/azure-saas/components/admin-service/) page in our documentation site.
## 2. How to Run Locally
## Provisioning the API
Once configured, this app presents an api service which exposes endpoints to perform CRUD operations on application tenant data. It may be run locally during development of service logic and for regenerating its included NSwag api client. (An NSwag file is included in the Admin project to generate its client.)
To work with the SaaS Admin Services API it must first be provisions to your Azure ASDK resource group. This is true even if you initially is planning to run the API in your local development environment. The provisioning ensure that configuration and settings to be correctly added to your Azure App Configuration store and readies the API for later deployment to Azure.
### i. Requirements
Provisioning is easy:
To run the web api, you must have the following installed on your machine:
1. Navigate to the sub folder `deployment`.
- [.NET 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)
- [ASP.NET Core 6.0](https://docs.microsoft.com/en-us/aspnet/core/introduction-to-aspnet-core?view=aspnetcore-6.0)
- (Recommended) [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Visual Studio Code](https://code.visualstudio.com/download)
- A connection string to a running, empty SQL Server Database.
- [Local DB](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/sql-server-express-localdb?view=sql-server-ver15) (Windows Only) - See `Additional Resources` below for basic config secret
- [SQL Server Docker Container](https://hub.docker.com/_/microsoft-mssql-server)
- [SQL Server Developer Edition](https://www.microsoft.com/en-us/sql-server/sql-server-downloads)
- A deployed [Identity Framework](https://azure.github.io/azure-saas/quick-start/) instance
- [Azure AD B2C](https://azure.microsoft.com/en-us/services/active-directory/external-identities/b2c/) - created automatically with Bicep deployment
2. Run these commands:
### ii. Development Tools
```bash
sudo chmod +x setup.sh
./setup.sh
./run.sh
```
- [NSwag](https://github.com/RicoSuter/NSwag) - An NSwag configuration file has been included to generate an appropriate client from the included Admin project.
*Consumes Clients:*
- [permissions-service-client-generator.nswag](Saas.Admin.Service/permissions-service-client-generator.nswag)
*Consumed By:*
- [Saas.SignupAdministration](../Saas.SignupAdministration)
- [Saas.Application](../Saas.Application)
Now you're ready to move on.
### iii. App Settings
## How to Run Locally
In order to run the project locally, the App Settings marked as `secret: true` must be set using the [.NET secrets manager](https://docs.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows). When deployed to azure using the bicep deployments, these secrets are [loaded from Azure Key Vault](https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?view=aspnetcore-6.0#secret-storage-in-the-development-environment) instead.
Guidelines for getting up and running with SaaS Signup Administration in your local development, are identical to the guidelines found the *[Requirements](./../Saas.Identity/Saas.Permissions/readme.md#Requirements)* and the *[Configuration, settings and secrets when running locally](./../Saas.Identity/Saas.Permissions/readme.md#running-the-saas-permissions-service-api-locally)* section in the [SaaS Permissions Service readme](./../Saas.Identity/Saas.Permissions/readme.md).
Default values for non secret app settings can be found in [appsettings.json](Saas.Admin.Service/appsettings.json)
## Running the SaaS Administration Service API Locally
| AppSetting Key | Description | Secret | Default Value |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------------------------------------- |
| AzureAdB2C:ClientId | The service client corresponding to the Signup Admin application | true | |
| AzureAdB2C:Domain | Domain name for the Azure AD B2C instance | true | |
| AzureAdB2C:Instance | URL for the root of the Azure AD B2C instance | true | |
| AzureAdB2C:SignedOutCallbackPath | Callback path (not full url) contacted after signout | false | /signout/B2C_1A_SIGNUP_SIGNIN |
| AzureAdB2C:SignUpSignInPolicyId | Name of signup/signin policy | false | B2C_1A_SIGNUP_SIGNIN |
| AzureAdB2C:TenantId | Identifier for the overall Azure AD B2C tenant for the overall SaaS ecosystem | true | |
| ClaimToRoleTransformer:AuthenticationType | Indicates the Authentication type for new identity | false | MyCustomRoleAuth |
| ClaimToRoleTransformer:RoleClaimtype | Type of the claim to use in the new Identity, works alongside built-in | false | MyCustomRoles |
| ClaimToRoleTransformer:SourceClaimType | Name of the claim custom roles are in | false | permissions |
| ConnectionStrings:TenantsContext | Connection String to SQL server database used to store permission data. | true | (localdb connnection string) |
| KeyVault:Url | KeyVault URL to pull secret values from in production | false | |
| Logging:LogLevel:Default | Logging level when no configured provider is matched | false | Information |
| Logging:LogLevel:Microsoft.AspNetCore | Logging level for AspNetCore logging | false | Warning |
| PermissionsApi:BaseUrl | URL for downstream [Permissions API](../Saas.Identity/Saas.Permissions/readme.md) | false | |
| PermissionsApi:ApiKey | API Key to use for authentication with the downstream [Permissions API](../Saas.Identity/Saas.Permissions/readme.md) | true | |
--- TODO BEGIN ---
### iv. Starting the App
*Add some guidelines about how to create valid JWT tokens to test the API locally etc...*
1. Insert secrets marked as required for running locally into your secrets manager (such as by using provided script).
1. Start app. Service will launch as presented Swagger API.
---TODO END ---
## 3. Additional Resources
## How to Deploy the SaaS Administration Service API to Azure
### i. LocalDB
If using the LocalDB persistance for local development, tables and data can be interacted with directly through Visual Studio. Under the `View` menu, find `SQL Server Object Explorer`. Additional documentation is available [here](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/sql-server-express-localdb?view=sql-server-ver16)
The guidelines are identity to *[How to Deploy SaaS Permissions Service API to Azure](./../Saas.Identity/Saas.Permissions/readme.md#how--to-deploy-saas-permissions-service-api-to-azure)*.
## Debugging in Azure
The guidelines are identity to *[Debugging in Azure](./../Saas.Identity/Saas.Permissions/readme.md#debugging-in-azure)* for the SaaS Permissions Service API.
Happy debugging!

Просмотреть файл

@ -1,42 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Saas.AspNetCore.Authorization.AuthHandlers
{
public class RouteBasedRoleCusomizer : IRoleCustomizer
{
private readonly IHttpContextAccessor _httpContextAccessor;
public RouteBasedRoleCusomizer(IHttpContextAccessor httpContextAccessor, string routeName, bool includeOriginals = false)
{
_httpContextAccessor = httpContextAccessor;
IncludeOriginals = includeOriginals;
RouteName = routeName;
}
public bool IncludeOriginals { get; internal set; }
public string RouteName { get; protected set; }
public IEnumerable<string> CustomizeRoles(IEnumerable<string> allowedRoles)
{
HttpContext httpContext = _httpContextAccessor.HttpContext;
string context = httpContext.GetRouteValue(RouteName) as string;
if (context != null && allowedRoles != null)
{
foreach (string role in allowedRoles)
{
yield return string.Format("{0}.{1}", context, role);
}
if (IncludeOriginals)
{
foreach (string role in allowedRoles)
{
yield return role;
}
}
}
}
}
}

Просмотреть файл

@ -1,60 +0,0 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Options;
namespace Saas.AspNetCore.Authorization.ClaimTransformers
{
/// <summary>
/// Transforms a custom claim in space delimited format to roles
/// The user principal will factor in the custom roles when IsInRole is called
/// </summary>
public class ClaimToRoleTransformer : IClaimsTransformation
{
private readonly string _sourceClaimType;
private readonly string _roleType;
private readonly string _authType;
/// <summary>
/// Constructor
/// </summary>
/// <param name="sourceClaimType">Name of the space delimited claim to transform</param>
/// <param name="roleClaimType">Type of the individual role claims generated</param>
/// <param name="authType">Authentication type to set the new identity to</param>
public ClaimToRoleTransformer(string sourceClaimType, string roleClaimType, string authType)
{
_sourceClaimType = sourceClaimType;
_roleType = roleClaimType;
_authType = authType;
}
public ClaimToRoleTransformer(IOptions<ClaimToRoleTransformerOptions> options)
: this(options.Value.SourceClaimType, options.Value.RoleClaimType, options.Value.AuthenticationType)
{
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
System.Collections.Generic.IEnumerable<Claim> customClaims = principal.Claims.Where(c => _sourceClaimType.Equals(c.Type, StringComparison.OrdinalIgnoreCase));
System.Collections.Generic.IEnumerable<Claim> roleClaims = customClaims.SelectMany(c =>
{
return c.Value.Split(' ').Select(s => new Claim(_roleType, s));
});
if (!roleClaims.Any())
{
return Task.FromResult(principal);
}
ClaimsPrincipal transformed = new ClaimsPrincipal(principal);
ClaimsIdentity rolesIdentity = new ClaimsIdentity(roleClaims, _authType, null, _roleType);
transformed.AddIdentity(rolesIdentity);
return Task.FromResult(transformed);
}
}
}

Просмотреть файл

@ -1,66 +0,0 @@
using System;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace Saas.AspNetCore.Authorization.ClaimTransformers
{
public static class ClaimToRoleTransformerExtensions
{
public static IServiceCollection AddClaimToRoleTransformer(this IServiceCollection services, string sourceClaimType,
string roleClaimType = ClaimToRoleTransformerOptions.DefaultRoleClaimType,
string authenticationType = ClaimToRoleTransformerOptions.DefaultAuthenticationType)
{
services.AddOptions<ClaimToRoleTransformerOptions>()
.Configure(options =>
{
options.RoleClaimType = roleClaimType;
options.SourceClaimType = sourceClaimType;
options.AuthenticationType = authenticationType;
});
services.RegisterTransformer();
return services;
}
public static IServiceCollection AddClaimToRoleTransformer(this IServiceCollection services,
IConfiguration configurationSection, Action<ClaimToRoleTransformerOptions> configure = null)
{
services.Configure<ClaimToRoleTransformerOptions>(configurationSection);
if (configure != null)
{
services.Configure<ClaimToRoleTransformerOptions>(configure);
}
services.RegisterTransformer();
return services;
}
public static IServiceCollection AddClaimToRoleTransformer(this IServiceCollection services,
IConfiguration configuration, string configSectionName,
Action<ClaimToRoleTransformerOptions> configure = null)
{
if (configuration == null)
{
throw new ArgumentException("configuration");
}
if (string.IsNullOrEmpty(configSectionName))
{
throw new ArgumentException("configSectionName");
}
IConfigurationSection section = configuration.GetSection(configSectionName);
AddClaimToRoleTransformer(services, section, configure);
return services;
}
private static void RegisterTransformer(this IServiceCollection services)
{
services.AddTransient<IClaimsTransformation, ClaimToRoleTransformer>();
}
}
}

Просмотреть файл

@ -1,25 +0,0 @@
namespace Saas.AspNetCore.Authorization.ClaimTransformers
{
public class ClaimToRoleTransformerOptions
{
public const string ConfigSectionName = "ClaimToRoleTransformer";
public const string DefaultRoleClaimType = "CustomRole";
public const string DefaultAuthenticationType = "CustomRoleAuthentication";
/// <summary>
/// Name of the space delimited claim to transform
/// </summary>
public string SourceClaimType { get; set; } = string.Empty;
/// <summary>
/// Type of the individual role claims generated
/// </summary>
public string RoleClaimType { get; set; } = string.Empty;
/// <summary>
/// Authentication type to set the new identity to
/// </summary>
public string AuthenticationType { get; set; } = string.Empty;
}
}

Просмотреть файл

@ -1,29 +1,41 @@
@description('Version')
param version string
@description('URL for downstream admin service.')
param appSettingsAdminServiceBaseUrl string
@description('The name of the key vault')
param keyVaultName string
@description('URL for downstream admin service.')
param azureB2CDomain string
// @description('URL for downstream admin service.')
// param azureB2CInstance string
@description('The B2C login endpoint in format of https://(Tenant Name).b2clogin.com.')
param azureB2CLoginEndpoint string
// @description('URL for downstream admin service.')
// param azureB2CClientId string
@description('Tenant Id found on your AD B2C dashboard.')
param azureB2CTenantId string
// @description('URL for downstream admin service.')
// param azureB2CDomain string
@description('The Client Id found on registered Permissions API app page.')
param permissionApiClientId string
// @description('The B2C login endpoint in format of https://(Tenant Name).b2clogin.com.')
// param azureB2CLoginEndpoint string
@description('Permissions API Certificate Name')
param permissionCertificateName string
// @description('Tenant Id found on your AD B2C dashboard.')
// param azureB2CTenantId string
@description('Permissions API Instance')
param permissionInstance string
// @description('The Azure B2C Signed Out Call Back Path.')
// param signedOutCallBackPath string
// @description('The Azure B2C Sign up/in Policy Id.')
// param signUpSignInPolicyId string
// @description('The Azure B2C Permissions API base Url.')
// param permissionsBaseUrl string
// @description('The Client Id found on registered Permissions API app page.')
// param permissionApiClientId string
// @description('Permissions API Certificate Name')
// param permissionCertificateName string
// @description('Permissions API Instance')
// param permissionInstance string
@description('Select an admin account name used for resource creation.')
param sqlAdministratorLogin string
@ -31,8 +43,8 @@ param sqlAdministratorLogin string
@description('User Identity Name')
param userAssignedIdentityName string
@description('Key Vault Url')
param keyVaultUrl string
// @description('Key Vault Url')
// param keyVaultUrl string
@description('App Configuration Name')
param appConfigurationName string
@ -47,24 +59,17 @@ param permissionsSqlServerFQDN string
@secure()
param sqlAdministratorLoginPassword string
// @description('PermissionsAPI key name')
// param permissionsApiKeyName string
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' existing = {
name: userAssignedIdentityName
}
// Create object with array of objects containing the kayname and value to be stored in Azure App Configuration store.
var azureB2C = 'AzureB2C'
var permissionApi = 'PermissionApi'
var msGraph = 'MsGraph'
var sql = 'Sql'
var permissionCertificates = [
{
SourceType: keyVaultName
KeyVaultUrl: keyVaultUrl
KeyVaultCertificateName: permissionCertificateName
}
]
var msGraphKeyName = 'MsGraph'
var sqlKeyName = 'Sql'
var appConfigStore = {
appConfigurationName: appConfigurationName
@ -73,103 +78,31 @@ var appConfigStore = {
label: version
entries: [
{
key: '${azureB2C}:AdminServiceBaseUrl'
value: appSettingsAdminServiceBaseUrl
isSecret: false
contentType: 'text/plain'
}
{
key: '${azureB2C}:Domain'
value: azureB2CDomain
isSecret: false
contentType: 'text/plain'
}
{
key: '${azureB2C}:LoginEndpoint'
value: azureB2CLoginEndpoint
isSecret: false
contentType: 'text/plain'
}
{
key: '${azureB2C}:TenantId'
value: azureB2CTenantId
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:ClientId'
value: permissionApiClientId
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:TenantId'
value: azureB2CTenantId
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:Domain'
value: azureB2CDomain
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:Instance'
value: permissionInstance
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:Audience'
value: permissionApiClientId
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:CallbackPath'
value: '/signin-oidc'
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:SignedOutCallbackPath'
value: '/signout-oidc'
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionApi}:Certificates'
value: replace('${permissionCertificates}', '\'','"') // replace single quotes with double quotes in the json string
isSecret: false
contentType: 'application/json'
}
{
key: '${sql}:SQLAdministratorLoginName'
key: '${sqlKeyName}:SQLAdministratorLoginName'
value: sqlAdministratorLogin
isSecret: false
contentType: 'text/plain'
}
{
key: '${sql}:SQLAdministratorLoginPassword'
key: '${sqlKeyName}:SQLAdministratorLoginPassword'
value: sqlAdministratorLoginPassword
isSecret: true
contentType: 'text/plain'
}
{
key: '${sql}:SQLConnectionString'
key: '${sqlKeyName}:SQLConnectionString'
value: 'Data Source=tcp:${permissionsSqlServerFQDN},1433;Initial Catalog=${permissionsSqlDatabaseName};User Id=${sqlAdministratorLogin}@${permissionsSqlServerFQDN};Password=${sqlAdministratorLoginPassword};'
isSecret: true
contentType: 'text/plain'
}
{
key: '${msGraph}:BaseUrl'
key: '${msGraphKeyName}:BaseUrl'
value: 'https://graph.microsoft.com/v1.0'
isSecret: false
contentType: 'text/plain'
}
{
key: '${msGraph}:Scopes'
key: '${msGraphKeyName}:Scopes'
value: 'https://graph.microsoft.com/.default'
isSecret: false
contentType: 'text/plain'
@ -178,8 +111,8 @@ var appConfigStore = {
}
// Adding App Configuration entries
module appConfigurationSettings 'addConfigEntry.bicep' = [ for entry in appConfigStore.entries: {
name: replace('AppConfigurationSettings-${entry.key}', ':', '-')
module appConfigurationSettings './../../../../../../Saas.Lib/Saas.Bicep.Module/addConfigEntry.bicep' = [ for entry in appConfigStore.entries: {
name: replace('Entry-${entry.key}', ':', '-')
params: {
appConfigurationName: appConfigStore.appConfigurationName
userAssignedIdentityName: appConfigStore.userAssignedIdentityName

Просмотреть файл

@ -0,0 +1,221 @@
@description('The App Service Plan ID.')
param appServicePlanName string
@description('The location for all resources.')
param location string
@description('Azure App Configuration User Assigned Identity Name.')
param userAssignedIdentityName string
@description('The name of the Log Analytics Workspace used by Application Insigths.')
param logAnalyticsWorkspaceName string
@description('The name of the Automation Account.')
param automationAccountName string
@description('The name of Application Insights.')
param applicationInsightsName string
@description('App Service Plan OS')
@allowed([
'linux'
'windows'
])
param appServicePlanOS string
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' existing = {
name: userAssignedIdentityName
}
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: logAnalyticsWorkspaceName
location: location
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: 30
features: {
enableLogAccessUsingOnlyResourcePermissions: true
}
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.id}': {}
}
}
}
resource automationAccount 'Microsoft.Automation/automationAccounts@2022-08-08' = {
name: automationAccountName
location: location
properties: {
sku: {
name: 'Basic'
}
}
}
var automationAccountLinkedWorkspaceName = 'Automation'
resource automationAccountLinkedWorkspace 'Microsoft.OperationalInsights/workspaces/linkedServices@2020-08-01' = {
name: '${logAnalyticsWorkspace.name}/${automationAccountLinkedWorkspaceName}'
properties: {
resourceId: automationAccount.id
}
}
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
name: applicationInsightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
WorkspaceResourceId: logAnalyticsWorkspace.id
}
}
resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
name: appServicePlanName
location: location
kind: appServicePlanOS
sku: {
name: 'S1'
}
properties: {
reserved: ((appServicePlanOS == 'linux') ? true : false)
}
}
// resource appConfig 'Microsoft.AppConfiguration/configurationStores@2022-05-01' existing = {
// name: appConfigurationName
// }
// // metadata :[
// // {
// // name:'CURRENT_STACK'
// // value:'dotnetcode'
// // }
// // ]
// resource permissionsApi 'Microsoft.Web/sites@2022-03-01' = {
// name: permissionsApiName
// location: location
// kind: 'app,windows'
// properties: {
// serverFarmId: appServicePlan.name
// httpsOnly: true
// // clientCertEnabled: true // https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth?tabs=bicep
// clientCertMode: 'Required'
// siteConfig: {
// ftpsState: 'FtpsOnly'
// alwaysOn: true
// http20Enabled: true
// keyVaultReferenceIdentity: userAssignedIdentity.id // Must specify this when using User Assigned Managed Identity. Read here: https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references?tabs=azure-cli#access-vaults-with-a-user-assigned-identity
// detailedErrorLoggingEnabled: true
// netFrameworkVersion: 'v7.0'
// // linuxFxVersion: 'DOTNETCORE|7.0'
// }
// }
// identity: {
// type: 'UserAssigned'
// userAssignedIdentities: {
// '${userAssignedIdentity.id}': {}
// }
// }
// resource appsettings 'config@2022-03-01' = {
// name: 'appsettings'
// properties: {
// Version: 'ver${version}'
// Logging__LogLevel__Default: 'Information'
// Logging__LogLevel__Microsoft__AspNetCore: 'Warning'
// KeyVault__Url: keyVaultUri
// ASPNETCORE_ENVIRONMENT: environment
// UserAssignedManagedIdentityClientId: userAssignedIdentity.properties.clientId
// AppConfiguration__Endpoint : appConfig.properties.endpoint
// APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString // https://learn.microsoft.com/en-us/azure/azure-monitor/app/migrate-from-instrumentation-keys-to-connection-strings
// ApplicationInsightsAgent_EXTENSION_VERSION: '~2'
// }
// }
// }
// resource diagnosticsSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
// name: 'string'
// scope: permissionsApi
// properties: {
// logs: [
// {
// categoryGroup: 'allLogs'
// enabled: true
// retentionPolicy: {
// days: 7
// enabled: true
// }
// }
// ]
// metrics: [
// {
// category: 'AllMetrics'
// enabled: true
// }
// ]
// workspaceId: logAnalyticsWorkspace.id
// }
// }
// // resource permissionsApiStagingSlot 'Microsoft.Web/sites/slots@2022-03-01' = {
// // name: 'PermissionsApi-Staging'
// // parent: permissionsApi
// // location: location
// // kind: 'app,linux'
// // properties: {
// // serverFarmId: appServicePlan.name
// // httpsOnly: true
// // siteConfig: {
// // alwaysOn: true
// // linuxFxVersion: 'DOTNETCORE|7.0'
// // http20Enabled: true
// // }
// // }
// // identity: {
// // type: 'UserAssigned'
// // userAssignedIdentities: { '${userAssignedIdentity.id}': {} }
// // }
// // resource appsettings 'config@2022-03-01' = {
// // name: 'appsettings'
// // properties: {
// // Version: version
// // Logging__LogLevel__Default: 'Information'
// // Logging__LogLevel__Microsoft__AspNetCore: 'Warning'
// // KeyVault__Url: keyVaultUri
// // ASPNETCORE_ENVIRONMENT: 'Development'
// // UserAssignedManagedIdentityClientId: userAssignedIdentity.properties.clientId
// // AppConfiguration__Endpoint : appConfig.properties.endpoint
// // }
// // }
// // resource metadata 'config@2022-03-01' = {
// // name: 'metadata'
// // properties: {
// // CURRENT_STACK: 'dotnet'
// // }
// // }
// // }
// // Resource - Permissions Api - Deployment
// //////////////////////////////////////////////////
// // resource permissionsApiDeployment 'Microsoft.Web/sites/extensions@2021-03-01' = {
// // parent: permissionsApi
// // name: 'MSDeploy'
// // properties: {
// // packageUri: 'https://stsaasdev001.blob.${environment().suffixes.storage}/artifacts/saas-provider/Saas.Provider.Web.zip?sv=2020-04-08&st=2021-06-07T19%3A23%3A20Z&se=2022-06-08T19%3A23%3A00Z&sr=c&sp=rl&sig=kNf0qwTfaCJg02xYeUHlfmHOJvI1bGU1HftjUJ5hl5o%3D'
// // }
// // }
// // Outputs
// //////////////////////////////////////////////////
// output permissionsApiHostName string = permissionsApi.properties.defaultHostName
output appServicePlanName string = appServicePlan.name

Просмотреть файл

@ -1,219 +0,0 @@
// Parameters
//////////////////////////////////////////////////
@description('Version')
param version string
@description('The App Service Plan ID.')
param appServicePlanName string
@description('The Uri of the Key Vault.')
param keyVaultUri string
@description('The location for all resources.')
param location string
@description('The Permissions Api name.')
param permissionsApiName string
@description('Azure App Configuration User Assigned Identity Name.')
param userAssignedIdentityName string
@description('The name of the Azure App Configuration.')
param appConfigurationName string
@description('The name of the Log Analytics Workspace used by Application Insigths.')
param logAnalyticsWorkspaceName string
@description('The name of the Automation Account.')
param automationAccountName string
@description('The name of Application Insights.')
param applicationInsightsName string
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' existing = {
name: userAssignedIdentityName
}
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: logAnalyticsWorkspaceName
location: location
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: 30
features: {
enableLogAccessUsingOnlyResourcePermissions: true
}
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.id}': {}
}
}
}
resource automationAccount 'Microsoft.Automation/automationAccounts@2022-08-08' = {
name: automationAccountName
location: location
properties: {
sku: {
name: 'Basic'
}
}
}
var automationAccountLinkedWorkspaceName = 'Automation'
resource automationAccountLinkedWorkspace 'Microsoft.OperationalInsights/workspaces/linkedServices@2020-08-01' = {
name: '${logAnalyticsWorkspace.name}/${automationAccountLinkedWorkspaceName}'
properties: {
resourceId: automationAccount.id
}
}
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
name: applicationInsightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
publicNetworkAccessForIngestion: 'Enabled'
publicNetworkAccessForQuery: 'Enabled'
WorkspaceResourceId: logAnalyticsWorkspace.id
}
}
resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
name: appServicePlanName
location: location
kind: 'windows'
sku: {
name: 'S1'
}
properties: {
reserved: false // change to true to enable request Linux rather than Windows. Go figure :)
}
}
resource appConfig 'Microsoft.AppConfiguration/configurationStores@2022-05-01' existing = {
name: appConfigurationName
}
resource permissionsApi 'Microsoft.Web/sites@2022-03-01' = {
name: permissionsApiName
location: location
kind: 'app,windows'
properties: {
serverFarmId: appServicePlan.name
httpsOnly: true
// clientCertEnabled: true // https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth?tabs=bicep
clientCertMode: 'Required'
siteConfig: {
ftpsState: 'FtpsOnly'
alwaysOn: true
// linuxFxVersion: 'DOTNETCORE|7.0'
http20Enabled: true
keyVaultReferenceIdentity: userAssignedIdentity.id // Must specify this when using User Assigned Managed Identity. Read here: https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references?tabs=azure-cli#access-vaults-with-a-user-assigned-identity
detailedErrorLoggingEnabled: true
}
}
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${userAssignedIdentity.id}': {}
}
}
resource appsettings 'config@2022-03-01' = {
name: 'appsettings'
properties: {
Version: 'ver${version}'
Logging__LogLevel__Default: 'Information'
Logging__LogLevel__Microsoft__AspNetCore: 'Warning'
KeyVault__Url: keyVaultUri
ASPNETCORE_ENVIRONMENT: 'Production'
UserAssignedManagedIdentityClientId: userAssignedIdentity.properties.clientId
AppConfiguration__Endpoint : appConfig.properties.endpoint
APPLICATIONINSIGHTS_CONNECTION_STRING: applicationInsights.properties.ConnectionString // https://learn.microsoft.com/en-us/azure/azure-monitor/app/migrate-from-instrumentation-keys-to-connection-strings
ApplicationInsightsAgent_EXTENSION_VERSION: '~2'
}
}
}
resource diagnosticsSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
name: 'string'
scope: permissionsApi
properties: {
logs: [
{
categoryGroup: 'allLogs'
enabled: true
retentionPolicy: {
days: 7
enabled: true
}
}
]
metrics: [
{
category: 'AllMetrics'
enabled: true
}
]
workspaceId: logAnalyticsWorkspace.id
}
}
// resource permissionsApiStagingSlot 'Microsoft.Web/sites/slots@2022-03-01' = {
// name: 'PermissionsApi-Staging'
// parent: permissionsApi
// location: location
// kind: 'app,linux'
// properties: {
// serverFarmId: appServicePlan.name
// httpsOnly: true
// siteConfig: {
// alwaysOn: true
// linuxFxVersion: 'DOTNETCORE|7.0'
// http20Enabled: true
// }
// }
// identity: {
// type: 'UserAssigned'
// userAssignedIdentities: { '${userAssignedIdentity.id}': {} }
// }
// resource appsettings 'config@2022-03-01' = {
// name: 'appsettings'
// properties: {
// Version: version
// Logging__LogLevel__Default: 'Information'
// Logging__LogLevel__Microsoft__AspNetCore: 'Warning'
// KeyVault__Url: keyVaultUri
// ASPNETCORE_ENVIRONMENT: 'Development'
// UserAssignedManagedIdentityClientId: userAssignedIdentity.properties.clientId
// AppConfiguration__Endpoint : appConfig.properties.endpoint
// }
// }
// resource metadata 'config@2022-03-01' = {
// name: 'metadata'
// properties: {
// CURRENT_STACK: 'dotnet'
// }
// }
// }
// Resource - Permissions Api - Deployment
//////////////////////////////////////////////////
// resource permissionsApiDeployment 'Microsoft.Web/sites/extensions@2021-03-01' = {
// parent: permissionsApi
// name: 'MSDeploy'
// properties: {
// packageUri: 'https://stsaasdev001.blob.${environment().suffixes.storage}/artifacts/saas-provider/Saas.Provider.Web.zip?sv=2020-04-08&st=2021-06-07T19%3A23%3A20Z&se=2022-06-08T19%3A23%3A00Z&sr=c&sp=rl&sig=kNf0qwTfaCJg02xYeUHlfmHOJvI1bGU1HftjUJ5hl5o%3D'
// }
// }
// Outputs
//////////////////////////////////////////////////
output permissionsApiHostName string = permissionsApi.properties.defaultHostName
output appServicePlanName string = appServicePlan.name

Просмотреть файл

@ -1,11 +1,17 @@
@description('Version')
param version string
@description('Environment')
@allowed([
'Development'
'Staging'
'Production'
])
param environment string
@description('The ip address of the dev machine')
param devMachineIp string
@description('URL for downstream admin service.')
param appSettingsAdminServiceBaseUrl string
@description('postfix')
param solutionPostfix string
@ -19,27 +25,6 @@ param solutionName string
@description('The name of the key vault')
param keyVaultName string
@description('URL for downstream admin service.')
param azureB2CDomain string
@description('The B2C login endpoint in format of https://(Tenant Name).b2clogin.com.')
param azureB2CLoginEndpoint string
@description('Tenant Id found on your AD B2C dashboard.')
param azureB2CTenantId string
@description('The Client Id found on registered Permissions API app page.')
param permissionApiClientId string
@description('Permissions API Certificate Name')
param permissionCertificateName string
@description('Permissions API Instance')
param permissionInstance string
@description('Permission API Name')
param permissionsApiName string
@description('Permissions API Secret key')
param permissionApiKey string
@ -49,6 +34,7 @@ param sqlAdministratorLogin string
@description('The location for all resources.')
param location string = resourceGroup().location
var appServicePlanOS = 'windows'
var appServicePlanName = 'plan-${solutionPrefix}-${solutionName}-${solutionPostfix}'
var appConfigurationName = 'appconfig-${solutionPrefix}-${solutionName}-${solutionPostfix}'
var permissionsSqlDatabaseName = 'sqldb-permissions-${solutionPrefix}-${solutionName}-${solutionPostfix}'
@ -85,8 +71,7 @@ resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = {
name: keyVaultName
}
// Create object w/ array of objects containing the kayname and value to be stored in Azure App Configuration store.
var permissionApi = 'PermissionApi'
module appConfigurationModule './Module/appConfigurationStore.bicep' = {
name: 'AppConfigurationDeployment'
@ -106,6 +91,9 @@ module keyVaultAccessPolicyModule 'Module/keyVaultAccessRBAC.bicep' = {
keyVault
]
}
// Create object w/ array of objects containing the kayname and value to be stored in Azure App Configuration store.
var permissionsApiKeyName = 'PermissionsApi'
module restApiKeyModule './Module/linkToExistingKeyVaultSecret.bicep' = {
name: 'PermissionApiKeyDeployment'
params: {
@ -114,7 +102,7 @@ module restApiKeyModule './Module/linkToExistingKeyVaultSecret.bicep' = {
appConfigurationName: appConfigurationName
userAssignedIdentityName: userAssignedIdentity.name
keyVaultKeyName: permissionApiKey
keyName: '${permissionApi}:apiKey'
keyName: '${permissionsApiKeyName}:ApiKey'
}
dependsOn: [
keyVaultAccessPolicyModule
@ -127,16 +115,13 @@ resource appConfigurationStore 'Microsoft.AppConfiguration/configurationStores@2
name: appConfigurationName
}
module permissionsApiModule './Module/permissionsApi.bicep' = {
module appPlanModule './Module/appPlan.bicep' = {
name: 'PermissionsApiDeployment'
params: {
version: version
appServicePlanOS: appServicePlanOS
appServicePlanName: appServicePlanName
keyVaultUri: keyVault.properties.vaultUri
location: location
permissionsApiName: permissionsApiName
userAssignedIdentityName: userAssignedIdentity.name
appConfigurationName: appConfigurationStore.name
applicationInsightsName: applicationInsightsName
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
automationAccountName: automationAccountName
@ -153,17 +138,9 @@ module configurationEntriesModule './Module/addConfigEntries.bicep' = {
name: 'ConfigurationEntriesDeployment'
params: {
version: version
appSettingsAdminServiceBaseUrl: appSettingsAdminServiceBaseUrl
keyVaultName: keyVault.name
azureB2CDomain: azureB2CDomain
azureB2CLoginEndpoint: azureB2CLoginEndpoint
azureB2CTenantId: azureB2CTenantId
permissionApiClientId: permissionApiClientId
permissionCertificateName: permissionCertificateName
permissionInstance: permissionInstance
sqlAdministratorLogin: sqlAdministratorLogin
userAssignedIdentityName: userAssignedIdentity.name
keyVaultUrl: keyVault.properties.vaultUri
appConfigurationName: appConfigurationStore.name
permissionsSqlDatabaseName: permissionsSqlDatabaseName
permissionsSqlServerFQDN: permissionsSqlModule.outputs.permissionsSqlServerFQDN
@ -174,21 +151,21 @@ module configurationEntriesModule './Module/addConfigEntries.bicep' = {
keyVaultAccessPolicyModule
keyVault
restApiKeyModule
permissionsApiModule
appPlanModule
]
}
output version string = version
output location string = location
output environment string = environment
output appConfigurationName string = appConfigurationName
output keyVaultName string = keyVault.name
output keyVaultUri string = keyVault.properties.vaultUri
output appServicePlanName string = permissionsApiModule.outputs.appServicePlanName
output appServicePlanName string = appPlanModule.outputs.appServicePlanName
output permissionsSqlServerName string = permissionsSqlModule.outputs.permissionsSqlServerName
output userAssignedIdentityName string = userAssignedIdentity.name
output userAssignedIdentityId string = userAssignedIdentity.id
output permissionsSqlServerFQDN string = permissionsSqlModule.outputs.permissionsSqlServerFQDN
output permissionsApiHostName string = permissionsApiModule.outputs.permissionsApiHostName
output applicationInsightsName string = applicationInsightsName
output logAnalyticsWorkspaceName string = logAnalyticsWorkspaceName
output automationAccountName string = automationAccountName

Просмотреть файл

@ -1,7 +1,22 @@
#!/usr/bin/env bash
force_update=false
while getopts f flag
do
case "${flag}" in
f) force_update=true;;
*) force_update=false;;
esac
done
repo_base="$( git rev-parse --show-toplevel )"
docker_file_folder="${repo_base}/src/Saas.lib/Deployment.Container"
# redirect to build.sh in the Deployment.Container folder
"${docker_file_folder}/build.sh"
if [[ "${force_update}" == false ]]; then
"${docker_file_folder}/build.sh"
else
"${docker_file_folder}/build.sh" -f
fi

Просмотреть файл

@ -16,8 +16,7 @@
}
},
"version": "0.8.0",
"environment": "development",
"production": false,
"environment": "Production",
"git": {
"branch": "main"
},
@ -42,6 +41,8 @@
}
},
"azureb2c": {
"signedOutCallBackPath": "/signout/B2C_1A_SIGNUP_SIGNIN",
"signUpSignInPolicyId": "B2C_1A_SIGNUP_SIGNIN",
"policyKeys": [
{
"name": "TokenSigningKeyContainer",
@ -62,10 +63,16 @@
"keyUsage": "Signature"
}
]
},
},
"claimToRoleTransformer": {
"authenticationType": "MyCustomRoleAuth",
"roleClaimType": "MyCustomRole",
"sourceClaimType": "permissions"
},
"appRegistrations": [
{
"name": "admin-api",
"appServiceName": null,
"certificate": false,
"redirectUri": null,
"applicationIdUri": null,
@ -117,6 +124,7 @@
},
{
"name": "signupadmin-app",
"appServiceName": null,
"certificate": true,
"redirectUri": null,
"appId": null,
@ -148,6 +156,7 @@
},
{
"name": "saas-app",
"appServiceName": null,
"certificate": true,
"redirectUri": null,
"appId": null,
@ -174,6 +183,8 @@
},
{
"name": "permissions-api",
"baseUrl": null,
"appServiceName": null,
"apiName": null,
"appId": null,
"objectId": null,
@ -182,7 +193,6 @@
"redirectUri": null,
"permissionsApiUrl": null,
"rolesApiUrl": null,
"instance": "https://login.microsoftonline.com/",
"publicKeyPath": null,
"certificateKeyName": null,
"scopes": null,
@ -195,15 +205,8 @@
"offline_access"
],
"appRoles": [
"User.Read.All"
]
},
{
"grantAdminConsent": true,
"resourceId": "00000003-0000-0000-c000-000000000000",
"scopes": [
"openid",
"offline_access"
"User.Read.All",
"Application.ReadWrite.OwnedBy"
]
}
]
@ -266,6 +269,18 @@
"sqlAdminLoginName": "sqlAdmin"
},
"deployment": {
"identityFoundation": {
"name": "IdentityFoundationDeployment"
},
"adminServiceApi": {
"name": "AdminServiceAPIDeployment"
},
"signupAdministration": {
"name": "SignupAdmininstrationDeployment"
},
"permissionApi": {
"name": "PermissionApiDeployment"
},
"users": [],
"azureCli": {
"configDir": null

Просмотреть файл

@ -4,11 +4,11 @@
"parameters": {
"version": {
"value": null
},
"devMachineIp": {
},
"environment": {
"value": null
},
"appSettingsAdminServiceBaseUrl": {
"devMachineIp": {
"value": null
},
"solutionPostfix": {
@ -23,27 +23,6 @@
"keyVaultName": {
"value": null
},
"azureB2CDomain": {
"value": null
},
"azureB2CLoginEndpoint": {
"value": null
},
"azureB2CTenantId": {
"value": null
},
"permissionsApiName": {
"value": null
},
"permissionApiClientId": {
"value": null
},
"permissionInstance": {
"value": null
},
"permissionCertificateName": {
"value": null
},
"permissionApiKey": {
"value": null
},

Просмотреть файл

@ -16,9 +16,11 @@ The purpose of the Azure SaaS Dev Kit is to boost your SaaS journey by providing
*Answer*: Yes, and no. In fact Bicep is used when ever possible as part of the deployment script. Yet, the ASDK Identity Foundation relies on [Azure Active Directory B2C](https://learn.microsoft.com/en-us/azure/active-directory-b2c/overview) as well as defining [Azure Active Directory App Registrations](https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-how-applications-are-added). At the time of writing those resources and their configurations cannot be automated by ARM and Bicep.
## Running the Deployment Script in a Container, Using Docker (recommended)
## Running the Deployment Script
Running the deployment script utilizing a container is highly recommend. Using a container will ensure that you have all the required dependencies in their correct configurations etc. In short; when the script runs and that you are running in a controlled environment. It will also minimize the chances that some other properties of your existing environment interferes with the script or that the script inadvertently interferes with your existing environment.
Running the deployment script requires utilizing a container and Docker.
Using a container will ensure that you have all the required dependencies in their correct configurations in a controlled environment. This will also minimize the chances that some other properties of your existing environment might with the script, or that the script inadvertently will interferes with your existing environment.
### Prerequisites
@ -43,7 +45,7 @@ No matter the operating system you're using, you will need these tools to be ins
- [**Docker Desktop**](https://docs.docker.com/get-docker/).
- If you have Docker already, make sure to get the latest updates before you begin. If you have Docker installed but haven't used it for a while. Reinstalling will often solve potential issues.
- [Azure Command Line Interface (**az cli**)](https://learn.microsoft.com/en-us/cli/azure/what-is-azure-cli) from the terminal: [How to install the Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli).
- [GitHubs official command line tool (**gh**)]([GitHub CLI | Take GitHub to the command line](https://cli.github.com/)). For more on installation see [here](https://github.com/cli/cli#installation).
- [GitHubs official command line tool (**gh**)](https://cli.github.com/). For more on installation see [here](https://github.com/cli/cli#installation).
- **Zip** which can be installed with the command: `sudo apt install zip` .
- Note: **zip** is already installed on MacOS per default.
@ -72,7 +74,9 @@ chmod +x setup.sh # only needed the first time to set execute permissions on set
This will take a few minutes to complete and you will only need to do it once. The container will be named `asdk-script-deployment`.
> Tip: If you make changes to `Dockerfile`, defining the container, you can update the container by running `./build.sh`.
> *Tip #1*: If you make changes to `Dockerfile`, defining the container, you can update the container by running `./build.sh`.
>
> Tip #2: If you want to force a rebuild of the container, please us `./build.sh -f`. This can be handy if there's a new version of az cli or GitHub cli that you want to update the container with.
### Running the deployment script using the container
@ -195,34 +199,6 @@ While running the script the second time, you will be asked to log in once, and
> Info: The script will cache this login session too, so that if you need to run the script multiple times, you will not be asked to log in to your Azure AD B2C tenant again. The login session for Azure B2C is cached here: `$HOME/asdk/.cache/`.
## Running Deployment Script on Your Computer Without Docker (not recommended)
While not recommended, you can also run the deployment script *bare-bone* on you computing without using a container. It will generally run slower. More importantly, since the run environment is not controlled, there is a higher risk for something going off the rails. That said, the script is tested for this and will work in many circumstances.
The script have been tested on:
- Windows 10/11 running in WSL with a Ubuntu 22.04 distro.
- Ubuntu 22.04.
- MacOS Ventura. 13.1+, including MacOS running on Apple Silicon.
- While not tested on other configurations, it will likely run recent Linux distros and versions as well as and earlier and recent versions of MacOS too.
Make sure that you have all the tools mentioned above as well as the following installed on your machine before running the script:
- [JQ v1.6+](https://linuxhint.com/bash_jq_command/) for Bash.
- [GitHubs official command line tool (gh)](https://github.com/cli/cli#installation)
- Specifically on MacOS, you'll need a more recent of `bash` as the default version is rather old.
- To do this you can use homebrew: [`brew install bash`](https://formulae.brew.sh/formula/bash).
When these requirements are met, the script can be run using the following command:
```bash
chmod +x start.sh
./start.sh
```
From there on everything else is virtually identical to running the script from inside a container, as described above.
## What If Something Goes Wrong?
It shouldn't happen, but we all know that it does - thank you [Murphy](https://en.wikipedia.org/wiki/Murphy%27s_law)! In most cases, when something goes wrong along the way, all you'll need to do is to run the script once again. The deployment script will skip the parts that have already been completed and re-try the parts that have not.

Просмотреть файл

@ -18,11 +18,12 @@ docker run \
--interactive \
--tty \
--rm \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/deployment/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment:ro \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/deployment/config/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/config \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/deployment/log/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/log \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/policies/":/asdk/src/Saas.Identity/Saas.IdentityProvider/policies \
--volume "${repo_base}/src/Saas.Lib/Deployment.Script.Modules/":/asdk/src/Saas.Lib/Deployment.Script.Modules:ro \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/deployment":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment:ro \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/deployment/config":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/config \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/deployment/log":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/log \
--volume "${repo_base}/src/Saas.Identity/Saas.IdentityProvider/policies":/asdk/src/Saas.Identity/Saas.IdentityProvider/policies \
--volume "${repo_base}/src/Saas.Lib/Deployment.Script.Modules":/asdk/src/Saas.Lib/Deployment.Script.Modules:ro \
--volume "${repo_base}/src/Saas.Lib/Saas.Bicep.Module":/asdk/src/Saas.Lib/Saas.Bicep.Module:ro \
--volume "${repo_base}/.git/":/asdk/.git:ro \
--volume "${HOME}/.azure/":/asdk/.azure:ro \
--volume "${HOME}/asdk/.cache/":/asdk/.cache \

Просмотреть файл

@ -41,14 +41,6 @@ echo "Adding app registrations to Azure B2C tenant." \
declare -i scopes_length
declare -i permissions_length
# b2c_name="$( get-value ".deployment.azureb2c.name" )"
# prefix="$( get-value ".initConfig.naming.solutionPrefix" )"
# postfix="$( get-value ".deployment.postfix" )"
# solution_name="$( get-value ".initConfig.naming.solutionName" )"
# app_id_uri="api://${b2c_name}/${prefix}-${solution_name}-${postfix}"
# put-value ".deployment.azureb2c.applicationIdUri" "${app_id_uri}"
# read each item in the JSON array to an item in the Bash array
readarray -t app_reg_array < <( jq --compact-output '.appRegistrations[]' "${CONFIG_FILE}")
@ -56,6 +48,20 @@ readarray -t app_reg_array < <( jq --compact-output '.appRegistrations[]' "${CON
# declare -i i
# i=1
b2c_tenant_name="$(get-value ".deployment.azureb2c.name" )" \
|| echo "Azure B2C tenant namenot found." \
| log-output \
--level error \
--header "Critical error" \
|| exit 1
echo "Setting instance ${b2c_tenant_name}.b2clogin.com" \
| log-output \
--level info \
--header "Azure B2C Instance"
put-value ".deployment.azureb2c.instance" "https://${b2c_tenant_name}.b2clogin.com"
# iterate through the Bash array of app registrations
for app in "${app_reg_array[@]}"; do
app_name=$( jq --raw-output '.name' <<< "${app}" )
@ -74,9 +80,9 @@ for app in "${app_reg_array[@]}"; do
display_name="${app_name}"
echo "Provisioning app registration for: ${display_name}..." \
| log-output \
--level info \
--header "${display_name}"
| log-output \
--level info \
--header "${display_name}"
if app-exist "${app_id}"; then
echo "App registration for ${app_name} already exist. If you made changes or updated the certificate, you will have to delete the app registration to use this script to update it. " \

Просмотреть файл

@ -1,7 +1,6 @@
#!/usr/bin/env bash
set -u -e -o pipefail
# shellcheck disable=SC1091
{
# include script modules into current shell
@ -42,13 +41,18 @@ if ! resource-exist "${b2c_type_name}" "${b2c_name}" ; then
name="${b2c_name}" \
skuName="${b2c_sku_name}" \
tier="${b2c_tier}" \
|| echo "Azure B2C deployment failed." | log-output \
--level error \
--header "Critical error" \
|| echo "Azure B2C deployment failed." \
| log-output \
--level error \
--header "Critical error" \
|| exit 1
echo "Provisionning of Azure B2C tenant Successful." | log-output --level success
echo "Provisionning of Azure B2C tenant Successful." |
log-output \
--level success
else
echo "Existing Azure B2C tenant found and will be used." | log-output --level success
echo "Existing Azure B2C tenant found and will be used." |
log-output \
--level success
fi

Просмотреть файл

@ -112,9 +112,7 @@ subject="repo:${git_org_project_name}:ref:refs/heads/main"
put-value ".oidc.credentials.subject" "${subject}"
federation_id="$( get-value ".oidc.federation.id" )"
if ! federation-exist "${oidc_app_id}" "${federation_id}"; then
if ! federation-exist "${oidc_app_id}" "${subject}" ; then
echo "Creating OIDC Connect Workflow federation..." \
| log-output \
--level info

Просмотреть файл

@ -10,44 +10,47 @@ set -e -o pipefail
source "$SHARED_MODULE_DIR/config-module.sh"
}
if ! [[ -f "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" ]]; then
echo "The file ${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE} does not exist, creating it now" \
| log-output \
if [[ ! -s "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" ||
! -f "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" ]]; then
echo "The file ${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE} does not exist or is empty, creating it now" |
log-output \
--level info
cp "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_TEMPLATE_FILE}" "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}"
fi
set -u
"${SCRIPT_DIR}/map-identity-paramenters.py" "${CONFIG_FILE}" "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" \
| log-output \
"${SCRIPT_DIR}/map-identity-paramenters.py" "${CONFIG_FILE}" "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" |
log-output \
--level info \
--header "Generating Identity Foundation services parameters..." \
|| echo "Failed to map Identity Foundation services parameters" \
| log-output \
--level error \
--header "Critical Error" \
|| exit 1
--header "Generating Identity Foundation services parameters..." ||
echo "Failed to map Identity Foundation services parameters" |
log-output \
--level error \
--header "Critical Error" ||
exit 1
resource_group="$( get-value ".deployment.resourceGroup.name" )"
resource_group="$(get-value ".deployment.resourceGroup.name")"
deployment_name="$(get-value ".deployment.identityFoundation.name")"
echo "Provisioning Identity Foundation services in resource group ${resource_group}..." \
| log-output \
echo "Provisioning '${deployment_name}' to resource group ${resource_group}..." |
log-output \
--level info
az deployment group create \
--resource-group "${resource_group}" \
--name "IdentityBicepDeployment" \
--name "${deployment_name}" \
--template-file "${DEPLOY_IDENTITY_FOUNDATION_FILE}" \
--parameters "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" \
| log-output \
--level info \
|| echo "Failed to deploy Identity Foundation services" \
| log-output \
--level error \
--header "Critical Error" \
|| exit 1
--parameters "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" |
log-output \
--level info ||
echo "Failed to deploy Identity Foundation services" |
log-output \
--level error \
--header "Critical Error" ||
exit 1
echo "Indentity Foundation services successfully provisioned in resource group ${resource_group}..." \
| log-output \
--level success
echo "'${deployment_name}' was successfully provisioned to resource group ${resource_group}..." |
log-output \
--level success

Просмотреть файл

@ -19,7 +19,7 @@ def create_appsettings_file(config_file: str, app_settings_file: str) -> None:
# get the values to be added to the appsettings file
name = config['environment']
production = config['production']
production = config['environment'] == 'Production'
tenant = config['deployment']['azureb2c']['domainName']
identityExperienceFrameworkAppId = get_app_value(config, "IdentityExperienceFramework", "appId")
proxyIdentityExperienceFrameworkAppId = get_app_value(config, "ProxyIdentityExperienceFramework", "appId")

Просмотреть файл

@ -58,7 +58,7 @@ function final-state() {
| log-output \
--level error \
--header "Deployment script completion" \
|| echo "Please review the log file for more details: ${LOG_FILE_DIR}/${ASDK_ID_PROVIDER_DEPLOYMENT_RUN_TIME}" \
|| echo "Please review the log file for more details: ${LOG_FILE_DIR}/${ASDK_DEPLOYMENT_SCRIPT_RUN_TIME}" \
| log-output \
--level warn
fi
@ -246,27 +246,48 @@ function populate-configuration-manifest() {
service_principal_name="${solution_prefix}-usr-sp-${postfix}"
put-value ".deployment.azureb2c.servicePrincipal.username" "${service_principal_name}"
admin_api_name="admin-api-${long_solution_name}"
put-app-value \
"admin-api" \
"appServiceName" \
"${admin_api_name}"
put-app-value \
"admin-api" \
"baseUrl" \
"api-admin-${long_solution_name}"
"https://${admin_api_name}.azurewebsites.net"
put-app-value \
"admin-api" \
"applicationIdUri" \
"api://${b2c_name}/${long_solution_name}/admin-api"
signup_admin_app_name="signupadmin-app-${long_solution_name}"
put-app-value \
"signupadmin-app" \
"appServiceName" \
"${signup_admin_app_name}"
# adding redirecturl to signupadmin-app
put-app-value \
"signupadmin-app" \
"redirectUri" \
"https://appsignup-${long_solution_name}.azurewebsites.net/signin-oidc"
"https://signupadmin-app-${long_solution_name}.azurewebsites.net/signin-oidc"
saas_app_name="saas-app-${long_solution_name}"
put-app-value \
"saas-app" \
"appServiceName" \
"${saas_app_name}"
# adding redirecturl to saas-app
put-app-value \
"saas-app" \
"redirectUri" \
"https://saasapp-${long_solution_name}.azurewebsites.net/signin-oidc"
"https://saas-app-${long_solution_name}.azurewebsites.net/signin-oidc"
permission_api_name="api-permission-${long_solution_name}"
@ -276,6 +297,16 @@ function populate-configuration-manifest() {
"apiName" \
"${permission_api_name}"
put-app-value \
"permissions-api" \
"appServiceName" \
"${permission_api_name}"
put-app-value \
"permissions-api" \
"baseUrl" \
"https://${permission_api_name}.azurewebsites.net"
# adding permission API Url to permissions-api
put-app-value \
"permissions-api" \

Просмотреть файл

@ -14,55 +14,55 @@ set -u -e -o pipefail
function check-prerequisites() {
echo "Checking prerequisites..." \
| log-output \
echo "Checking prerequisites..." |
log-output \
--level info \
--header "Checking prerequisites"
what_os="$( get-os )" \
|| echo "Unsupported OS: ${what_os}. This script support linux and macos." \
| log-output \
--level error \
--header "Critical Error" \
|| exit 1
what_os="$(get-os)" ||
echo "Unsupported OS: ${what_os}. This script support linux and macos." |
log-output \
--level error \
--header "Critical Error" ||
exit 1
echo "Supported operating system: ${what_os}" | \
log-output \
--level success \
echo "Supported operating system: ${what_os}" |
log-output \
--level success
# check if bash version is supported
is-valid-bash "5.0.0" \
| log-output \
is-valid-bash "5.0.0" |
log-output \
--level info \
--header "Checking bash version" \
|| echo "The version of bash is not supported." \
| log-output \
--level error \
--header "Critical error" \
|| exit 1
--header "Checking bash version" ||
echo "The version of bash is not supported." |
log-output \
--level error \
--header "Critical error" ||
exit 1
# check if az cli version is supported
is-valid-az-cli "2.42.0" \
| log-output \
is-valid-az-cli "2.42.0" |
log-output \
--level info \
--header "Checking az cli version" \
|| echo "The version of az cli is not supported." \
| log-output \
--level error \
--header "Critical error" \
|| exit 1
--header "Checking az cli version" ||
echo "The version of az cli is not supported." |
log-output \
--level error \
--header "Critical error" ||
exit 1
is-valid-jq "1.5" \
| log-output \
is-valid-jq "1.5" |
log-output \
--level info \
--header "Checking jq version" \
|| echo "The version of jq is not supported." \
| log-output \
--level error \
--header "Critical error" \
|| exit 1
# if running in a container copy the msal token cache
--header "Checking jq version" ||
echo "The version of jq is not supported." |
log-output \
--level error \
--header "Critical error" ||
exit 1
# if running in a container copy the msal token cache
# so that user may not have to log in again to main tenant.
if [ -f /.dockerenv ]; then
cp -f /asdk/.azure/msal_token_cache.* /root/.azure/
@ -77,58 +77,56 @@ function initialize-shell-scripts() {
sudo chmod +x ${SCRIPT_DIR}/*.sh
sudo chmod +x ${SHARED_MODULE_DIR}/*.py
fi
}
function initialize-configuration-manifest-file()
{
if [[ ! -f "${CONFIG_FILE}" ]]; then
function initialize-configuration-manifest-file() {
echo "It looks like this is the first time you're running this script. Setting things up..." \
| log-output \
if [[ ! -s "${CONFIG_FILE}" || ! -f "${CONFIG_FILE}" ]]; then
echo "It looks like this is the first time you're running this script. Setting things up..." |
log-output \
--level info
echo
echo "Creating new './config/config.json' from 'config-template.json.'" \
| log-output \
echo "Creating new './config/config.json' from 'config-template.json.'" |
log-output \
--level info
cp "${CONFIG_TEMPLATE_FILE}" "${CONFIG_FILE}"
sudo chown -R 666 "${CONFIG_DIR}"
echo
echo "Before beginning deployment you must specify initial configuration in the 'initConfig' object:" \
| log-output \
echo "Before beginning deployment you must specify initial configuration in the 'initConfig' object:" |
log-output \
--level warning
init_config="$( get-value ".initConfig" )"
init_config="$(get-value ".initConfig")"
echo "${init_config}" \
| log-output \
--level msg;
echo "${init_config}" |
log-output \
--level msg
echo
echo "Please add required initial settings to the initConfig object in ./config/config.json and run this script again." \
| log-output \
echo "Please add required initial settings to the initConfig object in ./config/config.json and run this script again." |
log-output \
--level warning
exit 2
else
# Setting configuration variables
echo "Initializing Configuration" \
| log-output \
echo "Initializing Configuration" |
log-output \
--level info \
--header "Configation Settings"
backup-config-beginning
fi
echo "Configuration settings: $CONFIG_FILE." \
| log-output \
echo "Configuration settings: $CONFIG_FILE." |
log-output \
--level success
}
@ -142,4 +140,4 @@ initialize-shell-scripts
initialize-configuration-manifest-file
# set to install az cli extensions without prompting
az config set extension.use_dynamic_install=yes_without_prompt &> /dev/null
az config set extension.use_dynamic_install=yes_without_prompt &>/dev/null

Просмотреть файл

@ -17,11 +17,11 @@ def patch_paramenters_file(config_file: str, paramenter_file: str) -> None:
parameters['parameters']['version']['value'] \
= config['version']
parameters['parameters']['environment']['value'] \
= config['environment']
parameters['parameters']['devMachineIp']['value'] \
= config['deployment']['devMachine']['ip']
parameters['parameters']['appSettingsAdminServiceBaseUrl']['value'] \
= get_app_value(config, "admin-api", "baseUrl")
parameters['parameters']['solutionPostfix']['value'] \
= config['deployment']['postfix']
@ -35,27 +35,6 @@ def patch_paramenters_file(config_file: str, paramenter_file: str) -> None:
parameters['parameters']['keyVaultName']['value'] \
= config['deployment']['keyVault']['name']
parameters['parameters']['azureB2CDomain']['value'] \
= config['deployment']['azureb2c']['domainName']
parameters['parameters']['azureB2CLoginEndpoint']['value'] \
= f"https://{config['deployment']['azureb2c']['name']}.b2clogin.com"
parameters['parameters']['azureB2CTenantId']['value'] \
= config['deployment']['azureb2c']['tenantId']
parameters['parameters']['permissionsApiName']['value'] \
= get_app_value(config, "permissions-api", "apiName")
parameters['parameters']['permissionApiClientId']['value'] \
= get_app_value(config, "permissions-api", "appId")
parameters['parameters']['permissionInstance']['value'] \
= get_app_value(config, "permissions-api", "instance")
parameters['parameters']['permissionCertificateName']['value'] \
= get_app_value(config, "permissions-api", "certificateKeyName")
parameters['parameters']['permissionApiKey']['value'] \
= 'RestApiKey'

Просмотреть файл

@ -6,15 +6,30 @@ source "./constants.sh"
echo "Setting up the deployment environment."
echo "Settings execute permissions on necessary scripts files."
sudo chmod +x ./*.sh
sudo chmod +x ./script/*.sh
sudo chmod +x ./script/*.py
(
sudo chmod +x ./*.sh || exit 1
sudo chmod +x ./script/*.sh || exit 1
sudo chmod +x ./script/*.py || exit 1
) ||
{
echo "Failed to set execute permissions on the necessary scripts."
exit 1
}
repo_base="$(git rev-parse --show-toplevel)" ||
{
echo "Failed to get the root of the repository."
exit 1
}
repo_base="$( git rev-parse --show-toplevel )"
docker_file_folder="${repo_base}/src/Saas.lib/Deployment.Container"
# redirect to build.sh in the Deployment.Container folder
sudo chmod +x "${docker_file_folder}/build.sh"
sudo chmod +x "${docker_file_folder}/build.sh" ||
{
echo "Failed to set execute permissions on the 'build.sh' script."
exit 1
}
echo "Building the deployment container."
./build.sh ||
@ -23,20 +38,28 @@ echo "Building the deployment container."
exit 1
}
echo "Setting up log folder..."
mkdir -p "$LOG_FILE_DIR"
sudo chown "${USER}" "$LOG_FILE_DIR"
(
echo "Setting up log folder..."
mkdir -p "$LOG_FILE_DIR" || exit 1
sudo chown "${USER}" "$LOG_FILE_DIR" || exit 1
echo "Setting up config folder..."
mkdir -p "${CONFIG_DIR}"
sudo chown "${USER}" "${CONFIG_DIR}"
sudo chown "${USER}" "${CONFIG_FILE}"
sudo chown "${USER}" "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}"
echo "Setting up config folder..."
mkdir -p "${CONFIG_DIR}" || exit 1
sudo chown "${USER}" "${CONFIG_DIR}" || exit 1
touch "${CONFIG_FILE}" || exit 1
sudo chown "${USER}" "${CONFIG_FILE}" || exit 1
touch "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" || exit 1
sudo chown "${USER}" "${IDENTITY_FOUNDATION_BICEP_PARAMETERS_FILE}" || exit 1
echo "Setting up policy folder..."
mkdir -p "${IDENTITY_EXPERIENCE_FRAMEWORK_POLICY_ENVIRONMENT_DIR}"
sudo chown "${USER}" "${IDENTITY_EXPERIENCE_FRAMEWORK_POLICY_ENVIRONMENT_DIR}"
sudo chown "${USER}" "${IDENTITY_EXPERIENCE_FRAMEWORK_POLICY_APP_SETTINGS_FILE}"
echo "Setting up policy folder..."
mkdir -p "${IDENTITY_EXPERIENCE_FRAMEWORK_POLICY_ENVIRONMENT_DIR}" || exit 1
sudo chown "${USER}" "${IDENTITY_EXPERIENCE_FRAMEWORK_POLICY_ENVIRONMENT_DIR}" || exit 1
sudo chown "${USER}" "${IDENTITY_EXPERIENCE_FRAMEWORK_POLICY_APP_SETTINGS_FILE}" || exit 1
) ||
{
echo "Failed to setting up folders with permissions."
exit 1
}
echo
echo "Setup complete. You can now run the deployment script using the command './run.sh'."
echo "Setup complete. You can now run the deployment script using the command './run.sh'."

Просмотреть файл

@ -2,6 +2,12 @@
export ASDK_CACHE_AZ_CLI_SESSIONS=true
# if not running in a container
if ! [ -f /.dockerenv ]; then
echo "Running outside of a container us not supported. Please run the deployment script using './run.sh'."
exit 0
fi
if [[ -z $ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE ]]; then
# repo base
echo "ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE is not set. Setting it to default value."
@ -31,7 +37,7 @@ set -u -e -o pipefail
now=$(date '+%Y-%m-%d--%H-%M-%S')
# set run time for deployment script instance
export ASDK_ID_PROVIDER_DEPLOYMENT_RUN_TIME="${now}"
export ASDK_DEPLOYMENT_SCRIPT_RUN_TIME="${now}"
# create log file directory if it does not exist
if ! [ -f /.dockerenv ] && [[ ! -d "${LOG_FILE_DIR}" ]]; then
@ -40,7 +46,7 @@ if ! [ -f /.dockerenv ] && [[ ! -d "${LOG_FILE_DIR}" ]]; then
fi
# create log file for this deployment script instance
touch "${LOG_FILE_DIR}/deploy-${ASDK_ID_PROVIDER_DEPLOYMENT_RUN_TIME}.log"
touch "${LOG_FILE_DIR}/deploy-${ASDK_DEPLOYMENT_SCRIPT_RUN_TIME}.log"
echo "Welcome to the Azure SaaS Dev Kit - Azure B2C Identity Provider deployment script." \
| log-output \
@ -70,6 +76,7 @@ if ! [ -f /.dockerenv ]; then
# make sure that the init script is executable
chmod +x "$SCRIPT_DIR/init.sh"
fi
# initialize deployment environment
"${SCRIPT_DIR}/init.sh" \
|| if [[ $? -eq 2 ]]; then exit 0; fi

Просмотреть файл

@ -1,2 +1,3 @@
Environments/
appsettings.json
appsettings.json
.vscode

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 73 KiB

После

Ширина:  |  Высота:  |  Размер: 73 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 129 KiB

После

Ширина:  |  Высота:  |  Размер: 129 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 68 KiB

После

Ширина:  |  Высота:  |  Размер: 68 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 60 KiB

После

Ширина:  |  Высота:  |  Размер: 60 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 60 KiB

После

Ширина:  |  Высота:  |  Размер: 60 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 150 KiB

После

Ширина:  |  Высота:  |  Размер: 150 KiB

Просмотреть файл

До

Ширина:  |  Высота:  |  Размер: 171 KiB

После

Ширина:  |  Высота:  |  Размер: 171 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 103 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 31 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 25 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 51 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 52 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 42 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 61 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 29 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 147 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 77 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 109 KiB

Просмотреть файл

@ -1,6 +1,6 @@
using Microsoft.Extensions.Options;
using Saas.Permissions.Service.Models;
using Saas.Permissions.Service.Options;
using Saas.Shared.Options;
namespace Saas.Permissions.Service.Middleware;
@ -8,9 +8,9 @@ public class ApiKeyMiddleware {
private readonly RequestDelegate _next;
private const string API_KEY = "x-api-key";
private readonly PermissionApiOptions _permissionOptions;
private readonly PermissionsApiOptions _permissionOptions;
public ApiKeyMiddleware(IOptions<PermissionApiOptions> permissionOptions, RequestDelegate next) {
public ApiKeyMiddleware(IOptions<PermissionsApiOptions> permissionOptions, RequestDelegate next) {
_next = next;
_permissionOptions = permissionOptions.Value;
}

Просмотреть файл

@ -1,23 +0,0 @@
using ClientAssertionWithKeyVault.Interface;
namespace Saas.Permissions.Service.Options;
public record PermissionApiOptions
{
public const string SectionName = "PermissionApi";
public string? Audience { get; init; }
public string? ApiKey { get; init; }
public string? ClientId { get; init; }
public Certificate[]? Certificates { get; init; }
public string? TenantId { get; init; }
public string? Domain { get; init; }
}
public record Certificate : IKeyInfo
{
public string? SourceType { get; init; }
public string? KeyVaultUrl { get; init; }
public string? KeyVaultCertificateName { get; init; }
}

Просмотреть файл

@ -1,14 +1,15 @@
using Azure.Identity;
using Saas.Permissions.Service.Data;
using Saas.Permissions.Service.Interfaces;
using Saas.Permissions.Service.Options;
using Saas.Shared.Options;
using Saas.Permissions.Service.Services;
using Saas.Permissions.Service.Swagger;
using Saas.Swagger;
using ClientAssertionWithKeyVault.Interface;
using ClientAssertionWithKeyVault;
using Saas.Permissions.Service.Middleware;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
using Polly;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
@ -20,7 +21,7 @@ builder.Services.AddApplicationInsightsTelemetry();
Instead we're utilizing the Azure App Configuration service for storing settings and the Azure Key Vault to store secrets.
Azure App Configuration still hold references to the secret, but not the secret themselves.
This approach is more secure, and allows us to have a single source of truth
This approach is more secure and allows us to have a single source of truth
for all settings and secrets.
The settings and secrets were provisioned to Azure App Configuration and Azure Key Vault
@ -30,7 +31,12 @@ builder.Services.AddApplicationInsightsTelemetry();
on how to set up and run this service in a local development environment - i.e., a local dev machine.
*/
var logger = LoggerFactory.Create(config => config.AddConsole()).CreateLogger("Saas.Permissions.API");
string projectName = Assembly.GetCallingAssembly().GetName().Name
?? throw new NullReferenceException("Project name cannot be null");
var logger = LoggerFactory.Create(config => config.AddConsole()).CreateLogger(projectName);
logger.LogInformation("001");
if (builder.Environment.IsDevelopment())
{
@ -43,8 +49,11 @@ else
// Add configuration settings data using Options Pattern.
// For more see: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-7.0
builder.Services.Configure<PermissionApiOptions>(
builder.Configuration.GetRequiredSection(PermissionApiOptions.SectionName));
builder.Services.Configure<PermissionsApiOptions>(
builder.Configuration.GetRequiredSection(PermissionsApiOptions.SectionName));
builder.Services.Configure<AzureB2CPermissionsApiOptions>(
builder.Configuration.GetRequiredSection(AzureB2CPermissionsApiOptions.SectionName));
builder.Services.Configure<SqlOptions>(
builder.Configuration.GetRequiredSection(SqlOptions.SectionName));
@ -140,7 +149,7 @@ void InitializeDevEnvironment()
{
// IMPORTANT
// The current version.
// Must corresspond exactly to the version string of our deployment as specificed in the deployment config.json.
// Must correspond exactly to the version string of our deployment as specificed in the deployment config.json.
var version = "ver0.8.0";
logger.LogInformation("Version: {version}", version);
@ -167,18 +176,16 @@ void InitializeDevEnvironment()
.ConfigureKeyVault(kv => kv.SetCredential(new ChainedTokenCredential(credential)))
.Select(KeyFilter.Any, version)); // <-- Important: since we're using labels in our Azure App Configuration store
// Configuring Swagger.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
// Enabling to option for add the 'x-api-key' header to swagger UI.
builder.Services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new() { Title = "Permissions API", Version = "v1.1" });
option.OperationFilter<SwagCustomHeaderFilter>();
});
// Configuring Swagger.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
}
void InitializeProdEnvironment()

Просмотреть файл

@ -26,11 +26,16 @@
<PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.25.10" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="7.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Saas.Lib\ClientAssertionWithKeyVault\ClientAssertionWithKeyVault.csproj" />
<ProjectReference Include="..\..\..\Saas.Lib\Saas.Shared\Saas.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Options\" />
</ItemGroup>
</Project>

Просмотреть файл

@ -8,7 +8,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Saas.Permissions.Service",
{5FF9E406-A16E-48B4-B6E2-E04B64F7BF28} = {5FF9E406-A16E-48B4-B6E2-E04B64F7BF28}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClientAssertionWithKeyVault", "..\..\..\Saas.Lib\ClientAssertionWithKeyVault\ClientAssertionWithKeyVault.csproj", "{5FF9E406-A16E-48B4-B6E2-E04B64F7BF28}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClientAssertionWithKeyVault", "..\..\..\Saas.Lib\ClientAssertionWithKeyVault\ClientAssertionWithKeyVault.csproj", "{5FF9E406-A16E-48B4-B6E2-E04B64F7BF28}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Saas.Shared", "..\..\..\Saas.Lib\Saas.Shared\Saas.Shared.csproj", "{D8A87153-45CB-4212-8231-EA1D0FA71554}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -24,6 +26,10 @@ Global
{5FF9E406-A16E-48B4-B6E2-E04B64F7BF28}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FF9E406-A16E-48B4-B6E2-E04B64F7BF28}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FF9E406-A16E-48B4-B6E2-E04B64F7BF28}.Release|Any CPU.Build.0 = Release|Any CPU
{D8A87153-45CB-4212-8231-EA1D0FA71554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8A87153-45CB-4212-8231-EA1D0FA71554}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8A87153-45CB-4212-8231-EA1D0FA71554}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8A87153-45CB-4212-8231-EA1D0FA71554}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

Просмотреть файл

@ -3,8 +3,8 @@ using Microsoft.Graph;
using Saas.Permissions.Service.Exceptions;
using Saas.Permissions.Service.Interfaces;
using Saas.Permissions.Service.Models;
using Saas.Permissions.Service.Options;
using System.Text;
using Saas.Shared.Options;
namespace Saas.Permissions.Service.Services;
@ -19,10 +19,10 @@ public class GraphAPIService : IGraphAPIService
"Client Assertion Signing Provider");
private readonly GraphServiceClient _graphServiceClient;
private readonly PermissionApiOptions _permissionOptions;
private readonly AzureB2CPermissionsApiOptions _permissionOptions;
public GraphAPIService(
IOptions<PermissionApiOptions> permissionApiOptions,
IOptions<AzureB2CPermissionsApiOptions> permissionApiOptions,
IGraphApiClientFactory graphClientFactory,
ILogger<GraphAPIService> logger)
{
@ -121,8 +121,8 @@ public class GraphAPIService : IGraphAPIService
try
{
var servicePrincipal = await _graphServiceClient.ServicePrincipals.Request()
.Filter($"appId eq '{clientId}'")
.GetAsync();
.Filter($"appId eq '{clientId}'")
.GetAsync();
return servicePrincipal.SingleOrDefault();
}

Просмотреть файл

@ -1,6 +1,6 @@
using Microsoft.Extensions.Options;
using Microsoft.Graph;
using Saas.Permissions.Service.Options;
using Saas.Shared.Options;
using Saas.Permissions.Service.Interfaces;
namespace Saas.Permissions.Service.Services;
@ -24,7 +24,7 @@ public class GraphApiClientFactory : IGraphApiClientFactory
public GraphServiceClient Create() =>
new(_httpClient, _msGraphOptions.BaseUrl)
{
AuthenticationProvider = _authenticationProvider,
AuthenticationProvider = _authenticationProvider
};
}

Просмотреть файл

@ -2,7 +2,7 @@
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Saas.Permissions.Service.Interfaces;
using Saas.Permissions.Service.Options;
using Saas.Shared.Options;
using System.Net.Http.Headers;
using ClientAssertionWithKeyVault.Interface;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
@ -25,7 +25,7 @@ public class KeyVaultSigningCredentialsAuthProvider : IAuthenticationProvider
public KeyVaultSigningCredentialsAuthProvider(
IOptions<MSGraphOptions> msGraphOptions,
IOptions<PermissionApiOptions> permissionApiOptions,
IOptions<AzureB2CPermissionsApiOptions> azureAdB2COptions,
IClientAssertionSigningProvider clientAssertionSigningProvider,
IKeyVaultCredentialService credentialService,
ILogger<KeyVaultSigningCredentialsAuthProvider> logger)
@ -34,19 +34,19 @@ public class KeyVaultSigningCredentialsAuthProvider : IAuthenticationProvider
_msGraphOptions = msGraphOptions.Value;
_clientAssertionSigningProvider = clientAssertionSigningProvider;
if (permissionApiOptions?.Value?.Certificates?[0] is null)
if (azureAdB2COptions?.Value?.ClientCertificates?[0] is null)
{
logger.LogError("Certificate cannot be null.");
throw new NullReferenceException("Certificate cannot be null.");
}
_msalClient = ConfidentialClientApplicationBuilder
.Create(permissionApiOptions.Value.ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, permissionApiOptions.Value.TenantId)
.Create(azureAdB2COptions.Value.ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, azureAdB2COptions.Value.TenantId)
.WithClientAssertion(
(AssertionRequestOptions options) =>
_clientAssertionSigningProvider.GetClientAssertion(
permissionApiOptions.Value.Certificates[0],
azureAdB2COptions.Value.ClientCertificates[0],
options.TokenEndpoint,
options.ClientID,
credentialService.GetCredential(),

Просмотреть файл

@ -1,3 +1,5 @@
# nektos/act
.secret
.secrets
.secrets
secret
secrets

Просмотреть файл

@ -1,6 +0,0 @@
#!/usr/bin/env bash
repo_base="$( git rev-parse --show-toplevel )"
docker_file_folder="${repo_base}/src/Saas.Identity/Saas.Permissions/deployment/act"
docker build --file "${docker_file_folder}/Dockerfile" --tag act-container:latest .

Просмотреть файл

@ -0,0 +1,25 @@
#!/usr/bin/env bash
# repo base
repo_base="$(git rev-parse --show-toplevel)"
REPO_BASE="${repo_base}"
host_deployment_dir="${repo_base}/src/Saas.Identity/Saas.Permissions/deployment"
container_deployment_dir="/asdk/src/Saas.Identity/Saas.Permissions/deployment"
# running the './act/script/clean-credentials' script using our ASDK deployment script container - i.e., not the act container
docker run \
--interactive \
--tty \
--rm \
--volume "${host_deployment_dir}":"${container_deployment_dir}":ro \
--volume "${REPO_BASE}/src/Saas.Lib/Deployment.Script.Modules/":/asdk/src/Saas.Lib/Deployment.Script.Modules:ro \
--volume "${REPO_BASE}/src/Saas.Identity/Saas.IdentityProvider/deployment/config/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/config:ro \
--volume "${REPO_BASE}/.git/":/asdk/.git:ro \
--volume "${HOME}/.azure/":/asdk/.azure:ro \
--volume "${HOME}/asdk/act/.secret":/asdk/act/.secret \
--env "ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE"="${container_deployment_dir}" \
"${DEPLOYMENT_CONTAINER_NAME}" \
bash /asdk/src/Saas.Lib/Deployment.Script.Modules/clean-credentials.sh
./setup.sh -s

Просмотреть файл

@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -u -e -o pipefail
# shellcheck disable=SC1091
{
source "./../constants.sh"
source "$SHARED_MODULE_DIR/config-module.sh"
}
repo_base="$(git rev-parse --show-toplevel)"
REPO_BASE="${repo_base}"
host_act_secrets_dir="${HOME}/asdk/act/.secret"
host_deployment_dir="${repo_base}/src/Saas.Identity/Saas.Permissions/deployment"
container_deployment_dir="/asdk/src/Saas.Identity/Saas.Permissions/deployment"
# running the './act/script/patch-app-name.sh' script using our ASDK deployment script container - i.e., not the act container
docker run \
--interactive \
--tty \
--rm \
--volume "${host_deployment_dir}":"${container_deployment_dir}":ro \
--volume "${host_deployment_dir}/act/workflows/":"${container_deployment_dir}/act/workflows" \
--volume "${REPO_BASE}/src/Saas.Lib/Deployment.Script.Modules/":/asdk/src/Saas.Lib/Deployment.Script.Modules:ro \
--volume "${REPO_BASE}/src/Saas.Identity/Saas.IdentityProvider/deployment/config/":/asdk/src/Saas.Identity/Saas.IdentityProvider/deployment/config:ro \
--volume "${REPO_BASE}/.git/":/asdk/.git:ro \
--volume "${HOME}/.azure/":/asdk/.azure:ro \
--volume "${host_act_secrets_dir}":/asdk/act/.secret \
--env "ASDK_DEPLOYMENT_SCRIPT_PROJECT_BASE"="${container_deployment_dir}" \
"${DEPLOYMENT_CONTAINER_NAME}" \
bash /asdk/src/Saas.Lib/Deployment.Script.Modules/deploy-debug.sh
# run act container to run github action locally, using local workflow file and local code base.
gh act workflow_dispatch \
--rm \
--bind \
--pull=false \
--secret-file "${host_act_secrets_dir}/secret" \
--directory "${REPO_BASE}" \
--workflows "${ACT_LOCAL_WORKFLOW_DEBUG_FILE}" \
--platform "ubuntu-latest=${ACT_CONTAINER_NAME}"

Просмотреть файл

@ -1,3 +0,0 @@
#!/usr/bin/env bash
gh act workflow_dispatch --secret-file .secrets -W ./workflows/permissions-api-deploy-debug.yml -P ubuntu-latest=act-container:latest

Просмотреть файл

@ -1 +1,39 @@
#!/usr/bin/env bash
#!/usr/bin/env bash
skip_docker_build=false
force_update=false
while getopts 'sf' flag; do
case "${flag}" in
s) skip_docker_build=true ;;
f) force_update=true ;;
*) skip_docker_build=false ;;
esac
done
# shellcheck disable=SC1091
source "../constants.sh"
echo "Setting up the SaaS Permissions Serivce API Act deployment environment."
echo "Settings execute permissions on necessary scripts files."
sudo mkdir -p "${ACT_SECRETS_DIR}"
sudo chmod +x ${ACT_DIR}/*.sh
sudo chmod +x ${SCRIPT_DIR}/*.sh >/dev/null 2>&1
sudo touch ${ACT_SECRETS_FILE}
sudo chown "${USER}" ${ACT_SECRETS_FILE}
sudo touch ${ACT_SECRETS_FILE_RG}
sudo chown "${USER}" ${ACT_SECRETS_FILE_RG}
if [ "${skip_docker_build}" = false ]; then
echo "Building the deployment container."
if [[ "${force_update}" == false ]]; then
"${ACT_CONTAINER_DIR}"/build.sh -n "${ACT_CONTAINER_NAME}"
else
"${ACT_CONTAINER_DIR}"/build.sh -n "${ACT_CONTAINER_NAME}" -f
fi
fi
echo "SaaS Permissions Service API Act environment setup complete. You can now run the local deployment script using the command './deploy.sh'."

Просмотреть файл

@ -1,42 +1,44 @@
---
name: Deploy Permission API to Azure Web Services
on:
workflow_dispatch:
# inputs:
# logLevel:
# description: 'Log level'
# required: true
# default: 'warning'
# tags:
# description: 'Test scenario tags'
permissions:
id-token: write
contents: read
env:
AZURE_WEBAPP_NAME: 'api-permission-asdk-test-b3yf' # set this to your application's name
APP_NAME: permissions-api
AZURE_WEBAPP_NAME: api-permission-asdk-test-fd4k # set this to your application's name
AZURE_WEBAPP_PACKAGE_PATH: "." # set this to the path to your web app project, defaults to the repository root
DOTNET_VERSION: 7.x.x
PROJECT_DIR: ./src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1
PROJECT_PATH: ./src/Saas.Identity/Saas.Permissions/Saas.Permissions.Service_v1.1/Saas.Permissions.Service.csproj
OUTPUT_PATH: ./publish
PROJECT_PATH: ${{ env.PROJECT_DIR }}/Saas.Permissions.Service.csproj
PUBLISH_PATH: ./publish
OUTPUT_PATH: ${{ env.PUBLISH_PATH }}/${{ env.APP_NAME }}/package
SYMBOLS_PATH: ${{ env.PUBLISH_PATH }}/symbols
BUILD_CONFIGURATION: Debug # setting the configuration manager build configuration value for our workflow.
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
##################################################
# this section is specific for local deployment. #
##################################################
# Azure login
- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
# checkout the repo specifying the branch name in 'ref:'
- uses: actions/checkout@v3
with:
ref: dev #IMPORTANT we're checking out and deploying the 'dev' branch here, not 'main'.
token: ${{ secrets.GITHUB_TOKEN }}
# checkout the _local_ repository
- name: Checkout
uses: actions/checkout@v3
#################################################
# end of local deployment specific section. #
#################################################
# Setup .NET Core SDK
- name: Setup .NET Core
@ -50,19 +52,31 @@ jobs:
dotnet restore ${{ env.PROJECT_DIR }}
dotnet build ${{ env.PROJECT_PATH }} \
--configuration Debug
--configuration ${{ env.BUILD_CONFIGURATION }}
dotnet publish ${{ env.PROJECT_PATH }} \
--configuration Debug \
--output '${{ env.OUTPUT_PATH }}/{{ env.AZURE_WEBAPP_NAME }}'
--configuration ${{ env.BUILD_CONFIGURATION }} \
--output ${{ env.OUTPUT_PATH }}
# Deploy to Azure Web apps
- name: "Run Azure webapp deploy action using publish profile credentials"
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }} # Replace with your app name
package: ${{ env.OUTPUT_PATH }}/{{ env.AZURE_WEBAPP_NAME }}
package: ${{ env.OUTPUT_PATH }}
# slot-name: 'PermissionsApi-Staging'
######################
# *** Debug only *** #
######################
# Copy symbols files (*.pdb)) to local publish folder # rm -rf ${{ env.OUTPUT_PATH }}/${{ env.AZURE_WEBAPP_NAME }}
- name: copy symbols files (*.pdb)) to local publish folder
run: |
mkdir -p ${{ env.PUBLISH_PATH }}/symbols
echo "Copying symbols files to '${{ env.SYMBOLS_PATH }}'"
cp -r ${{ env.OUTPUT_PATH }}/*.pdb ${{ env.SYMBOLS_PATH }}
######################
# *** End *** #
######################
# Azure logout
- name: logout

Просмотреть файл

@ -0,0 +1,2 @@
*-parameters.json
identity-foundation-outputs.json

Просмотреть файл

@ -0,0 +1,5 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {}
}

Просмотреть файл

@ -0,0 +1,51 @@
@description('The SaaS Permission API.')
param permissionsapi string
@description('Version')
param version string
@description('Environment')
@allowed([
'Development'
'Staging'
'Production'
])
param environment string
@description('The App Service Plan ID.')
param appServicePlanName string
@description('The Uri of the Key Vault.')
param keyVaultUri string
@description('The location for all resources.')
param location string
@description('Azure App Configuration User Assigned Identity Name.')
param userAssignedIdentityName string
@description('The name of the Azure App Configuration.')
param appConfigurationName string
@description('The name of the Log Analytics Workspace used by Application Insigths.')
param logAnalyticsWorkspaceName string
@description('The name of Application Insights.')
param applicationInsightsName string
module signupAdministrationWebApp './../../../../Saas.Lib/Saas.Bicep.Module/appServiceModuleWithObservability.bicep' = {
name: 'PermissionApi'
params: {
appServiceName: permissionsapi
version: version
environment: environment
appServicePlanName: appServicePlanName
keyVaultUri: keyVaultUri
location: location
userAssignedIdentityName: userAssignedIdentityName
appConfigurationName: appConfigurationName
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
applicationInsightsName: applicationInsightsName
}
}

Просмотреть файл

@ -0,0 +1,133 @@
@description('Version')
param version string
@description('The name of the key vault')
param keyVaultName string
@description('The URI of the key vault.')
param keyVaultUri string
@description('Azure B2C Domain Name.')
param azureB2CDomain string
@description('Azure B2C Tenant Id.')
param azureB2cTenantId string
@description('Azure AD Instance')
param azureAdInstance string
@description('The Azure B2C Signed Out Call Back Path.')
param signedOutCallBackPath string
@description('The Azure B2C Sign up/in Policy Id.')
param signUpSignInPolicyId string
@description('The Azure B2C Permissions API base Url.')
param baseUrl string
@description('The Client Id found on registered Permissions API app page.')
param clientId string
@description('User Identity Name')
param userAssignedIdentityName string
@description('App Configuration Name')
param appConfigurationName string
@description('The name of the certificate key.')
param certificateKeyName string
// Create object with array of objects containing the kayname and value to be stored in Azure App Configuration store.
resource userAssignedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' existing = {
name: userAssignedIdentityName
}
var azureB2CKeyName = 'AzureB2C'
var permissionsApiKeyName = 'permissionsApi'
var certificates = [
{
SourceType: keyVaultName
KeyVaultUrl: keyVaultUri
KeyVaultCertificateName: certificateKeyName
}
]
var appConfigStore = {
appConfigurationName: appConfigurationName
keyVaultName: keyVaultName
userAssignedIdentityName: userAssignedIdentity.name
label: version
entries: [
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:ClientCertificates'
value: ' ${string(certificates)}' // notice the space before the string, this is a necessary hack. https://github.com/Azure/bicep/issues/6167
isSecret: false
contentType: 'application/json'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:BaseUrl'
value: baseUrl
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:ClientId'
value: clientId
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:TenantId'
value: azureB2cTenantId
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:Domain'
value: azureB2CDomain
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:Instance'
value: azureAdInstance
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:Audience'
value: clientId
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:SignedOutCallbackPath'
value: signedOutCallBackPath
isSecret: false
contentType: 'text/plain'
}
{
key: '${permissionsApiKeyName}:${azureB2CKeyName}:SignUpSignInPolicyId'
value: signUpSignInPolicyId
isSecret: false
contentType: 'text/plain'
}
]
}
// Adding App Configuration entries
module appConfigurationSettings './../../../../Saas.Lib/Saas.Bicep.Module/addConfigEntry.bicep' = [ for entry in appConfigStore.entries: {
name: replace('Entry-${entry.key}', ':', '-')
params: {
appConfigurationName: appConfigStore.appConfigurationName
userAssignedIdentityName: appConfigStore.userAssignedIdentityName
keyVaultName: keyVaultName
value: entry.value
contentType: entry.contentType
keyName: entry.key
label: appConfigStore.label
isSecret: entry.isSecret
}
}]

Просмотреть файл

@ -1,7 +1,22 @@
#!/usr/bin/env bash
force_update=false
while getopts f flag
do
case "${flag}" in
f) force_update=true;;
*) force_update=false;;
esac
done
repo_base="$( git rev-parse --show-toplevel )"
docker_file_folder="${repo_base}/src/Saas.lib/Deployment.Container"
# redirect to build.sh in the Deployment.Container folder
"${docker_file_folder}/build.sh"
if [[ "${force_update}" == false ]]; then
"${docker_file_folder}/build.sh"
else
"${docker_file_folder}/build.sh" -f
fi

Просмотреть файл

@ -3,23 +3,16 @@
# disable unused variable warning https://www.shellcheck.net/wiki/SC2034
# shellcheck disable=SC2034
# user directories
BASE_AZURE_CONFIG_DIR="$HOME/.azure"
B2C_USR_AZURE_CONFIG_DIR="${HOME}/b2c/.azure"
SP_USR_AZURE_CONFIG_DIR="${HOME}/sp/.azure"
# app naming
APP_NAME="permissions-api"
APP_DEPLOYMENT_NAME="permissionApi"
# repo base
repo_base="$( git rev-parse --show-toplevel )"
repo_base="$(git rev-parse --show-toplevel)"
REPO_BASE="${repo_base}"
WORKFLOW_BASE="${REPO_BASE}/.github/workflows"
PERMISSIONS_DEPLOYMENT_WORKFLOW="${WORKFLOW_BASE}/permissions-api-deploy.yml"
# script directories
BASE_DIR="${ASDK_PERMISSIONS_API_DEPLOYMENT_BASE_DIR}"
# global script directory
SHARED_MODULE_DIR="${REPO_BASE}/src/Saas.Lib/Deployment.Script.Modules"
# project base directory
BASE_DIR="${REPO_BASE}/src/Saas.Identity/Saas.Permissions/deployment"
# local script directory
SCRIPT_DIR="${BASE_DIR}/script"
@ -27,6 +20,16 @@ SCRIPT_DIR="${BASE_DIR}/script"
#local log directory
LOG_FILE_DIR="${BASE_DIR}/log"
# configuration manifest for the Identity Foundation deployment, run previously
CONFIG_DIR="${REPO_BASE}/src/Saas.Identity/Saas.IdentityProvider/deployment/config"
CONFIG_FILE="${REPO_BASE}/src/Saas.Identity/Saas.IdentityProvider/deployment/config/config.json"
# act directory
ACT_DIR="${BASE_DIR}/act"
# GitHub workflows
WORKFLOW_BASE="${REPO_BASE}/.github/workflows"
GITHUB_ACTION_WORKFLOW_FILE="${WORKFLOW_BASE}/permissions-api-deploy.yml"
ACT_LOCAL_WORKFLOW_DEBUG_FILE="${ACT_DIR}/workflows/permissions-api-deploy-debug.yml"
# global script directory
SHARED_MODULE_DIR="${REPO_BASE}/src/Saas.Lib/Deployment.Script.Modules"
# adding app service global constants
source "${SHARED_MODULE_DIR}/app-service-constants.sh"

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше