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
Organization Module
Entity Definition
File: Domain/Entities/Organization.cs
Table: organizations
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PK | Primary key (UUIDv7) |
name | varchar(255) | Required | Organization name |
email | varchar(255) | Required, Unique | Contact email |
address | varchar(255) | Required | Street address |
province | varchar(255) | Required | Province |
city | varchar(255) | Required | City |
phone_number | varchar(255) | Required | Contact phone number |
oinkcode | varchar(255) | Nullable | Oinkcode for IDS rules |
website | varchar(255) | Nullable | Organization website |
created_at | timestamp | Creation timestamp | |
updated_at | timestamp | Last update timestamp |
Navigation Properties
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
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PK | Primary key (UUIDv7) |
name | varchar(255) | Required | Role name |
organization_id | uuid | FK, Nullable | Owning organization |
created_at | timestamp | Creation timestamp | |
updated_at | timestamp | Last update timestamp |
Navigation Properties
[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
| Column | Type | Constraints | Description |
|---|---|---|---|
id | uuid | PK | Primary key (UUIDv7) |
name | varchar(255) | Required | Human-readable name |
slug | varchar(255) | Required, Unique | Machine-readable slug |
created_at | timestamp | Creation timestamp | |
updated_at | timestamp | Last update timestamp |
Seeded Permissions
The following permissions are seeded via EF Core HasData in ApplicationDbContext:
| Permission | Slug | Category |
|---|---|---|
| Create User | create-user | User |
| Edit User | edit-user | User |
| Delete User | delete-user | User |
| Read User | read-user | User |
| Create Sensor | create-sensor | Sensor |
| Update Sensor | update-sensor | Sensor |
| Delete Sensor | delete-sensor | Sensor |
| Read Sensor | read-sensor | Sensor |
| Create Role | create-role | Role |
| Edit Role | edit-role | Role |
| Delete Role | delete-role | Role |
| Read Role | read-role | Role |
| Create Asset | create-asset | Asset |
| Edit Asset | edit-asset | Asset |
| Delete Asset | delete-asset | Asset |
| Read Asset | read-asset | Asset |
| Edit Organization | edit-organization | Organization |
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
| Column | Type | Constraints | Description |
|---|---|---|---|
user_id | uuid | Composite PK, FK | User reference |
role_id | uuid | Composite PK, FK | Role reference |
created_at | timestamp | Creation | |
updated_at | timestamp | Last update |
Uses a composite primary key (UserId, RoleId) configured in OnModelCreating.
OrganizationUserRole
File: Domain/Entities/OrganizationUserRole.cs
Table: organization_user_role
| Column | Type | Constraints | Description |
|---|---|---|---|
organization_id | uuid | Composite PK, FK | Organization reference |
user_id | uuid | Composite PK, FK | User reference |
role_id | uuid | Composite PK, FK | Role reference |
created_at | timestamp | Creation | |
updated_at | timestamp | Last update |
This table provides a three-way junction for organization-scoped user-role assignment.
Access Model
Effective access is determined by the following chain:
- Identity: Authenticated identity (JWT token required)
- Claims: Claims in JWT (
organization_id,role) - Scope: Organization scope (data filtered by org)
- Roles: Role → Permission mappings (many-to-many)
- Policies: Authorization policy assertions (
manage-*andedit-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 → Child | Behavior |
|---|---|
| Organization → Role | Cascade |
| Organization → Sensor | Cascade |
| Organization → Asset | SetNull |
| Organization → OrgUserRole | Cascade |
| Role → permission_role | Cascade |
| Role → UserRole | Cascade |
| Role → OrgUserRole | Cascade |
| User → UserRole | Cascade |
| User → OrgUserRole | Cascade |
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.