using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using DysonNetwork.Pass.Permission; using DysonNetwork.Shared.Models; using NodaTime; using System.Text.Json; namespace DysonNetwork.Pass; [ApiController] [Route("/api/permissions")] [Authorize] public class PermissionController( PermissionService permissionService, AppDatabase db ) : ControllerBase { /// /// Check if an actor has a specific permission /// [HttpGet("check/{actor}/{area}/{key}")] [RequiredPermission("maintenance", "permissions.check")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task CheckPermission(string actor, string area, string key) { try { var hasPermission = await permissionService.HasPermissionAsync(actor, area, key); return Ok(hasPermission); } catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to check permission", details = ex.Message }); } } /// /// Get all effective permissions for an actor (including group permissions) /// [HttpGet("actors/{actor}/permissions/effective")] [RequiredPermission("maintenance", "permissions.check")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetEffectivePermissions(string actor) { try { var permissions = await permissionService.ListEffectivePermissionsAsync(actor); return Ok(permissions); } catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to list permissions", details = ex.Message }); } } /// /// Get all direct permissions for an actor (excluding group permissions) /// [HttpGet("actors/{actor}/permissions/direct")] [RequiredPermission("maintenance", "permissions.check")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetDirectPermissions(string actor) { try { var permissions = await permissionService.ListDirectPermissionsAsync(actor); return Ok(permissions); } catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to list permissions", details = ex.Message }); } } /// /// Give a permission to an actor /// [HttpPost("actors/{actor}/permissions/{area}/{key}")] [RequiredPermission("maintenance", "permissions.manage")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GivePermission( string actor, string area, string key, [FromBody] PermissionRequest request) { try { var permission = await permissionService.AddPermissionNode( actor, area, key, JsonDocument.Parse(JsonSerializer.Serialize(request.Value)), request.ExpiredAt, request.AffectedAt ); return Created($"/api/permissions/actors/{actor}/permissions/{area}/{key}", permission); } catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to add permission", details = ex.Message }); } } /// /// Remove a permission from an actor /// [HttpDelete("actors/{actor}/permissions/{area}/{key}")] [RequiredPermission("maintenance", "permissions.manage")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task RemovePermission(string actor, string area, string key) { try { await permissionService.RemovePermissionNode(actor, area, key); return NoContent(); } catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to remove permission", details = ex.Message }); } } /// /// Get all groups for an actor /// [HttpGet("actors/{actor}/groups")] [RequiredPermission("maintenance", "permissions.groups.check")] [ProducesResponseType>(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task GetActorGroups(string actor) { try { var now = SystemClock.Instance.GetCurrentInstant(); var groups = await db.PermissionGroupMembers .Where(m => m.Actor == actor) .Where(m => m.ExpiredAt == null || m.ExpiredAt > now) .Where(m => m.AffectedAt == null || m.AffectedAt <= now) .Include(m => m.Group) .ToListAsync(); return Ok(groups); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to list actor groups", details = ex.Message }); } } /// /// Add an actor to a permission group /// [HttpPost("actors/{actor}/groups/{groupId}")] [RequiredPermission("maintenance", "permissions.groups.manage")] [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task AddActorToGroup( string actor, Guid groupId, [FromBody] GroupMembershipRequest? request = null) { try { var group = await db.PermissionGroups.FindAsync(groupId); if (group == null) { return NotFound(new { error = "Permission group not found" }); } // Check if actor is already in the group var existing = await db.PermissionGroupMembers .FirstOrDefaultAsync(m => m.Actor == actor && m.GroupId == groupId); if (existing != null) { return BadRequest(new { error = "Actor is already in this group" }); } var member = new SnPermissionGroupMember { Actor = actor, GroupId = groupId, Group = group, ExpiredAt = request?.ExpiredAt, AffectedAt = request?.AffectedAt }; db.PermissionGroupMembers.Add(member); await db.SaveChangesAsync(); // Clear actor cache await permissionService.ClearActorCacheAsync(actor); return Created($"/api/permissions/actors/{actor}/groups/{groupId}", member); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to add actor to group", details = ex.Message }); } } /// /// Remove an actor from a permission group /// [HttpDelete("actors/{actor}/groups/{groupId}")] [RequiredPermission("maintenance", "permissions.groups.manage")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task RemoveActorFromGroup(string actor, Guid groupId) { try { var member = await db.PermissionGroupMembers .FirstOrDefaultAsync(m => m.Actor == actor && m.GroupId == groupId); if (member == null) { return NotFound(new { error = "Actor is not in this group" }); } db.PermissionGroupMembers.Remove(member); await db.SaveChangesAsync(); // Clear actor cache await permissionService.ClearActorCacheAsync(actor); return NoContent(); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to remove actor from group", details = ex.Message }); } } /// /// Clear permission cache for an actor /// [HttpPost("actors/{actor}/cache/clear")] [RequiredPermission("maintenance", "permissions.cache.manage")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] public async Task ClearActorCache(string actor) { try { await permissionService.ClearActorCacheAsync(actor); return NoContent(); } catch (ArgumentException ex) { return BadRequest(new { error = ex.Message }); } catch (Exception ex) { return StatusCode(500, new { error = "Failed to clear cache", details = ex.Message }); } } /// /// Validate a permission pattern /// [HttpPost("validate-pattern")] [RequiredPermission("maintenance", "permissions.check")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public IActionResult ValidatePattern([FromBody] PatternValidationRequest request) { try { var isValid = PermissionService.IsValidPermissionPattern(request.Pattern); return Ok(new PatternValidationResponse { Pattern = request.Pattern, IsValid = isValid, Message = isValid ? "Pattern is valid" : "Pattern contains invalid characters or consecutive wildcards" }); } catch (Exception ex) { return BadRequest(new { error = ex.Message }); } } } public class PermissionRequest { public object? Value { get; set; } public NodaTime.Instant? ExpiredAt { get; set; } public NodaTime.Instant? AffectedAt { get; set; } } public class GroupMembershipRequest { public NodaTime.Instant? ExpiredAt { get; set; } public NodaTime.Instant? AffectedAt { get; set; } } public class PatternValidationRequest { public string Pattern { get; set; } = string.Empty; } public class PatternValidationResponse { public string Pattern { get; set; } = string.Empty; public bool IsValid { get; set; } public string Message { get; set; } = string.Empty; }