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:
Torsten Brendgen
2026-07-09 14:44:38 +02:00
parent 1aa2adac08
commit dc93ea0813
72 changed files with 32906 additions and 516 deletions

View File

@@ -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();
}
}
}