Skip to main content

Organizations, Roles, and Permissions

Introduction

This document covers the organization, role, and permission management features. Organizations are the top-level tenant entity, roles provide access grouping, and permissions define granular access control. The RBAC model uses a many-to-many relationship between roles and permissions, with users assigned to roles through junction tables. All business logic is encapsulated in OrganizationService, RoleService, and PermissionService, with controllers acting as thin HTTP transport layers.


Data Model

1:N1:N1:N1:N1:N1:N1:NOrganizationTenantSensorEntityAssetEntityUserEntityRoleEntityOrgUserRoleJunction (3-way)UserRoleJunction (2-way)PermissionRoleJunctionPermissionEntity

Organization Module

Entity Definition

File: Domain/Entities/Organization.cs Table: organizations

ColumnTypeConstraintsDescription
iduuidPKPrimary key (UUIDv7)
namevarchar(255)RequiredOrganization name
emailvarchar(255)Required, UniqueContact email
addressvarchar(255)RequiredStreet address
provincevarchar(255)RequiredProvince
cityvarchar(255)RequiredCity
phone_numbervarchar(255)RequiredContact phone number
oinkcodevarchar(255)NullableOinkcode for IDS rules
websitevarchar(255)NullableOrganization website
created_attimestampCreation timestamp
updated_attimestampLast update timestamp
public ICollection<User> Users { get; set; }
public ICollection<OrganizationUserRole> OrganizationUserRoles { get; set; }
public ICollection<Role> Roles { get; set; }
public ICollection<Sensor> Sensors { get; set; }
public ICollection<Asset> Assets { get; set; }

Controller

File: API/Controllers/Organizations/OrganizationsController.cs Route: api/organizations

Organization endpoints handle:

  • Organization CRUD operations
  • Organization listing
  • Organization-user-role assignment
  • Organization-level access policy enforcement (edit-organization)

Role Module

Entity Definition

File: Domain/Entities/Role.cs Table: roles

ColumnTypeConstraintsDescription
iduuidPKPrimary key (UUIDv7)
namevarchar(255)RequiredRole name
organization_iduuidFK, NullableOwning organization
created_attimestampCreation timestamp
updated_attimestampLast update timestamp
[ForeignKey("OrganizationId")]
public Organization? Organization { get; set; }
public ICollection<Permission> Permissions { get; set; }
public ICollection<UserRole> UserRoles { get; set; }

Role-Permission Relationship

The many-to-many relationship between roles and permissions is configured in ApplicationDbContext.OnModelCreating:

modelBuilder.Entity<Role>()
.HasMany(r => r.Permissions)
.WithMany(p => p.Roles)
.UsingEntity<Dictionary<string, object>>(
"permission_role",
j => j.HasOne<Permission>().WithMany()
.HasForeignKey("permission_id").OnDelete(DeleteBehavior.Cascade),
j => j.HasOne<Role>().WithMany()
.HasForeignKey("role_id").OnDelete(DeleteBehavior.Cascade),
j =>
{
j.Property<DateTime>("created_at").HasDefaultValueSql("CURRENT_TIMESTAMP");
j.Property<DateTime>("updated_at").HasDefaultValueSql("CURRENT_TIMESTAMP");
}
);

Controller

File: API/Controllers/Roles/RolesController.cs Route: api/roles

Role endpoints handle:

  • Role CRUD operations
  • Permission assignment to roles
  • Role detail with permission listings

Auto-Permission for Admin Roles

When a "Super Admin" or "Admin" role is created or assigned and has no permissions, the system automatically assigns all permissions from the catalog:

private async Task EnsureAdminPermissionsAsync(Role role)
{
if (role.Name != "Super Admin" && role.Name != "Admin")
return;

var roleWithPermissions = await _context.Roles
.Include(r => r.Permissions)
.FirstOrDefaultAsync(r => r.Id == role.Id);

if (roleWithPermissions != null && !roleWithPermissions.Permissions.Any())
{
var allPermissions = await _context.Permissions.ToListAsync();
roleWithPermissions.Permissions = allPermissions;
await _context.SaveChangesAsync();
}
}

Permission Module

Entity Definition

File: Domain/Entities/Permission.cs Table: permissions

ColumnTypeConstraintsDescription
iduuidPKPrimary key (UUIDv7)
namevarchar(255)RequiredHuman-readable name
slugvarchar(255)Required, UniqueMachine-readable slug
created_attimestampCreation timestamp
updated_attimestampLast update timestamp

Seeded Permissions

The following permissions are seeded via EF Core HasData in ApplicationDbContext:

PermissionSlugCategory
Create Usercreate-userUser
Edit Useredit-userUser
Delete Userdelete-userUser
Read Userread-userUser
Create Sensorcreate-sensorSensor
Update Sensorupdate-sensorSensor
Delete Sensordelete-sensorSensor
Read Sensorread-sensorSensor
Create Rolecreate-roleRole
Edit Roleedit-roleRole
Delete Roledelete-roleRole
Read Roleread-roleRole
Create Assetcreate-assetAsset
Edit Assetedit-assetAsset
Delete Assetdelete-assetAsset
Read Assetread-assetAsset
Edit Organizationedit-organizationOrganization

Note: Seeded permissions use deterministic UUIDs (e.g., 01936b7e-8000-7000-8000-000000000001) and a fixed timestamp (2025-11-19T00:00:00Z) to avoid EF Core migration warnings.

Controller

File: API/Controllers/Permissions/PermissionsController.cs Route: api/permissions

Permission endpoints maintain the permission catalog and support permission queries used by the authorization system.


Junction Tables

UserRole

File: Domain/Entities/UserRole.cs Table: user_role

ColumnTypeConstraintsDescription
user_iduuidComposite PK, FKUser reference
role_iduuidComposite PK, FKRole reference
created_attimestampCreation
updated_attimestampLast update

Uses a composite primary key (UserId, RoleId) configured in OnModelCreating.

OrganizationUserRole

File: Domain/Entities/OrganizationUserRole.cs Table: organization_user_role

ColumnTypeConstraintsDescription
organization_iduuidComposite PK, FKOrganization reference
user_iduuidComposite PK, FKUser reference
role_iduuidComposite PK, FKRole reference
created_attimestampCreation
updated_attimestampLast update

This table provides a three-way junction for organization-scoped user-role assignment.


Access Model

Effective access is determined by the following chain:

  1. Identity: Authenticated identity (JWT token required)
  2. Claims: Claims in JWT (organization_id, role)
  3. Scope: Organization scope (data filtered by org)
  4. Roles: Role → Permission mappings (many-to-many)
  5. Policies: Authorization policy assertions (manage-* and edit-organization)

Authorization Policy

Authorization policies are defined for write operations:

services.AddAuthorizationBuilder()
.AddPolicy("manage-users", ...)
.AddPolicy("manage-sensors", ...)
.AddPolicy("manage-assets", ...)
.AddPolicy("manage-roles", ...)
.AddPolicy("edit-organization", ...);

These policies are enforced on write endpoints in Users, Sensors, Assets, Roles, and Organizations controllers.


Cascade Delete Rules

Parent → ChildBehavior
Organization → RoleCascade
Organization → SensorCascade
Organization → AssetSetNull
Organization → OrgUserRoleCascade
Role → permission_roleCascade
Role → UserRoleCascade
Role → OrgUserRoleCascade
User → UserRoleCascade
User → OrgUserRoleCascade

Future Improvements

Completed

  • Standardized authorization failure envelope (ApiEnvelope).
  • Audit logging hooks for role/permission changes.
  • Organization-scoped permission policies beyond edit-organization.
  • Role management business logic in service layer.
  • User-role change logging for compliance.

Open

  • No high-priority open items in this module at the moment.