التفويض
يُنفّذ FORGE نظام تحكم بالوصول كامل مبني على الأدوار (RBAC) يحكم ما يمكن للمستخدمين المُصادق عليهم فعله داخل التطبيق. الأدوار تجمع الصلاحيات معاً، والصلاحيات تتحكم بالوصول للإجراءات الفردية على الموارد. النظام مرن بما يكفي لدعم إعدادات admin/user البسيطة والتسلسلات الهرمية المعقدة متعددة الأدوار.
معمارية RBAC
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ المستخدمون│──M:N──│ الأدوار │──M:N──│ الصلاحيات │
└──────────┘ └──────────┘ └──────────────┘
role_user permission_role
المستخدم "أحمد" ──▶ الدور "admin" ──▶ الصلاحية "users.create"
──▶ الصلاحية "users.edit"
──▶ الصلاحية "roles.view"
──▶ ...جميع الصلاحياتيمكن للمستخدم أن يكون له أدوار متعددة، وكل دور يمكن أن يكون له صلاحيات متعددة. عند التحقق من التفويض، يُجمّع النظام جميع الصلاحيات من جميع أدوار المستخدم.
مخطط قاعدة البيانات
أربعة جداول تشغّل نظام RBAC:
-- Roles table
CREATE TABLE roles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
display_name VARCHAR(255),
description TEXT,
is_system BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Permissions table
CREATE TABLE permissions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL UNIQUE,
display_name VARCHAR(255),
description TEXT,
group_name VARCHAR(255),
is_system BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Junction table: which users have which roles
CREATE TABLE role_user (
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, role_id)
);
-- Junction table: which roles have which permissions
CREATE TABLE permission_role (
permission_id UUID NOT NULL REFERENCES permissions(id) ON DELETE CASCADE,
role_id UUID NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
PRIMARY KEY (permission_id, role_id)
);تحذير
الصفوف التي فيها is_system = true لا يمكن حذفها أو إعادة تسميتها عبر API. هذا يحمي دور admin المدمج والصلاحيات الأساسية من الإزالة العرضية.
الأدوار الافتراضية
يبذر FORGE دورين عند أول ترحيل:
| الدور | الصلاحيات | نظامي | الوصف |
|---|---|---|---|
admin | جميع الصلاحيات | نعم | وصول كامل لكل مورد |
user | بدون صلاحيات | نعم | مُصادق عليه لكن بدون امتيازات |
دور admin يتلقى تلقائياً كل صلاحية. عند إضافة صلاحيات جديدة عبر الـ seeders، يُحدّث دور admin ليشملها.
اصطلاح تسمية الصلاحيات
الصلاحيات تتبع صيغة resource.action:
users.view # View user list and details
users.create # Create new users
users.edit # Update existing users
users.delete # Delete users
roles.view # View roles
roles.create # Create roles
roles.edit # Update roles
roles.delete # Delete roles
contents.view # View content entries
contents.create # Create content
contents.edit # Update content
contents.delete # Delete content
settings.view # View application settings
settings.edit # Update settingsنصيحة
عند إضافة وحدة جديدة، اتبع الاصطلاح وأنشئ أربع صلاحيات: module.view و module.create و module.edit و module.delete. دور admin سيلتقطها تلقائياً من الـ seeder.
استخدام Middleware
حماية المسارات بالصلاحيات
طبّق middleware الـ require_permission على المسارات التي تحتاج تفويضاً:
use crate::middleware::rbac::require_permission;
pub fn routes(pool: PgPool) -> Router {
Router::new()
// Only users with "users.view" permission can list users
.route(
"/users",
get(handlers::admin::users::list)
.route_layer(middleware::from_fn_with_state(
pool.clone(),
require_permission("users.view"),
)),
)
// Only users with "users.create" permission can create users
.route(
"/users",
post(handlers::admin::users::create)
.route_layer(middleware::from_fn_with_state(
pool.clone(),
require_permission("users.create"),
)),
)
}التحقق من الصلاحيات في الـ Handlers
للتحكم الدقيق داخل handler، تحقق من الصلاحيات مباشرة على المستخدم المُصادق عليه:
pub async fn update_user(
State(pool): State<PgPool>,
Extension(current_user): Extension<AuthUser>,
Path(user_id): Path<Uuid>,
Json(payload): Json<UpdateUserRequest>,
) -> Result<impl IntoResponse, AppError> {
// Check if user can edit other users
if !current_user.has_permission("users.edit") {
return Err(AppError::Forbidden(
"You do not have permission to edit users".into(),
));
}
// Additional check: prevent non-admins from assigning admin role
if payload.role_ids.contains(&admin_role_id)
&& !current_user.has_permission("roles.edit")
{
return Err(AppError::Forbidden(
"You do not have permission to assign admin role".into(),
));
}
let user = UserService::update(&pool, user_id, payload).await?;
Ok(Json(ApiResponse::success(user)))
}بنية AuthUser
middleware المصادقة يحقن AuthUser في request extensions. هذه البنية تحمل هوية المستخدم وصلاحياته المُجمّعة:
#[derive(Clone, Debug)]
pub struct AuthUser {
pub id: Uuid,
pub email: String,
pub name: String,
pub permissions: Vec<String>,
pub roles: Vec<String>,
}
impl AuthUser {
/// Check if user has a specific permission
pub fn has_permission(&self, permission: &str) -> bool {
self.permissions.contains(&permission.to_string())
}
/// Check if user has any of the given permissions
pub fn has_any_permission(&self, permissions: &[&str]) -> bool {
permissions.iter().any(|p| self.has_permission(p))
}
/// Check if user has all of the given permissions
pub fn has_all_permissions(&self, permissions: &[&str]) -> bool {
permissions.iter().all(|p| self.has_permission(p))
}
/// Check if user has a specific role
pub fn has_role(&self, role: &str) -> bool {
self.roles.contains(&role.to_string())
}
}إدارة الأدوار عبر API
تعيين أدوار لمستخدم
POST /api/admin/users/{id}/roles
Authorization: Bearer {token}
Content-Type: application/json
{
"role_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}عرض جميع الأدوار
GET /api/admin/roles
Authorization: Bearer {token}إنشاء دور مخصص
POST /api/admin/roles
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "editor",
"display_name": "محرر",
"description": "يمكنه إدارة المحتوى لكن ليس المستخدمين",
"permission_ids": [
"uuid-of-contents.view",
"uuid-of-contents.create",
"uuid-of-contents.edit"
]
}تحديث صلاحيات الدور
PUT /api/admin/roles/{id}
Authorization: Bearer {token}
Content-Type: application/json
{
"display_name": "محرر أول",
"permission_ids": [
"uuid-of-contents.view",
"uuid-of-contents.create",
"uuid-of-contents.edit",
"uuid-of-contents.delete"
]
}تحذير
الأدوار النظامية (is_system = true) لا يمكن حذفها عبر API. محاولة حذف دور admin أو user تُرجع خطأ 403 Forbidden. لا يزال بإمكانك تعديل الصلاحيات المُعيّنة للأدوار النظامية.
Permission Seeder
الصلاحيات تُبذر من ملف ترحيل لضمان الاتساق عبر البيئات:
-- database/seeders/02_permissions.sql
INSERT INTO permissions (name, display_name, group_name, is_system)
VALUES
('users.view', 'View Users', 'Users', true),
('users.create', 'Create Users', 'Users', true),
('users.edit', 'Edit Users', 'Users', true),
('users.delete', 'Delete Users', 'Users', true),
('roles.view', 'View Roles', 'Roles', true),
('roles.create', 'Create Roles', 'Roles', true),
('roles.edit', 'Edit Roles', 'Roles', true),
('roles.delete', 'Delete Roles', 'Roles', true),
('contents.view', 'View Content', 'Content', true),
('contents.create', 'Create Content', 'Content', true),
('contents.edit', 'Edit Content', 'Content', true),
('contents.delete', 'Delete Content', 'Content', true)
ON CONFLICT (name) DO NOTHING;
-- Assign all permissions to admin role
INSERT INTO permission_role (permission_id, role_id)
SELECT p.id, r.id
FROM permissions p
CROSS JOIN roles r
WHERE r.name = 'admin'
ON CONFLICT DO NOTHING;انظر أيضاً
- المصادقة — كيف يُثبت المستخدمون هويتهم
- الـ Middleware — تفاصيل تنفيذ RBAC middleware
- الـ Routes — تطبيق permission middleware على مجموعات المسارات
- الـ Handlers — فحوصات الصلاحيات داخل الـ handler