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.
This commit is contained in:
+17
-1
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,137 @@
|
||||
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)"
|
||||
}
|
||||
}
|
||||
|
||||
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 = 'deployment.read template.read'
|
||||
}
|
||||
|
||||
$token.accessToken | Should Not BeNullOrEmpty
|
||||
$token.tokenType | Should Be 'Bearer'
|
||||
$token.expiresIn | Should BeGreaterThan 0
|
||||
$token.scope | Should Be 'deployment.read template.read'
|
||||
}
|
||||
|
||||
It 'allows bearer-token API calls without Windows authentication' {
|
||||
$token = Invoke-TestApi -Method POST -Path 'auth/token' -Body @{
|
||||
clientId = $ClientId
|
||||
clientSecret = $ClientSecret
|
||||
scope = 'deployment.read template.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 '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,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
@@ -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 = "[\"deployment.read\",\"deployment.write\",\"queue.process\",\"template.read\",\"credential.resolve\",\"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
|
||||
{
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
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.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 = "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 = "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 = "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 = "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 = "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 = "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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,266 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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]
|
||||
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]
|
||||
[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}")]
|
||||
[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}")]
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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]
|
||||
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]
|
||||
[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}")]
|
||||
[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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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]
|
||||
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")]
|
||||
[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]
|
||||
[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}")]
|
||||
[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}")]
|
||||
[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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
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]
|
||||
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]
|
||||
[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")]
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,12 +190,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 +224,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();
|
||||
|
||||
@@ -108,7 +108,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);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ using Microsoft.AspNetCore.Mvc;
|
||||
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;
|
||||
@@ -223,6 +226,77 @@ 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")]
|
||||
[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")]
|
||||
[ProducesResponseType(204)]
|
||||
[ProducesResponseType(404)]
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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,26 @@ 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<AddCredentialSecretDto, CredentialSecretModel>();
|
||||
|
||||
/** Service Model **/
|
||||
CreateMap<ServiceModel, GetServiceDto>();
|
||||
CreateMap<GetServiceDto, ServiceModel>();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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" />
|
||||
|
||||
+3390
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3481
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+4689
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
+4778
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+4884
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,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; }
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+82
-3
@@ -1,11 +1,14 @@
|
||||
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.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 +30,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,8 +40,72 @@ 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();
|
||||
|
||||
@@ -45,6 +113,15 @@ builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
// By default, all incoming requests will be authorized according to the default policy.
|
||||
options.FallbackPolicy = options.DefaultPolicy;
|
||||
options.AddPolicy("QueueProcess", policy => policy.RequireClaim("scope", "queue.process"));
|
||||
options.AddPolicy("DeploymentRead", policy => policy.RequireClaim("scope", "deployment.read"));
|
||||
options.AddPolicy("CredentialResolve", policy => policy.RequireClaim("scope", "credential.resolve"));
|
||||
options.AddPolicy("TokenManage", policy => policy.RequireAssertion(context =>
|
||||
context.User.HasClaim("scope", "token.manage")
|
||||
|| context.User.Identity?.AuthenticationType == NegotiateDefaults.AuthenticationScheme));
|
||||
options.AddPolicy("TokenAdmin", policy => policy.RequireAssertion(context =>
|
||||
context.User.HasClaim("scope", "token.admin")
|
||||
|| context.User.Identity?.AuthenticationType == NegotiateDefaults.AuthenticationScheme));
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
@@ -101,3 +178,5 @@ if (Directory.Exists(frontendDistPath))
|
||||
|
||||
app.Run();
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 = "{}"
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
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.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
|
||||
[
|
||||
"deployment.read",
|
||||
"deployment.write",
|
||||
"queue.process",
|
||||
"template.read",
|
||||
"credential.resolve",
|
||||
"token.manage",
|
||||
"token.admin"
|
||||
];
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -6,6 +6,23 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"queue.process",
|
||||
"template.read",
|
||||
"credential.resolve",
|
||||
"token.manage",
|
||||
"token.admin"
|
||||
]
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"Context": "Server=.;Database=SSP;TrustServerCertificate=True;Encrypt=False;Trusted_Connection=True"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user