3 Commits
Author SHA1 Message Date
Torsten Brendgen dfd9c16f61 Add migration to update ApiClients with centralized authorization scopes
This migration updates the ScopesJson for the ApiClient with Id 91000000-0000-0000-0000-000000000001 to include additional authorization scopes for improved API access control. The Down method reverts the changes if necessary.
2026-07-09 23:49:01 +02:00
Torsten Brendgen dc93ea0813 feat: Implement API client and token models with hashing and JWT authentication
- Added ApiClientModel and ApiTokenModel for managing API clients and tokens.
- Introduced ConfigurationDefinitionModel and ConfigurationValueModel for configuration management.
- Created CredentialSecretModel for storing credential secrets.
- Developed DeploymentArtifactModel and DeploymentBatchModel for deployment management.
- Enhanced DeploymentTargetModel and DeploymentTemplateSelectionModel to support template revisions.
- Added TemplateRevisionModel and TemplateVersionModel for versioning templates.
- Implemented ApiClientSecretHasher for secure secret hashing.
- Created ApiTokenService for generating and validating JWT tokens.
- Updated QueueJobService to handle deployment requests with artifacts.
- Configured authentication settings in appsettings.json for JWT and Negotiate authentication.
2026-07-09 14:44:38 +02:00
Torsten Brendgen 1aa2adac08 Remove obsolete project.nuget.cache file and address security vulnerability in Microsoft.OpenApi package 2026-07-09 14:38:32 +02:00
466 changed files with 38496 additions and 19113 deletions
+17 -1
View File
@@ -4,17 +4,22 @@ obj/
out/
publish/
artifacts/
buildcheck/
*.nupkg
*.snupkg
# Visual Studio / Rider / VS Code
.vs/
.idea/
.vscode/.ropeproject
.vscode/.ionide/
*.user
*.suo
*.rsuser
*.sln.docstates
*.csproj.user
*.csproj.lscache
*.sln.DotSettings.user
# .NET / test artifacts
TestResults/
@@ -32,6 +37,8 @@ coverage.cobertura.xml
*.cache
*.bak
*.orig
*.pid
*.pid.lock
# Local configuration / secrets
appsettings.Local.json
@@ -39,9 +46,12 @@ appsettings.*.local.json
appsettings.*.Local.json
*.secrets.json
secrets.json
*.kdbx
*.key
# EF / local tooling noise
.ef/
efbundle.exe
# OS files
.DS_Store
@@ -57,4 +67,10 @@ dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
pnpm-debug.log*
# Keep source artifacts versioned:
# - Context/DemoData*.cs
# - Migrations/*.cs
# - .tests/*.ps1
# - Docs/**/*.md
+310
View File
@@ -0,0 +1,310 @@
param(
[string]$ApiBaseUrl = 'http://localhost:5286/api',
[string]$DeploymentBatchId = '80000000-0000-0000-0000-000000000101',
[int]$ApiTimeoutSec = 30,
[bool]$UseDefaultCredentials = $true,
[System.Management.Automation.PSCredential]$Credential
)
$ErrorActionPreference = 'Stop'
function Invoke-TestMergeApiJson {
param(
[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
[string]$Method,
[string]$Path
)
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
$parameters = @{
Method = $Method
Uri = $uri
TimeoutSec = $ApiTimeoutSec
ErrorAction = 'Stop'
}
if ($Credential) {
$parameters.Credential = $Credential
}
elseif ($UseDefaultCredentials) {
$parameters.UseDefaultCredentials = $true
}
try {
Invoke-RestMethod @parameters
}
catch {
$responseBody = '<empty response body>'
if ($_.Exception.Response) {
try {
$stream = $_.Exception.Response.GetResponseStream()
if ($stream) {
$reader = [System.IO.StreamReader]::new($stream)
$text = $reader.ReadToEnd()
if (-not [string]::IsNullOrWhiteSpace($text)) {
$responseBody = $text
}
}
}
catch {
$responseBody = '<could not read response body>'
}
}
throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)"
}
}
function ConvertFrom-TestJson {
param([string]$Json)
$Json | ConvertFrom-Json
}
function Get-TestDefinition {
param(
[object[]]$Definitions,
[string]$RevisionId,
[string]$Kind,
[string]$Name
)
@($Definitions | Where-Object {
[string]$_.templateRevisionId -eq $RevisionId -and
$_.kind -eq $Kind -and
$_.name -eq $Name
})[0]
}
function Get-TestFirst {
param([object[]]$Items)
@($Items | Select-Object -First 1)[0]
}
function Get-TestSelectionByRole {
param(
[object]$Composition,
[string]$Role
)
Get-TestFirst -Items @($Composition.templateSelections | Where-Object { $_.templateRole -eq $Role })
}
function Get-TestRevisionDefinitions {
param([object]$Selection)
$templateId = [string]$Selection.templateVersion.templateId
$versionId = [string]$Selection.templateVersionId
$revisionId = [string]$Selection.templateRevisionId
@(Invoke-TestMergeApiJson -Method GET -Path "Template/$templateId/Versions/$versionId/Revisions/$revisionId/Definitions")
}
function Assert-TestApiIsReachable {
try {
Invoke-TestMergeApiJson -Method GET -Path "deployment-batches/$DeploymentBatchId/composition" | Out-Null
}
catch {
if ($_.Exception.Message -match '401|Nicht autorisiert|Unauthorized') {
throw @"
API authentication failed for [$ApiBaseUrl].
Start the API with Windows authentication enabled and run the test from a session that can authenticate to it.
You can also pass explicit credentials:
Invoke-Pester -Script @{ Path = '.Net\Microsoft.SelfService.Portal.Core.API\.tests\Api.Tests.ps1'; Parameters = @{ Credential = (Get-Credential) } }
$($_.Exception.Message)
"@
}
throw
}
}
Describe ' API DemoData composition' {
BeforeAll {
Assert-TestApiIsReachable
$script:expectedTemplateAliases = @(
'Environment-Test'
'Domain-Contoso'
'Service-SharePoint-Contoso'
'Stage-Install'
)
$script:expectedTemplateRoles = @(
'Environment'
'Domain'
'Service'
'Stage'
)
$script:expectedTemplateRevisionIds = @(
'72000000-0000-0000-0000-000000000101'
'72000000-0000-0000-0000-000000000102'
'72000000-0000-0000-0000-000000000103'
'72000000-0000-0000-0000-000000000104'
)
$script:apiComposition = Invoke-TestMergeApiJson -Method GET -Path "deployment-batches/$DeploymentBatchId/composition"
$script:environmentSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Environment'
$script:domainSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Domain'
$script:sharePointSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Service'
$script:stageSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Stage'
$script:environmentRevisionId = [string]$script:environmentSelection.templateRevisionId
$script:domainRevisionId = [string]$script:domainSelection.templateRevisionId
$script:sharePointRevisionId = [string]$script:sharePointSelection.templateRevisionId
$script:environmentDefinitions = Get-TestRevisionDefinitions -Selection $script:environmentSelection
$script:domainDefinitions = Get-TestRevisionDefinitions -Selection $script:domainSelection
$script:sharePointDefinitions = Get-TestRevisionDefinitions -Selection $script:sharePointSelection
$script:stageDefinitions = Get-TestRevisionDefinitions -Selection $script:stageSelection
$script:deploymentArtifacts = @(Invoke-TestMergeApiJson -Method GET -Path "deployment-artifacts?deploymentGroupId=$DeploymentBatchId")
$script:credentialSecrets = @(Invoke-TestMergeApiJson -Method GET -Path 'credential-secrets')
$script:configurationDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-definitions')
$script:parameterDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-definitions?kind=Parameter')
$script:variableDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-definitions?kind=Variable')
$script:configurationValues = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-values')
}
It 'loads the seeded deployment composition from the API' {
$script:apiComposition.deploymentBatchId | Should Be $DeploymentBatchId
@($script:apiComposition.templateSelections).Count | Should Be 4
@($script:apiComposition.targetAssignments).Count | Should Be 3
}
It 'keeps the template selection order from the seeded deployment' {
$aliases = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.alias })
$roles = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.templateRole })
($aliases -join '|') | Should Be ($script:expectedTemplateAliases -join '|')
($roles -join '|') | Should Be ($script:expectedTemplateRoles -join '|')
}
It 'pins every template selection to an immutable template revision' {
$revisionIds = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { [string]$_.templateRevisionId })
($revisionIds -join '|') | Should Be ($script:expectedTemplateRevisionIds -join '|')
@($revisionIds | Where-Object { [string]::IsNullOrWhiteSpace($_) }).Count | Should Be 0
}
It 'loads each pinned template revision and its definitions from the API' {
foreach ($selection in @($script:apiComposition.templateSelections | Sort-Object sortOrder)) {
$templateId = [string]$selection.templateVersion.templateId
$versionId = [string]$selection.templateVersionId
$revisionId = [string]$selection.templateRevisionId
$revision = Invoke-TestMergeApiJson -Method GET -Path "Template/$templateId/Versions/$versionId/Revisions/$revisionId"
$definitions = @(Invoke-TestMergeApiJson -Method GET -Path "Template/$templateId/Versions/$versionId/Revisions/$revisionId/Definitions")
$revision.id | Should Be $revisionId
$revision.templateVersionId | Should Be $versionId
$revision.revisionNumber | Should Be 1
$revision.isCurrent | Should Be $true
$revision.isPublished | Should Be $true
if ($selection.templateRole -ne 'Stage') {
@($definitions).Count | Should BeGreaterThan 0
}
}
}
It 'exposes deployment artifact and credential store API surfaces' {
@($script:deploymentArtifacts).Count | Should Be 1
$script:deploymentArtifacts[0].deploymentGroupId | Should Be $DeploymentBatchId
$script:deploymentArtifacts[0].artifactType | Should Be 'ResolvedConfigurationData'
$script:deploymentArtifacts[0].isStale | Should Be $false
$secretNames = @($script:credentialSecrets | ForEach-Object { $_.name } | Sort-Object)
$secretNames -contains 'Windows/SharePoint/SetupAccount' | Should Be $true
$secretNames -contains 'Windows/SharePoint/FarmAccount' | Should Be $true
$secretNames -contains 'Windows/SharePoint/FarmPassphrase' | Should Be $true
$secretNames -contains 'Windows/SharePoint/DefaultServiceAccount' | Should Be $true
$secretNames -contains 'Windows/SharePoint/SearchAccount' | Should Be $true
}
It 'returns resolved deployment artifact values without reading local PSD1 files' {
$deploymentJson = ConvertFrom-TestJson -Json $script:deploymentArtifacts[0].deploymentJson
$deploymentJson.variables.DatabasePrefix | Should Be 'SharePoint_Contoso_Test'
$deploymentJson.variables.ServiceDbPrefix | Should Be 'SharePoint_Contoso_Test_Services'
$deploymentJson.variables.ConfigDbName | Should Be 'SharePoint_Contoso_Test_Farm_Config'
$deploymentJson.resources.NonNodeData.LocalConfigurationManager.ConfigurationMode | Should Be 'ApplyOnly'
@($deploymentJson.resources.AllNodes).Count | Should Be 3
$deploymentJson.resources.AllNodes[0].NodeName | Should Be 'CLD-SHP-01'
$deploymentJson.resources.AllNodes[0].RunCentralAdministration | Should Be $true
}
It 'seeds parameter and variable definitions in the Configuration Definition API' {
@($script:configurationDefinitions).Count | Should BeGreaterThan 0
@($script:parameterDefinitions).Count | Should BeGreaterThan 0
@($script:variableDefinitions).Count | Should BeGreaterThan 0
$landscape = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Parameter' -Name 'Landscape'
$domainFqdn = Get-TestDefinition -Definitions $script:domainDefinitions -RevisionId $script:domainRevisionId -Kind 'Parameter' -Name 'DomainFQDN'
$databasePrefix = Get-TestDefinition -Definitions $script:sharePointDefinitions -RevisionId $script:sharePointRevisionId -Kind 'Parameter' -Name 'DatabasePrefix'
$configDbName = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Variable' -Name 'ConfigDbName'
$landscape.id | Should Not BeNullOrEmpty
$domainFqdn.id | Should Not BeNullOrEmpty
$databasePrefix.id | Should Not BeNullOrEmpty
$configDbName.id | Should Not BeNullOrEmpty
(ConvertFrom-TestJson -Json $landscape.propertiesJson).AllowedValues -contains 'Test' | Should Be $true
(ConvertFrom-TestJson -Json $databasePrefix.propertiesJson).DefaultValue | Should Be 'SharePoint'
(ConvertFrom-TestJson -Json $configDbName.propertiesJson).Expression | Should Match 'joinNotEmpty'
}
It 'exposes configuration definitions through the standalone API and keeps revision definitions typed' {
$environmentDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-definitions?templateRevisionId=$($script:environmentRevisionId)")
$environmentVariables = @($script:environmentDefinitions | Where-Object { $_.kind -eq 'Variable' })
@($environmentDefinitions).Count | Should BeGreaterThan 0
@($environmentVariables).Count | Should BeGreaterThan 0
@($environmentVariables | Where-Object { $_.kind -ne 'Variable' }).Count | Should Be 0
$environmentDefinitionNames = @($script:environmentDefinitions | ForEach-Object { $_.name })
$environmentVariableNames = @($environmentVariables | ForEach-Object { $_.name })
$environmentDefinitionNames -contains 'Landscape' | Should Be $true
$environmentVariableNames -contains 'ConfigDbName' | Should Be $true
}
It 'seeds configuration values for parameters and variables' {
@($script:configurationValues).Count | Should BeGreaterThan 0
$landscape = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Parameter' -Name 'Landscape'
$domainFqdn = Get-TestDefinition -Definitions $script:domainDefinitions -RevisionId $script:domainRevisionId -Kind 'Parameter' -Name 'DomainFQDN'
$configDbName = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Variable' -Name 'ConfigDbName'
$landscapeValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-definitions/$($landscape.id)/values")
$domainFqdnValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-values?definitionId=$($domainFqdn.id)")
$configDbNameValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-values?definitionId=$($configDbName.id)")
@($landscapeValues).Count | Should Be 1
@($domainFqdnValues).Count | Should Be 1
@($configDbNameValues).Count | Should Be 1
(ConvertFrom-TestJson -Json $landscapeValues[0].valueJson).value | Should Be 'Test'
(ConvertFrom-TestJson -Json $domainFqdnValues[0].valueJson).value | Should Be 'contoso.local'
(ConvertFrom-TestJson -Json $configDbNameValues[0].valueJson).expression | Should Match 'joinNotEmpty'
$landscapeValues[0].scopeType | Should Be 'TemplateRevision'
[string]$landscapeValues[0].scopeId | Should Be $script:environmentRevisionId
$configDbNameValues[0].valueSourceType | Should Be 'Expression'
}
It 'marks sensitive parameter values as secret references' {
$setupCredential = Get-TestDefinition -Definitions $script:sharePointDefinitions -RevisionId $script:sharePointRevisionId -Kind 'Parameter' -Name 'SetupCredential'
$setupCredentialValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-values?definitionId=$($setupCredential.id)")
$setupValue = ConvertFrom-TestJson -Json $setupCredentialValues[0].valueJson
@($setupCredentialValues).Count | Should Be 1
$setupCredentialValues[0].valueSourceType | Should Be 'SecretReference'
$setupValue.value.Provider | Should Be 'SecretManagement'
$setupValue.value.Name | Should Be 'Windows/SharePoint/SetupAccount'
}
}
+422
View File
@@ -0,0 +1,422 @@
param(
[string]$ApiBaseUrl = 'http://localhost:5286/api',
[string]$ClientId = 'ssp-demo-worker',
[string]$ClientSecret = 'DemoOnly-DoNotUseInProduction!',
[int]$ApiTimeoutSec = 30
)
$ErrorActionPreference = 'Stop'
function Invoke-Api {
param(
[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
[string]$Method,
[string]$Path,
[object]$Body,
[string]$BearerToken,
[switch]$AllowNotFound
)
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
$parameters = @{
Method = $Method
Uri = $uri
TimeoutSec = $ApiTimeoutSec
ErrorAction = 'Stop'
}
if ($Body) {
$parameters.Body = ($Body | ConvertTo-Json -Depth 20)
$parameters.ContentType = 'application/json'
}
if ($BearerToken) {
$parameters.Headers = @{
Authorization = "Bearer $BearerToken"
}
}
try {
Invoke-RestMethod @parameters
}
catch {
$statusCode = $null
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
$statusCode = [int]$_.Exception.Response.StatusCode
}
if ($AllowNotFound -and $statusCode -eq 404) {
return $null
}
$responseBody = '<empty response body>'
if ($_.Exception.Response) {
try {
$stream = $_.Exception.Response.GetResponseStream()
if ($stream) {
$reader = [System.IO.StreamReader]::new($stream)
$text = $reader.ReadToEnd()
if (-not [string]::IsNullOrWhiteSpace($text)) {
$responseBody = $text
}
}
}
catch {
$responseBody = '<could not read response body>'
}
}
throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)"
}
}
function Get-AuthToken {
param([string]$Scope)
$token = Invoke-Api -Method POST -Path 'auth/token' -Body @{
clientId = $ClientId
clientSecret = $ClientSecret
scope = $Scope
}
return $token.accessToken
}
function Find-ByName {
param(
[object[]]$Items,
[string]$Name
)
return @($Items | Where-Object { $_.name -eq $Name })[0]
}
function ConvertTo-GuidValue {
param([object]$Value)
return [guid]@($Value)[0]
}
function ConvertTo-ApiItems {
param([object]$Value)
if ($null -eq $Value) {
return @()
}
$items = @($Value)
if ($items.Count -eq 1 -and $items[0] -is [array]) {
return @($items[0])
}
if ($items.Count -eq 1 -and $items[0].PSObject.Properties['value']) {
return @($items[0].value)
}
return $items
}
function Write-TestSection {
param([string]$Name)
Write-Host ""
Write-Host ("=== {0} ===" -f $Name) -ForegroundColor Cyan
}
function Write-TestInfo {
param(
[string]$Label,
[object]$Value
)
Write-Host (" - {0}: {1}" -f $Label, $Value)
}
Describe 'API controller workflow smoke test' {
$script:token = $null
$script:environmentId = $null
$script:domainIds = @()
$script:targetIds = @()
$script:deploymentBatchId = $null
$script:templateSelectionIds = @()
$script:targetAssignmentIds = @()
$script:configurationValueId = $null
$script:credentialSecretId = $null
$script:testRunId = ([guid]::NewGuid().ToString('N')).Substring(0, 8)
$script:environmentName = "Pester Environment $script:testRunId"
$script:domainName1 = "Pester Domain A $script:testRunId"
$script:domainName2 = "Pester Domain B $script:testRunId"
$script:targetName1 = "PST-$script:testRunId-01"
$script:targetName2 = "PST-$script:testRunId-02"
$script:targetName3 = "PST-$script:testRunId-03"
$script:credentialSecretName = "Pester/Workflow/$script:testRunId"
try {
It 'creates an environment, links two domains, creates targets and composes a deployment batch' {
Write-TestSection -Name 'test run'
Write-TestInfo -Label 'API base URL' -Value $ApiBaseUrl
Write-TestInfo -Label 'Run id' -Value $script:testRunId
Write-TestSection -Name 'auth and inventory'
$script:token = Get-AuthToken -Scope 'configuration.read configuration.write credential.read credential.write credential.resolve deployment.read deployment.write queue.read template.read token.manage token.admin'
Write-TestInfo -Label 'Bearer token' -Value 'issued'
$allowedScopesResponse = Invoke-Api -Method GET -Path 'tokens/scopes' -BearerToken $script:token
$allowedScopes = @($allowedScopesResponse)
if ($allowedScopesResponse.PSObject.Properties['value']) {
$allowedScopes = @($allowedScopesResponse.value)
}
$allowedScopes -contains 'deployment.write' | Should Be $true
Write-TestInfo -Label 'Allowed scopes' -Value @($allowedScopes).Count
$myTokens = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'tokens/my' -BearerToken $script:token))
$myTokens.Count | Should BeGreaterThan 0
Write-TestInfo -Label 'Managed tokens visible' -Value $myTokens.Count
$services = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Service' -BearerToken $script:token))
$services.Count | Should BeGreaterThan 0
Write-TestInfo -Label 'Services' -Value $services.Count
$service = $services | Select-Object -First 1
Invoke-Api -Method GET -Path ("Service/{0}" -f $service.id) -BearerToken $script:token | Should Not BeNullOrEmpty
Invoke-Api -Method GET -Path ("Service/{0}/RoleDefinitions" -f $service.id) -BearerToken $script:token | Out-Null
$templateCategories = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'TemplateCategory' -BearerToken $script:token))
$templateCategories.Count | Should BeGreaterThan 0
Write-TestInfo -Label 'Template categories' -Value $templateCategories.Count
Invoke-Api -Method GET -Path ("TemplateCategory/{0}" -f $templateCategories[0].id) -BearerToken $script:token | Should Not BeNullOrEmpty
$deploymentRules = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'deployment-rules' -BearerToken $script:token))
$deploymentRules.Count | Should BeGreaterThan 0
Write-TestInfo -Label 'Deployment rules' -Value $deploymentRules.Count
Invoke-Api -Method GET -Path ("deployment-rules/{0}" -f $deploymentRules[0].id) -BearerToken $script:token | Should Not BeNullOrEmpty
$configurationDefinitions = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'configuration-definitions' -BearerToken $script:token))
$configurationDefinitions.Count | Should BeGreaterThan 0
Write-TestInfo -Label 'Configuration definitions' -Value $configurationDefinitions.Count
$configurationDefinition = $configurationDefinitions | Select-Object -First 1
Invoke-Api -Method GET -Path ("configuration-definitions/{0}" -f $configurationDefinition.id) -BearerToken $script:token | Should Not BeNullOrEmpty
Invoke-Api -Method GET -Path ("configuration-definitions/{0}/values" -f $configurationDefinition.id) -BearerToken $script:token | Out-Null
Write-TestSection -Name 'credential secret'
$script:credentialSecretId = ConvertTo-GuidValue -Value (Invoke-Api -Method POST -Path 'credential-secrets' -BearerToken $script:token -Body @{
name = $script:credentialSecretName
userName = "CONTOSO\svc-pester-$script:testRunId"
secretValue = 'PesterSecret!'
secretType = 'Credential'
metadataJson = (@{ source = 'Api.Workflow.Tests.ps1'; runId = $script:testRunId } | ConvertTo-Json -Compress)
isEnabled = $true
})
Write-TestInfo -Label 'Created credential secret' -Value $script:credentialSecretId
Invoke-Api -Method GET -Path ("credential-secrets/{0}" -f $script:credentialSecretId) -BearerToken $script:token | Should Not BeNullOrEmpty
$resolvedSecret = Invoke-Api -Method GET -Path ("credential-secrets/resolve?name={0}" -f [uri]::EscapeDataString($script:credentialSecretName)) -BearerToken $script:token
$resolvedSecret.userName | Should Be "CONTOSO\svc-pester-$script:testRunId"
Write-TestInfo -Label 'Resolved credential user' -Value $resolvedSecret.userName
Write-TestSection -Name 'environment and domains'
Invoke-Api -Method POST -Path 'Environment' -BearerToken $script:token -Body @{
name = $script:environmentName
environmentType = 'Test'
hostingType = 'OnPrem'
providerType = 'Pester'
metadataJson = (@{ source = 'Api.Workflow.Tests.ps1'; runId = $script:testRunId } | ConvertTo-Json -Compress)
} | Out-Null
$environment = Find-ByName -Items @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Environment' -BearerToken $script:token)) -Name $script:environmentName
$environment | Should Not BeNullOrEmpty
$script:environmentId = ConvertTo-GuidValue -Value $environment.id
Write-TestInfo -Label 'Created environment' -Value ("{0} ({1})" -f $script:environmentName, $script:environmentId)
$script:configurationValueId = ConvertTo-GuidValue -Value (Invoke-Api -Method POST -Path 'configuration-values' -BearerToken $script:token -Body @{
configurationDefinitionId = [string](ConvertTo-GuidValue -Value $configurationDefinition.id)
scopeType = 'Environment'
scopeId = [string]$script:environmentId
valueSourceType = 'Static'
valueJson = (@{ value = "Pester-$script:testRunId" } | ConvertTo-Json -Compress)
sortOrder = 999
})
@(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path ("configuration-values?scopeType=Environment&scopeId={0}" -f $script:environmentId) -BearerToken $script:token)).Count | Should BeGreaterThan 0
Write-TestInfo -Label 'Created configuration value' -Value $script:configurationValueId
$domainId1 = Invoke-Api -Method POST -Path 'Domain' -BearerToken $script:token -Body @{
name = $script:domainName1
fqdn = "pester-a-$script:testRunId.contoso.local"
netBIOS = "PSTA$($script:testRunId.Substring(0, 4))".ToUpper()
}
$domainId2 = Invoke-Api -Method POST -Path 'Domain' -BearerToken $script:token -Body @{
name = $script:domainName2
fqdn = "pester-b-$script:testRunId.contoso.local"
netBIOS = "PSTB$($script:testRunId.Substring(0, 4))".ToUpper()
}
$script:domainIds = @(
(ConvertTo-GuidValue -Value $domainId1),
(ConvertTo-GuidValue -Value $domainId2)
)
Write-TestInfo -Label 'Created domains' -Value ($script:domainIds -join ', ')
foreach ($domainId in $script:domainIds) {
Invoke-Api -Method POST -Path ("Domain/{0}/Environment/{1}" -f $domainId, $script:environmentId) -BearerToken $script:token | Out-Null
}
Write-TestInfo -Label 'Linked domains to environment' -Value $script:domainIds.Count
Write-TestSection -Name 'targets'
$targetDefinitions = @(
@{ name = $script:targetName1; domainId = $script:domainIds[0]; role = 'WebFrontEnd'; order = 10 },
@{ name = $script:targetName2; domainId = $script:domainIds[0]; role = 'Application'; order = 20 },
@{ name = $script:targetName3; domainId = $script:domainIds[1]; role = 'Search'; order = 30 }
)
foreach ($targetDefinition in $targetDefinitions) {
$targetId = Invoke-Api -Method POST -Path 'Target' -BearerToken $script:token -Body @{
domainID = $targetDefinition.domainId
name = $targetDefinition.name
targetType = 'VirtualMachine'
providerType = 'OnPrem'
externalId = $targetDefinition.name
metadataJson = (@{ source = 'Api.Workflow.Tests.ps1'; role = $targetDefinition.role } | ConvertTo-Json -Compress)
}
$script:targetIds += ConvertTo-GuidValue -Value $targetId
Write-TestInfo -Label 'Created target' -Value ("{0} / {1} ({2})" -f $targetDefinition.name, $targetDefinition.role, $targetId)
}
@($script:targetIds).Count | Should Be 3
Write-TestSection -Name 'template selection'
$templates = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Template' -BearerToken $script:token))
$onPremServiceIds = @(
$services |
Where-Object { -not $_.isCloudService } |
ForEach-Object { [string]$_.id }
)
$onPremCategoryIds = @(
$templateCategories |
Where-Object { $onPremServiceIds -contains [string]$_.serviceId } |
ForEach-Object { [string]$_.id }
)
$selectedTemplates = @(
$templates |
Where-Object { $onPremCategoryIds -contains [string]$_.templateCategoryId } |
Select-Object -First 2
)
@($selectedTemplates).Count | Should BeGreaterThan 0
Write-TestInfo -Label 'Available templates' -Value $templates.Count
Write-TestInfo -Label 'Selected templates' -Value (($selectedTemplates | ForEach-Object { $_.name }) -join ', ')
$templateSelections = @()
$sortOrder = 10
foreach ($template in $selectedTemplates) {
$versions = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path ("Template/{0}/Versions" -f $template.id) -BearerToken $script:token))
$version = $versions | Select-Object -First 1
$version | Should Not BeNullOrEmpty
$templateSelections += @{
templateVersionId = [string](ConvertTo-GuidValue -Value $version.id)
templateRole = 'Service'
sortOrder = $sortOrder
alias = "Pester-$sortOrder"
}
Write-TestInfo -Label 'Pinned template version' -Value ("{0} -> {1}" -f $template.name, $version.id)
$sortOrder += 10
}
$targetAssignments = @()
foreach ($targetDefinition in $targetDefinitions) {
$targetId = $script:targetIds[$targetDefinitions.IndexOf($targetDefinition)]
$targetAssignments += @{
targetId = [string]$targetId
roleKey = $targetDefinition.role
sortOrder = $targetDefinition.order
nodeDataJson = (@{ nodeName = $targetDefinition.name; pester = $true } | ConvertTo-Json -Compress)
}
}
Write-TestSection -Name 'deployment batch'
$script:deploymentBatchId = ConvertTo-GuidValue -Value (Invoke-Api -Method POST -Path 'deployment-batches' -BearerToken $script:token -Body @{
status = 'Pending'
targetIds = @()
templateSelections = $templateSelections
targetAssignments = $targetAssignments
})
$script:deploymentBatchId | Should Not BeNullOrEmpty
Write-TestInfo -Label 'Created deployment batch' -Value $script:deploymentBatchId
$composition = Invoke-Api -Method GET -Path ("deployment-batches/{0}/composition" -f $script:deploymentBatchId) -BearerToken $script:token
@($composition.templateSelections).Count | Should Be @($templateSelections).Count
$composition = Invoke-Api -Method GET -Path ("deployment-batches/{0}/composition" -f $script:deploymentBatchId) -BearerToken $script:token
@($composition.targetAssignments).Count | Should Be 3
$script:targetAssignmentIds = @($composition.targetAssignments | ForEach-Object { ConvertTo-GuidValue -Value $_.id })
Write-TestInfo -Label 'Composition template selections' -Value @($composition.templateSelections).Count
Write-TestInfo -Label 'Composition target assignments' -Value @($composition.targetAssignments).Count
$deployments = @(ConvertTo-ApiItems -Value (Invoke-Api -Method GET -Path 'Deployment' -BearerToken $script:token) | Where-Object { [string]$_.deploymentGroupId -eq [string]$script:deploymentBatchId })
$deployments.Count | Should Be 3
Write-TestInfo -Label 'Deployment rows' -Value $deployments.Count
Invoke-Api -Method GET -Path ("Deployment/{0}" -f $deployments[0].id) -BearerToken $script:token | Should Not BeNullOrEmpty
Invoke-Api -Method GET -Path ("deployment-artifacts?deploymentGroupId={0}" -f $script:deploymentBatchId) -BearerToken $script:token | Out-Null
Invoke-Api -Method GET -Path 'Deployment/QueueJobs' -BearerToken $script:token | Out-Null
Write-TestInfo -Label 'Artifact and queue endpoints' -Value 'reachable'
}
}
finally {
if ($script:token) {
Write-TestSection -Name 'cleanup'
if ($script:credentialSecretId) {
Invoke-Api -Method DELETE -Path ("credential-secrets/{0}" -f $script:credentialSecretId) -BearerToken $script:token -AllowNotFound | Out-Null
Write-TestInfo -Label 'Deleted credential secret' -Value $script:credentialSecretId
}
if ($script:configurationValueId) {
Invoke-Api -Method DELETE -Path ("configuration-values/{0}" -f $script:configurationValueId) -BearerToken $script:token -AllowNotFound | Out-Null
Write-TestInfo -Label 'Deleted configuration value' -Value $script:configurationValueId
}
foreach ($assignmentId in @($script:targetAssignmentIds)) {
if ($script:deploymentBatchId) {
Invoke-Api -Method DELETE -Path ("deployment-batches/{0}/target-assignments/{1}" -f $script:deploymentBatchId, $assignmentId) -BearerToken $script:token -AllowNotFound | Out-Null
}
}
if ($script:deploymentBatchId) {
Invoke-Api -Method DELETE -Path ("deployment-batches/{0}" -f $script:deploymentBatchId) -BearerToken $script:token -AllowNotFound | Out-Null
Write-TestInfo -Label 'Deleted deployment batch' -Value $script:deploymentBatchId
}
foreach ($targetId in @($script:targetIds)) {
Invoke-Api -Method DELETE -Path ("Target/{0}" -f $targetId) -BearerToken $script:token -AllowNotFound | Out-Null
}
if (@($script:targetIds).Count -gt 0) {
Write-TestInfo -Label 'Deleted targets' -Value @($script:targetIds).Count
}
foreach ($domainId in @($script:domainIds)) {
if ($script:environmentId) {
Invoke-Api -Method DELETE -Path ("Domain/{0}/Environment/{1}" -f $domainId, $script:environmentId) -BearerToken $script:token -AllowNotFound | Out-Null
}
Invoke-Api -Method DELETE -Path ("Domain/{0}" -f $domainId) -BearerToken $script:token -AllowNotFound | Out-Null
}
if (@($script:domainIds).Count -gt 0) {
Write-TestInfo -Label 'Deleted domains' -Value @($script:domainIds).Count
}
if ($script:environmentId) {
Invoke-Api -Method DELETE -Path ("Environment/{0}" -f $script:environmentId) -BearerToken $script:token -AllowNotFound | Out-Null
Write-TestInfo -Label 'Deleted environment' -Value $script:environmentId
}
}
}
}
-421
View File
@@ -1,421 +0,0 @@
param(
[string]$ApiBaseUrl = 'http://localhost:5286/api',
[string]$BgwRoot = ('F:\Kunden Auftr' + [char]0x00E4 + 'ge\BGW'),
[string]$DeploymentBatchId = '80000000-0000-0000-0000-000000000101',
[string]$MergeModulePath = 'F:\Projekte\Coding\PowerShell\Merge-DSCConfigurationData\Merge-DSCConfigurationData.psd1',
[string]$ResolveModulePath = 'F:\Projekte\Coding\PowerShell\Resolve-DSCConfigurationData\Resolve-DSCConfigurationData.psd1',
[int]$ApiTimeoutSec = 30,
[bool]$UseDefaultCredentials = $true
)
$ErrorActionPreference = 'Stop'
function Invoke-TestBgwMergeApiJson {
param(
[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
[string]$Method,
[string]$Path
)
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
$parameters = @{
Method = $Method
Uri = $uri
TimeoutSec = $ApiTimeoutSec
ErrorAction = 'Stop'
}
if ($UseDefaultCredentials) {
$parameters.UseDefaultCredentials = $true
}
try {
Invoke-RestMethod @parameters
}
catch {
$responseBody = '<empty response body>'
if ($_.Exception.Response) {
try {
$stream = $_.Exception.Response.GetResponseStream()
if ($stream) {
$reader = [System.IO.StreamReader]::new($stream)
$text = $reader.ReadToEnd()
if (-not [string]::IsNullOrWhiteSpace($text)) {
$responseBody = $text
}
}
}
catch {
$responseBody = '<could not read response body>'
}
}
throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)"
}
}
function ConvertTo-TestBgwMergeConfigurationValue {
param([object]$Value)
if ($null -eq $Value) {
return $null
}
if ($Value -is [string] -or $Value -is [bool] -or $Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
return $Value
}
if ($Value -is [System.Collections.IDictionary]) {
$result = [ordered]@{}
foreach ($key in $Value.Keys) {
$result[[string]$key] = ConvertTo-TestBgwMergeConfigurationValue -Value $Value[$key]
}
Write-Output -NoEnumerate $result
return
}
if ($Value -is [pscustomobject]) {
$result = [ordered]@{}
foreach ($property in $Value.PSObject.Properties) {
$result[$property.Name] = ConvertTo-TestBgwMergeConfigurationValue -Value $property.Value
}
Write-Output -NoEnumerate $result
return
}
if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
$items = New-Object System.Collections.Generic.List[object]
foreach ($item in $Value) {
$items.Add((ConvertTo-TestBgwMergeConfigurationValue -Value $item))
}
Write-Output -NoEnumerate $items.ToArray()
return
}
return $Value
}
function ConvertFrom-TestBgwMergeTemplateJson {
param([string]$JsonData)
$document = $JsonData | ConvertFrom-Json
$metadata = ConvertTo-TestBgwMergeConfigurationValue -Value $document.metadata
if ($null -eq $metadata) {
$metadata = [ordered]@{}
}
if (-not $metadata.Contains('TemplateType')) {
$metadata['TemplateType'] = $document.templateType
}
[ordered]@{
Metadata = $metadata
Parameters = ConvertTo-TestBgwMergeConfigurationValue -Value $document.parameters
Variables = ConvertTo-TestBgwMergeConfigurationValue -Value $document.variables
Resources = ConvertTo-TestBgwMergeConfigurationValue -Value $document.resources
}
}
function ConvertFrom-TestBgwMergeTargetAssignments {
param([object[]]$TargetAssignments)
@($TargetAssignments | Sort-Object sortOrder | ForEach-Object {
$nodeData = ConvertTo-TestBgwMergeConfigurationValue -Value ($_.nodeDataJson | ConvertFrom-Json)
$node = @{}
foreach ($key in $nodeData.Keys) {
if ($key -eq 'nodeName') {
$node.NodeName = $nodeData[$key]
}
else {
$node[$key] = $nodeData[$key]
}
}
$node
})
}
function ConvertTo-TestBgwMergeStableValue {
param([object]$Value)
if ($null -eq $Value) {
return $null
}
if ($Value -is [string] -or $Value -is [bool] -or $Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
return $Value
}
if ($Value -is [System.Collections.IDictionary]) {
$result = [ordered]@{}
foreach ($key in @($Value.Keys | Sort-Object)) {
$result[[string]$key] = ConvertTo-TestBgwMergeStableValue -Value $Value[$key]
}
Write-Output -NoEnumerate $result
return
}
if ($Value -is [pscustomobject]) {
$result = [ordered]@{}
foreach ($property in @($Value.PSObject.Properties | Sort-Object Name)) {
$result[$property.Name] = ConvertTo-TestBgwMergeStableValue -Value $property.Value
}
Write-Output -NoEnumerate $result
return
}
if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
$items = New-Object System.Collections.Generic.List[object]
foreach ($item in $Value) {
$items.Add((ConvertTo-TestBgwMergeStableValue -Value $item))
}
Write-Output -NoEnumerate $items.ToArray()
return
}
return $Value
}
function ConvertTo-TestBgwMergeStableJson {
param([object]$Value)
(ConvertTo-TestBgwMergeStableValue -Value $Value) | ConvertTo-Json -Depth 100 -Compress
}
function Invoke-TestBgwMergeSnapshotScript {
param([string]$Script)
$encodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Script))
$output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encodedCommand
if ($LASTEXITCODE -ne 0) {
throw "Snapshot PowerShell process failed with exit code [$LASTEXITCODE]. Output: $($output -join [Environment]::NewLine)"
}
$json = ($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) -join [Environment]::NewLine
if ([string]::IsNullOrWhiteSpace($json)) {
throw 'Snapshot PowerShell process returned no JSON output.'
}
$json | ConvertFrom-Json
}
function New-TestBgwMergeSnapshotScript {
param(
[string]$ConfigurationDataPath,
[switch]$BuildLocalBaseline
)
$escapedBgwRoot = $BgwRoot.Replace("'", "''")
$escapedMergeModulePath = $MergeModulePath.Replace("'", "''")
$escapedResolveModulePath = $ResolveModulePath.Replace("'", "''")
$escapedConfigurationDataPath = if ($ConfigurationDataPath) { $ConfigurationDataPath.Replace("'", "''") } else { '' }
$buildLocalBaselineText = if ($BuildLocalBaseline) { 'True' } else { 'False' }
@"
`$ErrorActionPreference = 'Stop'
`$ProgressPreference = 'SilentlyContinue'
Import-Module '$escapedMergeModulePath' -Force
Import-Module '$escapedResolveModulePath' -Force
function ConvertTo-SnapshotStableValue {
param([object]`$Value)
if (`$null -eq `$Value) { return `$null }
if (`$Value -is [string] -or `$Value -is [bool] -or `$Value -is [int] -or `$Value -is [long] -or `$Value -is [double] -or `$Value -is [decimal]) { return `$Value }
if (`$Value -is [System.Collections.IDictionary]) {
`$result = [ordered]@{}
foreach (`$key in @(`$Value.Keys | Sort-Object)) {
`$result[[string]`$key] = ConvertTo-SnapshotStableValue -Value `$Value[`$key]
}
Write-Output -NoEnumerate `$result
return
}
if (`$Value -is [pscustomobject]) {
`$result = [ordered]@{}
foreach (`$property in @(`$Value.PSObject.Properties | Sort-Object Name)) {
`$result[`$property.Name] = ConvertTo-SnapshotStableValue -Value `$property.Value
}
Write-Output -NoEnumerate `$result
return
}
if (`$Value -is [System.Collections.IEnumerable] -and `$Value -isnot [string]) {
`$items = New-Object System.Collections.Generic.List[object]
foreach (`$item in `$Value) {
`$items.Add((ConvertTo-SnapshotStableValue -Value `$item))
}
Write-Output -NoEnumerate `$items.ToArray()
return
}
return `$Value
}
function ConvertTo-SnapshotStableJson {
param([object]`$Value)
(ConvertTo-SnapshotStableValue -Value `$Value) | ConvertTo-Json -Depth 100 -Compress
}
if ('$buildLocalBaselineText' -eq 'True') {
`$root = '$escapedBgwRoot'
`$sourceTemplates = @(
(Join-Path -Path `$root -ChildPath 'Environment\Test.psd1')
(Join-Path -Path `$root -ChildPath 'Domain\Contoso.psd1')
(Join-Path -Path `$root -ChildPath 'Service\SharePoint\Contoso.psd1')
(Join-Path -Path `$root -ChildPath 'Stage\Install.psd1')
)
`$allNodes = @(
@{ NodeName = 'CLD-SHP-01'; RunCentralAdministration = `$true }
@{ NodeName = 'CLD-SHP-02'; RunCentralAdministration = `$false }
@{ NodeName = 'CLD-SHP-03'; RunCentralAdministration = `$false }
)
`$configurationData = New-DSCConfigurationDataDeployment -Name 'Contoso-Test-SharePoint' -DeploymentId '8f6c2c1a' -SourceTemplatePath `$sourceTemplates -AllNodes `$allNodes
}
else {
`$configurationData = Import-PowerShellDataFile -LiteralPath '$escapedConfigurationDataPath'
}
`$resolved = Resolve-DSCConfigurationData -ConfigurationData `$configurationData -SkipSecrets
`$snapshot = [ordered]@{
ParametersJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Parameters
VariablesJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Variables
ResourcesJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Resources
DatabasePrefix = `$resolved.Variables.DatabasePrefix
ServiceDbPrefix = `$resolved.Variables.ServiceDbPrefix
ConfigDbName = `$resolved.Variables.ConfigDbName
LcmConfigurationMode = `$resolved.Resources.NonNodeData.LocalConfigurationManager.ConfigurationMode
AllNodes = @(`$resolved.Resources.AllNodes | Sort-Object NodeName | ForEach-Object {
[ordered]@{
NodeName = `$_.NodeName
RunCentralAdministration = `$_.RunCentralAdministration
}
})
}
`$snapshot | ConvertTo-Json -Depth 100 -Compress
"@
}
function New-TestBgwMergeLocalSnapshot {
Invoke-TestBgwMergeSnapshotScript -Script (New-TestBgwMergeSnapshotScript -BuildLocalBaseline)
}
function New-TestBgwMergeSnapshotFromConfigurationData {
param([hashtable]$ConfigurationData)
Import-Module $MergeModulePath -Force
$tempPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("BgwMergeApiSnapshot-{0}.psd1" -f ([guid]::NewGuid().ToString('N')))
try {
Export-PowerShellDataFile -InputObject $ConfigurationData -Path $tempPath -Force
Invoke-TestBgwMergeSnapshotScript -Script (New-TestBgwMergeSnapshotScript -ConfigurationDataPath $tempPath)
}
finally {
if (Test-Path -LiteralPath $tempPath) {
Remove-Item -LiteralPath $tempPath -Force
}
}
}
function New-TestBgwMergeApiConfigurationData {
param([object]$Composition)
Import-Module $MergeModulePath -Force
$mergedTemplateData = @{}
foreach ($selection in @($Composition.templateSelections | Sort-Object sortOrder)) {
$templateData = ConvertFrom-TestBgwMergeTemplateJson -JsonData $selection.templateVersion.jsonData
$mergedTemplateData = Merge-DSCConfigurationData -Template $mergedTemplateData -Deployment $templateData
}
$deploymentData = @{
Resources = @{
AllNodes = ConvertFrom-TestBgwMergeTargetAssignments -TargetAssignments @($Composition.targetAssignments)
}
}
Merge-DSCConfigurationData -Template $mergedTemplateData -Deployment $deploymentData
}
Describe 'BGW Test.Merge.ps1 API DemoData composition' {
BeforeAll {
$script:expectedTemplateAliases = @(
'Environment-Test'
'Domain-Contoso'
'Service-SharePoint-Contoso'
'Stage-Install'
)
$script:expectedTemplateRoles = @(
'Environment'
'Domain'
'Service'
'Stage'
)
$script:localSnapshot = New-TestBgwMergeLocalSnapshot
$script:apiComposition = Invoke-TestBgwMergeApiJson -Method GET -Path "deployment-batches/$DeploymentBatchId/composition"
$script:apiConfigurationData = New-TestBgwMergeApiConfigurationData -Composition $script:apiComposition
$script:apiSnapshot = New-TestBgwMergeSnapshotFromConfigurationData -ConfigurationData $script:apiConfigurationData
}
It 'loads the seeded deployment composition from the API' {
$script:apiComposition.deploymentBatchId | Should Be $DeploymentBatchId
@($script:apiComposition.templateSelections).Count | Should Be 4
@($script:apiComposition.targetAssignments).Count | Should Be 3
}
It 'keeps the template selection order from Test.Merge.ps1' {
$aliases = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.alias })
$roles = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.templateRole })
($aliases -join '|') | Should Be ($script:expectedTemplateAliases -join '|')
($roles -join '|') | Should Be ($script:expectedTemplateRoles -join '|')
}
It 'keeps the AllNodes data from Test.Merge.ps1' {
$nodes = @($script:apiSnapshot.AllNodes | Sort-Object NodeName)
$nodes.Count | Should Be 3
$nodes[0].NodeName | Should Be 'CLD-SHP-01'
$nodes[0].RunCentralAdministration | Should Be $true
$nodes[1].NodeName | Should Be 'CLD-SHP-02'
$nodes[1].RunCentralAdministration | Should Be $false
$nodes[2].NodeName | Should Be 'CLD-SHP-03'
$nodes[2].RunCentralAdministration | Should Be $false
}
It 'resolves the same core values as the local Test.Merge.ps1 baseline' {
$script:apiSnapshot.DatabasePrefix | Should Be $script:localSnapshot.DatabasePrefix
$script:apiSnapshot.ServiceDbPrefix | Should Be $script:localSnapshot.ServiceDbPrefix
$script:apiSnapshot.ConfigDbName | Should Be $script:localSnapshot.ConfigDbName
$script:apiSnapshot.LcmConfigurationMode | Should Be $script:localSnapshot.LcmConfigurationMode
}
It 'matches merged Parameters, Variables and Resources' {
$script:apiSnapshot.ParametersJson | Should Be $script:localSnapshot.ParametersJson
$script:apiSnapshot.VariablesJson | Should Be $script:localSnapshot.VariablesJson
$script:apiSnapshot.ResourcesJson | Should Be $script:localSnapshot.ResourcesJson
}
}
-42
View File
@@ -1,42 +0,0 @@
[CmdletBinding()]
param(
[string]$ApiBaseUrl = 'http://localhost:5286/api',
[string]$BgwRoot = ('F:\Kunden Auftr' + [char]0x00E4 + 'ge\BGW'),
[string]$DeploymentBatchId = '80000000-0000-0000-0000-000000000101',
[string]$MergeModulePath = 'F:\Projekte\Coding\PowerShell\Merge-DSCConfigurationData\Merge-DSCConfigurationData.psd1',
[string]$ResolveModulePath = 'F:\Projekte\Coding\PowerShell\Resolve-DSCConfigurationData\Resolve-DSCConfigurationData.psd1',
[int]$ApiTimeoutSec = 30,
[switch]$UseDefaultCredentials = $true
)
$ErrorActionPreference = 'Stop'
$testPath = Join-Path -Path $PSScriptRoot -ChildPath 'BgwMerge.Api.Tests.ps1'
if (-not (Test-Path -LiteralPath $testPath -PathType Leaf)) {
throw "Pester test file [$testPath] was not found."
}
if (-not (Get-Command Invoke-Pester -ErrorAction SilentlyContinue)) {
throw 'Pester is required. Install-Module Pester or run this on a machine where Pester is available.'
}
$parameters = @{
ApiBaseUrl = $ApiBaseUrl
BgwRoot = $BgwRoot
DeploymentBatchId = $DeploymentBatchId
MergeModulePath = $MergeModulePath
ResolveModulePath = $ResolveModulePath
ApiTimeoutSec = $ApiTimeoutSec
UseDefaultCredentials = [bool]$UseDefaultCredentials
}
$result = Invoke-Pester -Script @{ Path = $testPath; Parameters = $parameters } -PassThru
if ($result.FailedCount -gt 0) {
throw "Pester test run failed. Passed=[$($result.PassedCount)] Failed=[$($result.FailedCount)] Skipped=[$($result.SkippedCount)]."
}
@@ -0,0 +1,193 @@
param(
[string]$ApiBaseUrl = 'http://localhost:5286/api',
[string]$ClientId = 'ssp-demo-worker',
[string]$ClientSecret = 'DemoOnly-DoNotUseInProduction!',
[int]$ApiTimeoutSec = 30
)
$ErrorActionPreference = 'Stop'
function Invoke-TestApi {
param(
[ValidateSet('GET', 'POST', 'DELETE')]
[string]$Method,
[string]$Path,
[object]$Body,
[string]$BearerToken
)
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
$parameters = @{
Method = $Method
Uri = $uri
TimeoutSec = $ApiTimeoutSec
ErrorAction = 'Stop'
}
if ($Body) {
$parameters.Body = ($Body | ConvertTo-Json -Depth 10)
$parameters.ContentType = 'application/json'
}
if ($BearerToken) {
$parameters.Headers = @{
Authorization = "Bearer $BearerToken"
}
}
try {
Invoke-RestMethod @parameters
}
catch {
$responseBody = '<empty response body>'
if ($_.Exception.Response) {
try {
$stream = $_.Exception.Response.GetResponseStream()
if ($stream) {
$reader = [System.IO.StreamReader]::new($stream)
$text = $reader.ReadToEnd()
if (-not [string]::IsNullOrWhiteSpace($text)) {
$responseBody = $text
}
}
}
catch {
$responseBody = '<could not read response body>'
}
}
throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)"
}
}
function Invoke-TestApiExpectFailure {
param(
[ValidateSet('GET', 'POST', 'DELETE')]
[string]$Method,
[string]$Path,
[object]$Body,
[string]$BearerToken
)
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
$parameters = @{
Method = $Method
Uri = $uri
TimeoutSec = $ApiTimeoutSec
ErrorAction = 'Stop'
}
if ($Body) {
$parameters.Body = ($Body | ConvertTo-Json -Depth 10)
$parameters.ContentType = 'application/json'
}
if ($BearerToken) {
$parameters.Headers = @{
Authorization = "Bearer $BearerToken"
}
}
try {
Invoke-RestMethod @parameters | Out-Null
throw "API request was expected to fail but succeeded. Method=[$Method], Uri=[$uri]."
}
catch {
if ($_.Exception.Response -and $_.Exception.Response.StatusCode) {
return [int]$_.Exception.Response.StatusCode
}
throw
}
}
Describe 'On-prem client credentials API authentication' {
It 'issues a bearer token for the seeded demo worker client' {
$token = Invoke-TestApi -Method POST -Path 'auth/token' -Body @{
clientId = $ClientId
clientSecret = $ClientSecret
scope = 'configuration.read'
}
$token.accessToken | Should Not BeNullOrEmpty
$token.tokenType | Should Be 'Bearer'
$token.expiresIn | Should BeGreaterThan 0
$token.scope | Should Be 'configuration.read'
}
It 'allows bearer-token API calls without Windows authentication' {
$token = Invoke-TestApi -Method POST -Path 'auth/token' -Body @{
clientId = $ClientId
clientSecret = $ClientSecret
scope = 'configuration.read'
}
$definitions = @(Invoke-TestApi -Method GET -Path 'configuration-definitions?kind=Parameter' -BearerToken $token.accessToken)
@($definitions).Count | Should BeGreaterThan 0
@($definitions | Where-Object { $_.kind -ne 'Parameter' }).Count | Should Be 0
}
It 'rejects bearer-token API calls without the required scope' {
$token = Invoke-TestApi -Method POST -Path 'auth/token' -Body @{
clientId = $ClientId
clientSecret = $ClientSecret
scope = 'deployment.read'
}
$statusCode = Invoke-TestApiExpectFailure -Method GET -Path 'configuration-definitions?kind=Parameter' -BearerToken $token.accessToken
@(401, 403) -contains $statusCode | Should Be $true
}
It 'creates, lists and revokes managed tokens through the token API' {
$adminToken = Invoke-TestApi -Method POST -Path 'auth/token' -Body @{
clientId = $ClientId
clientSecret = $ClientSecret
scope = 'token.manage token.admin deployment.read'
}
$managedToken = Invoke-TestApi -Method POST -Path 'tokens/my' -BearerToken $adminToken.accessToken -Body @{
name = 'Pester managed token'
scope = 'deployment.read'
}
$managedToken.id | Should Not BeNullOrEmpty
$managedToken.accessToken | Should Not BeNullOrEmpty
$managedToken.scope | Should Be 'deployment.read'
$myTokens = @(Invoke-TestApi -Method GET -Path 'tokens/my' -BearerToken $adminToken.accessToken)
@($myTokens | Where-Object { $_.id -eq $managedToken.id }).Count | Should Be 1
$allTokens = @(Invoke-TestApi -Method GET -Path 'tokens/admin' -BearerToken $adminToken.accessToken)
@($allTokens | Where-Object { $_.id -eq $managedToken.id }).Count | Should Be 1
$deletedToken = Invoke-TestApi -Method DELETE -Path ('tokens/my/{0}' -f $managedToken.id) -BearerToken $adminToken.accessToken
$deletedToken.id | Should Be $managedToken.id
$deletedToken.revokedAt | Should Not BeNullOrEmpty
$deletedToken.isActive | Should Be $false
$myTokensAfterDelete = @(Invoke-TestApi -Method GET -Path 'tokens/my' -BearerToken $adminToken.accessToken)
$myTokenAfterDelete = $myTokensAfterDelete | Where-Object { [string]$_.id -eq [string]$managedToken.id } | Select-Object -First 1
if ($myTokenAfterDelete) {
$myTokenAfterDelete.isActive | Should Be $false
}
$adminTokensAfterDelete = @(Invoke-TestApi -Method GET -Path 'tokens/admin' -BearerToken $adminToken.accessToken)
$revokedToken = $adminTokensAfterDelete | Where-Object { [string]$_.id -eq [string]$managedToken.id } | Select-Object -First 1
if ($revokedToken) {
$revokedToken.revokedAt | Should Not BeNullOrEmpty
$revokedToken.isActive | Should Be $false
}
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace Microsoft.SelfService.Portal.Core.API.Authorization
{
public static class ApiPolicies
{
public const string ConfigurationRead = "ConfigurationRead";
public const string ConfigurationWrite = "ConfigurationWrite";
public const string CredentialRead = "CredentialRead";
public const string CredentialWrite = "CredentialWrite";
public const string CredentialResolve = "CredentialResolve";
public const string DeploymentRead = "DeploymentRead";
public const string DeploymentWrite = "DeploymentWrite";
public const string QueueRead = "QueueRead";
public const string QueueProcess = "QueueProcess";
public const string TemplateRead = "TemplateRead";
public const string TemplateWrite = "TemplateWrite";
public const string TokenManage = "TokenManage";
public const string TokenAdmin = "TokenAdmin";
}
}
+19
View File
@@ -0,0 +1,19 @@
namespace Microsoft.SelfService.Portal.Core.API.Authorization
{
public static class ApiScopes
{
public const string ConfigurationRead = "configuration.read";
public const string ConfigurationWrite = "configuration.write";
public const string CredentialRead = "credential.read";
public const string CredentialWrite = "credential.write";
public const string CredentialResolve = "credential.resolve";
public const string DeploymentRead = "deployment.read";
public const string DeploymentWrite = "deployment.write";
public const string QueueRead = "queue.read";
public const string QueueProcess = "queue.process";
public const string TemplateRead = "template.read";
public const string TemplateWrite = "template.write";
public const string TokenManage = "token.manage";
public const string TokenAdmin = "token.admin";
}
}
@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Authorization;
namespace Microsoft.SelfService.Portal.Core.API.Authorization
{
public static class AuthorizationServiceCollectionExtensions
{
public static IServiceCollection AddSelfServicePortalAuthorization(this IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
AddScopePolicy(options, ApiPolicies.ConfigurationRead, ApiScopes.ConfigurationRead, ApiScopes.ConfigurationWrite);
AddScopePolicy(options, ApiPolicies.ConfigurationWrite, ApiScopes.ConfigurationWrite);
AddScopePolicy(options, ApiPolicies.CredentialRead, ApiScopes.CredentialRead, ApiScopes.CredentialWrite, ApiScopes.CredentialResolve);
AddScopePolicy(options, ApiPolicies.CredentialWrite, ApiScopes.CredentialWrite);
AddScopePolicy(options, ApiPolicies.CredentialResolve, ApiScopes.CredentialResolve);
AddScopePolicy(options, ApiPolicies.DeploymentRead, ApiScopes.DeploymentRead, ApiScopes.DeploymentWrite);
AddScopePolicy(options, ApiPolicies.DeploymentWrite, ApiScopes.DeploymentWrite);
AddScopePolicy(options, ApiPolicies.QueueRead, ApiScopes.QueueRead, ApiScopes.QueueProcess);
AddScopePolicy(options, ApiPolicies.QueueProcess, ApiScopes.QueueProcess);
AddScopePolicy(options, ApiPolicies.TemplateRead, ApiScopes.TemplateRead, ApiScopes.TemplateWrite);
AddScopePolicy(options, ApiPolicies.TemplateWrite, ApiScopes.TemplateWrite);
AddScopePolicy(options, ApiPolicies.TokenManage, ApiScopes.TokenManage, ApiScopes.TokenAdmin);
AddScopePolicy(options, ApiPolicies.TokenAdmin, ApiScopes.TokenAdmin);
});
return services;
}
private static void AddScopePolicy(AuthorizationOptions options, string policyName, params string[] acceptedScopes)
{
options.AddPolicy(policyName, policy => policy.RequireAssertion(context =>
{
if (context.User.Identity?.IsAuthenticated != true)
{
return false;
}
var tokenScopes = context.User.FindAll("scope").Select(claim => claim.Value).ToList();
if (tokenScopes.Count == 0)
{
return true;
}
return acceptedScopes.Any(scope => tokenScopes.Contains(scope, StringComparer.OrdinalIgnoreCase));
}));
}
}
}
+180
View File
@@ -19,6 +19,13 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
public DbSet<DeploymentGroupModel> DeploymentGroups { get; set; }
public DbSet<TemplateModel> Templates { get; set; }
public DbSet<TemplateVersionModel> TemplateVersions { get; set; }
public DbSet<TemplateRevisionModel> TemplateRevisions { get; set; }
public DbSet<ConfigurationDefinitionModel> ConfigurationDefinitions { get; set; }
public DbSet<ConfigurationValueModel> ConfigurationValues { get; set; }
public DbSet<DeploymentArtifactModel> DeploymentArtifacts { get; set; }
public DbSet<CredentialSecretModel> CredentialSecrets { get; set; }
public DbSet<ApiClientModel> ApiClients { get; set; }
public DbSet<ApiTokenModel> ApiTokens { get; set; }
public DbSet<DeploymentRuleModel> DeploymentRules { get; set; }
public DbSet<DeploymentRuleStepModel> DeploymentRuleSteps { get; set; }
public DbSet<DeploymentTemplateSelectionModel> DeploymentTemplateSelections { get; set; }
@@ -148,6 +155,18 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
.WithMany(dg => dg.Deployments)
.HasForeignKey(d => d.DeploymentGroupId);
modelBuilder.Entity<DeploymentModel>()
.HasOne(deployment => deployment.TemplateRevision)
.WithMany(revision => revision.Deployments)
.HasForeignKey(deployment => deployment.TemplateRevisionId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DeploymentModel>()
.HasOne(deployment => deployment.CurrentArtifact)
.WithMany()
.HasForeignKey(deployment => deployment.CurrentArtifactId)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<DeploymentGroupModel>()
.HasOne(dg => dg.Template)
.WithMany(t => t.DeploymentGroups)
@@ -170,6 +189,12 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
.HasForeignKey(selection => selection.TemplateVersionId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DeploymentTemplateSelectionModel>()
.HasOne(selection => selection.TemplateRevision)
.WithMany(revision => revision.DeploymentTemplateSelections)
.HasForeignKey(selection => selection.TemplateRevisionId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DeploymentTemplateSelectionModel>()
.HasIndex(selection => new { selection.DeploymentGroupId, selection.SortOrder })
.IsUnique();
@@ -250,6 +275,161 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
modelBuilder.Entity<TemplateVersionModel>()
.ToTable(table => table.HasCheckConstraint("CK_TemplateVersions_JsonData_IsJson", "ISJSON([JsonData]) = 1"));
modelBuilder.Entity<TemplateRevisionModel>()
.HasOne(revision => revision.TemplateVersion)
.WithMany(version => version.TemplateRevisions)
.HasForeignKey(revision => revision.TemplateVersionId);
modelBuilder.Entity<TemplateRevisionModel>()
.HasIndex(revision => new { revision.TemplateVersionId, revision.RevisionNumber })
.IsUnique();
modelBuilder.Entity<TemplateRevisionModel>()
.HasIndex(revision => revision.TemplateVersionId)
.IsUnique()
.HasFilter("[IsCurrent] = 1");
modelBuilder.Entity<TemplateRevisionModel>()
.Property(revision => revision.JsonHash)
.HasMaxLength(64);
modelBuilder.Entity<TemplateRevisionModel>()
.Property(revision => revision.SchemaVersion)
.HasMaxLength(32);
modelBuilder.Entity<TemplateRevisionModel>()
.ToTable(table => table.HasCheckConstraint("CK_TemplateRevisions_JsonData_IsJson", "ISJSON([JsonData]) = 1"));
modelBuilder.Entity<ConfigurationDefinitionModel>()
.HasOne(definition => definition.TemplateRevision)
.WithMany(revision => revision.ConfigurationDefinitions)
.HasForeignKey(definition => definition.TemplateRevisionId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<ConfigurationDefinitionModel>()
.HasIndex(definition => new { definition.TemplateRevisionId, definition.Kind, definition.Name })
.IsUnique()
.HasFilter("[TemplateRevisionId] IS NOT NULL");
modelBuilder.Entity<ConfigurationDefinitionModel>()
.Property(definition => definition.Kind)
.HasMaxLength(32);
modelBuilder.Entity<ConfigurationDefinitionModel>()
.Property(definition => definition.DefinitionHash)
.HasMaxLength(64);
modelBuilder.Entity<ConfigurationDefinitionModel>()
.ToTable(table => table.HasCheckConstraint("CK_ConfigurationDefinitions_PropertiesJson_IsJson", "ISJSON([PropertiesJson]) = 1"));
modelBuilder.Entity<ConfigurationValueModel>()
.HasOne(value => value.ConfigurationDefinition)
.WithMany(definition => definition.Values)
.HasForeignKey(value => value.ConfigurationDefinitionId);
modelBuilder.Entity<ConfigurationValueModel>()
.HasIndex(value => new { value.ConfigurationDefinitionId, value.ScopeType, value.ScopeId, value.SortOrder });
modelBuilder.Entity<ConfigurationValueModel>()
.Property(value => value.ScopeType)
.HasMaxLength(64);
modelBuilder.Entity<ConfigurationValueModel>()
.Property(value => value.ValueSourceType)
.HasMaxLength(64);
modelBuilder.Entity<ConfigurationValueModel>()
.Property(value => value.ValueHash)
.HasMaxLength(64);
modelBuilder.Entity<ConfigurationValueModel>()
.ToTable(table => table.HasCheckConstraint("CK_ConfigurationValues_ValueJson_IsJson", "[ValueJson] IS NULL OR ISJSON([ValueJson]) = 1"));
modelBuilder.Entity<DeploymentArtifactModel>()
.HasOne(artifact => artifact.DeploymentGroup)
.WithMany(group => group.Artifacts)
.HasForeignKey(artifact => artifact.DeploymentGroupId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<DeploymentArtifactModel>()
.HasOne(artifact => artifact.Deployment)
.WithMany(deployment => deployment.Artifacts)
.HasForeignKey(artifact => new { artifact.TargetId, artifact.DeploymentGroupId })
.HasPrincipalKey(deployment => new { deployment.TargetId, deployment.DeploymentGroupId })
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<DeploymentArtifactModel>()
.HasOne(artifact => artifact.Target)
.WithMany()
.HasForeignKey(artifact => artifact.TargetId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DeploymentArtifactModel>()
.Property(artifact => artifact.ArtifactType)
.HasMaxLength(64);
modelBuilder.Entity<DeploymentArtifactModel>()
.Property(artifact => artifact.InputHash)
.HasMaxLength(64);
modelBuilder.Entity<DeploymentArtifactModel>()
.Property(artifact => artifact.OutputHash)
.HasMaxLength(64);
modelBuilder.Entity<DeploymentArtifactModel>()
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_DeploymentJson_IsJson", "ISJSON([DeploymentJson]) = 1"));
modelBuilder.Entity<DeploymentArtifactModel>()
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_SourceJson_IsJson", "[SourceJson] IS NULL OR ISJSON([SourceJson]) = 1"));
modelBuilder.Entity<DeploymentArtifactModel>()
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_ResolvedJson_IsJson", "[ResolvedJson] IS NULL OR ISJSON([ResolvedJson]) = 1"));
modelBuilder.Entity<DeploymentArtifactModel>()
.ToTable(table => table.HasCheckConstraint("CK_DeploymentArtifacts_SourceSnapshotJson_IsJson", "[SourceSnapshotJson] IS NULL OR ISJSON([SourceSnapshotJson]) = 1"));
modelBuilder.Entity<CredentialSecretModel>()
.HasIndex(secret => secret.Name)
.IsUnique();
modelBuilder.Entity<CredentialSecretModel>()
.ToTable(table => table.HasCheckConstraint("CK_CredentialSecrets_MetadataJson_IsJson", "[MetadataJson] IS NULL OR ISJSON([MetadataJson]) = 1"));
modelBuilder.Entity<ApiClientModel>()
.HasIndex(client => client.ClientId)
.IsUnique();
modelBuilder.Entity<ApiClientModel>()
.Property(client => client.ClientId)
.HasMaxLength(128);
modelBuilder.Entity<ApiClientModel>()
.ToTable(table => table.HasCheckConstraint("CK_ApiClients_ScopesJson_IsJson", "ISJSON([ScopesJson]) = 1"));
modelBuilder.Entity<ApiTokenModel>()
.HasOne(token => token.ApiClient)
.WithMany()
.HasForeignKey(token => token.ApiClientId)
.OnDelete(DeleteBehavior.SetNull);
modelBuilder.Entity<ApiTokenModel>()
.HasIndex(token => token.Jti)
.IsUnique();
modelBuilder.Entity<ApiTokenModel>()
.HasIndex(token => new { token.SubjectType, token.Subject });
modelBuilder.Entity<ApiTokenModel>()
.Property(token => token.Jti)
.HasMaxLength(64);
modelBuilder.Entity<ApiTokenModel>()
.Property(token => token.SubjectType)
.HasMaxLength(32);
modelBuilder.Entity<ApiTokenModel>()
.ToTable(table => table.HasCheckConstraint("CK_ApiTokens_ScopesJson_IsJson", "ISJSON([ScopesJson]) = 1"));
modelBuilder.Entity<DeploymentRuleStepModel>()
.HasOne(step => step.DeploymentRule)
.WithMany(rule => rule.Steps)
+366 -10
View File
@@ -1,5 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Models;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.SelfService.Portal.Core.API.Services;
namespace Microsoft.SelfService.Portal.Core.API.Context
{
@@ -61,6 +65,15 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
internal static readonly Guid TemplateBgwSharePointContosoVersionId = Guid.Parse("71000000-0000-0000-0000-000000000103");
internal static readonly Guid TemplateBgwStageInstallVersionId = Guid.Parse("71000000-0000-0000-0000-000000000104");
internal static readonly Guid TemplateActiveDirectoryRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000001");
internal static readonly Guid TemplateSqlServerRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000002");
internal static readonly Guid TemplateSharePointRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000003");
internal static readonly Guid TemplateTeamsRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000004");
internal static readonly Guid TemplateBgwEnvironmentTestRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000101");
internal static readonly Guid TemplateBgwDomainContosoRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000102");
internal static readonly Guid TemplateBgwSharePointContosoRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000103");
internal static readonly Guid TemplateBgwStageInstallRevisionId = Guid.Parse("72000000-0000-0000-0000-000000000104");
internal static readonly Guid DeploymentBatchSharePointId = Guid.Parse("80000000-0000-0000-0000-000000000001");
internal static readonly Guid DeploymentBatchBgwMergeId = Guid.Parse("80000000-0000-0000-0000-000000000101");
internal static readonly Guid DeploymentSp01Id = Guid.Parse("81000000-0000-0000-0000-000000000001");
@@ -81,6 +94,15 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
internal static readonly Guid DeploymentTargetBgwSp02Id = Guid.Parse("84000000-0000-0000-0000-000000000102");
internal static readonly Guid DeploymentTargetBgwSp03Id = Guid.Parse("84000000-0000-0000-0000-000000000103");
internal static readonly Guid DeploymentArtifactBgwResolvedId = Guid.Parse("85000000-0000-0000-0000-000000000101");
internal static readonly Guid CredentialSharePointSetupId = Guid.Parse("90000000-0000-0000-0000-000000000001");
internal static readonly Guid CredentialSharePointFarmId = Guid.Parse("90000000-0000-0000-0000-000000000002");
internal static readonly Guid CredentialSharePointFarmPassphraseId = Guid.Parse("90000000-0000-0000-0000-000000000003");
internal static readonly Guid CredentialSharePointDefaultServiceId = Guid.Parse("90000000-0000-0000-0000-000000000004");
internal static readonly Guid CredentialSharePointSearchId = Guid.Parse("90000000-0000-0000-0000-000000000005");
internal static readonly Guid ApiClientDemoWorkerId = Guid.Parse("91000000-0000-0000-0000-000000000001");
internal static void Seed(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EnvironmentModel>().HasData(Environments());
@@ -94,11 +116,17 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
modelBuilder.Entity<DeploymentRuleStepModel>().HasData(DeploymentRuleSteps());
modelBuilder.Entity<TemplateModel>().HasData(Templates());
modelBuilder.Entity<TemplateVersionModel>().HasData(TemplateVersions());
modelBuilder.Entity<TemplateRevisionModel>().HasData(TemplateRevisions());
modelBuilder.Entity<ConfigurationDefinitionModel>().HasData(ConfigurationDefinitions());
modelBuilder.Entity<ConfigurationValueModel>().HasData(ConfigurationValues());
modelBuilder.Entity<DeploymentGroupModel>().HasData(DeploymentBatches());
modelBuilder.Entity<DeploymentModel>().HasData(Deployments());
modelBuilder.Entity<DeploymentTemplateSelectionModel>().HasData(DeploymentTemplateSelections());
modelBuilder.Entity<DeploymentParameterValueModel>().HasData(DeploymentParameterValues());
modelBuilder.Entity<DeploymentTargetAssignmentModel>().HasData(DeploymentTargetAssignments());
modelBuilder.Entity<DeploymentArtifactModel>().HasData(DeploymentArtifacts());
modelBuilder.Entity<CredentialSecretModel>().HasData(CredentialSecrets());
modelBuilder.Entity<ApiClientModel>().HasData(ApiClients());
}
private static object[] Environments() =>
@@ -197,6 +225,18 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
TemplateVersion(TemplateBgwStageInstallVersionId, TemplateBgwStageInstallId, BgwStageInstallTemplateJson)
];
private static object[] TemplateRevisions() =>
[
TemplateRevision(TemplateActiveDirectoryRevisionId, TemplateActiveDirectoryVersionId, ActiveDirectoryTemplateJson),
TemplateRevision(TemplateSqlServerRevisionId, TemplateSqlServerVersionId, SqlServerTemplateJson),
TemplateRevision(TemplateSharePointRevisionId, TemplateSharePointVersionId, SharePointTemplateJson),
TemplateRevision(TemplateTeamsRevisionId, TemplateTeamsVersionId, TeamsTemplateJson),
TemplateRevision(TemplateBgwEnvironmentTestRevisionId, TemplateBgwEnvironmentTestVersionId, BgwEnvironmentTestTemplateJson),
TemplateRevision(TemplateBgwDomainContosoRevisionId, TemplateBgwDomainContosoVersionId, BgwDomainContosoTemplateJson),
TemplateRevision(TemplateBgwSharePointContosoRevisionId, TemplateBgwSharePointContosoVersionId, BgwSharePointContosoTemplateJson),
TemplateRevision(TemplateBgwStageInstallRevisionId, TemplateBgwStageInstallVersionId, BgwStageInstallTemplateJson)
];
private static object[] DeploymentBatches() =>
[
Base(new DeploymentGroupModel { Id = DeploymentBatchSharePointId, TemplateId = TemplateSharePointId, DeploymentRuleId = RuleStandardId, Status = QueueJobStatus.Pending }),
@@ -205,20 +245,20 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
private static object[] Deployments() =>
[
Base(new DeploymentModel { Id = DeploymentSp01Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp01Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"WebFrontEnd\"}" }),
Base(new DeploymentModel { Id = DeploymentSp02Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp02Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Application\"}" }),
Base(new DeploymentModel { Id = DeploymentBgwSp01Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp01Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
Base(new DeploymentModel { Id = DeploymentBgwSp02Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp02Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
Base(new DeploymentModel { Id = DeploymentBgwSp03Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp03Id, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" })
Base(new DeploymentModel { Id = DeploymentSp01Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp01Id, TemplateRevisionId = TemplateSharePointRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"WebFrontEnd\"}" }),
Base(new DeploymentModel { Id = DeploymentSp02Id, DeploymentGroupId = DeploymentBatchSharePointId, TargetId = VmSp02Id, TemplateRevisionId = TemplateSharePointRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Application\"}" }),
Base(new DeploymentModel { Id = DeploymentBgwSp01Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp01Id, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
Base(new DeploymentModel { Id = DeploymentBgwSp02Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp02Id, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" }),
Base(new DeploymentModel { Id = DeploymentBgwSp03Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp03Id, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, Status = QueueJobStatus.Pending, JSONData = "{\"role\":\"Node\"}" })
];
private static object[] DeploymentTemplateSelections() =>
[
Base(new DeploymentTemplateSelectionModel { Id = DeploymentSharePointTemplateSelectionId, DeploymentGroupId = DeploymentBatchSharePointId, TemplateVersionId = TemplateSharePointVersionId, TemplateRole = "Service", SortOrder = 10, Alias = "SharePoint" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwEnvironmentSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwEnvironmentTestVersionId, TemplateRole = "Environment", SortOrder = 10, Alias = "Environment-Test" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwDomainSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwDomainContosoVersionId, TemplateRole = "Domain", SortOrder = 20, Alias = "Domain-Contoso" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwSharePointSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwSharePointContosoVersionId, TemplateRole = "Service", SortOrder = 30, Alias = "Service-SharePoint-Contoso" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwStageSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwStageInstallVersionId, TemplateRole = "Stage", SortOrder = 40, Alias = "Stage-Install" })
Base(new DeploymentTemplateSelectionModel { Id = DeploymentSharePointTemplateSelectionId, DeploymentGroupId = DeploymentBatchSharePointId, TemplateVersionId = TemplateSharePointVersionId, TemplateRevisionId = TemplateSharePointRevisionId, TemplateRole = "Service", SortOrder = 10, Alias = "SharePoint" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwEnvironmentSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwEnvironmentTestVersionId, TemplateRevisionId = TemplateBgwEnvironmentTestRevisionId, TemplateRole = "Environment", SortOrder = 10, Alias = "Environment-Test" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwDomainSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwDomainContosoVersionId, TemplateRevisionId = TemplateBgwDomainContosoRevisionId, TemplateRole = "Domain", SortOrder = 20, Alias = "Domain-Contoso" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwSharePointSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwSharePointContosoVersionId, TemplateRevisionId = TemplateBgwSharePointContosoRevisionId, TemplateRole = "Service", SortOrder = 30, Alias = "Service-SharePoint-Contoso" }),
Base(new DeploymentTemplateSelectionModel { Id = DeploymentBgwStageSelectionId, DeploymentGroupId = DeploymentBatchBgwMergeId, TemplateVersionId = TemplateBgwStageInstallVersionId, TemplateRevisionId = TemplateBgwStageInstallRevisionId, TemplateRole = "Stage", SortOrder = 40, Alias = "Stage-Install" })
];
private static object[] DeploymentParameterValues() =>
@@ -236,6 +276,99 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
Base(new DeploymentTargetAssignmentModel { Id = DeploymentTargetBgwSp03Id, DeploymentGroupId = DeploymentBatchBgwMergeId, TargetId = TargetBgwSp03Id, RoleKey = "Node", SortOrder = 30, NodeDataJson = "{\"nodeName\":\"CLD-SHP-03\",\"RunCentralAdministration\":false}" })
];
private static object[] ConfigurationDefinitions()
{
return TemplateRevisionJsonDocuments()
.SelectMany(template => ExtractConfigurationDefinitions(template.RevisionId, template.JsonData))
.Select(definition => Base(definition))
.Cast<object>()
.ToArray();
}
private static object[] ConfigurationValues()
{
return TemplateRevisionJsonDocuments()
.SelectMany(template => ExtractConfigurationValues(template.RevisionId, template.JsonData))
.Select(value => Base(value))
.Cast<object>()
.ToArray();
}
private static object[] DeploymentArtifacts()
{
var deploymentJson = """
{
"metadata": {
"name": "Contoso-Test-SharePoint",
"deploymentId": "8f6c2c1a",
"source": "DemoData"
},
"parameters": {
"DatabasePrefix": "SharePoint_Contoso_Test"
},
"variables": {
"DatabasePrefix": "SharePoint_Contoso_Test",
"ServiceDbPrefix": "SharePoint_Contoso_Test_Services",
"ConfigDbName": "SharePoint_Contoso_Test_Farm_Config"
},
"resources": {
"AllNodes": [
{ "NodeName": "CLD-SHP-01", "RunCentralAdministration": true },
{ "NodeName": "CLD-SHP-02", "RunCentralAdministration": false },
{ "NodeName": "CLD-SHP-03", "RunCentralAdministration": false }
],
"NonNodeData": {
"LocalConfigurationManager": {
"ConfigurationMode": "ApplyOnly"
}
}
}
}
""";
var sourceSnapshotJson = BgwMergeSourceSnapshotJson();
return
[
Base(new DeploymentArtifactModel
{
Id = DeploymentArtifactBgwResolvedId,
DeploymentGroupId = DeploymentBatchBgwMergeId,
ArtifactType = DeploymentArtifactTypes.ResolvedConfigurationData,
Status = QueueJobStatus.Pending,
DeploymentJson = deploymentJson,
SourceJson = "{\"source\":\"DemoData\",\"composition\":\"Test.Merge.ps1\"}",
ResolvedJson = deploymentJson,
SourceSnapshotJson = sourceSnapshotJson,
InputHash = TemplateVersionModel.ComputeSha256(sourceSnapshotJson),
OutputHash = TemplateVersionModel.ComputeSha256(deploymentJson),
IsStale = false
})
];
}
private static object[] CredentialSecrets() =>
[
CredentialSecret(CredentialSharePointSetupId, "Windows/SharePoint/SetupAccount", "CONTOSO\\svc_sp_setup"),
CredentialSecret(CredentialSharePointFarmId, "Windows/SharePoint/FarmAccount", "CONTOSO\\svc_sp_farm"),
CredentialSecret(CredentialSharePointFarmPassphraseId, "Windows/SharePoint/FarmPassphrase", null, "Secret"),
CredentialSecret(CredentialSharePointDefaultServiceId, "Windows/SharePoint/DefaultServiceAccount", "CONTOSO\\svc_sp_service"),
CredentialSecret(CredentialSharePointSearchId, "Windows/SharePoint/SearchAccount", "CONTOSO\\svc_sp_search")
];
private static object[] ApiClients() =>
[
Base(new ApiClientModel
{
Id = ApiClientDemoWorkerId,
ClientId = "ssp-demo-worker",
Name = "Demo Worker Client",
SecretHash = ApiClientSecretHasher.HashSecretForDemoData("DemoOnly-DoNotUseInProduction!", "ssp-demo-worker"),
ScopesJson = "[\"configuration.read\",\"configuration.write\",\"credential.read\",\"credential.write\",\"credential.resolve\",\"deployment.read\",\"deployment.write\",\"queue.read\",\"queue.process\",\"template.read\",\"template.write\",\"token.manage\",\"token.admin\"]",
IsEnabled = true
})
];
private static TemplateVersionModel TemplateVersion(Guid id, Guid templateId, string jsonData)
{
return Base(new TemplateVersionModel
@@ -252,6 +385,229 @@ namespace Microsoft.SelfService.Portal.Core.API.Context
});
}
private static TemplateRevisionModel TemplateRevision(Guid id, Guid templateVersionId, string jsonData)
{
return Base(new TemplateRevisionModel
{
Id = id,
TemplateVersionId = templateVersionId,
RevisionNumber = 1,
JsonData = jsonData,
JsonHash = TemplateVersionModel.ComputeSha256(jsonData),
SchemaVersion = "1.0",
IsCurrent = true,
IsPublished = true,
PublishedAt = Timestamp,
PublishedBy = Owner
});
}
private static CredentialSecretModel CredentialSecret(Guid id, string name, string? userName, string secretType = "Credential")
{
return Base(new CredentialSecretModel
{
Id = id,
Name = name,
UserName = userName,
SecretValue = "DemoOnly-DoNotUseInProduction!",
SecretType = secretType,
MetadataJson = "{\"environment\":\"Demo\",\"plaintext\":true}",
IsEnabled = true
});
}
private static IEnumerable<(Guid RevisionId, string JsonData)> TemplateRevisionJsonDocuments()
{
yield return (TemplateActiveDirectoryRevisionId, ActiveDirectoryTemplateJson);
yield return (TemplateSqlServerRevisionId, SqlServerTemplateJson);
yield return (TemplateSharePointRevisionId, SharePointTemplateJson);
yield return (TemplateTeamsRevisionId, TeamsTemplateJson);
yield return (TemplateBgwEnvironmentTestRevisionId, BgwEnvironmentTestTemplateJson);
yield return (TemplateBgwDomainContosoRevisionId, BgwDomainContosoTemplateJson);
yield return (TemplateBgwSharePointContosoRevisionId, BgwSharePointContosoTemplateJson);
yield return (TemplateBgwStageInstallRevisionId, BgwStageInstallTemplateJson);
}
private static IEnumerable<ConfigurationDefinitionModel> ExtractConfigurationDefinitions(Guid revisionId, string jsonData)
{
using var document = JsonDocument.Parse(jsonData);
var root = document.RootElement;
foreach (var definition in ExtractConfigurationDefinitions(revisionId, root, "parameters", ConfigurationDefinitionKinds.Parameter))
yield return definition;
foreach (var definition in ExtractConfigurationDefinitions(revisionId, root, "variables", ConfigurationDefinitionKinds.Variable))
yield return definition;
}
private static IEnumerable<ConfigurationDefinitionModel> ExtractConfigurationDefinitions(Guid revisionId, JsonElement root, string propertyName, string kind)
{
if (!root.TryGetProperty(propertyName, out var definitions) || definitions.ValueKind != JsonValueKind.Object)
yield break;
foreach (var property in definitions.EnumerateObject())
{
var propertiesJson = kind == ConfigurationDefinitionKinds.Variable
? $$"""{"Expression":{{property.Value.GetRawText()}}}"""
: property.Value.GetRawText();
yield return new ConfigurationDefinitionModel
{
Id = ConfigurationDefinitionId(revisionId, kind, property.Name),
TemplateRevisionId = revisionId,
Kind = kind,
Name = property.Name,
PropertiesJson = propertiesJson,
DefinitionHash = TemplateVersionModel.ComputeSha256(propertiesJson)
};
}
}
private static IEnumerable<ConfigurationValueModel> ExtractConfigurationValues(Guid revisionId, string jsonData)
{
using var document = JsonDocument.Parse(jsonData);
var root = document.RootElement;
foreach (var value in ExtractParameterValues(revisionId, root))
yield return value;
foreach (var value in ExtractVariableValues(revisionId, root))
yield return value;
}
private static IEnumerable<ConfigurationValueModel> ExtractParameterValues(Guid revisionId, JsonElement root)
{
if (!root.TryGetProperty("parameters", out var parameters) || parameters.ValueKind != JsonValueKind.Object)
yield break;
var sortOrder = 0;
foreach (var parameter in parameters.EnumerateObject())
{
sortOrder += 10;
var valueSourceType = ConfigurationValueSourceTypes.Static;
var valueJson = "{}";
if (parameter.Value.ValueKind == JsonValueKind.Object && parameter.Value.TryGetProperty("Value", out var explicitValue))
{
valueJson = $$"""{"value":{{explicitValue.GetRawText()}}}""";
}
else if (parameter.Value.ValueKind == JsonValueKind.Object && parameter.Value.TryGetProperty("DefaultValue", out var defaultValue))
{
valueJson = $$"""{"value":{{defaultValue.GetRawText()}}}""";
}
if (parameter.Value.ValueKind == JsonValueKind.Object
&& parameter.Value.TryGetProperty("Sensitive", out var sensitive)
&& sensitive.ValueKind == JsonValueKind.True)
{
valueSourceType = ConfigurationValueSourceTypes.SecretReference;
}
yield return ConfigurationValue(
revisionId,
ConfigurationDefinitionKinds.Parameter,
parameter.Name,
valueSourceType,
valueJson,
sortOrder);
}
}
private static IEnumerable<ConfigurationValueModel> ExtractVariableValues(Guid revisionId, JsonElement root)
{
if (!root.TryGetProperty("variables", out var variables) || variables.ValueKind != JsonValueKind.Object)
yield break;
var sortOrder = 0;
foreach (var variable in variables.EnumerateObject())
{
sortOrder += 10;
var valueJson = $$"""{"expression":{{variable.Value.GetRawText()}}}""";
yield return ConfigurationValue(
revisionId,
ConfigurationDefinitionKinds.Variable,
variable.Name,
ConfigurationValueSourceTypes.Expression,
valueJson,
sortOrder);
}
}
private static ConfigurationValueModel ConfigurationValue(Guid revisionId, string kind, string name, string valueSourceType, string valueJson, int sortOrder)
{
return new ConfigurationValueModel
{
Id = ConfigurationValueId(revisionId, kind, name),
ConfigurationDefinitionId = ConfigurationDefinitionId(revisionId, kind, name),
ScopeType = ConfigurationValueScopeTypes.TemplateRevision,
ScopeId = revisionId,
ValueSourceType = valueSourceType,
ValueJson = valueJson,
ValueHash = TemplateVersionModel.ComputeSha256(valueJson),
SortOrder = sortOrder
};
}
private static Guid ConfigurationDefinitionId(Guid revisionId, string kind, string name)
{
return DeterministicGuid($"ConfigurationDefinition:{revisionId:N}:{kind}:{name}");
}
private static Guid ConfigurationValueId(Guid revisionId, string kind, string name)
{
return DeterministicGuid($"ConfigurationValue:{revisionId:N}:{kind}:{name}");
}
private static Guid DeterministicGuid(string value)
{
var hash = MD5.HashData(Encoding.UTF8.GetBytes(value));
return new Guid(hash);
}
private static string BgwMergeSourceSnapshotJson()
{
return $$"""
{
"templateRevisions": [
{
"id": "{{TemplateBgwEnvironmentTestRevisionId}}",
"templateVersionId": "{{TemplateBgwEnvironmentTestVersionId}}",
"role": "Environment",
"alias": "Environment-Test",
"hash": "{{TemplateVersionModel.ComputeSha256(BgwEnvironmentTestTemplateJson)}}"
},
{
"id": "{{TemplateBgwDomainContosoRevisionId}}",
"templateVersionId": "{{TemplateBgwDomainContosoVersionId}}",
"role": "Domain",
"alias": "Domain-Contoso",
"hash": "{{TemplateVersionModel.ComputeSha256(BgwDomainContosoTemplateJson)}}"
},
{
"id": "{{TemplateBgwSharePointContosoRevisionId}}",
"templateVersionId": "{{TemplateBgwSharePointContosoVersionId}}",
"role": "Service",
"alias": "Service-SharePoint-Contoso",
"hash": "{{TemplateVersionModel.ComputeSha256(BgwSharePointContosoTemplateJson)}}"
},
{
"id": "{{TemplateBgwStageInstallRevisionId}}",
"templateVersionId": "{{TemplateBgwStageInstallVersionId}}",
"role": "Stage",
"alias": "Stage-Install",
"hash": "{{TemplateVersionModel.ComputeSha256(BgwStageInstallTemplateJson)}}"
}
],
"targets": [
{ "id": "{{TargetBgwSp01Id}}", "nodeName": "CLD-SHP-01" },
{ "id": "{{TargetBgwSp02Id}}", "nodeName": "CLD-SHP-02" },
{ "id": "{{TargetBgwSp03Id}}", "nodeName": "CLD-SHP-03" }
]
}
""";
}
private static T Base<T>(T model)
where T : BaseModel
{
+222
View File
@@ -0,0 +1,222 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.Auth;
using Microsoft.SelfService.Portal.Core.API.Models;
using Microsoft.SelfService.Portal.Core.API.Services;
using System.Security.Claims;
using System.Text.Json;
namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/tokens")]
[ApiController]
public class ApiTokenController : Controller
{
private readonly DataContext _context;
private readonly ApiTokenService _tokenService;
public ApiTokenController(DataContext context, ApiTokenService tokenService)
{
_context = context;
_tokenService = tokenService;
}
[HttpGet("my")]
[Authorize(Policy = ApiPolicies.TokenManage)]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetManagedTokenDto>))]
public IActionResult GetMyTokens()
{
var subject = GetCurrentSubject();
var subjectType = GetCurrentSubjectType();
var tokens = _context.ApiTokens
.Include(token => token.ApiClient)
.Where(token => token.Subject == subject && token.SubjectType == subjectType)
.OrderByDescending(token => token.IssuedAt)
.ToList();
return Ok(tokens.Select(ToDto));
}
[HttpPost("my")]
[Authorize(Policy = ApiPolicies.TokenManage)]
[ProducesResponseType(200, Type = typeof(CreateManagedTokenResponseDto))]
[ProducesResponseType(400)]
public IActionResult CreateMyToken([FromBody] CreateManagedTokenRequestDto request)
{
if (request == null || string.IsNullOrWhiteSpace(request.Scope))
{
return BadRequest(new { message = "Scope is required." });
}
IReadOnlyCollection<string> scopes;
try
{
scopes = _tokenService.ResolveRequestedUserScopes(request.Scope);
}
catch (InvalidOperationException ex)
{
return BadRequest(new { message = ex.Message });
}
var subject = GetCurrentSubject();
var subjectType = GetCurrentSubjectType();
var name = string.IsNullOrWhiteSpace(request.Name)
? $"Token for {subject}"
: request.Name.Trim();
var token = _tokenService.CreateAccessToken(
subject,
subjectType == "ApiClient" ? subject : null,
scopes);
var now = DateTime.UtcNow;
var expiresAt = request.ExpiresAt.HasValue && request.ExpiresAt.Value < token.ExpiresAt
? request.ExpiresAt.Value
: token.ExpiresAt;
if (expiresAt <= now)
{
return BadRequest(new { message = "ExpiresAt must be in the future." });
}
var model = new ApiTokenModel
{
Id = Guid.NewGuid(),
Jti = token.Jti,
Subject = subject,
SubjectType = subjectType,
Name = name,
ScopesJson = JsonSerializer.Serialize(scopes),
IssuedAt = token.IssuedAt,
ExpiresAt = expiresAt,
Created = now,
CreatedBy = subject,
Modified = now,
ModifiedBy = subject
};
_context.ApiTokens.Add(model);
_context.SaveChanges();
return Ok(new CreateManagedTokenResponseDto
{
Id = model.Id,
AccessToken = token.AccessToken,
ExpiresIn = Math.Max(0, Convert.ToInt32((expiresAt - now).TotalSeconds)),
ExpiresAt = expiresAt,
Scope = string.Join(' ', scopes)
});
}
[HttpDelete("my/{id}")]
[Authorize(Policy = ApiPolicies.TokenManage)]
[ProducesResponseType(200, Type = typeof(GetManagedTokenDto))]
[ProducesResponseType(404)]
public IActionResult RevokeMyToken(Guid id)
{
var subject = GetCurrentSubject();
var subjectType = GetCurrentSubjectType();
var token = _context.ApiTokens.FirstOrDefault(existing =>
existing.Id == id
&& existing.Subject == subject
&& existing.SubjectType == subjectType);
if (token == null)
{
return NotFound();
}
RevokeToken(token, subject);
_context.SaveChanges();
return Ok(ToDto(token));
}
[HttpGet("admin")]
[Authorize(Policy = ApiPolicies.TokenAdmin)]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetManagedTokenDto>))]
public IActionResult GetAllTokens()
{
var tokens = _context.ApiTokens
.Include(token => token.ApiClient)
.OrderByDescending(token => token.IssuedAt)
.ToList();
return Ok(tokens.Select(ToDto));
}
[HttpDelete("admin/{id}")]
[Authorize(Policy = ApiPolicies.TokenAdmin)]
[ProducesResponseType(200, Type = typeof(GetManagedTokenDto))]
[ProducesResponseType(404)]
public IActionResult RevokeTokenAsAdmin(Guid id)
{
var token = _context.ApiTokens.FirstOrDefault(existing => existing.Id == id);
if (token == null)
{
return NotFound();
}
RevokeToken(token, GetCurrentSubject());
_context.SaveChanges();
return Ok(ToDto(token));
}
[HttpGet("scopes")]
[Authorize(Policy = ApiPolicies.TokenManage)]
[ProducesResponseType(200, Type = typeof(IEnumerable<string>))]
public IActionResult GetAllowedScopes()
{
return Ok(_tokenService.ReadAllowedTokenScopes());
}
private string GetCurrentSubject()
{
return User.FindFirstValue("client_id")
?? User.FindFirstValue(ClaimTypes.Name)
?? User.Identity?.Name
?? "Unknown";
}
private string GetCurrentSubjectType()
{
return User.HasClaim(claim => claim.Type == "client_id") ? "ApiClient" : "User";
}
private static void RevokeToken(ApiTokenModel token, string revokedBy)
{
if (!token.RevokedAt.HasValue)
{
token.RevokedAt = DateTime.UtcNow;
token.RevokedBy = revokedBy;
token.Modified = DateTime.UtcNow;
token.ModifiedBy = revokedBy;
}
}
private static GetManagedTokenDto ToDto(ApiTokenModel token)
{
var scopes = JsonSerializer.Deserialize<string[]>(token.ScopesJson) ?? Array.Empty<string>();
return new GetManagedTokenDto
{
Id = token.Id,
Subject = token.Subject,
SubjectType = token.SubjectType,
ClientId = token.ApiClient?.ClientId,
Name = token.Name,
Scope = string.Join(' ', scopes),
IssuedAt = token.IssuedAt,
ExpiresAt = token.ExpiresAt,
RevokedAt = token.RevokedAt,
RevokedBy = token.RevokedBy,
LastUsedAt = token.LastUsedAt,
IsActive = !token.RevokedAt.HasValue && token.ExpiresAt > DateTime.UtcNow
};
}
}
}
+94
View File
@@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.Auth;
using Microsoft.SelfService.Portal.Core.API.Models;
using Microsoft.SelfService.Portal.Core.API.Services;
using System.Text.Json;
namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/auth")]
[ApiController]
public class AuthController : Controller
{
private readonly DataContext _context;
private readonly ApiTokenService _tokenService;
public AuthController(DataContext context, ApiTokenService tokenService)
{
_context = context;
_tokenService = tokenService;
}
[HttpPost("token")]
[AllowAnonymous]
[ProducesResponseType(200, Type = typeof(TokenResponseDto))]
[ProducesResponseType(400)]
[ProducesResponseType(401)]
public IActionResult CreateToken([FromBody] TokenRequestDto request)
{
if (request == null
|| string.IsNullOrWhiteSpace(request.ClientId)
|| string.IsNullOrWhiteSpace(request.ClientSecret))
{
return BadRequest(new { message = "ClientId and ClientSecret are required." });
}
var client = _context.ApiClients
.FirstOrDefault(existing => existing.ClientId == request.ClientId);
if (client == null
|| !client.IsEnabled
|| (client.ExpiresAt.HasValue && client.ExpiresAt.Value <= DateTime.UtcNow)
|| !ApiClientSecretHasher.VerifySecret(request.ClientSecret, client.SecretHash))
{
return Unauthorized(new { message = "Invalid client credentials." });
}
IReadOnlyCollection<string> scopes;
try
{
scopes = _tokenService.ResolveRequestedScopes(client, request.Scope);
}
catch (InvalidOperationException ex)
{
return Unauthorized(new { message = ex.Message });
}
var token = _tokenService.CreateAccessToken(client, scopes);
var now = DateTime.UtcNow;
client.LastUsedAt = now;
client.Modified = now;
client.ModifiedBy = client.ClientId;
_context.ApiTokens.Add(new ApiTokenModel
{
Id = Guid.NewGuid(),
Jti = token.Jti,
Subject = client.ClientId,
SubjectType = "ApiClient",
ApiClientId = client.Id,
Name = $"Client credentials token for {client.Name}",
ScopesJson = JsonSerializer.Serialize(scopes),
IssuedAt = token.IssuedAt,
ExpiresAt = token.ExpiresAt,
Created = now,
CreatedBy = client.ClientId,
Modified = now,
ModifiedBy = client.ClientId
});
_context.SaveChanges();
return Ok(new TokenResponseDto
{
AccessToken = token.AccessToken,
ExpiresIn = _tokenService.TokenLifetimeSeconds,
Scope = string.Join(' ', scopes)
});
}
}
}
@@ -0,0 +1,272 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationValue.Get;
using Microsoft.SelfService.Portal.Core.API.JsonDocuments;
using Microsoft.SelfService.Portal.Core.API.Models;
namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/configuration-definitions")]
[ApiController]
[Authorize(Policy = ApiPolicies.ConfigurationRead)]
public class ConfigurationDefinitionController : Controller
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public ConfigurationDefinitionController(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpGet]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetConfigurationDefinitionDto>))]
public IActionResult GetConfigurationDefinitions([FromQuery] Guid? templateRevisionId, [FromQuery] string? kind, [FromQuery] string? name)
{
var query = _context.ConfigurationDefinitions
.AsNoTracking()
.AsQueryable();
if (templateRevisionId.HasValue)
query = query.Where(definition => definition.TemplateRevisionId == templateRevisionId.Value);
if (!string.IsNullOrWhiteSpace(kind))
query = query.Where(definition => definition.Kind == kind);
if (!string.IsNullOrWhiteSpace(name))
query = query.Where(definition => definition.Name == name);
var definitions = query
.OrderBy(definition => definition.TemplateRevisionId)
.ThenBy(definition => definition.Kind)
.ThenBy(definition => definition.Name)
.ToList();
return Ok(_mapper.Map<List<GetConfigurationDefinitionDto>>(definitions));
}
[HttpGet("{id}")]
[ProducesResponseType(200, Type = typeof(GetConfigurationDefinitionDto))]
[ProducesResponseType(404)]
public IActionResult GetConfigurationDefinitionById(Guid id)
{
var definition = _context.ConfigurationDefinitions
.AsNoTracking()
.FirstOrDefault(existing => existing.Id == id);
if (definition == null)
return NotFound();
return Ok(_mapper.Map<GetConfigurationDefinitionDto>(definition));
}
[HttpGet("{id}/values")]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetConfigurationValueDto>))]
[ProducesResponseType(404)]
public IActionResult GetConfigurationDefinitionValues(Guid id)
{
if (!_context.ConfigurationDefinitions.Any(definition => definition.Id == id))
return NotFound();
var values = _context.ConfigurationValues
.AsNoTracking()
.Where(value => value.ConfigurationDefinitionId == id)
.OrderBy(value => value.ScopeType)
.ThenBy(value => value.SortOrder)
.ToList();
return Ok(_mapper.Map<List<GetConfigurationValueDto>>(values));
}
[HttpPost]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
[ProducesResponseType(422)]
public IActionResult AddConfigurationDefinition([FromBody] AddConfigurationDefinitionDto configurationDefinition)
{
if (configurationDefinition == null)
return BadRequest(ModelState);
if (!TryPrepareConfigurationDefinition(
configurationDefinition.TemplateRevisionId,
configurationDefinition.Kind,
configurationDefinition.Name,
configurationDefinition.PropertiesJson,
null,
out var propertiesJson,
out var errorField,
out var errorMessage))
{
ModelState.AddModelError(errorField, errorMessage);
return BadRequest(ModelState);
}
if (ConfigurationDefinitionExists(configurationDefinition.TemplateRevisionId, configurationDefinition.Kind, configurationDefinition.Name, null))
{
ModelState.AddModelError(nameof(configurationDefinition.Name), "Configuration definition already exists for this scope, kind and name.");
return StatusCode(422, ModelState);
}
var model = _mapper.Map<ConfigurationDefinitionModel>(configurationDefinition);
model.Id = Guid.NewGuid();
model.PropertiesJson = propertiesJson;
model.DefinitionHash = TemplateVersionModel.ComputeSha256(model.PropertiesJson);
model.CreatedBy = User?.Identity?.Name ?? "System";
model.ModifiedBy = model.CreatedBy;
_context.ConfigurationDefinitions.Add(model);
_context.SaveChanges();
return Ok(model.Id);
}
[HttpPut("{id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
[ProducesResponseType(422)]
public IActionResult UpdateConfigurationDefinition(Guid id, [FromBody] EditConfigurationDefinitionDto configurationDefinition)
{
if (configurationDefinition == null)
return BadRequest(ModelState);
var existing = _context.ConfigurationDefinitions.FirstOrDefault(definition => definition.Id == id);
if (existing == null)
return NotFound();
if (!TryPrepareConfigurationDefinition(
configurationDefinition.TemplateRevisionId,
configurationDefinition.Kind,
configurationDefinition.Name,
configurationDefinition.PropertiesJson,
id,
out var propertiesJson,
out var errorField,
out var errorMessage))
{
ModelState.AddModelError(errorField, errorMessage);
return BadRequest(ModelState);
}
if (ConfigurationDefinitionExists(configurationDefinition.TemplateRevisionId, configurationDefinition.Kind, configurationDefinition.Name, id))
{
ModelState.AddModelError(nameof(configurationDefinition.Name), "Configuration definition already exists for this scope, kind and name.");
return StatusCode(422, ModelState);
}
existing.TemplateRevisionId = configurationDefinition.TemplateRevisionId;
existing.Kind = configurationDefinition.Kind;
existing.Name = configurationDefinition.Name;
existing.PropertiesJson = propertiesJson;
existing.DefinitionHash = TemplateVersionModel.ComputeSha256(propertiesJson);
existing.Modified = DateTime.UtcNow;
existing.ModifiedBy = User?.Identity?.Name ?? "System";
_context.SaveChanges();
return NoContent();
}
[HttpDelete("{id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
[ProducesResponseType(409)]
public IActionResult DeleteConfigurationDefinition(Guid id)
{
var definition = _context.ConfigurationDefinitions
.Include(existing => existing.Values)
.FirstOrDefault(existing => existing.Id == id);
if (definition == null)
return NotFound();
if (definition.Values.Any())
{
ModelState.AddModelError(nameof(id), "Configuration definition cannot be deleted while configuration values reference it.");
return StatusCode(409, ModelState);
}
_context.ConfigurationDefinitions.Remove(definition);
_context.SaveChanges();
return NoContent();
}
private bool TryPrepareConfigurationDefinition(
Guid? templateRevisionId,
string kind,
string name,
string propertiesJson,
Guid? existingId,
out string normalizedPropertiesJson,
out string errorField,
out string errorMessage)
{
normalizedPropertiesJson = "{}";
errorField = string.Empty;
errorMessage = string.Empty;
if (templateRevisionId.HasValue && !_context.TemplateRevisions.Any(revision => revision.Id == templateRevisionId.Value))
{
errorField = nameof(templateRevisionId);
errorMessage = "Template revision was not found.";
return false;
}
if (!string.Equals(kind, ConfigurationDefinitionKinds.Parameter, StringComparison.OrdinalIgnoreCase)
&& !string.Equals(kind, ConfigurationDefinitionKinds.Variable, StringComparison.OrdinalIgnoreCase))
{
errorField = nameof(kind);
errorMessage = $"Configuration definition kind must be [{ConfigurationDefinitionKinds.Parameter}] or [{ConfigurationDefinitionKinds.Variable}].";
return false;
}
if (string.IsNullOrWhiteSpace(name))
{
errorField = nameof(name);
errorMessage = "Configuration definition name is required.";
return false;
}
try
{
normalizedPropertiesJson = ConfigurationDocumentValidator.NormalizeAndValidate(
propertiesJson,
ConfigurationDocumentKind.Metadata).JsonData;
}
catch (ConfigurationDocumentValidationException ex)
{
errorField = nameof(propertiesJson);
errorMessage = ex.Message;
return false;
}
return true;
}
private bool ConfigurationDefinitionExists(Guid? templateRevisionId, string kind, string name, Guid? excludeId)
{
var query = _context.ConfigurationDefinitions.AsQueryable();
if (excludeId.HasValue)
query = query.Where(definition => definition.Id != excludeId.Value);
query = templateRevisionId.HasValue
? query.Where(definition => definition.TemplateRevisionId == templateRevisionId.Value)
: query.Where(definition => definition.TemplateRevisionId == null);
return query.Any(definition => definition.Kind == kind && definition.Name == name);
}
}
}
+111
View File
@@ -0,0 +1,111 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationValue.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationValue.Get;
using Microsoft.SelfService.Portal.Core.API.JsonDocuments;
using Microsoft.SelfService.Portal.Core.API.Models;
namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/configuration-values")]
[ApiController]
[Authorize(Policy = ApiPolicies.ConfigurationRead)]
public class ConfigurationValueController : Controller
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public ConfigurationValueController(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpGet]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetConfigurationValueDto>))]
public IActionResult GetConfigurationValues([FromQuery] Guid? definitionId, [FromQuery] string? scopeType, [FromQuery] Guid? scopeId)
{
var query = _context.ConfigurationValues
.AsNoTracking()
.Include(value => value.ConfigurationDefinition)
.AsQueryable();
if (definitionId.HasValue)
query = query.Where(value => value.ConfigurationDefinitionId == definitionId.Value);
if (!string.IsNullOrWhiteSpace(scopeType))
query = query.Where(value => value.ScopeType == scopeType);
if (scopeId.HasValue)
query = query.Where(value => value.ScopeId == scopeId.Value);
var values = query
.OrderBy(value => value.ScopeType)
.ThenBy(value => value.SortOrder)
.ThenBy(value => value.ConfigurationDefinition.Name)
.ToList();
return Ok(_mapper.Map<List<GetConfigurationValueDto>>(values));
}
[HttpPost]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
public IActionResult AddConfigurationValue([FromBody] AddConfigurationValueDto configurationValue)
{
if (configurationValue == null)
return BadRequest(ModelState);
if (!_context.ConfigurationDefinitions.Any(definition => definition.Id == configurationValue.ConfigurationDefinitionId))
{
ModelState.AddModelError(nameof(configurationValue.ConfigurationDefinitionId), "Configuration definition was not found.");
return BadRequest(ModelState);
}
if (!string.IsNullOrWhiteSpace(configurationValue.ValueJson))
{
try
{
configurationValue.ValueJson = ConfigurationDocumentValidator.NormalizeAndValidate(
configurationValue.ValueJson,
ConfigurationDocumentKind.Metadata).JsonData;
}
catch (ConfigurationDocumentValidationException ex)
{
ModelState.AddModelError(nameof(configurationValue.ValueJson), ex.Message);
return BadRequest(ModelState);
}
}
var model = _mapper.Map<ConfigurationValueModel>(configurationValue);
model.Id = Guid.NewGuid();
model.ValueHash = TemplateVersionModel.ComputeSha256(model.ValueJson ?? model.SourcePath ?? string.Empty);
_context.ConfigurationValues.Add(model);
_context.SaveChanges();
return Ok(model.Id);
}
[HttpDelete("{id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult DeleteConfigurationValue(Guid id)
{
var value = _context.ConfigurationValues.FirstOrDefault(existing => existing.Id == id);
if (value == null)
return NotFound();
_context.ConfigurationValues.Remove(value);
_context.SaveChanges();
return NoContent();
}
}
}
+150
View File
@@ -0,0 +1,150 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.CredentialSecret.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.CredentialSecret.Get;
using Microsoft.SelfService.Portal.Core.API.JsonDocuments;
using Microsoft.SelfService.Portal.Core.API.Models;
namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/credential-secrets")]
[ApiController]
[Authorize(Policy = ApiPolicies.CredentialRead)]
public class CredentialSecretController : Controller
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public CredentialSecretController(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpGet]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetCredentialSecretDto>))]
public IActionResult GetCredentialSecrets()
{
var secrets = _context.CredentialSecrets
.AsNoTracking()
.OrderBy(secret => secret.Name)
.ToList();
return Ok(_mapper.Map<List<GetCredentialSecretDto>>(secrets));
}
[HttpGet("{id}")]
[ProducesResponseType(200, Type = typeof(GetCredentialSecretDto))]
[ProducesResponseType(404)]
public IActionResult GetCredentialSecretById(Guid id)
{
var secret = _context.CredentialSecrets
.AsNoTracking()
.FirstOrDefault(existing => existing.Id == id);
if (secret == null)
return NotFound();
return Ok(_mapper.Map<GetCredentialSecretDto>(secret));
}
[HttpGet("resolve")]
[Authorize(Policy = ApiPolicies.CredentialResolve)]
[ProducesResponseType(200, Type = typeof(GetCredentialSecretValueDto))]
[ProducesResponseType(404)]
public IActionResult ResolveCredentialSecret([FromQuery] string name)
{
var secret = _context.CredentialSecrets
.AsNoTracking()
.FirstOrDefault(existing => existing.Name == name && existing.IsEnabled);
if (secret == null)
return NotFound();
return Ok(_mapper.Map<GetCredentialSecretValueDto>(secret));
}
[HttpPost]
[Authorize(Policy = ApiPolicies.CredentialWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
public IActionResult AddCredentialSecret([FromBody] AddCredentialSecretDto credentialSecret)
{
if (credentialSecret == null)
return BadRequest(ModelState);
if (_context.CredentialSecrets.Any(existing => existing.Name == credentialSecret.Name))
{
ModelState.AddModelError(nameof(credentialSecret.Name), "Credential secret already exists.");
return StatusCode(422, ModelState);
}
if (!string.IsNullOrWhiteSpace(credentialSecret.MetadataJson))
{
try
{
credentialSecret.MetadataJson = ConfigurationDocumentValidator.NormalizeAndValidate(
credentialSecret.MetadataJson,
ConfigurationDocumentKind.Metadata).JsonData;
}
catch (ConfigurationDocumentValidationException ex)
{
ModelState.AddModelError(nameof(credentialSecret.MetadataJson), ex.Message);
return BadRequest(ModelState);
}
}
var model = _mapper.Map<CredentialSecretModel>(credentialSecret);
model.Id = Guid.NewGuid();
_context.CredentialSecrets.Add(model);
_context.SaveChanges();
return Ok(model.Id);
}
[HttpPut("{id}")]
[Authorize(Policy = ApiPolicies.CredentialWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult UpdateCredentialSecret(Guid id, [FromBody] AddCredentialSecretDto credentialSecret)
{
var existing = _context.CredentialSecrets.FirstOrDefault(secret => secret.Id == id);
if (existing == null)
return NotFound();
existing.Name = credentialSecret.Name;
existing.UserName = credentialSecret.UserName;
existing.SecretValue = credentialSecret.SecretValue;
existing.SecretType = credentialSecret.SecretType;
existing.MetadataJson = credentialSecret.MetadataJson;
existing.IsEnabled = credentialSecret.IsEnabled;
existing.Modified = DateTime.UtcNow;
existing.ModifiedBy = User?.Identity?.Name ?? "System";
_context.SaveChanges();
return NoContent();
}
[HttpDelete("{id}")]
[Authorize(Policy = ApiPolicies.CredentialWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult DeleteCredentialSecret(Guid id)
{
var secret = _context.CredentialSecrets.FirstOrDefault(existing => existing.Id == id);
if (secret == null)
return NotFound();
_context.CredentialSecrets.Remove(secret);
_context.SaveChanges();
return NoContent();
}
}
}
+203
View File
@@ -0,0 +1,203 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentArtifact.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentArtifact.Get;
using Microsoft.SelfService.Portal.Core.API.JsonDocuments;
using Microsoft.SelfService.Portal.Core.API.Models;
using System.Text.Json;
namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/deployment-artifacts")]
[ApiController]
[Authorize(Policy = ApiPolicies.DeploymentRead)]
public class DeploymentArtifactController : Controller
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public DeploymentArtifactController(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
[HttpGet]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetDeploymentArtifactDto>))]
public IActionResult GetDeploymentArtifacts([FromQuery] Guid? deploymentGroupId, [FromQuery] Guid? targetId)
{
var query = _context.DeploymentArtifacts
.AsNoTracking()
.AsQueryable();
if (deploymentGroupId.HasValue)
query = query.Where(artifact => artifact.DeploymentGroupId == deploymentGroupId.Value);
if (targetId.HasValue)
query = query.Where(artifact => artifact.TargetId == targetId.Value);
var artifacts = query
.OrderByDescending(artifact => artifact.Created)
.ToList();
return Ok(_mapper.Map<List<GetDeploymentArtifactDto>>(artifacts));
}
[HttpGet("{id}")]
[ProducesResponseType(200, Type = typeof(GetDeploymentArtifactDto))]
[ProducesResponseType(404)]
public IActionResult GetDeploymentArtifactById(Guid id)
{
var artifact = _context.DeploymentArtifacts
.AsNoTracking()
.FirstOrDefault(existing => existing.Id == id);
if (artifact == null)
return NotFound();
return Ok(_mapper.Map<GetDeploymentArtifactDto>(artifact));
}
[HttpPost]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
public IActionResult AddDeploymentArtifact([FromBody] AddDeploymentArtifactDto deploymentArtifact)
{
if (deploymentArtifact == null)
return BadRequest(ModelState);
if (!_context.DeploymentGroups.Any(group => group.Id == deploymentArtifact.DeploymentGroupId))
{
ModelState.AddModelError(nameof(deploymentArtifact.DeploymentGroupId), "Deployment group was not found.");
return BadRequest(ModelState);
}
if (deploymentArtifact.TargetId.HasValue && !_context.Targets.Any(target => target.Id == deploymentArtifact.TargetId.Value))
{
ModelState.AddModelError(nameof(deploymentArtifact.TargetId), "Target was not found.");
return BadRequest(ModelState);
}
try
{
deploymentArtifact.DeploymentJson = NormalizeJson(deploymentArtifact.DeploymentJson);
deploymentArtifact.SourceJson = NormalizeOptionalJson(deploymentArtifact.SourceJson);
deploymentArtifact.ResolvedJson = NormalizeOptionalJson(deploymentArtifact.ResolvedJson);
deploymentArtifact.SourceSnapshotJson = NormalizeOptionalJson(deploymentArtifact.SourceSnapshotJson);
}
catch (ConfigurationDocumentValidationException ex)
{
ModelState.AddModelError(nameof(deploymentArtifact.DeploymentJson), ex.Message);
return BadRequest(ModelState);
}
var model = _mapper.Map<DeploymentArtifactModel>(deploymentArtifact);
model.Id = Guid.NewGuid();
model.InputHash = TemplateVersionModel.ComputeSha256(model.SourceJson ?? model.DeploymentJson);
model.OutputHash = TemplateVersionModel.ComputeSha256(model.ResolvedJson ?? model.DeploymentJson);
model.IsStale = false;
_context.DeploymentArtifacts.Add(model);
if (model.TargetId.HasValue)
{
var deployment = _context.Deployments.FirstOrDefault(existing =>
existing.DeploymentGroupId == model.DeploymentGroupId
&& existing.TargetId == model.TargetId.Value);
if (deployment != null)
{
deployment.CurrentArtifactId = model.Id;
deployment.SourceJson = model.SourceJson;
deployment.JSONData = model.DeploymentJson;
}
}
_context.SaveChanges();
return Ok(model.Id);
}
[HttpPost("{id}/stale-check")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(200, Type = typeof(GetDeploymentArtifactDto))]
[ProducesResponseType(404)]
public IActionResult CheckDeploymentArtifactStale(Guid id)
{
var artifact = _context.DeploymentArtifacts.FirstOrDefault(existing => existing.Id == id);
if (artifact == null)
return NotFound();
var staleReasons = BuildStaleReasons(artifact);
artifact.IsStale = staleReasons.Count > 0;
artifact.StaleReasonJson = staleReasons.Count > 0
? JsonSerializer.Serialize(staleReasons)
: null;
_context.SaveChanges();
return Ok(_mapper.Map<GetDeploymentArtifactDto>(artifact));
}
private static string NormalizeJson(string json)
{
return ConfigurationDocumentValidator.NormalizeAndValidate(
string.IsNullOrWhiteSpace(json) ? "{}" : json,
ConfigurationDocumentKind.Metadata).JsonData;
}
private static string? NormalizeOptionalJson(string? json)
{
return string.IsNullOrWhiteSpace(json) ? null : NormalizeJson(json);
}
private List<object> BuildStaleReasons(DeploymentArtifactModel artifact)
{
var reasons = new List<object>();
if (string.IsNullOrWhiteSpace(artifact.SourceSnapshotJson))
{
return reasons;
}
using var snapshot = JsonDocument.Parse(artifact.SourceSnapshotJson);
if (!snapshot.RootElement.TryGetProperty("templateRevisions", out var revisions)
|| revisions.ValueKind != JsonValueKind.Array)
{
return reasons;
}
foreach (var source in revisions.EnumerateArray())
{
if (!source.TryGetProperty("id", out var idElement)
|| !Guid.TryParse(idElement.GetString(), out var revisionId)
|| !source.TryGetProperty("hash", out var hashElement))
{
continue;
}
var current = _context.TemplateRevisions
.AsNoTracking()
.FirstOrDefault(revision => revision.Id == revisionId);
if (current == null)
{
reasons.Add(new { Type = "TemplateRevisionMissing", RevisionId = revisionId });
continue;
}
var expectedHash = hashElement.GetString();
if (!string.Equals(current.JsonHash, expectedHash, StringComparison.OrdinalIgnoreCase))
{
reasons.Add(new { Type = "TemplateRevisionHashChanged", RevisionId = revisionId, OldHash = expectedHash, CurrentHash = current.JsonHash });
}
}
return reasons;
}
}
}
+28 -1
View File
@@ -1,6 +1,8 @@
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentBatch.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentBatch.Edit;
@@ -15,6 +17,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/deployment-batches")]
[ApiController]
[Authorize(Policy = ApiPolicies.DeploymentRead)]
public class DeploymentBatchController : Controller
{
private readonly IDeploymentBatchInterface _deploymentBatchInterface;
@@ -82,6 +85,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
public IActionResult AddDeploymentBatch([FromBody] AddDeploymentBatchDto deploymentBatch)
@@ -118,6 +122,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{id}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -144,6 +149,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{id}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -182,6 +188,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("{id}/template-selections")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -190,12 +197,26 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
if (!_deploymentBatchInterface.CheckDeploymentBatchById(id))
return NotFound();
if (!_context.TemplateVersions.Any(version => version.Id == templateSelection.TemplateVersionId))
var templateVersion = _context.TemplateVersions
.Include(version => version.TemplateRevisions)
.FirstOrDefault(version => version.Id == templateSelection.TemplateVersionId);
if (templateVersion == null)
{
ModelState.AddModelError(nameof(templateSelection.TemplateVersionId), "Template version was not found.");
return BadRequest(ModelState);
}
var templateRevisionId = templateSelection.TemplateRevisionId
?? templateVersion.TemplateRevisions.FirstOrDefault(revision => revision.IsCurrent)?.Id;
if (templateRevisionId.HasValue
&& !templateVersion.TemplateRevisions.Any(revision => revision.Id == templateRevisionId.Value))
{
ModelState.AddModelError(nameof(templateSelection.TemplateRevisionId), "Template revision was not found in this template version.");
return BadRequest(ModelState);
}
var sortOrder = templateSelection.SortOrder > 0
? templateSelection.SortOrder
: GetNextTemplateSelectionSortOrder(id);
@@ -210,6 +231,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
model.Id = Guid.NewGuid();
model.DeploymentGroupId = id;
model.SortOrder = sortOrder;
model.TemplateRevisionId = templateRevisionId;
_context.DeploymentTemplateSelections.Add(model);
_context.SaveChanges();
@@ -218,6 +240,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{id}/template-selections/{selectionId}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult DeleteTemplateSelection(Guid id, Guid selectionId)
@@ -235,6 +258,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("{id}/parameter-values")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -284,6 +308,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{id}/parameter-values/{parameterValueId}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult DeleteParameterValue(Guid id, Guid parameterValueId)
@@ -301,6 +326,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("{id}/target-assignments")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -352,6 +378,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{id}/target-assignments/{targetAssignmentId}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult DeleteTargetAssignment(Guid id, Guid targetAssignmentId)
+13 -1
View File
@@ -1,5 +1,7 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Dto.Deployment.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Deployment.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.Deployment.Edit;
@@ -13,6 +15,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = ApiPolicies.DeploymentRead)]
public class DeploymentController : Controller
{
private readonly IDeploymentInterface _deploymentInterface;
@@ -62,6 +65,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
public IActionResult AddDeploymentById([FromBody] AddDeploymentDto deployment)
@@ -93,6 +97,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("Request")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult AddDeploymentRequest([FromBody] AddDeploymentRequestDto request)
@@ -108,7 +113,8 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
var queueJobId = _queueJobService.EnqueueDeploymentRequest(
request.DeploymentGroupId,
request.TargetIds,
request.JsonData);
request.JsonData,
request.DeploymentArtifactId);
return Ok(queueJobId);
}
@@ -233,6 +239,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("QueueJobs/{Id}/Retry")]
[Authorize(Policy = ApiPolicies.QueueProcess)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult RetryQueueJob(Guid Id)
@@ -244,6 +251,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("QueueJobs/Steps/{stepId}/Approve")]
[Authorize(Policy = ApiPolicies.QueueProcess)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult ApproveQueueJobStep(Guid stepId, [FromBody] QueueJobStepApprovalDto? payload)
@@ -256,6 +264,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("QueueJobs/Steps/{stepId}/Reject")]
[Authorize(Policy = ApiPolicies.QueueProcess)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult RejectQueueJobStep(Guid stepId, [FromBody] QueueJobStepApprovalDto? payload)
@@ -268,6 +277,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -290,6 +300,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("Batch/{deploymentBatchId}/Target/{targetId}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -316,6 +327,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
+7 -2
View File
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentRule.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentRule.Get;
@@ -9,6 +11,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/deployment-rules")]
[ApiController]
[Authorize(Policy = ApiPolicies.DeploymentRead)]
public class DeploymentRuleController : Controller
{
private readonly DataContext _context;
@@ -49,6 +52,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
public IActionResult AddDeploymentRule([FromBody] AddDeploymentRuleDto deploymentRule)
@@ -84,6 +88,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{id}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult EditDeploymentRule(Guid id, [FromBody] AddDeploymentRuleDto deploymentRule)
@@ -123,6 +128,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{id}")]
[Authorize(Policy = ApiPolicies.DeploymentWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult DeleteDeploymentRule(Guid id)
@@ -181,4 +187,3 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
}
}
+8
View File
@@ -1,5 +1,7 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Dto.Domain.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Domain.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.Domain.Get;
@@ -10,6 +12,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = ApiPolicies.ConfigurationRead)]
public class DomainController : Controller
{
private readonly IDomainInterface _domainInterface;
@@ -53,6 +56,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
public IActionResult AddDomainById([FromBody] AddDomainDto domain)
@@ -83,6 +87,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -105,6 +110,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -149,6 +155,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("{DomainId}/Environment/{EnvironmentId}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(200, Type = typeof(EnvironmentDomainsModel))]
[ProducesResponseType(400)]
public IActionResult LinkDomainByIdToEnvironment(Guid DomainId, Guid EnvironmentId)
@@ -183,6 +190,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{DomainId}/Environment/{EnvironmentId}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(200, Type = typeof(EnvironmentDomainsModel))]
[ProducesResponseType(400)]
public IActionResult UnlinkDomainByIdFromEnvironment(Guid DomainId, Guid EnvironmentId)
+6
View File
@@ -1,5 +1,7 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Dto.Environment.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Environment.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.Environment.Get;
@@ -12,6 +14,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = ApiPolicies.ConfigurationRead)]
public class EnvironmentController : Controller
{
private readonly IEnvironmentInterface _environmentInterface;
@@ -53,6 +56,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
public IActionResult AddEnvironmentById([FromBody] AddEnvironmentDto environment)
@@ -88,6 +92,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -110,6 +115,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
+9
View File
@@ -1,5 +1,7 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Dto.Service.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Service.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.Service.Get;
@@ -11,6 +13,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = ApiPolicies.ConfigurationRead)]
public class ServiceController : Controller
{
private readonly IMapper _mapper;
@@ -51,6 +54,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult AddServiceById([FromBody] AddServiceDto service)
@@ -79,6 +83,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -106,6 +111,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -141,6 +147,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("{Id}/RoleDefinitions")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -177,6 +184,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}/RoleDefinitions/{RoleDefinitionId}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -216,6 +224,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}/RoleDefinitions/{RoleDefinitionId}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult DeleteRoleDefinition(Guid Id, Guid RoleDefinitionId)
+8
View File
@@ -1,5 +1,7 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Dto.Target.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Target.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.Target.Get;
@@ -10,6 +12,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = ApiPolicies.ConfigurationRead)]
public class TargetController : Controller
{
private readonly ITargetInterface _targetInterface;
@@ -52,6 +55,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult AddTargetById([FromBody] AddTargetDto target)
@@ -86,6 +90,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -120,6 +125,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("{Id}/Domain/{domainId}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -151,6 +157,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}/Domain")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult UnlinkTargetFromDomain(Guid Id)
@@ -177,6 +184,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}")]
[Authorize(Policy = ApiPolicies.ConfigurationWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -1,5 +1,7 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateCategory.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateCategory.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateCategory.Get;
@@ -10,6 +12,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = ApiPolicies.TemplateRead)]
public class TemplateCategoryController : Controller
{
private readonly IMapper _mapper;
@@ -50,6 +53,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult AddTemplateCategoryById([FromBody] AddTemplateCategoryDto templateCategory)
@@ -78,6 +82,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}")]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -105,6 +110,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}")]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
+83
View File
@@ -1,8 +1,13 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Dto.Template.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Template.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.Template.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateRevision.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateRevision.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateVersion.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateVersion.Get;
using Microsoft.SelfService.Portal.Core.API.Interfaces;
@@ -13,6 +18,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize(Policy = ApiPolicies.TemplateRead)]
public class TemplateController : Controller
{
private readonly ITemplateInterface _templateInterface;
@@ -58,6 +64,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
public IActionResult AddTemplateById([FromBody] AddTemplateDto template)
@@ -94,6 +101,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPut("{Id}")]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -136,6 +144,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpDelete("{Id}")]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -183,6 +192,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
}
[HttpPost("{Id}/Versions")]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
@@ -223,7 +233,80 @@ namespace Microsoft.SelfService.Portal.Core.API.Controllers
return Ok(version.Id);
}
[HttpGet("{Id}/Versions/{VersionId}/Revisions")]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetTemplateRevisionDto>))]
[ProducesResponseType(404)]
public IActionResult GetTemplateRevisions(Guid Id, Guid VersionId)
{
if (!_templateInterface.CheckTemplateVersionById(Id, VersionId))
return NotFound();
return Ok(_mapper.Map<List<GetTemplateRevisionDto>>(_templateInterface.GetTemplateRevisions(Id, VersionId)));
}
[HttpGet("{Id}/Versions/{VersionId}/Revisions/{RevisionId}")]
[ProducesResponseType(200, Type = typeof(GetTemplateRevisionDto))]
[ProducesResponseType(404)]
public IActionResult GetTemplateRevisionById(Guid Id, Guid VersionId, Guid RevisionId)
{
var revision = _templateInterface.GetTemplateRevisionById(Id, VersionId, RevisionId);
if (revision == null)
return NotFound();
return Ok(_mapper.Map<GetTemplateRevisionDto>(revision));
}
[HttpGet("{Id}/Versions/{VersionId}/Revisions/{RevisionId}/Definitions")]
[ProducesResponseType(200, Type = typeof(IEnumerable<GetConfigurationDefinitionDto>))]
[ProducesResponseType(404)]
public IActionResult GetTemplateRevisionDefinitions(Guid Id, Guid VersionId, Guid RevisionId)
{
var revision = _templateInterface.GetTemplateRevisionById(Id, VersionId, RevisionId);
if (revision == null)
return NotFound();
return Ok(_mapper.Map<List<GetConfigurationDefinitionDto>>(revision.ConfigurationDefinitions));
}
[HttpPost("{Id}/Versions/{VersionId}/Revisions")]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(200, Type = typeof(Guid))]
[ProducesResponseType(400)]
[ProducesResponseType(404)]
public IActionResult AddTemplateRevision(Guid Id, Guid VersionId, [FromBody] AddTemplateRevisionDto templateRevision)
{
if (templateRevision == null)
return BadRequest(ModelState);
if (!_templateInterface.CheckTemplateVersionById(Id, VersionId))
return NotFound();
if (!ModelState.IsValid)
return BadRequest(ModelState);
var revision = _mapper.Map<TemplateRevisionModel>(templateRevision);
revision.PublishedBy = templateRevision.PublishedBy ?? User?.Identity?.Name ?? "System";
try
{
if (!_templateInterface.AddTemplateRevision(Id, VersionId, revision, templateRevision.Publish))
{
ModelState.AddModelError("", "Something went wrong while saving template revision");
return StatusCode(500, ModelState);
}
}
catch (ConfigurationDocumentValidationException ex)
{
ModelState.AddModelError(nameof(templateRevision.JsonData), ex.Message);
return BadRequest(ModelState);
}
var savedRevision = _templateInterface.GetCurrentTemplateRevision(VersionId);
return Ok(savedRevision?.Id ?? revision.Id);
}
[HttpPost("{Id}/Versions/{VersionId}/Publish")]
[Authorize(Policy = ApiPolicies.TemplateWrite)]
[ProducesResponseType(204)]
[ProducesResponseType(404)]
public IActionResult PublishTemplateVersion(Guid Id, Guid VersionId)
+11
View File
@@ -0,0 +1,11 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.Auth
{
public class CreateManagedTokenRequestDto
{
public string Name { get; set; } = string.Empty;
public string Scope { get; set; } = string.Empty;
public DateTime? ExpiresAt { get; set; }
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.Auth
{
public class CreateManagedTokenResponseDto
{
public Guid Id { get; set; }
public string AccessToken { get; set; } = string.Empty;
public string TokenType { get; set; } = "Bearer";
public int ExpiresIn { get; set; }
public DateTime ExpiresAt { get; set; }
public string Scope { get; set; } = string.Empty;
}
}
+29
View File
@@ -0,0 +1,29 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.Auth
{
public class GetManagedTokenDto
{
public Guid Id { get; set; }
public string Subject { get; set; } = string.Empty;
public string SubjectType { get; set; } = string.Empty;
public string? ClientId { get; set; }
public string Name { get; set; } = string.Empty;
public string Scope { get; set; } = string.Empty;
public DateTime IssuedAt { get; set; }
public DateTime ExpiresAt { get; set; }
public DateTime? RevokedAt { get; set; }
public string? RevokedBy { get; set; }
public DateTime? LastUsedAt { get; set; }
public bool IsActive { get; set; }
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.Auth
{
public class TokenRequestDto
{
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty;
public string? Scope { get; set; }
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.Auth
{
public class TokenResponseDto
{
public string AccessToken { get; set; } = string.Empty;
public string TokenType { get; set; } = "Bearer";
public int ExpiresIn { get; set; }
public string Scope { get; set; } = string.Empty;
}
}
@@ -0,0 +1,10 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Add
{
public class AddConfigurationDefinitionDto
{
public Guid? TemplateRevisionId { get; set; }
public string Kind { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string PropertiesJson { get; set; } = "{}";
}
}
@@ -0,0 +1,10 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Edit
{
public class EditConfigurationDefinitionDto
{
public Guid? TemplateRevisionId { get; set; }
public string Kind { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string PropertiesJson { get; set; } = "{}";
}
}
@@ -0,0 +1,13 @@
using Microsoft.SelfService.Portal.Core.API.Dto;
namespace Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Get
{
public class GetConfigurationDefinitionDto : BaseDetailsDto
{
public Guid? TemplateRevisionId { get; set; }
public string Kind { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string PropertiesJson { get; set; } = "{}";
public string DefinitionHash { get; set; } = string.Empty;
}
}
@@ -0,0 +1,13 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationValue.Add
{
public class AddConfigurationValueDto
{
public Guid ConfigurationDefinitionId { get; set; }
public string ScopeType { get; set; } = "TemplateRevision";
public Guid ScopeId { get; set; }
public string ValueSourceType { get; set; } = "Static";
public string? SourcePath { get; set; }
public string? ValueJson { get; set; }
public int SortOrder { get; set; }
}
}
@@ -0,0 +1,16 @@
using Microsoft.SelfService.Portal.Core.API.Dto;
namespace Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationValue.Get
{
public class GetConfigurationValueDto : BaseDetailsDto
{
public Guid ConfigurationDefinitionId { get; set; }
public string ScopeType { get; set; } = string.Empty;
public Guid ScopeId { get; set; }
public string ValueSourceType { get; set; } = string.Empty;
public string? SourcePath { get; set; }
public string? ValueJson { get; set; }
public string ValueHash { get; set; } = string.Empty;
public int SortOrder { get; set; }
}
}
@@ -0,0 +1,12 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.CredentialSecret.Add
{
public class AddCredentialSecretDto
{
public string Name { get; set; } = string.Empty;
public string? UserName { get; set; }
public string SecretValue { get; set; } = string.Empty;
public string SecretType { get; set; } = "Credential";
public string? MetadataJson { get; set; }
public bool IsEnabled { get; set; } = true;
}
}
@@ -0,0 +1,13 @@
using Microsoft.SelfService.Portal.Core.API.Dto;
namespace Microsoft.SelfService.Portal.Core.API.Dto.CredentialSecret.Get
{
public class GetCredentialSecretDto : BaseDetailsDto
{
public string Name { get; set; } = string.Empty;
public string? UserName { get; set; }
public string SecretType { get; set; } = "Credential";
public string? MetadataJson { get; set; }
public bool IsEnabled { get; set; }
}
}
@@ -0,0 +1,11 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.CredentialSecret.Get
{
public class GetCredentialSecretValueDto
{
public string Name { get; set; } = string.Empty;
public string? UserName { get; set; }
public string SecretValue { get; set; } = string.Empty;
public string SecretType { get; set; } = "Credential";
public string? MetadataJson { get; set; }
}
}
@@ -3,6 +3,7 @@
public class AddDeploymentRequestDto
{
public Guid DeploymentGroupId { get; set; }
public Guid? DeploymentArtifactId { get; set; }
public ICollection<Guid> TargetIds { get; set; } = new List<Guid>();
public string JsonData { get; set; } = "{}";
}
@@ -0,0 +1,14 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.DeploymentArtifact.Add
{
public class AddDeploymentArtifactDto
{
public Guid DeploymentGroupId { get; set; }
public Guid? TargetId { get; set; }
public string ArtifactType { get; set; } = "ResolvedConfigurationData";
public string Status { get; set; } = "Pending";
public string DeploymentJson { get; set; } = "{}";
public string? SourceJson { get; set; }
public string? ResolvedJson { get; set; }
public string? SourceSnapshotJson { get; set; }
}
}
@@ -0,0 +1,20 @@
using Microsoft.SelfService.Portal.Core.API.Dto;
namespace Microsoft.SelfService.Portal.Core.API.Dto.DeploymentArtifact.Get
{
public class GetDeploymentArtifactDto : BaseDetailsDto
{
public Guid DeploymentGroupId { get; set; }
public Guid? TargetId { get; set; }
public string ArtifactType { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string DeploymentJson { get; set; } = "{}";
public string? SourceJson { get; set; }
public string? ResolvedJson { get; set; }
public string? SourceSnapshotJson { get; set; }
public string InputHash { get; set; } = string.Empty;
public string OutputHash { get; set; } = string.Empty;
public bool IsStale { get; set; }
public string? StaleReasonJson { get; set; }
}
}
@@ -3,6 +3,7 @@
public class AddDeploymentTemplateSelectionDto
{
public Guid TemplateVersionId { get; set; }
public Guid? TemplateRevisionId { get; set; }
public string TemplateRole { get; set; } = "Service";
public int SortOrder { get; set; }
public string? Alias { get; set; }
@@ -6,6 +6,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Dto.DeploymentComposition.Get
{
public Guid DeploymentGroupId { get; set; }
public Guid TemplateVersionId { get; set; }
public Guid? TemplateRevisionId { get; set; }
public string TemplateRole { get; set; } = string.Empty;
public int SortOrder { get; set; }
public string? Alias { get; set; }
@@ -0,0 +1,10 @@
namespace Microsoft.SelfService.Portal.Core.API.Dto.TemplateRevision.Add
{
public class AddTemplateRevisionDto
{
public string JsonData { get; set; } = "{}";
public string SchemaVersion { get; set; } = "1.0";
public bool Publish { get; set; }
public string? PublishedBy { get; set; }
}
}
@@ -0,0 +1,17 @@
using Microsoft.SelfService.Portal.Core.API.Dto;
namespace Microsoft.SelfService.Portal.Core.API.Dto.TemplateRevision.Get
{
public class GetTemplateRevisionDto : BaseDetailsDto
{
public Guid TemplateVersionId { get; set; }
public int RevisionNumber { get; set; }
public string JsonData { get; set; } = "{}";
public string JsonHash { get; set; } = string.Empty;
public string SchemaVersion { get; set; } = "1.0";
public bool IsCurrent { get; set; }
public bool IsPublished { get; set; }
public DateTime? PublishedAt { get; set; }
public string? PublishedBy { get; set; }
}
}
+32
View File
@@ -23,6 +23,17 @@ using Microsoft.SelfService.Portal.Core.API.Dto.Template.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Template.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateVersion.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateVersion.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateRevision.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.TemplateRevision.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Edit;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationDefinition.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationValue.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.ConfigurationValue.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentArtifact.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.DeploymentArtifact.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.CredentialSecret.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.CredentialSecret.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.Service.Get;
using Microsoft.SelfService.Portal.Core.API.Dto.Service.Add;
using Microsoft.SelfService.Portal.Core.API.Dto.Service.Edit;
@@ -131,6 +142,27 @@ namespace Microsoft.SelfService.Portal.Core.API.Helper
CreateMap<TemplateVersionModel, AddTemplateVersionDto>();
CreateMap<AddTemplateVersionDto, TemplateVersionModel>();
/** Template Revision Model **/
CreateMap<TemplateRevisionModel, GetTemplateRevisionDto>();
CreateMap<GetTemplateRevisionDto, TemplateRevisionModel>();
CreateMap<AddTemplateRevisionDto, TemplateRevisionModel>();
/** Configuration Data Model **/
CreateMap<ConfigurationDefinitionModel, GetConfigurationDefinitionDto>();
CreateMap<AddConfigurationDefinitionDto, ConfigurationDefinitionModel>();
CreateMap<EditConfigurationDefinitionDto, ConfigurationDefinitionModel>();
CreateMap<ConfigurationValueModel, GetConfigurationValueDto>();
CreateMap<AddConfigurationValueDto, ConfigurationValueModel>();
/** Deployment Artifact Model **/
CreateMap<DeploymentArtifactModel, GetDeploymentArtifactDto>();
CreateMap<AddDeploymentArtifactDto, DeploymentArtifactModel>();
/** Credential Secret Model **/
CreateMap<CredentialSecretModel, GetCredentialSecretDto>();
CreateMap<CredentialSecretModel, GetCredentialSecretValueDto>();
CreateMap<AddCredentialSecretDto, CredentialSecretModel>();
/** Service Model **/
CreateMap<ServiceModel, GetServiceDto>();
CreateMap<GetServiceDto, ServiceModel>();
+1 -1
View File
@@ -3,7 +3,7 @@
public interface IQueueJobService
{
Guid EnqueueTemplateJsonChanged(Guid templateId, string oldJsonData, string newJsonData);
Guid EnqueueDeploymentRequest(Guid deploymentGroupId, ICollection<Guid> targetIds, string jsonData);
Guid EnqueueDeploymentRequest(Guid deploymentGroupId, ICollection<Guid> targetIds, string jsonData, Guid? deploymentArtifactId = null);
bool RetryQueueJob(Guid queueJobId);
bool ApproveQueueJobStep(Guid queueJobStepId, string approvedBy, string? comment);
bool RejectQueueJobStep(Guid queueJobStepId, string approvedBy, string? comment);
+4
View File
@@ -18,6 +18,10 @@ namespace Microsoft.SelfService.Portal.Core.API.Interfaces
ICollection<TemplateVersionModel> GetTemplateVersions(Guid templateId);
TemplateVersionModel? GetTemplateVersionById(Guid templateId, Guid versionId);
TemplateVersionModel? GetPublishedTemplateVersion(Guid templateId);
ICollection<TemplateRevisionModel> GetTemplateRevisions(Guid templateId, Guid versionId);
TemplateRevisionModel? GetTemplateRevisionById(Guid templateId, Guid versionId, Guid revisionId);
TemplateRevisionModel? GetCurrentTemplateRevision(Guid versionId);
bool AddTemplateRevision(Guid templateId, Guid versionId, TemplateRevisionModel revision, bool publish);
bool AddTemplateVersion(Guid templateId, TemplateVersionModel version, bool publish);
bool PublishTemplateVersion(Guid templateId, Guid versionId, string? publishedBy);
bool CheckTemplateVersionById(Guid templateId, Guid versionId);
@@ -5,10 +5,12 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>9c90fee1-4576-4f20-be83-715728173b96</UserSecretsId>
<DefaultItemExcludes>$(DefaultItemExcludes);buildcheck\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Negotiate" Version="10.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.8" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.8" />
@@ -1,604 +0,0 @@
version=1
# This file caches language service data to improve the performance of C# Dev Kit.
# It is not intended for manual editing. It can safely be deleted and will be
# regenerated automatically. For more information, see https://aka.ms/lscache
#
# To control where cache files are stored, use the following VS Code setting:
# "dotnet.projectsystem.cacheInProjectFolder": true
[project]
language=C#
primary
lastDtbSucceeded
[properties]
AssemblyName=Microsoft.SelfService.Portal.Core.API
CommandLineArgsForDesignTimeEvaluation=-langversion:14.0 -define:TRACE
CompilerGeneratedFilesOutputPath=
MaxSupportedLangVersion=14.0
ProjectAssetsFile=<PATH>obj/project.assets.json
RootNamespace=Microsoft.SelfService.Portal.Core.API
RunAnalyzers=
RunAnalyzersDuringLiveAnalysis=
SolutionPath=<PATH>../../Coding.sln
TargetFrameworkIdentifier=.NETCoreApp
TargetPath=<PATH>bin/Debug/net10.0/Microsoft.SelfService.Portal.Core.API.dll
TargetRefPath=<PATH>obj/Debug/net10.0/ref/Microsoft.SelfService.Portal.Core.API.dll
TemporaryDependencyNodeTargetIdentifier=net10.0
[commandLineArguments]
/noconfig
/unsafe-
/checked-
/nowarn:1701,1702,1701,1702,8002
/fullpaths
/nostdlib+
/errorreport:prompt
/warn:10
/define:TRACE;DEBUG;NET;NET10_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NET9_0_OR_GREATER;NET10_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER
/highentropyva+
/nullable:enable
/features:"InterceptorsNamespaces=;Microsoft.AspNetCore.OpenApi.Generated;;Microsoft.Extensions.Validation.Generated"
/debug+
/debug:portable
/filealign:512
/optimize-
/out:obj\Debug\net10.0\Microsoft.SelfService.Portal.Core.API.dll
/refout:obj\Debug\net10.0\refint\Microsoft.SelfService.Portal.Core.API.dll
/target:exe
/warnaserror-
/utf8output
/deterministic+
/langversion:14.0
/features:use-roslyn-tokenizer=true
/warnaserror+:NU1605,SYSLIB0011
[sourceFiles]
Context/DataContext.cs
Controllers/
AbstractController.cs
DeploymentController.cs
DeploymentGroupController.cs
DomainController.cs
EnvironmentController.cs
RunbookController.cs
ServiceController.cs
TemplateCategoryController.cs
TemplateController.cs
VirtualMachineController.cs
Dto/
AddEnvironmentDomainDto.cs
BaseDetailsDto.cs
BaseDto.cs
Deployment/Add/
AddDeploymentDto.cs
AddDeploymentRequestDto.cs
Deployment/
Edit/EditDeploymentDto.cs
Get/
GetDeploymentDetailsDto.cs
GetDeploymentDto.cs
GetQueueJobDetailsDto.cs
GetQueueJobDto.cs
GetQueueJobTargetDto.cs
DeploymentGroup/
Add/AddDeploymentGroupDto.cs
Edit/EditDeploymentGroupDto.cs
Get/
GetDeploymentGroupDetailsDto.cs
GetDeploymentGroupDto.cs
Domain/
Add/AddDomainDto.cs
Edit/EditDomainDto.cs
Get/
GetDomainDetailsDto.cs
GetDomainDto.cs
GetDomainEnvironmentDetailsDto.cs
GetDomainEnvironmentDto.cs
GetDomainVirtualMachineDetailsDto.cs
Environment/
Add/AddEnvironmentDto.cs
Edit/EditEnvironmentDto.cs
Get/
GetEnvironmentDetailsDto.cs
GetEnvironmentDomainDetailsDto.cs
GetEnvironmentDomainDto.cs
GetEnvironmentDto.cs
Runbook/
Add/AddRunbookDto.cs
Get/
GetRunbookDetailsDto.cs
GetRunbookDto.cs
Service/
Add/AddServiceDto.cs
Edit/EditServiceDto.cs
Get/
GetServiceDetailsDto.cs
GetServiceDto.cs
RoleDefinition/
AddServiceRoleDefinitionDto.cs
EditServiceRoleDefinitionDto.cs
Template/
Add/AddTemplateDto.cs
Edit/EditTemplateDto.cs
Get/
GetTemplateDetailsDto.cs
GetTemplateDto.cs
TemplateCategory/
Add/AddTemplateCategoryDto.cs
Edit/EditTemplateCategoryDto.cs
Get/
GetTemplateCategoryDetailsDto.cs
GetTemplateCategoryDto.cs
VirtualMachine/
Add/AddVirtualMachineDto.cs
Edit/EditVirtualMachineDto.cs
Get/
GetVirtualMachineDetailsDto.cs
GetVirtualMachineDto.cs
Events/
AbstractEventHandler.cs
Interfaces/IEventHandlerInterface.cs
Extensions/Dataannotations/DefaultValueSqlAttribute.cs
Handlers/DomainEditedHandler.cs
Helper/
APIHelper.cs
MappingProfilesHelper.cs
Interfaces/
IAbstractInterface.cs
IDeploymentGroupInterface.cs
IDeploymentInterface.cs
IDomainInterface.cs
IEnvironmentInterface.cs
IJobInterface.cs
IQueueJobService.cs
IRunbookInterface.cs
IServiceInterface.cs
ITemplateCategoryInterface.cs
ITemplateInterface.cs
IVirtualMachineInterface.cs
Migrations/
20231020093825_Initial.cs
20231020093825_Initial.Designer.cs
20260514210821_AddQueueJobs.cs
20260514210821_AddQueueJobs.Designer.cs
20260514210842_AddQueueJobModelSnapshotFix.cs
20260514210842_AddQueueJobModelSnapshotFix.Designer.cs
20260515195005_AddEnvironmentAndTargetProviderModel.cs
20260515195005_AddEnvironmentAndTargetProviderModel.Designer.cs
20260515195742_RemoveCloudEnabledFromEnvironment.cs
20260515195742_RemoveCloudEnabledFromEnvironment.Designer.cs
20260515204426_RemoveVirtualMachineTargetAndProvider.cs
20260515204426_RemoveVirtualMachineTargetAndProvider.Designer.cs
20260515205512_MakeVirtualMachineDomainOptionalAndAddLinkUnlinkFlow.cs
20260515205512_MakeVirtualMachineDomainOptionalAndAddLinkUnlinkFlow.Designer.cs
20260515214839_AddServiceCloudFlagAndRoleDefinitions.cs
20260515214839_AddServiceCloudFlagAndRoleDefinitions.Designer.cs
DataContextModelSnapshot.cs
Models/
BaseJunctionModel.cs
BaseModel.cs
DeploymentGroupModel.cs
DeploymentModel.cs
DomainModel.cs
EnvironmentDomainsModel.cs
EnvironmentModel.cs
EnvironmentTypes.cs
EventModel.cs
JobModel.cs
OptionCategoryModel.cs
OptionModel.cs
QueueJobModel.cs
QueueJobStatus.cs
QueueJobTargetModel.cs
QueueJobType.cs
RunbookModel.cs
ServiceModel.cs
ServiceRoleDefinitionModel.cs
TemplateCategoryModel.cs
TemplateModel.cs
TemplateOptionModel.cs
VirtualMachineModel.cs
obj/Debug/net10.0/
.NETCoreApp,Version=v10.0.AssemblyAttributes.cs
Microsoft.SelfService.Portal.Core.API.AssemblyInfo.cs
Microsoft.SelfService.Portal.Core.API.GlobalUsings.g.cs
Program.cs
Repository/
DeploymentGroupRepository.cs
DeploymentRepository.cs
DomainRepository.cs
EnvironmentRepository.cs
JobRepository.cs
RunbookRepository.cs
ServiceRepository.cs
TemplateCategoryRepository.cs
TemplateRepository.cs
VirtualMachineRepository.cs
Services/QueueJobService.cs
[metadataReferences]
<DOTNET>/packs/Microsoft.AspNetCore.App.Ref/10.0.8/ref/net10.0/
Microsoft.AspNetCore.Antiforgery.dll
Microsoft.AspNetCore.Authentication.Abstractions.dll
Microsoft.AspNetCore.Authentication.BearerToken.dll
Microsoft.AspNetCore.Authentication.Cookies.dll
Microsoft.AspNetCore.Authentication.Core.dll
Microsoft.AspNetCore.Authentication.dll
Microsoft.AspNetCore.Authentication.OAuth.dll
Microsoft.AspNetCore.Authorization.dll
Microsoft.AspNetCore.Authorization.Policy.dll
Microsoft.AspNetCore.Components.Authorization.dll
Microsoft.AspNetCore.Components.dll
Microsoft.AspNetCore.Components.Endpoints.dll
Microsoft.AspNetCore.Components.Forms.dll
Microsoft.AspNetCore.Components.Server.dll
Microsoft.AspNetCore.Components.Web.dll
Microsoft.AspNetCore.Connections.Abstractions.dll
Microsoft.AspNetCore.CookiePolicy.dll
Microsoft.AspNetCore.Cors.dll
Microsoft.AspNetCore.Cryptography.Internal.dll
Microsoft.AspNetCore.Cryptography.KeyDerivation.dll
Microsoft.AspNetCore.DataProtection.Abstractions.dll
Microsoft.AspNetCore.DataProtection.dll
Microsoft.AspNetCore.DataProtection.Extensions.dll
Microsoft.AspNetCore.Diagnostics.Abstractions.dll
Microsoft.AspNetCore.Diagnostics.dll
Microsoft.AspNetCore.Diagnostics.HealthChecks.dll
Microsoft.AspNetCore.dll
Microsoft.AspNetCore.HostFiltering.dll
Microsoft.AspNetCore.Hosting.Abstractions.dll
Microsoft.AspNetCore.Hosting.dll
Microsoft.AspNetCore.Hosting.Server.Abstractions.dll
Microsoft.AspNetCore.Html.Abstractions.dll
Microsoft.AspNetCore.Http.Abstractions.dll
Microsoft.AspNetCore.Http.Connections.Common.dll
Microsoft.AspNetCore.Http.Connections.dll
Microsoft.AspNetCore.Http.dll
Microsoft.AspNetCore.Http.Extensions.dll
Microsoft.AspNetCore.Http.Features.dll
Microsoft.AspNetCore.Http.Results.dll
Microsoft.AspNetCore.HttpLogging.dll
Microsoft.AspNetCore.HttpOverrides.dll
Microsoft.AspNetCore.HttpsPolicy.dll
Microsoft.AspNetCore.Identity.dll
Microsoft.AspNetCore.Localization.dll
Microsoft.AspNetCore.Localization.Routing.dll
Microsoft.AspNetCore.Metadata.dll
Microsoft.AspNetCore.Mvc.Abstractions.dll
Microsoft.AspNetCore.Mvc.ApiExplorer.dll
Microsoft.AspNetCore.Mvc.Core.dll
Microsoft.AspNetCore.Mvc.Cors.dll
Microsoft.AspNetCore.Mvc.DataAnnotations.dll
Microsoft.AspNetCore.Mvc.dll
Microsoft.AspNetCore.Mvc.Formatters.Json.dll
Microsoft.AspNetCore.Mvc.Formatters.Xml.dll
Microsoft.AspNetCore.Mvc.Localization.dll
Microsoft.AspNetCore.Mvc.Razor.dll
Microsoft.AspNetCore.Mvc.RazorPages.dll
Microsoft.AspNetCore.Mvc.TagHelpers.dll
Microsoft.AspNetCore.Mvc.ViewFeatures.dll
Microsoft.AspNetCore.OutputCaching.dll
Microsoft.AspNetCore.RateLimiting.dll
Microsoft.AspNetCore.Razor.dll
Microsoft.AspNetCore.Razor.Runtime.dll
Microsoft.AspNetCore.RequestDecompression.dll
Microsoft.AspNetCore.ResponseCaching.Abstractions.dll
Microsoft.AspNetCore.ResponseCaching.dll
Microsoft.AspNetCore.ResponseCompression.dll
Microsoft.AspNetCore.Rewrite.dll
Microsoft.AspNetCore.Routing.Abstractions.dll
Microsoft.AspNetCore.Routing.dll
Microsoft.AspNetCore.Server.HttpSys.dll
Microsoft.AspNetCore.Server.IIS.dll
Microsoft.AspNetCore.Server.IISIntegration.dll
Microsoft.AspNetCore.Server.Kestrel.Core.dll
Microsoft.AspNetCore.Server.Kestrel.dll
Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.dll
Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.dll
Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.dll
Microsoft.AspNetCore.Session.dll
Microsoft.AspNetCore.SignalR.Common.dll
Microsoft.AspNetCore.SignalR.Core.dll
Microsoft.AspNetCore.SignalR.dll
Microsoft.AspNetCore.SignalR.Protocols.Json.dll
Microsoft.AspNetCore.StaticAssets.dll
Microsoft.AspNetCore.StaticFiles.dll
Microsoft.AspNetCore.WebSockets.dll
Microsoft.AspNetCore.WebUtilities.dll
Microsoft.Extensions.Caching.Abstractions.dll
Microsoft.Extensions.Caching.Memory.dll
Microsoft.Extensions.Configuration.Abstractions.dll
Microsoft.Extensions.Configuration.Binder.dll
Microsoft.Extensions.Configuration.CommandLine.dll
Microsoft.Extensions.Configuration.dll
Microsoft.Extensions.Configuration.EnvironmentVariables.dll
Microsoft.Extensions.Configuration.FileExtensions.dll
Microsoft.Extensions.Configuration.Ini.dll
Microsoft.Extensions.Configuration.Json.dll
Microsoft.Extensions.Configuration.KeyPerFile.dll
Microsoft.Extensions.Configuration.UserSecrets.dll
Microsoft.Extensions.Configuration.Xml.dll
Microsoft.Extensions.DependencyInjection.Abstractions.dll
Microsoft.Extensions.DependencyInjection.dll
Microsoft.Extensions.Diagnostics.Abstractions.dll
Microsoft.Extensions.Diagnostics.dll
Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions.dll
Microsoft.Extensions.Diagnostics.HealthChecks.dll
Microsoft.Extensions.Features.dll
Microsoft.Extensions.FileProviders.Abstractions.dll
Microsoft.Extensions.FileProviders.Composite.dll
Microsoft.Extensions.FileProviders.Embedded.dll
Microsoft.Extensions.FileProviders.Physical.dll
Microsoft.Extensions.FileSystemGlobbing.dll
Microsoft.Extensions.Hosting.Abstractions.dll
Microsoft.Extensions.Hosting.dll
Microsoft.Extensions.Http.dll
Microsoft.Extensions.Identity.Core.dll
Microsoft.Extensions.Identity.Stores.dll
Microsoft.Extensions.Localization.Abstractions.dll
Microsoft.Extensions.Localization.dll
Microsoft.Extensions.Logging.Abstractions.dll
Microsoft.Extensions.Logging.Configuration.dll
Microsoft.Extensions.Logging.Console.dll
Microsoft.Extensions.Logging.Debug.dll
Microsoft.Extensions.Logging.dll
Microsoft.Extensions.Logging.EventLog.dll
Microsoft.Extensions.Logging.EventSource.dll
Microsoft.Extensions.Logging.TraceSource.dll
Microsoft.Extensions.ObjectPool.dll
Microsoft.Extensions.Options.ConfigurationExtensions.dll
Microsoft.Extensions.Options.DataAnnotations.dll
Microsoft.Extensions.Options.dll
Microsoft.Extensions.Primitives.dll
Microsoft.Extensions.Validation.dll
Microsoft.Extensions.WebEncoders.dll
Microsoft.JSInterop.dll
Microsoft.Net.Http.Headers.dll
System.Diagnostics.EventLog.dll
System.Formats.Cbor.dll
System.Security.Cryptography.Xml.dll
System.Threading.RateLimiting.dll
<DOTNET>/packs/Microsoft.NETCore.App.Ref/10.0.8/ref/net10.0/
Microsoft.CSharp.dll
Microsoft.VisualBasic.Core.dll
Microsoft.VisualBasic.dll
Microsoft.Win32.Primitives.dll
Microsoft.Win32.Registry.dll
mscorlib.dll
netstandard.dll
System.AppContext.dll
System.Buffers.dll
System.Collections.Concurrent.dll
System.Collections.dll
System.Collections.Immutable.dll
System.Collections.NonGeneric.dll
System.Collections.Specialized.dll
System.ComponentModel.Annotations.dll
System.ComponentModel.DataAnnotations.dll
System.ComponentModel.dll
System.ComponentModel.EventBasedAsync.dll
System.ComponentModel.Primitives.dll
System.ComponentModel.TypeConverter.dll
System.Configuration.dll
System.Console.dll
System.Core.dll
System.Data.Common.dll
System.Data.DataSetExtensions.dll
System.Data.dll
System.Diagnostics.Contracts.dll
System.Diagnostics.Debug.dll
System.Diagnostics.DiagnosticSource.dll
System.Diagnostics.FileVersionInfo.dll
System.Diagnostics.Process.dll
System.Diagnostics.StackTrace.dll
System.Diagnostics.TextWriterTraceListener.dll
System.Diagnostics.Tools.dll
System.Diagnostics.TraceSource.dll
System.Diagnostics.Tracing.dll
System.dll
System.Drawing.dll
System.Drawing.Primitives.dll
System.Dynamic.Runtime.dll
System.Formats.Asn1.dll
System.Formats.Tar.dll
System.Globalization.Calendars.dll
System.Globalization.dll
System.Globalization.Extensions.dll
System.IO.Compression.Brotli.dll
System.IO.Compression.dll
System.IO.Compression.FileSystem.dll
System.IO.Compression.ZipFile.dll
System.IO.dll
System.IO.FileSystem.AccessControl.dll
System.IO.FileSystem.dll
System.IO.FileSystem.DriveInfo.dll
System.IO.FileSystem.Primitives.dll
System.IO.FileSystem.Watcher.dll
System.IO.IsolatedStorage.dll
System.IO.MemoryMappedFiles.dll
System.IO.Pipelines.dll
System.IO.Pipes.AccessControl.dll
System.IO.Pipes.dll
System.IO.UnmanagedMemoryStream.dll
System.Linq.AsyncEnumerable.dll
System.Linq.dll
System.Linq.Expressions.dll
System.Linq.Parallel.dll
System.Linq.Queryable.dll
System.Memory.dll
System.Net.dll
System.Net.Http.dll
System.Net.Http.Json.dll
System.Net.HttpListener.dll
System.Net.Mail.dll
System.Net.NameResolution.dll
System.Net.NetworkInformation.dll
System.Net.Ping.dll
System.Net.Primitives.dll
System.Net.Quic.dll
System.Net.Requests.dll
System.Net.Security.dll
System.Net.ServerSentEvents.dll
System.Net.ServicePoint.dll
System.Net.Sockets.dll
System.Net.WebClient.dll
System.Net.WebHeaderCollection.dll
System.Net.WebProxy.dll
System.Net.WebSockets.Client.dll
System.Net.WebSockets.dll
System.Numerics.dll
System.Numerics.Vectors.dll
System.ObjectModel.dll
System.Reflection.DispatchProxy.dll
System.Reflection.dll
System.Reflection.Emit.dll
System.Reflection.Emit.ILGeneration.dll
System.Reflection.Emit.Lightweight.dll
System.Reflection.Extensions.dll
System.Reflection.Metadata.dll
System.Reflection.Primitives.dll
System.Reflection.TypeExtensions.dll
System.Resources.Reader.dll
System.Resources.ResourceManager.dll
System.Resources.Writer.dll
System.Runtime.CompilerServices.Unsafe.dll
System.Runtime.CompilerServices.VisualC.dll
System.Runtime.dll
System.Runtime.Extensions.dll
System.Runtime.Handles.dll
System.Runtime.InteropServices.dll
System.Runtime.InteropServices.JavaScript.dll
System.Runtime.InteropServices.RuntimeInformation.dll
System.Runtime.Intrinsics.dll
System.Runtime.Loader.dll
System.Runtime.Numerics.dll
System.Runtime.Serialization.dll
System.Runtime.Serialization.Formatters.dll
System.Runtime.Serialization.Json.dll
System.Runtime.Serialization.Primitives.dll
System.Runtime.Serialization.Xml.dll
System.Security.AccessControl.dll
System.Security.Claims.dll
System.Security.Cryptography.Algorithms.dll
System.Security.Cryptography.Cng.dll
System.Security.Cryptography.Csp.dll
System.Security.Cryptography.dll
System.Security.Cryptography.Encoding.dll
System.Security.Cryptography.OpenSsl.dll
System.Security.Cryptography.Primitives.dll
System.Security.Cryptography.X509Certificates.dll
System.Security.dll
System.Security.Principal.dll
System.Security.Principal.Windows.dll
System.Security.SecureString.dll
System.ServiceModel.Web.dll
System.ServiceProcess.dll
System.Text.Encoding.CodePages.dll
System.Text.Encoding.dll
System.Text.Encoding.Extensions.dll
System.Text.Encodings.Web.dll
System.Text.Json.dll
System.Text.RegularExpressions.dll
System.Threading.AccessControl.dll
System.Threading.Channels.dll
System.Threading.dll
System.Threading.Overlapped.dll
System.Threading.Tasks.Dataflow.dll
System.Threading.Tasks.dll
System.Threading.Tasks.Extensions.dll
System.Threading.Tasks.Parallel.dll
System.Threading.Thread.dll
System.Threading.ThreadPool.dll
System.Threading.Timer.dll
System.Transactions.dll
System.Transactions.Local.dll
System.ValueTuple.dll
System.Web.dll
System.Web.HttpUtility.dll
System.Windows.dll
System.Xml.dll
System.Xml.Linq.dll
System.Xml.ReaderWriter.dll
System.Xml.Serialization.dll
System.Xml.XDocument.dll
System.Xml.XmlDocument.dll
System.Xml.XmlSerializer.dll
System.Xml.XPath.dll
System.Xml.XPath.XDocument.dll
WindowsBase.dll
<NUGET>/
automapper/16.1.1/lib/net10.0/AutoMapper.dll
azure.core/1.47.1/lib/net8.0/Azure.Core.dll
azure.identity/1.14.2/lib/net8.0/Azure.Identity.dll
microsoft.aspnetcore.authentication.negotiate/10.0.8/lib/net10.0/Microsoft.AspNetCore.Authentication.Negotiate.dll
microsoft.aspnetcore.jsonpatch/10.0.8/lib/net10.0/Microsoft.AspNetCore.JsonPatch.dll
microsoft.aspnetcore.mvc.newtonsoftjson/10.0.8/lib/net10.0/Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll
microsoft.aspnetcore.openapi/10.0.8/lib/net10.0/Microsoft.AspNetCore.OpenApi.dll
microsoft.bcl.asyncinterfaces/8.0.0/lib/netstandard2.1/Microsoft.Bcl.AsyncInterfaces.dll
microsoft.bcl.cryptography/9.0.4/lib/net9.0/Microsoft.Bcl.Cryptography.dll
microsoft.data.sqlclient/6.1.1/ref/net9.0/Microsoft.Data.SqlClient.dll
microsoft.entityframeworkcore.abstractions/10.0.8/lib/net10.0/Microsoft.EntityFrameworkCore.Abstractions.dll
microsoft.entityframeworkcore.relational/10.0.8/lib/net10.0/Microsoft.EntityFrameworkCore.Relational.dll
microsoft.entityframeworkcore.sqlserver/10.0.8/lib/net10.0/Microsoft.EntityFrameworkCore.SqlServer.dll
microsoft.entityframeworkcore/10.0.8/lib/net10.0/Microsoft.EntityFrameworkCore.dll
microsoft.identity.client.extensions.msal/4.73.1/lib/net8.0/Microsoft.Identity.Client.Extensions.Msal.dll
microsoft.identity.client/4.73.1/lib/net8.0/Microsoft.Identity.Client.dll
microsoft.identitymodel.abstractions/8.14.0/lib/net9.0/Microsoft.IdentityModel.Abstractions.dll
microsoft.identitymodel.jsonwebtokens/8.14.0/lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll
microsoft.identitymodel.logging/8.14.0/lib/net9.0/Microsoft.IdentityModel.Logging.dll
microsoft.identitymodel.protocols.openidconnect/7.7.1/lib/net8.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll
microsoft.identitymodel.protocols/7.7.1/lib/net8.0/Microsoft.IdentityModel.Protocols.dll
microsoft.identitymodel.tokens/8.14.0/lib/net9.0/Microsoft.IdentityModel.Tokens.dll
microsoft.openapi/2.4.1/lib/net8.0/Microsoft.OpenApi.dll
microsoft.sqlserver.server/1.0.0/lib/netstandard2.0/Microsoft.SqlServer.Server.dll
newtonsoft.json.bson/1.0.2/lib/netstandard2.0/Newtonsoft.Json.Bson.dll
newtonsoft.json/13.0.3/lib/net6.0/Newtonsoft.Json.dll
swashbuckle.aspnetcore.swagger/10.1.7/lib/net10.0/Swashbuckle.AspNetCore.Swagger.dll
swashbuckle.aspnetcore.swaggergen/10.1.7/lib/net10.0/Swashbuckle.AspNetCore.SwaggerGen.dll
swashbuckle.aspnetcore.swaggerui/10.1.7/lib/net10.0/Swashbuckle.AspNetCore.SwaggerUI.dll
system.clientmodel/1.5.1/lib/net8.0/System.ClientModel.dll
system.directoryservices.protocols/10.0.8/lib/net10.0/System.DirectoryServices.Protocols.dll
system.identitymodel.tokens.jwt/7.7.1/lib/net8.0/System.IdentityModel.Tokens.Jwt.dll
system.memory.data/8.0.1/lib/net8.0/System.Memory.Data.dll
system.security.cryptography.pkcs/9.0.4/lib/net9.0/System.Security.Cryptography.Pkcs.dll
system.security.cryptography.protecteddata/9.0.4/lib/net9.0/System.Security.Cryptography.ProtectedData.dll
[analyzerReferences]
<DOTNET>/packs/Microsoft.AspNetCore.App.Ref/10.0.8/analyzers/dotnet/cs/
Microsoft.AspNetCore.App.Analyzers.dll
Microsoft.AspNetCore.App.CodeFixes.dll
Microsoft.AspNetCore.App.SourceGenerators.dll
Microsoft.AspNetCore.Components.Analyzers.dll
Microsoft.Extensions.Logging.Generators.dll
Microsoft.Extensions.Options.SourceGeneration.dll
Microsoft.Extensions.Validation.ValidationsGenerator.dll
<DOTNET>/packs/Microsoft.NETCore.App.Ref/10.0.8/analyzers/dotnet/cs/
Microsoft.Interop.ComInterfaceGenerator.dll
Microsoft.Interop.JavaScript.JSImportGenerator.dll
Microsoft.Interop.LibraryImportGenerator.dll
Microsoft.Interop.SourceGeneration.dll
System.Text.Json.SourceGeneration.dll
System.Text.RegularExpressions.Generator.dll
<DOTNET>/sdk/10.0.300/Sdks/Microsoft.NET.Sdk.Razor/source-generators/
Microsoft.AspNetCore.Razor.Utilities.Shared.dll
Microsoft.CodeAnalysis.Razor.Compiler.dll
Microsoft.Extensions.ObjectPool.dll
<DOTNET>/sdk/10.0.300/Sdks/Microsoft.NET.Sdk.Web/analyzers/cs/
Microsoft.AspNetCore.Analyzers.dll
Microsoft.AspNetCore.Mvc.Analyzers.dll
<DOTNET>/sdk/10.0.300/Sdks/Microsoft.NET.Sdk/analyzers/
Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll
Microsoft.CodeAnalysis.NetAnalyzers.dll
<NUGET>/
microsoft.aspnetcore.openapi/10.0.8/analyzers/dotnet/cs/Microsoft.AspNetCore.OpenApi.SourceGenerators.dll
microsoft.codeanalysis.analyzers/3.11.0/analyzers/dotnet/cs/
Microsoft.CodeAnalysis.Analyzers.dll
Microsoft.CodeAnalysis.CSharp.Analyzers.dll
microsoft.entityframeworkcore.analyzers/10.0.8/analyzers/dotnet/cs/Microsoft.EntityFrameworkCore.Analyzers.dll
system.clientmodel/1.5.1/analyzers/dotnet/cs/System.ClientModel.SourceGeneration.dll
[analyzerConfigFiles]
<DOTNET>/sdk/10.0.300/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_10_default.globalconfig
obj/Debug/net10.0/Microsoft.SelfService.Portal.Core.API.GeneratedMSBuildEditorConfig.editorconfig
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>https</ActiveDebugProfile>
<Controller_SelectedScaffolderID>ApiControllerWithActionsScaffolder</Controller_SelectedScaffolderID>
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath>
<WebStackScaffolding_ControllerDialogWidth>650</WebStackScaffolding_ControllerDialogWidth>
<WebStackScaffolding_DbContextDialogWidth>650</WebStackScaffolding_DbContextDialogWidth>
<WebStackScaffolding_IsLayoutPageSelected>True</WebStackScaffolding_IsLayoutPageSelected>
<WebStackScaffolding_IsPartialViewSelected>False</WebStackScaffolding_IsPartialViewSelected>
<WebStackScaffolding_IsReferencingScriptLibrariesSelected>True</WebStackScaffolding_IsReferencingScriptLibrariesSelected>
<WebStackScaffolding_LayoutPageFile />
<WebStackScaffolding_DbContextTypeFullName>Microsoft.SelfService.Portal.Core.API.Data.Context</WebStackScaffolding_DbContextTypeFullName>
<WebStackScaffolding_IsAsyncSelected>False</WebStackScaffolding_IsAsyncSelected>
<NameOfLastUsedPublishProfile>C:\Users\ASA_Administrator.CCIS-P01S01-CM\source\repos\Microsoft.SelfService.Portal\Microsoft.SelfService.Portal.Core.API\Properties\PublishProfiles\FolderProfile.pubxml</NameOfLastUsedPublishProfile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,68 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Microsoft.SelfService.Portal.Core.API.Migrations
{
/// <inheritdoc />
public partial class SeedCredentialSecretsAndDeploymentArtifacts : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "CredentialSecrets",
columns: new[] { "Id", "Created", "CreatedBy", "IsEnabled", "MetadataJson", "Modified", "ModifiedBy", "Name", "SecretType", "SecretValue", "UserName" },
values: new object[,]
{
{ new Guid("90000000-0000-0000-0000-000000000001"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", true, "{\"environment\":\"Demo\",\"plaintext\":true}", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "Windows/SharePoint/SetupAccount", "Credential", "DemoOnly-DoNotUseInProduction!", "CONTOSO\\svc_sp_setup" },
{ new Guid("90000000-0000-0000-0000-000000000002"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", true, "{\"environment\":\"Demo\",\"plaintext\":true}", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "Windows/SharePoint/FarmAccount", "Credential", "DemoOnly-DoNotUseInProduction!", "CONTOSO\\svc_sp_farm" },
{ new Guid("90000000-0000-0000-0000-000000000003"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", true, "{\"environment\":\"Demo\",\"plaintext\":true}", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "Windows/SharePoint/FarmPassphrase", "Secret", "DemoOnly-DoNotUseInProduction!", null },
{ new Guid("90000000-0000-0000-0000-000000000004"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", true, "{\"environment\":\"Demo\",\"plaintext\":true}", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "Windows/SharePoint/DefaultServiceAccount", "Credential", "DemoOnly-DoNotUseInProduction!", "CONTOSO\\svc_sp_service" },
{ new Guid("90000000-0000-0000-0000-000000000005"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", true, "{\"environment\":\"Demo\",\"plaintext\":true}", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "Windows/SharePoint/SearchAccount", "Credential", "DemoOnly-DoNotUseInProduction!", "CONTOSO\\svc_sp_search" }
});
migrationBuilder.InsertData(
table: "DeploymentArtifacts",
columns: new[] { "Id", "ArtifactType", "Created", "CreatedBy", "DeploymentGroupId", "DeploymentJson", "DeploymentTargetId", "InputHash", "IsStale", "Modified", "ModifiedBy", "OutputHash", "ResolvedJson", "SourceJson", "SourceSnapshotJson", "StaleReasonJson", "Status", "TargetId" },
values: new object[] { new Guid("85000000-0000-0000-0000-000000000101"), "ResolvedConfigurationData", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("80000000-0000-0000-0000-000000000101"), "{\n \"metadata\": {\n \"name\": \"Contoso-Test-SharePoint\",\n \"deploymentId\": \"8f6c2c1a\",\n \"source\": \"DemoData\"\n },\n \"parameters\": {\n \"DatabasePrefix\": \"SharePoint_Contoso_Test\"\n },\n \"variables\": {\n \"DatabasePrefix\": \"SharePoint_Contoso_Test\",\n \"ServiceDbPrefix\": \"SharePoint_Contoso_Test_Services\",\n \"ConfigDbName\": \"SharePoint_Contoso_Test_Farm_Config\"\n },\n \"resources\": {\n \"AllNodes\": [\n { \"NodeName\": \"CLD-SHP-01\", \"RunCentralAdministration\": true },\n { \"NodeName\": \"CLD-SHP-02\", \"RunCentralAdministration\": false },\n { \"NodeName\": \"CLD-SHP-03\", \"RunCentralAdministration\": false }\n ],\n \"NonNodeData\": {\n \"LocalConfigurationManager\": {\n \"ConfigurationMode\": \"ApplyOnly\"\n }\n }\n }\n}", null, "F081B9BACB21534D9673317615B990CC3836D68416CD6C94E06045E524640020", false, new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "8A49E86D9D571FDE9FE243849A5399A73750798F165B0D9DCE1318B8561CEF01", "{\n \"metadata\": {\n \"name\": \"Contoso-Test-SharePoint\",\n \"deploymentId\": \"8f6c2c1a\",\n \"source\": \"DemoData\"\n },\n \"parameters\": {\n \"DatabasePrefix\": \"SharePoint_Contoso_Test\"\n },\n \"variables\": {\n \"DatabasePrefix\": \"SharePoint_Contoso_Test\",\n \"ServiceDbPrefix\": \"SharePoint_Contoso_Test_Services\",\n \"ConfigDbName\": \"SharePoint_Contoso_Test_Farm_Config\"\n },\n \"resources\": {\n \"AllNodes\": [\n { \"NodeName\": \"CLD-SHP-01\", \"RunCentralAdministration\": true },\n { \"NodeName\": \"CLD-SHP-02\", \"RunCentralAdministration\": false },\n { \"NodeName\": \"CLD-SHP-03\", \"RunCentralAdministration\": false }\n ],\n \"NonNodeData\": {\n \"LocalConfigurationManager\": {\n \"ConfigurationMode\": \"ApplyOnly\"\n }\n }\n }\n}", "{\"source\":\"DemoData\",\"composition\":\"Test.Merge.ps1\"}", "{\n \"templateRevisions\": [\n {\n \"id\": \"72000000-0000-0000-0000-000000000101\",\n \"templateVersionId\": \"71000000-0000-0000-0000-000000000101\",\n \"role\": \"Environment\",\n \"alias\": \"Environment-Test\",\n \"hash\": \"58553C7A4E902B5E39D30272754FEF9EF805A7F36363D8316A9B37540C6D4646\"\n },\n {\n \"id\": \"72000000-0000-0000-0000-000000000102\",\n \"templateVersionId\": \"71000000-0000-0000-0000-000000000102\",\n \"role\": \"Domain\",\n \"alias\": \"Domain-Contoso\",\n \"hash\": \"B265C4D542826423BAAF50BF939F026CC67FF631FEC19F708067516BCE4C7667\"\n },\n {\n \"id\": \"72000000-0000-0000-0000-000000000103\",\n \"templateVersionId\": \"71000000-0000-0000-0000-000000000103\",\n \"role\": \"Service\",\n \"alias\": \"Service-SharePoint-Contoso\",\n \"hash\": \"E3B0E2A8E0C85CCD92AEA521ECD243E61E34C61D7078D6715E6317F5669A1D69\"\n },\n {\n \"id\": \"72000000-0000-0000-0000-000000000104\",\n \"templateVersionId\": \"71000000-0000-0000-0000-000000000104\",\n \"role\": \"Stage\",\n \"alias\": \"Stage-Install\",\n \"hash\": \"ACA23E44C9F625F080D2653646555A9DE5546D8DE7BB2166324E775BCF47F3C1\"\n }\n ],\n \"targets\": [\n { \"id\": \"30000000-0000-0000-0000-000000000101\", \"nodeName\": \"CLD-SHP-01\" },\n { \"id\": \"30000000-0000-0000-0000-000000000102\", \"nodeName\": \"CLD-SHP-02\" },\n { \"id\": \"30000000-0000-0000-0000-000000000103\", \"nodeName\": \"CLD-SHP-03\" }\n ]\n}", null, "Pending", null });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "CredentialSecrets",
keyColumn: "Id",
keyValue: new Guid("90000000-0000-0000-0000-000000000001"));
migrationBuilder.DeleteData(
table: "CredentialSecrets",
keyColumn: "Id",
keyValue: new Guid("90000000-0000-0000-0000-000000000002"));
migrationBuilder.DeleteData(
table: "CredentialSecrets",
keyColumn: "Id",
keyValue: new Guid("90000000-0000-0000-0000-000000000003"));
migrationBuilder.DeleteData(
table: "CredentialSecrets",
keyColumn: "Id",
keyValue: new Guid("90000000-0000-0000-0000-000000000004"));
migrationBuilder.DeleteData(
table: "CredentialSecrets",
keyColumn: "Id",
keyValue: new Guid("90000000-0000-0000-0000-000000000005"));
migrationBuilder.DeleteData(
table: "DeploymentArtifacts",
keyColumn: "Id",
keyValue: new Guid("85000000-0000-0000-0000-000000000101"));
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,551 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
namespace Microsoft.SelfService.Portal.Core.API.Migrations
{
/// <inheritdoc />
public partial class SeedConfigurationDefinitionsAndValues : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.InsertData(
table: "ConfigurationDefinitions",
columns: new[] { "Id", "Created", "CreatedBy", "DefinitionHash", "Kind", "Modified", "ModifiedBy", "Name", "PropertiesJson", "TemplateRevisionId" },
values: new object[,]
{
{ new Guid("04091788-4a0a-7911-b11d-b70335ad378a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "66CA01A01EE073BAD5B77E2ECC9F3CB54967A3F4AE820E317C24C35DB6AF5ED2", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ServiceDbPrefix", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ServiceDatabaseSegment\\u0027))]\"}", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("07333ab1-de72-442d-d91a-0bb2dd969443"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "81D3F97A03D7745EEB15174781144CD222A27E71F28A08F0C7F03496B74A0C87", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ContentDbPrefix", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ContentDatabaseSegment\\u0027))]\"}", new Guid("72000000-0000-0000-0000-000000000101") },
{ new Guid("13f4b031-f8b8-1ef4-21b0-ab825dc54aee"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "16F0D197D56D4E12B3BEB98AEA78A9D9C16DB35C1B09F3FA2298620504820B73", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "SearchServiceApplicationPoolAccount", "{\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Kontoname fuer den Search-Application-Pool der SharePoint-Serviceanwendungen.\",\r\n \"en-US\": \"Account name used by the search application pool for SharePoint service applications.\"\r\n }\r\n },\r\n \"Sensitive\": true,\r\n \"Value\": {\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/SearchAccount\"\r\n },\r\n \"Type\": \"credential\",\r\n \"Required\": true\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("14bcd250-6d40-e1a6-5aed-e563c7a2d589"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "B020AA02247152F414B03ACFB1281A6A955D4F48CA3CB26A5B42EB3A3169113A", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ConfigDbName", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_Config\\u0027)]\"}", new Guid("72000000-0000-0000-0000-000000000101") },
{ new Guid("16fa79cc-52be-7ba0-56ad-051ce1879851"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "8F7A22AEDBCA0CCC09451FBE6B969687E56042826C989B85CD3F02569837B761", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "WebApplicationPoolDefault", "{\r\n \"DefaultValue\": \"SharePoint Web Applications\",\r\n \"Type\": \"string\",\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Anzeigename des Standard-Application-Pools fuer SharePoint-Webanwendungen.\",\r\n \"en-US\": \"Display name of the default application pool for SharePoint web applications.\"\r\n }\r\n }\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("1e409601-2e11-453e-09bc-ccc0e61a2eaf"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "BCDC01B9105DB0D42D6E8CD49092E10713CF4CBB42178218F5B2450CCB1A359A", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabaseServerName", "{\r\n \"DefaultValue\": \"SQL_Server\",\r\n \"Type\": \"string\",\r\n \"Value\": \"CL-SQL-01\"\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("1ee4b631-fc15-85dc-3057-a1ca26da7ba9"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "48BB0B2F36F4CA5BA7A2326F64B19AFA55D58345E675540A806877A78F61F97D", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "Landscape", "{\r\n \"DefaultValue\": \"Prod\",\r\n \"Value\": \"Test\",\r\n \"Type\": \"string\",\r\n \"AllowedValues\": [\r\n \"Prod\",\r\n \"QA\",\r\n \"Test\"\r\n ]\r\n }", new Guid("72000000-0000-0000-0000-000000000101") },
{ new Guid("1fc14a66-5b69-00da-e92c-a38e6d674b50"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "50EDDD3C7853B59491CA782C3A45A9E33EE6709FF4B964D6E42FB2DC4D6D546D", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "CentralAdminPort", "{\r\n \"DefaultValue\": 443,\r\n \"Type\": \"int\",\r\n \"Value\": 4000\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("2b7b2531-54ed-e778-e547-f0b696591d0c"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "8FF8AE6C17C434DEC71C889173C0AD3E1759B94F99C0ACAFECB657D3327708BA", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ConfigDbName", "{\"Expression\":\"[concat(parameters('DatabasePrefix'), '_Farm_Config')]\"}", new Guid("72000000-0000-0000-0000-000000000003") },
{ new Guid("3bdcaa78-354c-487f-b0c5-6a3e0b20f8a1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "AB14380C5D597E9B70A8F42F768BF86A71118D86A8B9E28237B70952CE0BEBBD", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "AdminDbName", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_AdminContent\\u0027)]\"}", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("4b3283a0-f2df-1edd-fa3f-a90cacf2a7f1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "071CCD19384E43C652F0CF2E633702F12BBC1CE8A46472DCF1B7EBB9A500795E", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DefaultServiceApplicationPoolAccount", "{\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Kontoname fuer den Standard-Application-Pool der SharePoint-Serviceanwendungen.\",\r\n \"en-US\": \"Account name used by the default application pool for SharePoint service applications.\"\r\n }\r\n },\r\n \"Sensitive\": true,\r\n \"Value\": {\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/DefaultServiceAccount\"\r\n },\r\n \"Type\": \"credential\",\r\n \"Required\": true\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("50f73169-1bda-2644-6963-b7cfad914a6a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "00139B0DB8374A47087A5AE9114E53200D7B2FEF79163BC3E2D627159986141A", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "AdminContentDbName", "{\"Expression\":\"[concat(parameters('DatabasePrefix'), '_AdminContent')]\"}", new Guid("72000000-0000-0000-0000-000000000003") },
{ new Guid("57fe8654-dbf4-c6c1-3a99-ac68fe393731"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "81D3F97A03D7745EEB15174781144CD222A27E71F28A08F0C7F03496B74A0C87", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ContentDbPrefix", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ContentDatabaseSegment\\u0027))]\"}", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("5d326698-5daf-cf78-b44d-2bbe6625d506"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "66CA01A01EE073BAD5B77E2ECC9F3CB54967A3F4AE820E317C24C35DB6AF5ED2", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ServiceDbPrefix", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ServiceDatabaseSegment\\u0027))]\"}", new Guid("72000000-0000-0000-0000-000000000101") },
{ new Guid("62443ac9-c783-01b8-0cf4-21dc6bd57e44"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "4F3A9ED930F155C1D23ACD9FD640C0D2253B250467200E68020E1BA9FF25CA15", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabasePrefix", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, parameters(\\u0027DatabasePrefix\\u0027), parameters(\\u0027DomainLabel\\u0027), if(equals(parameters(\\u0027Landscape\\u0027), \\u0027Test\\u0027), \\u0027Test\\u0027, \\u0027\\u0027))]\"}", new Guid("72000000-0000-0000-0000-000000000101") },
{ new Guid("64767ffc-812e-b48e-db29-f295a287258b"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "5469237A902DBDD5D1D5365F83BC4369D466C6283B618D5EFFF18037B954D206", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "MeetingPolicyName", "{ \"type\": \"string\", \"defaultValue\": \"Contoso-Standard-Meetings\" }", new Guid("72000000-0000-0000-0000-000000000004") },
{ new Guid("67ca0430-31c6-553d-1553-60401cf4015c"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "5A4F2C381DC5EBA68A416A739E7DCF0179EA6FF84D884BAF3A554D488DA9F5CE", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DomainName", "{ \"type\": \"string\", \"defaultValue\": \"corp.contoso.com\" }", new Guid("72000000-0000-0000-0000-000000000001") },
{ new Guid("68c57a16-e728-4c39-f245-8a41d77d0aa7"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "62DE452BEBAE7CC7332895F461CD648707B5434F1EC4D55FA01E6B10A0FC4F74", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DomainNetBIOS", "{\r\n \"Type\": \"string\",\r\n \"MaxLength\": 15,\r\n \"Value\": \"CONTOSO\",\r\n \"DefaultValue\": \"\",\r\n \"Pattern\": \"^[A-Za-z0-9_-]+$\",\r\n \"MinLength\": 1,\r\n \"Required\": true\r\n }", new Guid("72000000-0000-0000-0000-000000000102") },
{ new Guid("7232ce0f-9f3e-e10b-9370-7aec1c610ecc"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "4F3A9ED930F155C1D23ACD9FD640C0D2253B250467200E68020E1BA9FF25CA15", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabasePrefix", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, parameters(\\u0027DatabasePrefix\\u0027), parameters(\\u0027DomainLabel\\u0027), if(equals(parameters(\\u0027Landscape\\u0027), \\u0027Test\\u0027), \\u0027Test\\u0027, \\u0027\\u0027))]\"}", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("72fbe523-8aba-6aed-0918-d3e96f12254f"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "112452BFA4BC12A22D96FE3095743046C3BDFBEB7DDC17E473BB2D54E6134AF6", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "InstanceName", "{ \"type\": \"string\", \"defaultValue\": \"MSSQLSERVER\" }", new Guid("72000000-0000-0000-0000-000000000002") },
{ new Guid("73be057e-cd31-1658-186b-bcf4695a48f7"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "B020AA02247152F414B03ACFB1281A6A955D4F48CA3CB26A5B42EB3A3169113A", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ConfigDbName", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_Config\\u0027)]\"}", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("753acf7d-8ef6-69ef-b710-d430bfef3a98"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "F45D71DEA1E2C611B54BD9077A92D438B2DBA2866781ABF397AFA20C2E985E07", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ServiceApplicationPoolDefault", "{\r\n \"DefaultValue\": \"SharePoint Service Applications\",\r\n \"Type\": \"string\",\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Anzeigename des Standard-Application-Pools fuer SharePoint-Serviceanwendungen.\",\r\n \"en-US\": \"Display name of the default application pool for SharePoint service applications.\"\r\n }\r\n }\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("75c6a433-0dfe-1e3e-d324-322a2fad0d71"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "FE5E0E49AA2D785B2ECA765C15A57889372826684A465793C1A69B528406D7AB", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DomainFQDN", "{\r\n \"DefaultValue\": \"\",\r\n \"Pattern\": \"^[A-Za-z0-9.-]+$\",\r\n \"Type\": \"string\",\r\n \"Value\": \"contoso.local\",\r\n \"Required\": true\r\n }", new Guid("72000000-0000-0000-0000-000000000102") },
{ new Guid("80733c50-d68a-d8f9-bb5f-fb6fce6f66a1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "161F0B7F00446101C1C6CBFFAACC28EFE56418E7636DCCEC2C4EE57538ACC182", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ServiceDatabaseSegment", "{\r\n \"DefaultValue\": \"Services\",\r\n \"Type\": \"string\",\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Namenssegment fuer SharePoint-Service-Datenbanken innerhalb des Datenbanknamens.\",\r\n \"en-US\": \"Name segment used for SharePoint service databases within the database name.\"\r\n }\r\n }\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("8e334e04-0465-ca82-b4f0-fd01561cdb4a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "75224F964656A4CFD8BBB38F26767A098A327F35A986F0E63247B75B0CD0C834", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "FarmAccount", "{ \"type\": \"credential\", \"metadata\": { \"description\": \"Farm account resolved by the credential provider.\" } }", new Guid("72000000-0000-0000-0000-000000000003") },
{ new Guid("971fc778-2e79-6408-aec3-1beeaf7fce8e"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DB3469AE0FFB2D1669BD5E8B3078C3FA04FF8DB4FA67998C9610BE9C1C96FB53", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "NetBIOSName", "{ \"type\": \"string\", \"defaultValue\": \"CONTOSO\" }", new Guid("72000000-0000-0000-0000-000000000001") },
{ new Guid("9c60f6a6-a0c8-9f27-d31e-030403f78cd9"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "8BAB062E6AAAE0E29902434B02DDFEC4A79908E307530CC3177F081F587317BD", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "CallingPolicyName", "{ \"type\": \"string\", \"defaultValue\": \"Contoso-Standard-Calling\" }", new Guid("72000000-0000-0000-0000-000000000004") },
{ new Guid("a1bbb850-c83a-f3ee-474c-dc9484456e2a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "21654CC3E82D2DE59E438E554C32F074BA88894F1F6825BC0154D6B127CDCDD8", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "WebApplicationPoolDefaultAccount", "{\r\n \"DefaultValue\": \"SVC_SHP_WAP\",\r\n \"Type\": \"string\",\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Kontoname fuer den Standard-Application-Pool der SharePoint-Webanwendungen.\",\r\n \"en-US\": \"Account name used by the default application pool for SharePoint web applications.\"\r\n }\r\n }\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("a7cc8607-ab28-8765-6fb3-87222c485f65"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "2BFD19F4E9E81007EBA8B7EDE220316E042FF6B1471CF1C33AD47E8FEE083121", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabaseInstanceName", "{\r\n \"DefaultValue\": \"SQL_Server\",\r\n \"Type\": \"string\",\r\n \"Value\": \"SQLServer\"\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("a9be4c68-f2c0-279f-6a98-92b5ac3c0e80"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "AB14380C5D597E9B70A8F42F768BF86A71118D86A8B9E28237B70952CE0BEBBD", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "AdminDbName", "{\"Expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_AdminContent\\u0027)]\"}", new Guid("72000000-0000-0000-0000-000000000101") },
{ new Guid("afa8d0b4-7fc7-d419-7f01-6fb1fd59be4d"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "E61970B4774DA1EABE34D93AE56C4647008872A0E01B9F1862D67B7049E02CDB", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ConfigDatabase", "{\"Expression\":\"[concat(parameters('DatabasePrefix'), '_Config')]\"}", new Guid("72000000-0000-0000-0000-000000000002") },
{ new Guid("b5f3de9e-251e-ebb1-c6cc-b4a1a2afc987"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "C16A8CDDCD81BAA020E26C1EC06AB229A4483E095A9259E1C471CC7322CCF011", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ProductKey", "{\r\n \"DefaultValue\": \"0000-0000-0000-0000-0000\",\r\n \"Type\": \"string\",\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"SharePoint-Produktlizenzschluessel.\",\r\n \"en-US\": \"SharePoint product license key.\"\r\n }\r\n }\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("c32f0de1-59a8-ffb2-a421-92209185de89"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "28AF3E6D01302114A9A83652445875D944E00AE5291CDC76F34BE5D412328B4D", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ContentDatabaseSegment", "{\r\n \"DefaultValue\": \"Content\",\r\n \"Type\": \"string\",\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Namenssegment fuer SharePoint-Content-Datenbanken innerhalb des Datenbanknamens.\",\r\n \"en-US\": \"Name segment used for SharePoint content databases within the database name.\"\r\n }\r\n }\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("d0fbeeb9-628d-fa56-c106-6f54e3080b11"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "4BE38B4AB1097AB42C79DA38FE0E6B6F4399319E22B23CCE5461BF7D1D1E586E", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabasePrefix", "{\r\n \"DefaultValue\": \"SharePoint\",\r\n \"Type\": \"string\",\r\n \"Value\": \"SharePoint\"\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("d7d653e4-d29b-b9be-d701-b4eae2b34d2e"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "B259744A1EB2E69B9D7B2A4BA1B0A9B1930AD837872C7D4F4ABDEEFA2F61A868", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabasePrefix", "{ \"type\": \"string\", \"defaultValue\": \"SharePoint_Contoso_Test\" }", new Guid("72000000-0000-0000-0000-000000000003") },
{ new Guid("dc4af326-9a3d-c4a6-9462-669bcf16103a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DDC6599447376095A217761107BAB8795DBDB6CD0D7418A8B1A2E49684E1EFB8", "Variable", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DefaultSiteName", "{\"Expression\":\"[concat(parameters('DomainName'), '-DefaultSite')]\"}", new Guid("72000000-0000-0000-0000-000000000001") },
{ new Guid("dc6781e4-7ca7-7307-bf91-42078ba9dea1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DED3F6C00C32BA6888DEFA172C837415D118388478A160D872AEA098B38C9298", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabaseTcpPort", "{\r\n \"DefaultValue\": 1433,\r\n \"Type\": \"int\",\r\n \"Value\": 1433\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("de747816-08b8-faf8-d4ee-57b47222a7f4"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "1E280B017D410A45BBF262CBED384ADA7AE42FB0D1390BDFF68909FD4389043A", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "FarmPassphrase", "{\r\n \"Sensitive\": true,\r\n \"Value\": {\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/FarmPassphrase\"\r\n },\r\n \"Type\": \"credential\",\r\n \"Required\": true\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("dfb00c19-363f-7518-47be-1ba0ec16abd7"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ECF2FC72BEF5492B23764A1316ABEE9652F23D5C02A569A6FF59D35AAB4DE603", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DomainLabel", "{\r\n \"Type\": \"string\",\r\n \"MaxLength\": 32,\r\n \"Value\": \"Contoso\",\r\n \"DefaultValue\": \"\",\r\n \"Pattern\": \"^[A-Za-z][A-Za-z0-9_-]*$\",\r\n \"MinLength\": 2,\r\n \"Required\": false\r\n }", new Guid("72000000-0000-0000-0000-000000000102") },
{ new Guid("e9a93051-120a-00c2-c69d-794d3d3c6c86"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "4A1AAAAA8D012E87FDFCB9C7B45C490E35B4FDD1341A6D8682591E25DE782A1F", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "SetupCredential", "{\r\n \"Sensitive\": true,\r\n \"Value\": {\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/SetupAccount\"\r\n },\r\n \"Type\": \"credential\",\r\n \"Required\": true\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("eb2d6268-83d4-ecc7-1696-eff819ac5db5"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "0975CC4EB86BE0F5D26FF2E44A7663ECDC550671F88270DE44E402AA36793609", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "DatabasePrefix", "{ \"type\": \"string\", \"defaultValue\": \"Contoso\" }", new Guid("72000000-0000-0000-0000-000000000002") },
{ new Guid("efbb9cc6-ed11-105b-91e4-0d925c214e32"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "8EF099B36AF59BC20F3FBB9D9CBBFAC9F1E32D52521A890F9510216D862DCDA2", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "FarmCredential", "{\r\n \"Sensitive\": true,\r\n \"Value\": {\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/FarmAccount\"\r\n },\r\n \"Type\": \"credential\",\r\n \"Required\": true\r\n }", new Guid("72000000-0000-0000-0000-000000000103") },
{ new Guid("f7ea1bce-9cf1-ddc4-320f-e26ef51a6108"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "3932CF99DA0C5583F34A16C94762F0EA1E94A2D197E4E56D5F2226863686577E", "Parameter", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "ServiceApplicationPoolSearch", "{\r\n \"DefaultValue\": \"SharePoint Search Service Applications\",\r\n \"Type\": \"string\",\r\n \"Metadata\": {\r\n \"Description\": {\r\n \"de-DE\": \"Anzeigename des Application-Pools fuer SharePoint Search-Serviceanwendungen.\",\r\n \"en-US\": \"Display name of the application pool for SharePoint Search service applications.\"\r\n }\r\n }\r\n }", new Guid("72000000-0000-0000-0000-000000000103") }
});
migrationBuilder.InsertData(
table: "ConfigurationValues",
columns: new[] { "Id", "ConfigurationDefinitionId", "Created", "CreatedBy", "Modified", "ModifiedBy", "ScopeId", "ScopeType", "SortOrder", "SourcePath", "ValueHash", "ValueJson", "ValueSourceType" },
values: new object[,]
{
{ new Guid("0d47df0b-4ae8-e379-42f7-660dbf80cff1"), new Guid("73be057e-cd31-1658-186b-bcf4695a48f7"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 50, null, "261FCBE546960FDBA45B615045DC04AE5B717633C4368C87A25A81B31339BE96", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_Config\\u0027)]\"}", "Expression" },
{ new Guid("146d60ab-37ba-8fbf-83f1-89a0976b70c7"), new Guid("d7d653e4-d29b-b9be-d701-b4eae2b34d2e"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000003"), "TemplateRevision", 10, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("1a37be2a-89b9-4784-e89e-604946b4a2a0"), new Guid("e9a93051-120a-00c2-c69d-794d3d3c6c86"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 130, null, "178FED388ECABE98936E5F474887112583588BF34C39241AC452A48A3E1CEA11", "{\"value\":{\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/SetupAccount\"\r\n }}", "SecretReference" },
{ new Guid("27a2b22c-871b-a5e9-fda1-a862c1238f03"), new Guid("80733c50-d68a-d8f9-bb5f-fb6fce6f66a1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 70, null, "2E39290C9FFD6829B4EFE9004DF7D8236A17E02EE49D03EA79A0DAA936A1F249", "{\"value\":\"Services\"}", "Static" },
{ new Guid("2e2fe570-7204-6b79-d073-b62171e5c5ea"), new Guid("dc6781e4-7ca7-7307-bf91-42078ba9dea1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 100, null, "635B61BE7E1B2CF8197647383A166882478C6632F878068D383D1307C2678EC4", "{\"value\":1433}", "Static" },
{ new Guid("42f554e8-2d02-ce15-b5eb-e9344b3af1de"), new Guid("a9be4c68-f2c0-279f-6a98-92b5ac3c0e80"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000101"), "TemplateRevision", 10, null, "F1D875A6571735F22CA408E51F3793292BD9051C28443A107D3D3AE3E67CC18F", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_AdminContent\\u0027)]\"}", "Expression" },
{ new Guid("49c62e61-ae10-259f-188c-618cd50e22a4"), new Guid("9c60f6a6-a0c8-9f27-d31e-030403f78cd9"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000004"), "TemplateRevision", 10, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("4a01be5c-e40f-5a3e-e2af-548739f4d0ae"), new Guid("68c57a16-e728-4c39-f245-8a41d77d0aa7"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000102"), "TemplateRevision", 30, null, "41D77AE0AA9812940953F0E186514FBC3AF0E59F19573213C33A206FBE74C903", "{\"value\":\"CONTOSO\"}", "Static" },
{ new Guid("4a2c7cf6-41f3-b20f-de61-6efaf96444f9"), new Guid("67ca0430-31c6-553d-1553-60401cf4015c"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000001"), "TemplateRevision", 10, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("4d9d3f0a-dd2e-7f7a-4bbd-8e33526dcbce"), new Guid("3bdcaa78-354c-487f-b0c5-6a3e0b20f8a1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 10, null, "F1D875A6571735F22CA408E51F3793292BD9051C28443A107D3D3AE3E67CC18F", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_AdminContent\\u0027)]\"}", "Expression" },
{ new Guid("534db414-821b-59d3-b082-3c57efff2bff"), new Guid("dfb00c19-363f-7518-47be-1ba0ec16abd7"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000102"), "TemplateRevision", 20, null, "03289E6E378FD3D96F2565E9A7B802E0D8AB301F7CAE63F1853CB4080B19EEDF", "{\"value\":\"Contoso\"}", "Static" },
{ new Guid("57913480-45b6-f13d-504d-b05d239a2366"), new Guid("72fbe523-8aba-6aed-0918-d3e96f12254f"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000002"), "TemplateRevision", 10, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("651d7a24-db44-f18e-2607-1da3a1afbd9c"), new Guid("eb2d6268-83d4-ecc7-1696-eff819ac5db5"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000002"), "TemplateRevision", 20, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("665a2e6b-80a7-f64d-85b8-7386c9366549"), new Guid("de747816-08b8-faf8-d4ee-57b47222a7f4"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 80, null, "ED585C568AA8DAB5EEBB883F90FB02B5B31C614A290A806CFD44E451EC8303ED", "{\"value\":{\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/FarmPassphrase\"\r\n }}", "SecretReference" },
{ new Guid("67204f36-a09b-233d-c022-9b4c713f40e1"), new Guid("13f4b031-f8b8-1ef4-21b0-ab825dc54aee"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 150, null, "5B90965D61E7AD5163813A169214CA5CF9F2297487CEEBFE591487661CBFEBE1", "{\"value\":{\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/SearchAccount\"\r\n }}", "SecretReference" },
{ new Guid("680f4dac-c18b-0646-f036-478caf967a7a"), new Guid("1fc14a66-5b69-00da-e92c-a38e6d674b50"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 160, null, "80473B1B926C839E59F1D74E99D16E1BBC9E57E96B87545421BA23E9BF9E2536", "{\"value\":4000}", "Static" },
{ new Guid("6c056e2d-4714-80d0-905a-f49c62ea8d31"), new Guid("64767ffc-812e-b48e-db29-f295a287258b"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000004"), "TemplateRevision", 20, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("7ac33a52-7ee3-6b42-78fb-c125f218fa2b"), new Guid("62443ac9-c783-01b8-0cf4-21dc6bd57e44"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000101"), "TemplateRevision", 40, null, "7EB8E130BDEA0111303714519A04B078E07970AEBD2C51EC0244F90D3E3DFC98", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, parameters(\\u0027DatabasePrefix\\u0027), parameters(\\u0027DomainLabel\\u0027), if(equals(parameters(\\u0027Landscape\\u0027), \\u0027Test\\u0027), \\u0027Test\\u0027, \\u0027\\u0027))]\"}", "Expression" },
{ new Guid("7e85c8b9-3fd6-fef6-eea5-d8789601e7de"), new Guid("8e334e04-0465-ca82-b4f0-fd01561cdb4a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000003"), "TemplateRevision", 20, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("8175dee7-8e0a-c7e7-8e66-d4f12a4eb42c"), new Guid("c32f0de1-59a8-ffb2-a421-92209185de89"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 20, null, "425625B9EBF4642A17AAEA773F1304A83FC7AA89A00BD4C934B7B63434AA605C", "{\"value\":\"Content\"}", "Static" },
{ new Guid("834efeb6-4b29-efa5-9856-465663c13458"), new Guid("75c6a433-0dfe-1e3e-d324-322a2fad0d71"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000102"), "TemplateRevision", 10, null, "9EA8921C32C58E37F911E22432BEC6E68686DE6D3B4732B3B08029D606F09FA5", "{\"value\":\"contoso.local\"}", "Static" },
{ new Guid("851ce8ed-dc94-d437-4947-f1fbe53e5469"), new Guid("753acf7d-8ef6-69ef-b710-d430bfef3a98"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 30, null, "6B1F221C6266934F56A72E241B1D8D3426B59088F46B12BF78291F75CDC8008C", "{\"value\":\"SharePoint Service Applications\"}", "Static" },
{ new Guid("93bcf261-facd-49b5-376a-fdbb046e46a3"), new Guid("f7ea1bce-9cf1-ddc4-320f-e26ef51a6108"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 170, null, "7A32645CBAED087EDCAF78976456E695575F839D30D6F09986DDFA3B59E0AA75", "{\"value\":\"SharePoint Search Service Applications\"}", "Static" },
{ new Guid("9550114f-755b-9512-b430-603954351fed"), new Guid("afa8d0b4-7fc7-d419-7f01-6fb1fd59be4d"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000002"), "TemplateRevision", 10, null, "6ED96528075B87998FDAF609CB1E2A95224A347D109435B05E4C40A0A2F5FC44", "{\"expression\":\"[concat(parameters('DatabasePrefix'), '_Config')]\"}", "Expression" },
{ new Guid("a1c445b1-e063-e267-8721-54c0cdb6b36c"), new Guid("16fa79cc-52be-7ba0-56ad-051ce1879851"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 40, null, "0370D254CED4857D0DEEBF1E9E8D1061DB1503E1E3E12B7393EE534BBD5AF783", "{\"value\":\"SharePoint Web Applications\"}", "Static" },
{ new Guid("a2c1e504-20be-aff3-bde2-bcd9ba98bbba"), new Guid("4b3283a0-f2df-1edd-fa3f-a90cacf2a7f1"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 110, null, "1843C3695744571EDFEA9C4765EAC5AE430FC8C704AFE1FFDE02FF6E68D8BE45", "{\"value\":{\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/DefaultServiceAccount\"\r\n }}", "SecretReference" },
{ new Guid("ab4a8935-6d46-f6da-d17f-0e2abed7b413"), new Guid("14bcd250-6d40-e1a6-5aed-e563c7a2d589"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000101"), "TemplateRevision", 50, null, "261FCBE546960FDBA45B615045DC04AE5B717633C4368C87A25A81B31339BE96", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), \\u0027Farm_Config\\u0027)]\"}", "Expression" },
{ new Guid("aed99da4-e113-0a05-287e-5cca3abc1c7c"), new Guid("d0fbeeb9-628d-fa56-c106-6f54e3080b11"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 140, null, "E062DA6C7583AEC7061A78C9650B5B9CE023DD629C9E6FC3184CF8D0EFCF5755", "{\"value\":\"SharePoint\"}", "Static" },
{ new Guid("b08bf2a8-6bd3-1ed9-bf9b-a4f20738a6eb"), new Guid("1e409601-2e11-453e-09bc-ccc0e61a2eaf"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 50, null, "E3D4EFD336BE54EA0E9E1BFC14536244717CCCDF5291FAB78721936BA47673C4", "{\"value\":\"CL-SQL-01\"}", "Static" },
{ new Guid("b21cf5c3-284f-eca7-cf39-dd7451002ea4"), new Guid("971fc778-2e79-6408-aec3-1beeaf7fce8e"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000001"), "TemplateRevision", 20, null, "44136FA355B3678A1146AD16F7E8649E94FB4FC21FE77E8310C060F61CAAFF8A", "{}", "Static" },
{ new Guid("bf6abd6f-d02c-55f4-a27d-b89c19bc9c35"), new Guid("5d326698-5daf-cf78-b44d-2bbe6625d506"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000101"), "TemplateRevision", 30, null, "4D54C78468AB68A528415889AC8B50B89E2F5AFD0B0C36D3F6B060036422009E", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ServiceDatabaseSegment\\u0027))]\"}", "Expression" },
{ new Guid("bfc73c02-2df3-916e-7f54-f432bab37613"), new Guid("07333ab1-de72-442d-d91a-0bb2dd969443"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000101"), "TemplateRevision", 20, null, "B00E74A711838E3C2D4D3779ED0DEAECCF66172565F88F8F9ACE644EEBFBCD91", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ContentDatabaseSegment\\u0027))]\"}", "Expression" },
{ new Guid("cd120ede-010a-7433-d59c-13f90301b9e2"), new Guid("b5f3de9e-251e-ebb1-c6cc-b4a1a2afc987"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 120, null, "09773BB7E26265A65AB115E500C4CD051F10FEAA92C43C4260004F920BFDFAA5", "{\"value\":\"0000-0000-0000-0000-0000\"}", "Static" },
{ new Guid("cd9e8e80-1975-10d8-8272-d95ba8275299"), new Guid("a1bbb850-c83a-f3ee-474c-dc9484456e2a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 60, null, "16EC9E2621F1BA6E974D9005EECC34771B1D6303EE73DCC86214BAD2F5E4CA81", "{\"value\":\"SVC_SHP_WAP\"}", "Static" },
{ new Guid("d336f688-57f4-4df1-a992-27e775a48440"), new Guid("04091788-4a0a-7911-b11d-b70335ad378a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 30, null, "4D54C78468AB68A528415889AC8B50B89E2F5AFD0B0C36D3F6B060036422009E", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ServiceDatabaseSegment\\u0027))]\"}", "Expression" },
{ new Guid("d746cb22-0a95-7464-d693-aa5af56ae901"), new Guid("efbb9cc6-ed11-105b-91e4-0d925c214e32"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 10, null, "D6BBF1B384A92232566CF22E32A5D99FC9AC6F92E70D86EC00BF1FD5876F6FFB", "{\"value\":{\r\n \"Provider\": \"SecretManagement\",\r\n \"Vault\": \"Test\",\r\n \"Name\": \"Windows/SharePoint/FarmAccount\"\r\n }}", "SecretReference" },
{ new Guid("e5c9e96a-c891-0854-60b0-b2246870d0c0"), new Guid("2b7b2531-54ed-e778-e547-f0b696591d0c"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000003"), "TemplateRevision", 10, null, "AB48409D8EC6C4009EFCDEE399B7DEED9BC132543EE5C2CF518A9201B6BEF41D", "{\"expression\":\"[concat(parameters('DatabasePrefix'), '_Farm_Config')]\"}", "Expression" },
{ new Guid("e69c4719-077b-8ebc-bf06-708bbbca4052"), new Guid("a7cc8607-ab28-8765-6fb3-87222c485f65"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 90, null, "794D1C1505B58390120A1BE2139A2056C5ED852D4653730DFD4A206060DD3155", "{\"value\":\"SQLServer\"}", "Static" },
{ new Guid("e79ccc48-28ec-72d3-1331-6c825dc1b7c0"), new Guid("50f73169-1bda-2644-6963-b7cfad914a6a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000003"), "TemplateRevision", 20, null, "7B7FA4E9CF1492587605741B7CEE29F579EC357030B794ED646A19C4474907A6", "{\"expression\":\"[concat(parameters('DatabasePrefix'), '_AdminContent')]\"}", "Expression" },
{ new Guid("ea4bdba4-397b-d93a-fc6d-03ff3826d2c1"), new Guid("1ee4b631-fc15-85dc-3057-a1ca26da7ba9"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000101"), "TemplateRevision", 10, null, "94FA03DA6461D6142CFA6448B23D207BC7AFCC89E3CC4B9A507ADA54864A6B6F", "{\"value\":\"Test\"}", "Static" },
{ new Guid("eb107c2a-335d-5148-aecf-33ffd3ba51bc"), new Guid("dc4af326-9a3d-c4a6-9462-669bcf16103a"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000001"), "TemplateRevision", 10, null, "C02CA81E91040D74400B8E4D152614D790E8D741A993754383057E87A581AF09", "{\"expression\":\"[concat(parameters('DomainName'), '-DefaultSite')]\"}", "Expression" },
{ new Guid("f1520907-d5c3-3e33-1daa-0fbdec706c57"), new Guid("57fe8654-dbf4-c6c1-3a99-ac68fe393731"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 20, null, "B00E74A711838E3C2D4D3779ED0DEAECCF66172565F88F8F9ACE644EEBFBCD91", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, variables(\\u0027DatabasePrefix\\u0027), parameters(\\u0027ContentDatabaseSegment\\u0027))]\"}", "Expression" },
{ new Guid("fc6347da-d00e-db4c-5b5b-5a5e80c568ab"), new Guid("7232ce0f-9f3e-e10b-9370-7aec1c610ecc"), new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", new Guid("72000000-0000-0000-0000-000000000103"), "TemplateRevision", 40, null, "7EB8E130BDEA0111303714519A04B078E07970AEBD2C51EC0244F90D3E3DFC98", "{\"expression\":\"[joinNotEmpty(\\u0027_\\u0027, parameters(\\u0027DatabasePrefix\\u0027), parameters(\\u0027DomainLabel\\u0027), if(equals(parameters(\\u0027Landscape\\u0027), \\u0027Test\\u0027), \\u0027Test\\u0027, \\u0027\\u0027))]\"}", "Expression" }
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("0d47df0b-4ae8-e379-42f7-660dbf80cff1"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("146d60ab-37ba-8fbf-83f1-89a0976b70c7"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("1a37be2a-89b9-4784-e89e-604946b4a2a0"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("27a2b22c-871b-a5e9-fda1-a862c1238f03"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("2e2fe570-7204-6b79-d073-b62171e5c5ea"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("42f554e8-2d02-ce15-b5eb-e9344b3af1de"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("49c62e61-ae10-259f-188c-618cd50e22a4"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("4a01be5c-e40f-5a3e-e2af-548739f4d0ae"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("4a2c7cf6-41f3-b20f-de61-6efaf96444f9"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("4d9d3f0a-dd2e-7f7a-4bbd-8e33526dcbce"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("534db414-821b-59d3-b082-3c57efff2bff"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("57913480-45b6-f13d-504d-b05d239a2366"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("651d7a24-db44-f18e-2607-1da3a1afbd9c"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("665a2e6b-80a7-f64d-85b8-7386c9366549"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("67204f36-a09b-233d-c022-9b4c713f40e1"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("680f4dac-c18b-0646-f036-478caf967a7a"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("6c056e2d-4714-80d0-905a-f49c62ea8d31"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("7ac33a52-7ee3-6b42-78fb-c125f218fa2b"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("7e85c8b9-3fd6-fef6-eea5-d8789601e7de"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("8175dee7-8e0a-c7e7-8e66-d4f12a4eb42c"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("834efeb6-4b29-efa5-9856-465663c13458"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("851ce8ed-dc94-d437-4947-f1fbe53e5469"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("93bcf261-facd-49b5-376a-fdbb046e46a3"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("9550114f-755b-9512-b430-603954351fed"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("a1c445b1-e063-e267-8721-54c0cdb6b36c"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("a2c1e504-20be-aff3-bde2-bcd9ba98bbba"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("ab4a8935-6d46-f6da-d17f-0e2abed7b413"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("aed99da4-e113-0a05-287e-5cca3abc1c7c"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("b08bf2a8-6bd3-1ed9-bf9b-a4f20738a6eb"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("b21cf5c3-284f-eca7-cf39-dd7451002ea4"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("bf6abd6f-d02c-55f4-a27d-b89c19bc9c35"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("bfc73c02-2df3-916e-7f54-f432bab37613"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("cd120ede-010a-7433-d59c-13f90301b9e2"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("cd9e8e80-1975-10d8-8272-d95ba8275299"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("d336f688-57f4-4df1-a992-27e775a48440"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("d746cb22-0a95-7464-d693-aa5af56ae901"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("e5c9e96a-c891-0854-60b0-b2246870d0c0"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("e69c4719-077b-8ebc-bf06-708bbbca4052"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("e79ccc48-28ec-72d3-1331-6c825dc1b7c0"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("ea4bdba4-397b-d93a-fc6d-03ff3826d2c1"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("eb107c2a-335d-5148-aecf-33ffd3ba51bc"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("f1520907-d5c3-3e33-1daa-0fbdec706c57"));
migrationBuilder.DeleteData(
table: "ConfigurationValues",
keyColumn: "Id",
keyValue: new Guid("fc6347da-d00e-db4c-5b5b-5a5e80c568ab"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("04091788-4a0a-7911-b11d-b70335ad378a"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("07333ab1-de72-442d-d91a-0bb2dd969443"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("13f4b031-f8b8-1ef4-21b0-ab825dc54aee"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("14bcd250-6d40-e1a6-5aed-e563c7a2d589"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("16fa79cc-52be-7ba0-56ad-051ce1879851"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("1e409601-2e11-453e-09bc-ccc0e61a2eaf"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("1ee4b631-fc15-85dc-3057-a1ca26da7ba9"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("1fc14a66-5b69-00da-e92c-a38e6d674b50"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("2b7b2531-54ed-e778-e547-f0b696591d0c"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("3bdcaa78-354c-487f-b0c5-6a3e0b20f8a1"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("4b3283a0-f2df-1edd-fa3f-a90cacf2a7f1"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("50f73169-1bda-2644-6963-b7cfad914a6a"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("57fe8654-dbf4-c6c1-3a99-ac68fe393731"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("5d326698-5daf-cf78-b44d-2bbe6625d506"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("62443ac9-c783-01b8-0cf4-21dc6bd57e44"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("64767ffc-812e-b48e-db29-f295a287258b"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("67ca0430-31c6-553d-1553-60401cf4015c"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("68c57a16-e728-4c39-f245-8a41d77d0aa7"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("7232ce0f-9f3e-e10b-9370-7aec1c610ecc"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("72fbe523-8aba-6aed-0918-d3e96f12254f"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("73be057e-cd31-1658-186b-bcf4695a48f7"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("753acf7d-8ef6-69ef-b710-d430bfef3a98"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("75c6a433-0dfe-1e3e-d324-322a2fad0d71"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("80733c50-d68a-d8f9-bb5f-fb6fce6f66a1"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("8e334e04-0465-ca82-b4f0-fd01561cdb4a"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("971fc778-2e79-6408-aec3-1beeaf7fce8e"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("9c60f6a6-a0c8-9f27-d31e-030403f78cd9"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("a1bbb850-c83a-f3ee-474c-dc9484456e2a"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("a7cc8607-ab28-8765-6fb3-87222c485f65"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("a9be4c68-f2c0-279f-6a98-92b5ac3c0e80"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("afa8d0b4-7fc7-d419-7f01-6fb1fd59be4d"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("b5f3de9e-251e-ebb1-c6cc-b4a1a2afc987"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("c32f0de1-59a8-ffb2-a421-92209185de89"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("d0fbeeb9-628d-fa56-c106-6f54e3080b11"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("d7d653e4-d29b-b9be-d701-b4eae2b34d2e"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("dc4af326-9a3d-c4a6-9462-669bcf16103a"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("dc6781e4-7ca7-7307-bf91-42078ba9dea1"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("de747816-08b8-faf8-d4ee-57b47222a7f4"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("dfb00c19-363f-7518-47be-1ba0ec16abd7"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("e9a93051-120a-00c2-c69d-794d3d3c6c86"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("eb2d6268-83d4-ecc7-1696-eff819ac5db5"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("efbb9cc6-ed11-105b-91e4-0d925c214e32"));
migrationBuilder.DeleteData(
table: "ConfigurationDefinitions",
keyColumn: "Id",
keyValue: new Guid("f7ea1bce-9cf1-ddc4-320f-e26ef51a6108"));
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,56 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Microsoft.SelfService.Portal.Core.API.Migrations
{
/// <inheritdoc />
public partial class AddOnPremApiClients : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ApiClients",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"),
ClientId = table.Column<string>(type: "nvarchar(128)", maxLength: 128, nullable: false),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
SecretHash = table.Column<string>(type: "nvarchar(max)", nullable: false),
ScopesJson = table.Column<string>(type: "nvarchar(max)", nullable: false),
IsEnabled = table.Column<bool>(type: "bit", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "datetime2", nullable: true),
LastUsedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
Modified = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETDATE()"),
ModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: false),
Created = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETDATE()"),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiClients", x => x.Id);
table.CheckConstraint("CK_ApiClients_ScopesJson_IsJson", "ISJSON([ScopesJson]) = 1");
});
migrationBuilder.InsertData(
table: "ApiClients",
columns: new[] { "Id", "ClientId", "Created", "CreatedBy", "ExpiresAt", "IsEnabled", "LastUsedAt", "Modified", "ModifiedBy", "Name", "ScopesJson", "SecretHash" },
values: new object[] { new Guid("91000000-0000-0000-0000-000000000001"), "ssp-demo-worker", new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", null, true, null, new DateTime(2026, 7, 7, 0, 0, 0, 0, DateTimeKind.Utc), "DemoSeed", "Demo Worker Client", "[\"deployment.read\",\"deployment.write\",\"queue.process\",\"template.read\",\"credential.resolve\"]", "PBKDF2-SHA256.100000.c3NwLWRlbW8td29ya2VyAA==.xQxe7BHCn9pdkqHozdyspEmKnz95uCUuxVvU2vgCEbo=" });
migrationBuilder.CreateIndex(
name: "IX_ApiClients_ClientId",
table: "ApiClients",
column: "ClientId",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApiClients");
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,71 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Microsoft.SelfService.Portal.Core.API.Migrations
{
/// <inheritdoc />
public partial class AddApiTokenManagement : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ApiTokens",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false, defaultValueSql: "NEWID()"),
Jti = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: false),
Subject = table.Column<string>(type: "nvarchar(450)", nullable: false),
SubjectType = table.Column<string>(type: "nvarchar(32)", maxLength: 32, nullable: false),
ApiClientId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
ScopesJson = table.Column<string>(type: "nvarchar(max)", nullable: false),
IssuedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "datetime2", nullable: false),
RevokedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
RevokedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
LastUsedAt = table.Column<DateTime>(type: "datetime2", nullable: true),
Modified = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETDATE()"),
ModifiedBy = table.Column<string>(type: "nvarchar(max)", nullable: false),
Created = table.Column<DateTime>(type: "datetime2", nullable: false, defaultValueSql: "GETDATE()"),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ApiTokens", x => x.Id);
table.CheckConstraint("CK_ApiTokens_ScopesJson_IsJson", "ISJSON([ScopesJson]) = 1");
table.ForeignKey(
name: "FK_ApiTokens_ApiClients_ApiClientId",
column: x => x.ApiClientId,
principalTable: "ApiClients",
principalColumn: "Id",
onDelete: ReferentialAction.SetNull);
});
migrationBuilder.CreateIndex(
name: "IX_ApiTokens_ApiClientId",
table: "ApiTokens",
column: "ApiClientId");
migrationBuilder.CreateIndex(
name: "IX_ApiTokens_Jti",
table: "ApiTokens",
column: "Jti",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ApiTokens_SubjectType_Subject",
table: "ApiTokens",
columns: new[] { "SubjectType", "Subject" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ApiTokens");
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Microsoft.SelfService.Portal.Core.API.Migrations
{
/// <inheritdoc />
public partial class AddDemoApiClientTokenScopes : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "ApiClients",
keyColumn: "Id",
keyValue: new Guid("91000000-0000-0000-0000-000000000001"),
column: "ScopesJson",
value: "[\"deployment.read\",\"deployment.write\",\"queue.process\",\"template.read\",\"credential.resolve\",\"token.manage\",\"token.admin\"]");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "ApiClients",
keyColumn: "Id",
keyValue: new Guid("91000000-0000-0000-0000-000000000001"),
column: "ScopesJson",
value: "[\"deployment.read\",\"deployment.write\",\"queue.process\",\"template.read\",\"credential.resolve\"]");
}
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Microsoft.SelfService.Portal.Core.API.Migrations
{
/// <inheritdoc />
public partial class AddCentralizedAuthorizationScopes : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "ApiClients",
keyColumn: "Id",
keyValue: new Guid("91000000-0000-0000-0000-000000000001"),
column: "ScopesJson",
value: "[\"configuration.read\",\"configuration.write\",\"credential.read\",\"credential.write\",\"credential.resolve\",\"deployment.read\",\"deployment.write\",\"queue.read\",\"queue.process\",\"template.read\",\"template.write\",\"token.manage\",\"token.admin\"]");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.UpdateData(
table: "ApiClients",
keyColumn: "Id",
keyValue: new Guid("91000000-0000-0000-0000-000000000001"),
column: "ScopesJson",
value: "[\"deployment.read\",\"deployment.write\",\"queue.process\",\"template.read\",\"credential.resolve\",\"token.manage\",\"token.admin\"]");
}
}
}
File diff suppressed because one or more lines are too long
+28
View File
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.SelfService.Portal.Core.API.Models
{
public class ApiClientModel : BaseModel
{
[Column(Order = 1)]
public string ClientId { get; set; } = string.Empty;
[Column(Order = 2)]
public string Name { get; set; } = string.Empty;
[Column(Order = 3)]
public string SecretHash { get; set; } = string.Empty;
[Column(Order = 4)]
public string ScopesJson { get; set; } = "[]";
[Column(Order = 5)]
public bool IsEnabled { get; set; } = true;
[Column(Order = 6)]
public DateTime? ExpiresAt { get; set; }
[Column(Order = 7)]
public DateTime? LastUsedAt { get; set; }
}
}
+42
View File
@@ -0,0 +1,42 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.SelfService.Portal.Core.API.Models
{
public class ApiTokenModel : BaseModel
{
[Column(Order = 1)]
public string Jti { get; set; } = string.Empty;
[Column(Order = 2)]
public string Subject { get; set; } = string.Empty;
[Column(Order = 3)]
public string SubjectType { get; set; } = "User";
[Column(Order = 4)]
public Guid? ApiClientId { get; set; }
[Column(Order = 5)]
public string Name { get; set; } = string.Empty;
[Column(Order = 6)]
public string ScopesJson { get; set; } = "[]";
[Column(Order = 7)]
public DateTime IssuedAt { get; set; } = DateTime.UtcNow;
[Column(Order = 8)]
public DateTime ExpiresAt { get; set; }
[Column(Order = 9)]
public DateTime? RevokedAt { get; set; }
[Column(Order = 10)]
public string? RevokedBy { get; set; }
[Column(Order = 11)]
public DateTime? LastUsedAt { get; set; }
public ApiClientModel? ApiClient { get; set; }
}
}
+31
View File
@@ -0,0 +1,31 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.SelfService.Portal.Core.API.Models
{
public static class ConfigurationDefinitionKinds
{
public const string Parameter = "Parameter";
public const string Variable = "Variable";
}
public class ConfigurationDefinitionModel : BaseModel
{
[Column(Order = 1)]
public Guid? TemplateRevisionId { get; set; }
[Column(Order = 2)]
public string Kind { get; set; } = ConfigurationDefinitionKinds.Parameter;
[Column(Order = 3)]
public string Name { get; set; } = string.Empty;
[Column(Order = 4)]
public string PropertiesJson { get; set; } = "{}";
[Column(Order = 5)]
public string DefinitionHash { get; set; } = string.Empty;
public TemplateRevisionModel? TemplateRevision { get; set; }
public ICollection<ConfigurationValueModel> Values { get; set; } = new List<ConfigurationValueModel>();
}
}
+52
View File
@@ -0,0 +1,52 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.SelfService.Portal.Core.API.Models
{
public static class ConfigurationValueSourceTypes
{
public const string Static = "Static";
public const string ScopeProperty = "ScopeProperty";
public const string Expression = "Expression";
public const string SecretReference = "SecretReference";
}
public static class ConfigurationValueScopeTypes
{
public const string TemplateRevision = "TemplateRevision";
public const string Environment = "Environment";
public const string Domain = "Domain";
public const string Target = "Target";
public const string DeploymentGroup = "DeploymentGroup";
public const string Deployment = "Deployment";
public const string TemplateSelection = "TemplateSelection";
}
public class ConfigurationValueModel : BaseModel
{
[Column(Order = 1)]
public Guid ConfigurationDefinitionId { get; set; }
[Column(Order = 2)]
public string ScopeType { get; set; } = ConfigurationValueScopeTypes.TemplateRevision;
[Column(Order = 3)]
public Guid ScopeId { get; set; }
[Column(Order = 4)]
public string ValueSourceType { get; set; } = ConfigurationValueSourceTypes.Static;
[Column(Order = 5)]
public string? SourcePath { get; set; }
[Column(Order = 6)]
public string? ValueJson { get; set; }
[Column(Order = 7)]
public string ValueHash { get; set; } = string.Empty;
[Column(Order = 8)]
public int SortOrder { get; set; }
public ConfigurationDefinitionModel ConfigurationDefinition { get; set; } = null!;
}
}
+25
View File
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.SelfService.Portal.Core.API.Models
{
public class CredentialSecretModel : BaseModel
{
[Column(Order = 1)]
public string Name { get; set; } = string.Empty;
[Column(Order = 2)]
public string? UserName { get; set; }
[Column(Order = 3)]
public string SecretValue { get; set; } = string.Empty;
[Column(Order = 4)]
public string SecretType { get; set; } = "Credential";
[Column(Order = 5)]
public string? MetadataJson { get; set; }
[Column(Order = 6)]
public bool IsEnabled { get; set; } = true;
}
}
+58
View File
@@ -0,0 +1,58 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.SelfService.Portal.Core.API.Models
{
public static class DeploymentArtifactTypes
{
public const string Preview = "Preview";
public const string ResolvedConfigurationData = "ResolvedConfigurationData";
public const string Mof = "Mof";
public const string PullServerPackage = "PullServerPackage";
}
public class DeploymentArtifactModel : BaseModel
{
[Column(Order = 1)]
public Guid DeploymentGroupId { get; set; }
[Column(Order = 2)]
public Guid? DeploymentTargetId { get; set; }
[Column(Order = 3)]
public Guid? TargetId { get; set; }
[Column(Order = 4)]
public string ArtifactType { get; set; } = DeploymentArtifactTypes.ResolvedConfigurationData;
[Column(Order = 5)]
public string Status { get; set; } = QueueJobStatus.Pending;
[Column(Order = 6)]
public string DeploymentJson { get; set; } = "{}";
[Column(Order = 7)]
public string? SourceJson { get; set; }
[Column(Order = 8)]
public string? ResolvedJson { get; set; }
[Column(Order = 9)]
public string? SourceSnapshotJson { get; set; }
[Column(Order = 10)]
public string InputHash { get; set; } = string.Empty;
[Column(Order = 11)]
public string OutputHash { get; set; } = string.Empty;
[Column(Order = 12)]
public bool IsStale { get; set; }
[Column(Order = 13)]
public string? StaleReasonJson { get; set; }
public DeploymentGroupModel DeploymentGroup { get; set; } = null!;
public DeploymentModel? Deployment { get; set; }
public TargetModel? Target { get; set; }
}
}
+1
View File
@@ -22,6 +22,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Models
public ICollection<DeploymentTemplateSelectionModel> TemplateSelections { get; set; } = new List<DeploymentTemplateSelectionModel>();
public ICollection<DeploymentParameterValueModel> ParameterValues { get; set; } = new List<DeploymentParameterValueModel>();
public ICollection<DeploymentTargetAssignmentModel> TargetAssignments { get; set; } = new List<DeploymentTargetAssignmentModel>();
public ICollection<DeploymentArtifactModel> Artifacts { get; set; } = new List<DeploymentArtifactModel>();
}
}
+12 -1
View File
@@ -9,12 +9,23 @@ namespace Microsoft.SelfService.Portal.Core.API.Models
[Column(Order = 3)]
public Guid TargetId { get; set; }
[Column(Order = 4)]
public string Status { get; set; }
public Guid? TemplateRevisionId { get; set; }
[Column(Order = 5)]
public Guid? CurrentArtifactId { get; set; }
[Column(Order = 6)]
public string Status { get; set; }
[Column(Order = 7)]
public string JSONData { get; set; }
[Column(Order = 8)]
public string? OverridesJson { get; set; }
[Column(Order = 9)]
public string? SourceJson { get; set; }
public DeploymentGroupModel DeploymentGroup { get; set; }
public TargetModel Target { get; set; }
public TemplateRevisionModel? TemplateRevision { get; set; }
public DeploymentArtifactModel? CurrentArtifact { get; set; }
public ICollection<DeploymentArtifactModel> Artifacts { get; set; } = new List<DeploymentArtifactModel>();
}
}
+6 -2
View File
@@ -11,16 +11,20 @@ namespace Microsoft.SelfService.Portal.Core.API.Models
public Guid TemplateVersionId { get; set; }
[Column(Order = 3)]
public string TemplateRole { get; set; } = "Service";
public Guid? TemplateRevisionId { get; set; }
[Column(Order = 4)]
public int SortOrder { get; set; }
public string TemplateRole { get; set; } = "Service";
[Column(Order = 5)]
public int SortOrder { get; set; }
[Column(Order = 6)]
public string? Alias { get; set; }
public DeploymentGroupModel DeploymentGroup { get; set; } = null!;
public TemplateVersionModel TemplateVersion { get; set; } = null!;
public TemplateRevisionModel? TemplateRevision { get; set; }
}
}
+45
View File
@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Microsoft.SelfService.Portal.Core.API.Models
{
public class TemplateRevisionModel : BaseModel
{
[Column(Order = 1)]
public Guid TemplateVersionId { get; set; }
[Column(Order = 2)]
public int RevisionNumber { get; set; }
[Column(Order = 3)]
public string JsonData { get; set; } = "{}";
[Column(Order = 4)]
public string JsonHash { get; set; } = string.Empty;
[Column(Order = 5)]
public string SchemaVersion { get; set; } = "1.0";
[Column(Order = 6)]
public bool IsCurrent { get; set; }
[Column(Order = 7)]
public bool IsPublished { get; set; }
[Column(Order = 8)]
public DateTime? PublishedAt { get; set; }
[Column(Order = 9)]
public string? PublishedBy { get; set; }
public TemplateVersionModel TemplateVersion { get; set; } = null!;
public ICollection<ConfigurationDefinitionModel> ConfigurationDefinitions { get; set; } = new List<ConfigurationDefinitionModel>();
public ICollection<DeploymentTemplateSelectionModel> DeploymentTemplateSelections { get; set; } = new List<DeploymentTemplateSelectionModel>();
public ICollection<DeploymentModel> Deployments { get; set; } = new List<DeploymentModel>();
public void SetJsonData(string jsonData)
{
JsonData = string.IsNullOrWhiteSpace(jsonData) ? "{}" : jsonData;
JsonHash = TemplateVersionModel.ComputeSha256(JsonData);
}
}
}
+1
View File
@@ -31,6 +31,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Models
public string? PublishedBy { get; set; }
public TemplateModel Template { get; set; } = null!;
public ICollection<TemplateRevisionModel> TemplateRevisions { get; set; } = new List<TemplateRevisionModel>();
public void SetJsonData(string jsonData)
{
+75 -8
View File
@@ -1,11 +1,15 @@
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authentication.Negotiate;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Interfaces;
using Microsoft.SelfService.Portal.Core.API.Repository;
using Microsoft.SelfService.Portal.Core.API.Services;
using Microsoft.Extensions.FileProviders;
using System.IdentityModel.Tokens.Jwt;
using System.Text.Json.Serialization;
@@ -27,6 +31,7 @@ builder.Services.AddScoped<IDeploymentInterface, DeploymentRepository>();
builder.Services.AddScoped<ITemplateInterface, TemplateRepository>();
builder.Services.AddScoped<ITemplateCategoryInterface, TemplateCategoryRepository>();
builder.Services.AddScoped<IQueueJobService, QueueJobService>();
builder.Services.AddScoped<ApiTokenService>();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
@@ -36,16 +41,76 @@ builder.Services.AddAutoMapper(_ => { }, AppDomain.CurrentDomain.GetAssemblies()
builder.Services.AddDbContext<DataContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Context") ?? throw new InvalidOperationException("Connection string 'Context' not found.")));
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
var tokenService = new ApiTokenService(builder.Configuration);
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = "Smart";
options.DefaultChallengeScheme = "Smart";
})
.AddPolicyScheme("Smart", "Negotiate or Bearer", options =>
{
options.ForwardDefaultSelector = context =>
{
var authorization = context.Request.Headers.Authorization.ToString();
return authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
? JwtBearerDefaults.AuthenticationScheme
: NegotiateDefaults.AuthenticationScheme;
};
})
.AddNegotiate()
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = tokenService.Issuer,
ValidateAudience = true,
ValidAudience = tokenService.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = tokenService.GetSigningKey(),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(2)
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var jti = context.Principal?.FindFirst(JwtRegisteredClaimNames.Jti)?.Value;
if (string.IsNullOrWhiteSpace(jti))
{
context.Fail("Token does not contain a token id.");
return Task.CompletedTask;
}
var dataContext = context.HttpContext.RequestServices.GetRequiredService<DataContext>();
var token = dataContext.ApiTokens.FirstOrDefault(existing => existing.Jti == jti);
if (token == null)
{
context.Fail("Token is not registered.");
return Task.CompletedTask;
}
if (token.RevokedAt.HasValue || token.ExpiresAt <= DateTime.UtcNow)
{
context.Fail("Token is revoked or expired.");
return Task.CompletedTask;
}
token.LastUsedAt = DateTime.UtcNow;
token.Modified = DateTime.UtcNow;
token.ModifiedBy = "BearerToken";
dataContext.SaveChanges();
return Task.CompletedTask;
}
};
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddSelfServicePortalAuthorization();
var app = builder.Build();
var frontendDistPath = Path.GetFullPath(Path.Combine(
@@ -101,3 +166,5 @@ if (Directory.Exists(frontendDistPath))
app.Run();
+27
View File
@@ -55,6 +55,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
var templateVersion = requestedTemplateVersionId.HasValue
? _context.TemplateVersions
.Include(version => version.TemplateRevisions)
.Include(version => version.Template)
.ThenInclude(template => template.TemplateCategory)
.ThenInclude(category => category.Service)
@@ -66,6 +67,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
.Include(existing => existing.TemplateCategory)
.ThenInclude(existing => existing.Service)
.Include(existing => existing.TemplateVersions)
.ThenInclude(version => version.TemplateRevisions)
.FirstOrDefault(existing => existing.Id == deploymentBatch.TemplateId);
if (template == null)
@@ -128,11 +130,32 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
Id = Guid.NewGuid(),
DeploymentGroupId = deploymentBatch.Id,
TemplateVersionId = templateVersion.Id,
TemplateRevisionId = templateVersion.TemplateRevisions?.FirstOrDefault(revision => revision.IsCurrent)?.Id,
TemplateRole = "Service",
SortOrder = 10,
Alias = template.Name
});
}
else
{
foreach (var selection in deploymentBatch.TemplateSelections)
{
if (selection.TemplateRevisionId.HasValue)
{
continue;
}
var selectedVersion = selection.TemplateVersionId == templateVersion?.Id
? templateVersion
: _context.TemplateVersions
.Include(version => version.TemplateRevisions)
.FirstOrDefault(version => version.Id == selection.TemplateVersionId);
selection.TemplateRevisionId = selectedVersion?.TemplateRevisions
?.FirstOrDefault(revision => revision.IsCurrent)
?.Id;
}
}
if (deploymentBatch.TargetAssignments.Count == 0)
{
@@ -170,6 +193,10 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
{
DeploymentGroupId = deploymentBatch.Id,
TargetId = targetId,
TemplateRevisionId = deploymentBatch.TemplateSelections
.OrderBy(selection => selection.SortOrder)
.Select(selection => selection.TemplateRevisionId)
.FirstOrDefault(),
Status = QueueJobStatus.Pending,
JSONData = "{}"
});
+189 -18
View File
@@ -3,6 +3,7 @@ using Microsoft.SelfService.Portal.Core.API.Context;
using Microsoft.SelfService.Portal.Core.API.Interfaces;
using Microsoft.SelfService.Portal.Core.API.JsonDocuments;
using Microsoft.SelfService.Portal.Core.API.Models;
using System.Text.Json;
namespace Microsoft.SelfService.Portal.Core.API.Repository
{
@@ -67,6 +68,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
PublishedBy = template.CreatedBy
};
version.SetJsonData(document.JsonData);
version.TemplateRevisions.Add(CreateTemplateRevision(version.Id, document.JsonData, document.SchemaVersion, true, template.CreatedBy));
template.TemplateVersions.Add(version);
_context.Add(template);
@@ -87,7 +89,8 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
var document = ConfigurationDocumentValidator.NormalizeAndValidate(template.JSONData, ConfigurationDocumentKind.Template);
var jsonData = document.JsonData;
var publishedVersion = existingTemplate.TemplateVersions.FirstOrDefault(version => version.IsPublished);
var jsonChanged = publishedVersion == null || !string.Equals(publishedVersion.JsonData, jsonData, StringComparison.Ordinal);
var currentRevision = publishedVersion == null ? null : GetCurrentTemplateRevision(publishedVersion.Id);
var jsonChanged = currentRevision == null || !string.Equals(currentRevision.JsonData, jsonData, StringComparison.Ordinal);
existingTemplate.Name = template.Name;
existingTemplate.Version = template.Version;
@@ -100,28 +103,39 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
if (jsonChanged)
{
var versionName = GetUniqueVersionName(
existingTemplate.Id,
string.IsNullOrWhiteSpace(template.Version) ? DateTime.UtcNow.ToString("yyyyMMddHHmmss") : template.Version);
var version = new TemplateVersionModel
var version = publishedVersion;
if (version == null || !string.Equals(version.Version, template.Version, StringComparison.OrdinalIgnoreCase))
{
Id = Guid.NewGuid(),
TemplateId = existingTemplate.Id,
Version = versionName,
SchemaVersion = document.SchemaVersion,
IsPublished = true,
PublishedAt = DateTime.UtcNow,
PublishedBy = template.ModifiedBy
};
version.SetJsonData(jsonData);
var versionName = GetUniqueVersionName(
existingTemplate.Id,
string.IsNullOrWhiteSpace(template.Version) ? DateTime.UtcNow.ToString("yyyyMMddHHmmss") : template.Version);
foreach (var existingVersion in existingTemplate.TemplateVersions)
version = new TemplateVersionModel
{
Id = Guid.NewGuid(),
TemplateId = existingTemplate.Id,
Version = versionName,
SchemaVersion = document.SchemaVersion,
IsPublished = true,
PublishedAt = DateTime.UtcNow,
PublishedBy = template.ModifiedBy
};
existingTemplate.TemplateVersions.Add(version);
}
version.SetJsonData(jsonData);
version.SchemaVersion = document.SchemaVersion;
version.IsPublished = true;
version.PublishedAt = DateTime.UtcNow;
version.PublishedBy = template.ModifiedBy;
foreach (var existingVersion in existingTemplate.TemplateVersions.Where(existing => existing.Id != version.Id))
{
existingVersion.IsPublished = false;
}
existingTemplate.TemplateVersions.Add(version);
var revision = CreateTemplateRevision(version.Id, jsonData, document.SchemaVersion, true, template.ModifiedBy);
_context.TemplateRevisions.Add(revision);
}
return SaveChanges();
@@ -157,6 +171,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
{
return _context.TemplateVersions
.AsNoTracking()
.Include(version => version.TemplateRevisions)
.Where(version => version.TemplateId == templateId)
.OrderByDescending(version => version.Created)
.ToList();
@@ -166,6 +181,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
{
return _context.TemplateVersions
.AsNoTracking()
.Include(version => version.TemplateRevisions)
.FirstOrDefault(version => version.TemplateId == templateId && version.Id == versionId);
}
@@ -173,9 +189,45 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
{
return _context.TemplateVersions
.AsNoTracking()
.Include(version => version.TemplateRevisions)
.FirstOrDefault(version => version.TemplateId == templateId && version.IsPublished);
}
public ICollection<TemplateRevisionModel> GetTemplateRevisions(Guid templateId, Guid versionId)
{
if (!_context.TemplateVersions.Any(version => version.TemplateId == templateId && version.Id == versionId))
{
return new List<TemplateRevisionModel>();
}
return _context.TemplateRevisions
.AsNoTracking()
.Include(revision => revision.ConfigurationDefinitions)
.Where(revision => revision.TemplateVersionId == versionId)
.OrderByDescending(revision => revision.RevisionNumber)
.ToList();
}
public TemplateRevisionModel? GetTemplateRevisionById(Guid templateId, Guid versionId, Guid revisionId)
{
if (!_context.TemplateVersions.Any(version => version.TemplateId == templateId && version.Id == versionId))
{
return null;
}
return _context.TemplateRevisions
.AsNoTracking()
.Include(revision => revision.ConfigurationDefinitions)
.FirstOrDefault(revision => revision.TemplateVersionId == versionId && revision.Id == revisionId);
}
public TemplateRevisionModel? GetCurrentTemplateRevision(Guid versionId)
{
return _context.TemplateRevisions
.AsNoTracking()
.FirstOrDefault(revision => revision.TemplateVersionId == versionId && revision.IsCurrent);
}
public bool AddTemplateVersion(Guid templateId, TemplateVersionModel version, bool publish)
{
if (!_context.Templates.Any(template => template.Id == templateId))
@@ -192,6 +244,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
version.SchemaVersion);
version.SchemaVersion = document.SchemaVersion;
version.SetJsonData(document.JsonData);
version.TemplateRevisions.Add(CreateTemplateRevision(version.Id, document.JsonData, document.SchemaVersion, publish, version.PublishedBy));
if (publish)
{
@@ -208,6 +261,42 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
return SaveChanges();
}
public bool AddTemplateRevision(Guid templateId, Guid versionId, TemplateRevisionModel revision, bool publish)
{
var version = _context.TemplateVersions
.Include(existing => existing.TemplateRevisions)
.FirstOrDefault(existing => existing.TemplateId == templateId && existing.Id == versionId);
if (version == null)
{
return false;
}
var document = ConfigurationDocumentValidator.NormalizeAndValidate(
revision.JsonData,
ConfigurationDocumentKind.Template,
revision.SchemaVersion);
revision = CreateTemplateRevision(versionId, document.JsonData, document.SchemaVersion, publish, revision.PublishedBy);
if (publish)
{
foreach (var existingRevision in _context.TemplateRevisions.Where(existing => existing.TemplateVersionId == versionId && existing.IsCurrent))
{
existingRevision.IsCurrent = false;
existingRevision.IsPublished = false;
}
version.SetJsonData(document.JsonData);
version.SchemaVersion = document.SchemaVersion;
version.PublishedAt = DateTime.UtcNow;
version.PublishedBy = revision.PublishedBy;
}
_context.TemplateRevisions.Add(revision);
return SaveChanges();
}
public bool PublishTemplateVersion(Guid templateId, Guid versionId, string? publishedBy)
{
var version = _context.TemplateVersions
@@ -226,6 +315,13 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
version.IsPublished = true;
version.PublishedAt = DateTime.UtcNow;
version.PublishedBy = string.IsNullOrWhiteSpace(publishedBy) ? "System" : publishedBy;
var currentRevision = _context.TemplateRevisions.FirstOrDefault(revision => revision.TemplateVersionId == version.Id && revision.IsCurrent);
if (currentRevision != null)
{
currentRevision.IsPublished = true;
currentRevision.PublishedAt = DateTime.UtcNow;
currentRevision.PublishedBy = version.PublishedBy;
}
var template = _context.Templates.FirstOrDefault(existing => existing.Id == templateId);
if (template != null)
@@ -266,7 +362,82 @@ namespace Microsoft.SelfService.Portal.Core.API.Repository
}
template.Version = publishedVersion.Version;
template.JSONData = publishedVersion.JsonData;
var currentRevision = publishedVersion.TemplateRevisions?.FirstOrDefault(revision => revision.IsCurrent);
template.JSONData = currentRevision?.JsonData ?? publishedVersion.JsonData;
}
private TemplateRevisionModel CreateTemplateRevision(Guid templateVersionId, string jsonData, string schemaVersion, bool publish, string? publishedBy)
{
var revision = new TemplateRevisionModel
{
Id = Guid.NewGuid(),
TemplateVersionId = templateVersionId,
RevisionNumber = GetNextRevisionNumber(templateVersionId),
SchemaVersion = schemaVersion,
IsCurrent = true,
IsPublished = publish,
PublishedAt = publish ? DateTime.UtcNow : null,
PublishedBy = publishedBy ?? "System"
};
revision.SetJsonData(jsonData);
foreach (var existingRevision in _context.TemplateRevisions.Where(existing => existing.TemplateVersionId == templateVersionId && existing.IsCurrent))
{
existingRevision.IsCurrent = false;
}
foreach (var definition in ExtractConfigurationDefinitions(revision.Id, jsonData))
{
revision.ConfigurationDefinitions.Add(definition);
}
return revision;
}
private int GetNextRevisionNumber(Guid templateVersionId)
{
return (_context.TemplateRevisions
.Where(revision => revision.TemplateVersionId == templateVersionId)
.Select(revision => (int?)revision.RevisionNumber)
.Max() ?? 0) + 1;
}
private static IEnumerable<ConfigurationDefinitionModel> ExtractConfigurationDefinitions(Guid revisionId, string jsonData)
{
using var document = JsonDocument.Parse(jsonData);
var root = document.RootElement;
foreach (var definition in ExtractConfigurationDefinitions(revisionId, root, "parameters", ConfigurationDefinitionKinds.Parameter))
{
yield return definition;
}
foreach (var definition in ExtractConfigurationDefinitions(revisionId, root, "variables", ConfigurationDefinitionKinds.Variable))
{
yield return definition;
}
}
private static IEnumerable<ConfigurationDefinitionModel> ExtractConfigurationDefinitions(Guid revisionId, JsonElement root, string propertyName, string kind)
{
if (!root.TryGetProperty(propertyName, out var definitions) || definitions.ValueKind != JsonValueKind.Object)
{
yield break;
}
foreach (var property in definitions.EnumerateObject())
{
var propertiesJson = property.Value.GetRawText();
yield return new ConfigurationDefinitionModel
{
Id = Guid.NewGuid(),
TemplateRevisionId = revisionId,
Kind = kind,
Name = property.Name,
PropertiesJson = propertiesJson,
DefinitionHash = TemplateVersionModel.ComputeSha256(propertiesJson)
};
}
}
private string GetUniqueVersionName(Guid templateId, string version)
+58
View File
@@ -0,0 +1,58 @@
using System.Security.Cryptography;
namespace Microsoft.SelfService.Portal.Core.API.Services
{
public static class ApiClientSecretHasher
{
private const int SaltSize = 16;
private const int KeySize = 32;
private const int DefaultIterations = 100_000;
public static string HashSecret(string secret)
{
var salt = RandomNumberGenerator.GetBytes(SaltSize);
var hash = Rfc2898DeriveBytes.Pbkdf2(secret, salt, DefaultIterations, HashAlgorithmName.SHA256, KeySize);
return string.Join(
'.',
"PBKDF2-SHA256",
DefaultIterations.ToString(),
Convert.ToBase64String(salt),
Convert.ToBase64String(hash));
}
public static bool VerifySecret(string secret, string storedHash)
{
var parts = storedHash.Split('.');
if (parts.Length != 4 || parts[0] != "PBKDF2-SHA256")
{
return false;
}
if (!int.TryParse(parts[1], out var iterations))
{
return false;
}
var salt = Convert.FromBase64String(parts[2]);
var expectedHash = Convert.FromBase64String(parts[3]);
var actualHash = Rfc2898DeriveBytes.Pbkdf2(secret, salt, iterations, HashAlgorithmName.SHA256, expectedHash.Length);
return CryptographicOperations.FixedTimeEquals(actualHash, expectedHash);
}
internal static string HashSecretForDemoData(string secret, string saltText)
{
var salt = System.Text.Encoding.UTF8.GetBytes(saltText);
Array.Resize(ref salt, SaltSize);
var hash = Rfc2898DeriveBytes.Pbkdf2(secret, salt, DefaultIterations, HashAlgorithmName.SHA256, KeySize);
return string.Join(
'.',
"PBKDF2-SHA256",
DefaultIterations.ToString(),
Convert.ToBase64String(salt),
Convert.ToBase64String(hash));
}
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace Microsoft.SelfService.Portal.Core.API.Services
{
public class ApiTokenCreateResult
{
public string AccessToken { get; set; } = string.Empty;
public string Jti { get; set; } = string.Empty;
public DateTime IssuedAt { get; set; }
public DateTime ExpiresAt { get; set; }
}
}
+169
View File
@@ -0,0 +1,169 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using Microsoft.IdentityModel.Tokens;
using Microsoft.SelfService.Portal.Core.API.Authorization;
using Microsoft.SelfService.Portal.Core.API.Models;
namespace Microsoft.SelfService.Portal.Core.API.Services
{
public class ApiTokenService
{
private readonly IConfiguration _configuration;
public ApiTokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public int TokenLifetimeSeconds =>
_configuration.GetValue<int?>("Authentication:OnPremClientCredentials:TokenLifetimeSeconds") ?? 3600;
public string Issuer =>
_configuration["Authentication:OnPremClientCredentials:Issuer"] ?? "Microsoft.SelfService.Portal.Core.API";
public string Audience =>
_configuration["Authentication:OnPremClientCredentials:Audience"] ?? "Microsoft.SelfService.Portal.Core.API";
public ApiTokenCreateResult CreateAccessToken(ApiClientModel client, IReadOnlyCollection<string> scopes)
{
return CreateAccessToken(client.ClientId, client.ClientId, scopes);
}
public ApiTokenCreateResult CreateAccessToken(string subject, string? clientId, IReadOnlyCollection<string> scopes)
{
var signingKey = GetSigningKey();
var credentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
var now = DateTime.UtcNow;
var jti = Guid.NewGuid().ToString("N");
var expiresAt = now.AddSeconds(TokenLifetimeSeconds);
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, subject),
new(JwtRegisteredClaimNames.Jti, jti)
};
if (!string.IsNullOrWhiteSpace(clientId))
{
claims.Add(new Claim("client_id", clientId));
}
foreach (var scope in scopes)
{
claims.Add(new Claim("scope", scope));
}
var token = new JwtSecurityToken(
issuer: Issuer,
audience: Audience,
claims: claims,
notBefore: now,
expires: expiresAt,
signingCredentials: credentials);
return new ApiTokenCreateResult
{
AccessToken = new JwtSecurityTokenHandler().WriteToken(token),
Jti = jti,
IssuedAt = now,
ExpiresAt = expiresAt
};
}
public IReadOnlyCollection<string> ReadClientScopes(ApiClientModel client)
{
if (string.IsNullOrWhiteSpace(client.ScopesJson))
{
return Array.Empty<string>();
}
return JsonSerializer.Deserialize<string[]>(client.ScopesJson) ?? Array.Empty<string>();
}
public IReadOnlyCollection<string> ResolveRequestedScopes(ApiClientModel client, string? requestedScope)
{
var allowedScopes = ReadClientScopes(client);
if (string.IsNullOrWhiteSpace(requestedScope))
{
return allowedScopes;
}
var requestedScopes = requestedScope
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
if (requestedScopes.Any(scope => !allowedScopes.Contains(scope, StringComparer.OrdinalIgnoreCase)))
{
throw new InvalidOperationException("Requested scope is not allowed for this client.");
}
return requestedScopes;
}
public IReadOnlyCollection<string> ResolveRequestedUserScopes(string? requestedScope)
{
if (string.IsNullOrWhiteSpace(requestedScope))
{
throw new InvalidOperationException("At least one scope is required.");
}
var allowedScopes = ReadAllowedTokenScopes();
var requestedScopes = requestedScope
.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
if (requestedScopes.Length == 0)
{
throw new InvalidOperationException("At least one scope is required.");
}
if (requestedScopes.Any(scope => !allowedScopes.Contains(scope, StringComparer.OrdinalIgnoreCase)))
{
throw new InvalidOperationException("Requested scope is not allowed.");
}
return requestedScopes;
}
public IReadOnlyCollection<string> ReadAllowedTokenScopes()
{
var configuredScopes = _configuration.GetSection("Authentication:OnPremClientCredentials:AllowedTokenScopes").Get<string[]>();
if (configuredScopes != null && configuredScopes.Length > 0)
{
return configuredScopes;
}
return
[
ApiScopes.ConfigurationRead,
ApiScopes.ConfigurationWrite,
ApiScopes.CredentialRead,
ApiScopes.CredentialWrite,
ApiScopes.CredentialResolve,
ApiScopes.DeploymentRead,
ApiScopes.DeploymentWrite,
ApiScopes.QueueRead,
ApiScopes.QueueProcess,
ApiScopes.TemplateRead,
ApiScopes.TemplateWrite,
ApiScopes.TokenManage,
ApiScopes.TokenAdmin
];
}
public SymmetricSecurityKey GetSigningKey()
{
var signingKey = _configuration["Authentication:OnPremClientCredentials:SigningKey"];
if (string.IsNullOrWhiteSpace(signingKey))
{
signingKey = "DevelopmentOnly-OnPremClientCredentials-SigningKey-ChangeMe";
}
return new SymmetricSecurityKey(Encoding.UTF8.GetBytes(signingKey));
}
}
}
+37 -6
View File
@@ -55,13 +55,8 @@ namespace Microsoft.SelfService.Portal.Core.API.Services
return queueJob.Id;
}
public Guid EnqueueDeploymentRequest(Guid deploymentGroupId, ICollection<Guid> targetIds, string jsonData)
public Guid EnqueueDeploymentRequest(Guid deploymentGroupId, ICollection<Guid> targetIds, string jsonData, Guid? deploymentArtifactId = null)
{
var deploymentDocument = ConfigurationDocumentValidator.NormalizeAndValidate(
jsonData,
ConfigurationDocumentKind.DeploymentOverride);
jsonData = deploymentDocument.JsonData;
var deploymentGroup = _context.DeploymentGroups
.AsNoTracking()
.Include(group => group.Template)
@@ -80,6 +75,39 @@ namespace Microsoft.SelfService.Portal.Core.API.Services
throw new InvalidOperationException("DeploymentGroup does not exist.");
}
var currentArtifact = deploymentArtifactId.HasValue
? _context.DeploymentArtifacts
.AsNoTracking()
.FirstOrDefault(artifact =>
artifact.Id == deploymentArtifactId.Value
&& artifact.DeploymentGroupId == deploymentGroupId
&& !artifact.IsStale)
: _context.DeploymentArtifacts
.AsNoTracking()
.Where(artifact =>
artifact.DeploymentGroupId == deploymentGroupId
&& artifact.ArtifactType == DeploymentArtifactTypes.ResolvedConfigurationData
&& !artifact.IsStale)
.OrderByDescending(artifact => artifact.Created)
.FirstOrDefault();
if (deploymentArtifactId.HasValue && currentArtifact == null)
{
throw new InvalidOperationException("Deployment artifact does not exist or is stale.");
}
if (currentArtifact != null)
{
jsonData = currentArtifact.DeploymentJson;
}
else
{
var deploymentDocument = ConfigurationDocumentValidator.NormalizeAndValidate(
jsonData,
ConfigurationDocumentKind.DeploymentOverride);
jsonData = deploymentDocument.JsonData;
}
var primaryTemplateSelection = deploymentGroup.TemplateSelections
.OrderBy(selection => selection.SortOrder)
.FirstOrDefault();
@@ -156,6 +184,7 @@ namespace Microsoft.SelfService.Portal.Core.API.Services
{
selection.Id,
selection.TemplateVersionId,
selection.TemplateRevisionId,
TemplateId = selection.TemplateVersion.TemplateId,
TemplateName = selection.TemplateVersion.Template.Name,
selection.TemplateVersion.Version,
@@ -176,6 +205,8 @@ namespace Microsoft.SelfService.Portal.Core.API.Services
assignment.NodeDataJson
}),
TargetIds = resolvedTargetIds,
DeploymentArtifactId = currentArtifact?.Id,
DeploymentJson = currentArtifact?.DeploymentJson,
JsonData = jsonData,
TargetCount = resolvedTargetIds.Count,
Created = DateTime.UtcNow
+23
View File
@@ -6,6 +6,29 @@
}
},
"AllowedHosts": "*",
"Authentication": {
"OnPremClientCredentials": {
"Issuer": "Microsoft.SelfService.Portal.Core.API",
"Audience": "Microsoft.SelfService.Portal.Core.API",
"SigningKey": "DevelopmentOnly-OnPremClientCredentials-SigningKey-ChangeMe",
"TokenLifetimeSeconds": 3600,
"AllowedTokenScopes": [
"deployment.read",
"deployment.write",
"configuration.read",
"configuration.write",
"credential.read",
"credential.write",
"queue.process",
"queue.read",
"template.read",
"template.write",
"credential.resolve",
"token.manage",
"token.admin"
]
}
},
"ConnectionStrings": {
"Context": "Server=.;Database=SSP;TrustServerCertificate=True;Encrypt=False;Trusted_Connection=True"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Utilities.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Tasks.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.IO.Redist" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.1.0.0" newVersion="6.1.0.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.4.0" newVersion="4.0.4.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.1.0" newVersion="6.0.1.0" />
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More