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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user