Commit 4977e6ef authored by Eric Dieckman's avatar Eric Dieckman
Browse files

feat: split ib_role_groups from ib_role_access and add nested role endpoints

Group membership (which groups belong to a role) is now tracked in a
dedicated ib_role_groups junction table, while ib_role_access retains
only network/domain access grants. Adds nested REST endpoints under
/infoblox/roles/{roleId}/groups and /infoblox/roles/{roleId}/access,
replacing the flat /infoblox/role_access controller.
parent b8e17bf9
Loading
Loading
Loading
Loading
Loading
+0 −77
Original line number Diff line number Diff line
using Cals.Visor.Api.Authorization;
using Cals.Visor.Infrastructure.Contexts;
using Cals.Visor.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Cals.Visor.Api.Controllers;

[Route("infoblox/role_access")]
[ApiController]
public class InfobloxRoleAccessController : ControllerBase
{
    private readonly ApplicationDbContext _context;

    public InfobloxRoleAccessController(ApplicationDbContext context) => _context = context;

    // GET: /infoblox/role_access
    [ProducesResponseType<IList<InfobloxRoleAccess>>(StatusCodes.Status200OK)]
    [HttpGet(Name = "GetAllInfobloxRoleAccess")]
    public async Task<IActionResult> GetAllAsync()
    {
        List<InfobloxRoleAccess> result = await _context.InfobloxRoleAccess
            .ToListAsync();

        return Ok(result);
    }

    // GET: /infoblox/role_access/1
    [ProducesResponseType<InfobloxRoleAccess>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpGet("{roleAccessId:long}", Name = "GetInfobloxRoleAccessById")]
    public async Task<IActionResult> GetByIdAsync(long roleAccessId)
    {
        InfobloxRoleAccess? result = await _context.InfobloxRoleAccess
            .Where(e => e.InfobloxRoleAccessId == roleAccessId)
            .FirstOrDefaultAsync();

        if (result == null)
            return NotFound();

        return Ok(result);
    }

    // POST: /infoblox/role_access
    [Authorize(Policy = Policies.CanEdit)]
    [ProducesResponseType<InfobloxRoleAccess>(StatusCodes.Status201Created)]
    [HttpPost(Name = "CreateNewInfobloxRoleAccess")]
    public async Task<IActionResult> CreateNewAsync(InfobloxRoleAccess data)
    {
        _context.Add(data);
        await _context.SaveChangesAsync();

        return CreatedAtRoute("GetInfobloxRoleAccessById", new { roleAccessId = data.InfobloxRoleAccessId }, data);
    }

    // DELETE: /infoblox/role_access/1
    [Authorize(Policy = Policies.CanEdit)]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpDelete("{roleAccessId:long}", Name = "DeleteInfobloxRoleAccess")]
    public async Task<IActionResult> DeleteAsync(long roleAccessId)
    {
        InfobloxRoleAccess? entity = await _context.InfobloxRoleAccess
            .Where(e => e.InfobloxRoleAccessId == roleAccessId)
            .FirstOrDefaultAsync();

        if (entity == null)
            return NotFound();

        _context.InfobloxRoleAccess.Remove(entity);
        await _context.SaveChangesAsync();

        return NoContent();
    }
}
+174 −0
Original line number Diff line number Diff line
@@ -78,4 +78,178 @@ public class InfobloxRolesController : ControllerBase

        return Ok(entity);
    }

    // GET: /infoblox/roles/1/groups
    [ProducesResponseType<IList<InfobloxGroup>>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpGet("{roleId:long}/groups", Name = "GetInfobloxRoleGroups")]
    public async Task<IActionResult> GetGroupsAsync(long roleId)
    {
        bool roleExists = await _context.InfobloxRoles
            .AnyAsync(e => e.InfobloxRoleId == roleId);

        if (!roleExists)
            return NotFound();

        List<InfobloxGroup> result = await _context.InfobloxRoleGroups
            .Where(e => e.RoleId == roleId)
            .Select(e => e.Group!)
            .ToListAsync();

        return Ok(result);
    }

    // POST: /infoblox/roles/1/groups/2
    [Authorize(Policy = Policies.CanEdit)]
    [ProducesResponseType<InfobloxRoleGroup>(StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpPost("{roleId:long}/groups/{groupId:long}", Name = "AddGroupToInfobloxRole")]
    public async Task<IActionResult> AddGroupAsync(long roleId, long groupId)
    {
        bool roleExists = await _context.InfobloxRoles
            .AnyAsync(e => e.InfobloxRoleId == roleId);

        if (!roleExists)
            return NotFound();

        bool groupExists = await _context.InfobloxGroups
            .AnyAsync(e => e.InfobloxGroupId == groupId);

        if (!groupExists)
            return NotFound();

        InfobloxRoleGroup entry = new()
        {
            RoleId = roleId,
            GroupId = groupId,
        };

        _context.InfobloxRoleGroups.Add(entry);
        await _context.SaveChangesAsync();

        return CreatedAtRoute("GetInfobloxRoleGroups", new { roleId }, entry);
    }

    // DELETE: /infoblox/roles/1/groups/2
    [Authorize(Policy = Policies.CanEdit)]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpDelete("{roleId:long}/groups/{groupId:long}", Name = "RemoveGroupFromInfobloxRole")]
    public async Task<IActionResult> RemoveGroupAsync(long roleId, long groupId)
    {
        InfobloxRoleGroup? entity = await _context.InfobloxRoleGroups
            .Where(e => e.RoleId == roleId && e.GroupId == groupId)
            .FirstOrDefaultAsync();

        if (entity == null)
            return NotFound();

        _context.InfobloxRoleGroups.Remove(entity);
        await _context.SaveChangesAsync();

        return NoContent();
    }

    // GET: /infoblox/roles/1/access
    [ProducesResponseType<IList<InfobloxRoleAccess>>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpGet("{roleId:long}/access", Name = "GetInfobloxRoleAccess")]
    public async Task<IActionResult> GetAccessAsync(long roleId)
    {
        bool roleExists = await _context.InfobloxRoles
            .AnyAsync(e => e.InfobloxRoleId == roleId);

        if (!roleExists)
            return NotFound();

        List<InfobloxRoleAccess> result = await _context.InfobloxRoleAccess
            .Where(e => e.RoleId == roleId)
            .ToListAsync();

        return Ok(result);
    }

    // GET: /infoblox/roles/1/access/5
    [ProducesResponseType<InfobloxRoleAccess>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpGet("{roleId:long}/access/{roleAccessId:long}", Name = "GetInfobloxRoleAccessById")]
    public async Task<IActionResult> GetAccessByIdAsync(long roleId, long roleAccessId)
    {
        InfobloxRoleAccess? result = await _context.InfobloxRoleAccess
            .Where(e => e.RoleId == roleId && e.InfobloxRoleAccessId == roleAccessId)
            .FirstOrDefaultAsync();

        if (result == null)
            return NotFound();

        return Ok(result);
    }

    // POST: /infoblox/roles/1/access
    [Authorize(Policy = Policies.CanEdit)]
    [ProducesResponseType<InfobloxRoleAccess>(StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpPost("{roleId:long}/access", Name = "AddInfobloxRoleAccess")]
    public async Task<IActionResult> AddAccessAsync(long roleId, InfobloxRoleAccess data)
    {
        bool roleExists = await _context.InfobloxRoles
            .AnyAsync(e => e.InfobloxRoleId == roleId);

        if (!roleExists)
            return NotFound();

        bool hasNetwork = data.NetworkId != null;
        bool hasDomain = data.DomainId != null;

        if (hasNetwork == hasDomain)
            return BadRequest("Exactly one of NetworkId or DomainId must be set.");

        if (hasNetwork)
        {
            bool networkExists = await _context.InfobloxNetworks
                .AnyAsync(e => e.InfobloxNetworkId == data.NetworkId);

            if (!networkExists)
                return NotFound();
        }
        else
        {
            bool domainExists = await _context.InfobloxDomains
                .AnyAsync(e => e.InfobloxDomainId == data.DomainId);

            if (!domainExists)
                return NotFound();
        }

        data.RoleId = roleId;

        _context.InfobloxRoleAccess.Add(data);
        await _context.SaveChangesAsync();

        return CreatedAtRoute(
            "GetInfobloxRoleAccessById",
            new { roleId, roleAccessId = data.InfobloxRoleAccessId },
            data);
    }

    // DELETE: /infoblox/roles/1/access/5
    [Authorize(Policy = Policies.CanEdit)]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    [HttpDelete("{roleId:long}/access/{roleAccessId:long}", Name = "DeleteInfobloxRoleAccess")]
    public async Task<IActionResult> DeleteAccessAsync(long roleId, long roleAccessId)
    {
        InfobloxRoleAccess? entity = await _context.InfobloxRoleAccess
            .Where(e => e.RoleId == roleId && e.InfobloxRoleAccessId == roleAccessId)
            .FirstOrDefaultAsync();

        if (entity == null)
            return NotFound();

        _context.InfobloxRoleAccess.Remove(entity);
        await _context.SaveChangesAsync();

        return NoContent();
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ public class ApplicationDbContext : DbContext
    public DbSet<InfobloxNetwork> InfobloxNetworks => Set<InfobloxNetwork>();
    public DbSet<InfobloxRole> InfobloxRoles => Set<InfobloxRole>();
    public DbSet<InfobloxRoleAccess> InfobloxRoleAccess => Set<InfobloxRoleAccess>();
    public DbSet<InfobloxRoleGroup> InfobloxRoleGroups => Set<InfobloxRoleGroup>();
    public DbSet<Location> Locations => Set<Location>();
    public DbSet<LocationVlan> LocationVlans => Set<LocationVlan>();
    public DbSet<PaloAltoAccessDomain> PaloAltoAccessDomains => Set<PaloAltoAccessDomain>();
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ internal sealed class ColumnNames
        internal const string NetworkId = "ib_network_id";
        internal const string RoleId = "ib_role_id";
        internal const string RoleAccessId = "ib_role_access_id";
        internal const string RoleGroupId = "ib_role_group_id";
    }

    internal sealed class WiscNic
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@ internal sealed class TableNames
    internal const string InfobloxGroups = "ib_groups";
    internal const string InfobloxNetworks = "ib_networks";
    internal const string InfobloxRoleAccess = "ib_role_access";
    internal const string InfobloxRoleGroups = "ib_role_groups";
    internal const string InfobloxRoles = "ib_roles";
    internal const string Locations = "locations";
    internal const string LocationVlans = "locations_vlans";
Loading