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))] public IActionResult GetCredentialSecrets() { var secrets = _context.CredentialSecrets .AsNoTracking() .OrderBy(secret => secret.Name) .ToList(); return Ok(_mapper.Map>(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(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(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(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(); } } }