Skip to content

Commit 28b2e16

Browse files
committed
feat: implement RBAC for access control
- Added permissions and roles - Created AuthorizationService - Defined role-permission mapping
1 parent 50174d2 commit 28b2e16

File tree

3 files changed

+182
-0
lines changed

3 files changed

+182
-0
lines changed

lib/src/permissions.dart

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/// Defines the roles and permissions used in the RBAC system.
2+
///
3+
/// Permissions are defined as constants in the format `resource.action`.
4+
/// Roles are defined as constants.
5+
/// The `rolePermissions` map defines which permissions are granted to each role.
6+
7+
/// {@template role}
8+
/// Defines the available user roles in the system.
9+
/// {@endtemplate}
10+
abstract class Role {
11+
/// Administrator role with full access.
12+
static const String admin = 'admin';
13+
14+
/// Standard user role with limited access.
15+
static const String standardUser = 'standard_user';
16+
17+
// Add other roles here as needed.
18+
}
19+
20+
/// {@template permission}
21+
/// Defines the available permissions in the system.
22+
///
23+
/// Permissions follow the format `resource.action`.
24+
/// {@endtemplate}
25+
abstract class Permission {
26+
// Headline Permissions
27+
static const String headlineRead = 'headline.read';
28+
static const String headlineCreate = 'headline.create';
29+
static const String headlineUpdate = 'headline.update';
30+
static const String headlineDelete = 'headline.delete';
31+
32+
// Category Permissions
33+
static const String categoryRead = 'category.read';
34+
static const String categoryCreate = 'category.create';
35+
static const String categoryUpdate = 'category.update';
36+
static const String categoryDelete = 'category.delete';
37+
38+
// Source Permissions
39+
static const String sourceRead = 'source.read';
40+
static const String sourceCreate = 'source.create';
41+
static const String sourceUpdate = 'source.update';
42+
static const String sourceDelete = 'source.delete';
43+
44+
// Country Permissions
45+
static const String countryRead = 'country.read';
46+
static const String countryCreate = 'country.create';
47+
static const String countryUpdate = 'country.update';
48+
static const String countryDelete = 'country.delete';
49+
50+
// User Settings Permissions
51+
static const String userSettingsRead = 'user_settings.read';
52+
static const String userSettingsUpdate = 'user_settings.update';
53+
// Note: User settings delete is handled via account deletion, no separate permission needed here.
54+
55+
// Add other resource permissions here as needed.
56+
}
57+
58+
/// A map defining which permissions are granted to each role.
59+
///
60+
/// The key is the role string, and the value is a set of permission strings.
61+
final Map<String, Set<String>> rolePermissions = {
62+
Role.admin: {
63+
// Admins have all permissions. In a real system, you might have a more
64+
// sophisticated way to represent this, but listing them explicitly is clear.
65+
Permission.headlineRead,
66+
Permission.headlineCreate,
67+
Permission.headlineUpdate,
68+
Permission.headlineDelete,
69+
Permission.categoryRead,
70+
Permission.categoryCreate,
71+
Permission.categoryUpdate,
72+
Permission.categoryDelete,
73+
Permission.sourceRead,
74+
Permission.sourceCreate,
75+
Permission.sourceUpdate,
76+
Permission.sourceDelete,
77+
Permission.countryRead,
78+
Permission.countryCreate,
79+
Permission.countryUpdate,
80+
Permission.countryDelete,
81+
Permission.userSettingsRead,
82+
Permission.userSettingsUpdate,
83+
// Add other admin permissions here.
84+
},
85+
Role.standardUser: {
86+
// Standard users can read public data and manage their own settings.
87+
Permission.headlineRead,
88+
Permission.categoryRead,
89+
Permission.sourceRead,
90+
Permission.countryRead,
91+
Permission.userSettingsRead, // Can read their own settings
92+
Permission.userSettingsUpdate, // Can update their own settings
93+
// Add other standard user permissions here.
94+
},
95+
// Add mappings for other roles here.
96+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'package:ht_api/src/permissions.dart';
2+
import 'package:ht_shared/ht_shared.dart'; // Assuming User model is here
3+
4+
/// {@template authorization_service}
5+
/// Service responsible for checking user permissions based on roles.
6+
/// {@endtemplate}
7+
class AuthorizationService {
8+
/// {@macro authorization_service}
9+
const AuthorizationService();
10+
11+
/// Checks if the given [user] has the specified [permission].
12+
///
13+
/// Assumes the [User] model has a `role` property (String).
14+
/// Returns `true` if the user has the permission, `false` otherwise.
15+
bool hasPermission(User user, String permission) {
16+
// Admins always have permission.
17+
// Assuming user.role exists and 'admin' is the admin role string.
18+
if (user.role == Role.admin) {
19+
return true;
20+
}
21+
22+
// Get the permissions for the user's role.
23+
final permissionsForRole = rolePermissions[user.role];
24+
25+
// If the role is not found or has no permissions, deny access.
26+
if (permissionsForRole == null) {
27+
return false;
28+
}
29+
30+
// Check if the requested permission is in the set of permissions for the role.
31+
return permissionsForRole.contains(permission);
32+
}
33+
34+
// Optional: Add methods for checking ownership or other complex authorization rules here later.
35+
}

rbac-plan.dart

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/// High-Level Plan for Implementing Role-Based Access Control (RBAC)
2+
/// in the Headlines Toolkit API (ht-api).
3+
///
4+
/// This plan outlines the key tasks required to transition from the basic
5+
/// ModelOwnership approach to a more flexible RBAC system, tailored to the
6+
/// project's existing architecture and shared packages.
7+
///
8+
/// This is a high-level overview and does not include implementation details.
9+
10+
// Assuming the User model in ht_shared has been updated to include a 'role' property.
11+
12+
// 1. Define Roles and Permissions (Initially within ht_api)
13+
// - Determine the specific roles needed (e.g., admin, standard_user, editor).
14+
// - Define granular permissions for each resource and action (e.g., 'headline.read',
15+
// 'category.create', 'user_settings.update', 'favorite_list.add').
16+
// - Store these permission strings as static constants within the ht_api package
17+
// (e.g., in a new file like lib/src/permissions.dart).
18+
// - Map which permissions are assigned to which roles (e.g., using a Map or class
19+
// within ht_api).
20+
21+
// 2. Create Authorization Service (Within ht_api)
22+
// - Implement a dedicated service (e.g., AuthorizationService in lib/src/services/)
23+
// that encapsulates the logic for checking permissions.
24+
// - This service will take an authenticated User object and a requested permission
25+
// string, and determine if the user's role(s) grant them that permission based
26+
// on the hardcoded role-permission mapping.
27+
28+
// 3. Integrate Authorization Checks into Middleware/Routes
29+
// - Modify the /api/v1/data/_middleware.dart to check permissions based on
30+
// the requested model and HTTP method using the new AuthorizationService.
31+
// (e.g., check for 'modelName.read' for GET, 'modelName.create' for POST).
32+
// - For user-owned resources (like settings or future favorite lists), update
33+
// middleware (e.g., routes/api/v1/users/[userId]/settings/_middleware.dart)
34+
// or handlers to combine the permission check (e.g., 'user_settings.update')
35+
// with the ownership check (authenticated user ID matches resource owner ID,
36+
// unless user is admin).
37+
38+
// 4. Refine Ownership Checks (Within ht_api middleware/handlers or Repositories)
39+
// - For user-owned resources, ensure repository methods accept the authenticated
40+
// userId and enforce that operations are limited to resources owned by that user
41+
// (unless the user is an admin). This pattern is already partially used and
42+
// can be consistently applied.
43+
44+
// 5. Refactor Existing Access Control
45+
// - Replace existing basic isAdmin checks and simple ownership comparisons
46+
// in route handlers with calls to the new AuthorizationService and/or rely
47+
// on the updated middleware/repository checks.
48+
49+
// 6. Add Tests
50+
// - Write unit and integration tests for the new AuthorizationService, middleware,
51+
// and affected route handlers to ensure permissions and ownership are enforced correctly.

0 commit comments

Comments
 (0)