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;
}