Update Documentation
This commit is contained in:
parent
74ae17dfbd
commit
09362f9bf5
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@ -0,0 +1,21 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2025 c77_rbac Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
194
README.md
194
README.md
@ -1,14 +1,13 @@
|
||||
# c77_rbac
|
||||
|
||||
A PostgreSQL extension for Role-Based Access Control (RBAC) with Row-Level Security (RLS) integration. Designed to be agnostic, `c77_rbac` works with any application framework, providing fine-grained, database-driven authorization. The companion extension `c77_rbac_laravel` integrates seamlessly with Laravel applications.
|
||||
A PostgreSQL extension for Role-Based Access Control (RBAC) with Row-Level Security (RLS) integration. Designed to be framework-agnostic, `c77_rbac` works with any application, providing fine-grained, database-driven authorization.
|
||||
|
||||
## Features
|
||||
|
||||
- **Agnostic RBAC Core**: Manage roles, features, and subjects with flexible `external_id` (TEXT) for compatibility with any system (Laravel, Django, Rails, etc.).
|
||||
- **Agnostic RBAC Core**: Manage roles, features, and subjects with flexible `external_id` (TEXT) for compatibility with any system.
|
||||
- **Row-Level Security (RLS)**: Enforce access control directly in PostgreSQL using RLS policies tied to RBAC rules.
|
||||
- **Scoped Permissions**: Assign roles with scope (e.g., `campus/chicago`) for granular control.
|
||||
- **Admin Role Support**: Optional `admin` role bypasses scope restrictions for universal access.
|
||||
- **Laravel Integration**: `c77_rbac_laravel` maps Laravel user IDs to RBAC subjects and sets session variables via middleware.
|
||||
- **Secure Design**: Uses `SECURITY DEFINER` functions to protect RBAC metadata, requiring no direct table access for application users.
|
||||
|
||||
## Requirements
|
||||
@ -19,13 +18,12 @@ A PostgreSQL extension for Role-Based Access Control (RBAC) with Row-Level Secur
|
||||
|
||||
## Installation
|
||||
|
||||
### Step 1: Install Extensions
|
||||
### Step 1: Install Extension
|
||||
|
||||
Copy the extension files to your PostgreSQL extension directory (e.g., `/usr/share/postgresql/17/extension/`):
|
||||
Copy the extension files to your PostgreSQL extension directory:
|
||||
|
||||
```bash
|
||||
sudo cp c77_rbac--1.0.sql c77_rbac.control /usr/share/postgresql/17/extension/
|
||||
sudo cp c77_rbac_laravel--1.0.sql c77_rbac_laravel.control /usr/share/postgresql/17/extension/
|
||||
```
|
||||
|
||||
### Step 2: Create a Database
|
||||
@ -36,7 +34,7 @@ CREATE DATABASE myapp;
|
||||
GRANT ALL ON DATABASE myapp TO app_user;
|
||||
```
|
||||
|
||||
### Step 3: Enable Extensions
|
||||
### Step 3: Enable Extension
|
||||
|
||||
Connect to your database as `app_user`:
|
||||
|
||||
@ -44,12 +42,11 @@ Connect to your database as `app_user`:
|
||||
psql -d myapp -U app_user -h localhost
|
||||
|
||||
CREATE EXTENSION c77_rbac;
|
||||
CREATE EXTENSION c77_rbac_laravel; -- Optional, for Laravel integration
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Core Tables (`c77_rbac`)
|
||||
### Core Tables
|
||||
|
||||
- `c77_rbac_subjects`: Tracks entities (users, systems) with a unique `external_id` (TEXT), `scope_type`, and `scope_id`.
|
||||
- `c77_rbac_roles`: Defines roles (e.g., `sales_manager`).
|
||||
@ -63,11 +60,6 @@ CREATE EXTENSION c77_rbac_laravel; -- Optional, for Laravel integration
|
||||
- `c77_rbac_grant_feature(p_role_name, p_feature_name)`: Grant a feature to a role.
|
||||
- `c77_rbac_can_access(p_feature_name, p_external_id, p_scope_type, p_scope_id)`: Check if a subject has access to a feature within a scope.
|
||||
|
||||
### Laravel Functions (`c77_rbac_laravel`)
|
||||
|
||||
- `c77_rbac_laravel_auth_id()`: Retrieves `external_id` from session variable (`c77_rbac.external_id`).
|
||||
- `c77_rbac_laravel_assign_user(p_laravel_id, p_role_name, p_scope_type, p_scope_id)`: Assigns a Laravel user ID to a role.
|
||||
|
||||
## Usage
|
||||
|
||||
### Example: Basic Setup
|
||||
@ -75,7 +67,7 @@ CREATE EXTENSION c77_rbac_laravel; -- Optional, for Laravel integration
|
||||
```sql
|
||||
-- As app_user
|
||||
-- Assign a user (external_id = '1') to sales_manager role for chicago campus
|
||||
SELECT public.c77_rbac_laravel_assign_user(1, 'sales_manager', 'campus', 'chicago');
|
||||
SELECT public.c77_rbac_assign_subject('1', 'sales_manager', 'campus', 'chicago');
|
||||
|
||||
-- Grant view_sales_page feature to sales_manager
|
||||
SELECT public.c77_rbac_grant_feature('sales_manager', 'view_sales_page');
|
||||
@ -88,8 +80,11 @@ CREATE TABLE public.sales (
|
||||
);
|
||||
INSERT INTO public.sales (campus, amount) VALUES ('chicago', 1000), ('miami', 2000);
|
||||
ALTER TABLE public.sales ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Create an RLS policy using c77_rbac_can_access
|
||||
-- Note: You'll need to set c77_rbac.external_id in your application context
|
||||
CREATE POLICY rbac_policy ON public.sales FOR ALL TO PUBLIC USING (
|
||||
public.c77_rbac_can_access('view_sales_page', public.c77_rbac_laravel_auth_id(), 'campus', campus)
|
||||
public.c77_rbac_can_access('view_sales_page', current_setting('c77_rbac.external_id', true), 'campus', campus)
|
||||
);
|
||||
|
||||
-- Test as user 1
|
||||
@ -109,7 +104,7 @@ SELECT * FROM public.sales;
|
||||
|
||||
```sql
|
||||
-- Assign admin role to user 999 (no scope restrictions)
|
||||
SELECT public.c77_rbac_laravel_assign_user(999, 'admin', NULL, NULL);
|
||||
SELECT public.c77_rbac_assign_subject('999', 'admin', NULL, NULL);
|
||||
SELECT public.c77_rbac_grant_feature('admin', 'view_sales_page');
|
||||
|
||||
-- Test as admin
|
||||
@ -126,143 +121,36 @@ SELECT * FROM public.sales;
|
||||
(2 rows)
|
||||
```
|
||||
|
||||
## Laravel Integration
|
||||
## Application Integration
|
||||
|
||||
When ready to use with Laravel, configure your `.env`:
|
||||
To integrate with your application:
|
||||
|
||||
```env
|
||||
DB_CONNECTION=pgsql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_DATABASE=myapp
|
||||
DB_USERNAME=app_user
|
||||
DB_PASSWORD=your_password
|
||||
```
|
||||
1. **Set External ID**: Set the session variable before queries:
|
||||
```sql
|
||||
SET "c77_rbac.external_id" TO 'your_user_id';
|
||||
```
|
||||
|
||||
### Middleware
|
||||
2. **Apply RLS Policies**: Create policies on tables that check permissions:
|
||||
```sql
|
||||
CREATE POLICY rbac_policy ON your_table FOR ALL TO PUBLIC USING (
|
||||
public.c77_rbac_can_access('feature_name', current_setting('c77_rbac.external_id', true), 'scope_type', scope_column)
|
||||
);
|
||||
```
|
||||
|
||||
Add middleware to set `external_id`:
|
||||
|
||||
```php
|
||||
// app/Http/Middleware/SetRbacExternalId.php
|
||||
namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SetRbacExternalId
|
||||
{
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if (Auth::check()) {
|
||||
DB::statement('SET "c77_rbac.external_id" TO ?', [Auth::id()]);
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Register in `app/Http/Kernel.php`:
|
||||
|
||||
```php
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
// Other middleware...
|
||||
\App\Http\Middleware\SetRbacExternalId::class,
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Migration
|
||||
|
||||
Set up RBAC and tables:
|
||||
|
||||
```php
|
||||
// database/migrations/2025_04_12_000001_setup_rbac_subjects.php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SetupRbacSubjects extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$users = DB::table('users')->get();
|
||||
foreach ($users as $user) {
|
||||
DB::statement("SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)", [
|
||||
$user->id, 'sales_manager', 'campus', 'chicago'
|
||||
]);
|
||||
}
|
||||
DB::statement("SELECT public.c77_rbac_grant_feature(?, ?)", [
|
||||
'sales_manager', 'view_sales_page'
|
||||
]);
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
DB::statement("TRUNCATE public.c77_rbac_subjects CASCADE");
|
||||
DB::statement("TRUNCATE public.c77_rbac_roles CASCADE");
|
||||
DB::statement("TRUNCATE public.c77_rbac_features CASCADE");
|
||||
}
|
||||
}
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateSalesTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('sales', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('campus');
|
||||
$table->decimal('amount');
|
||||
});
|
||||
|
||||
DB::statement("ALTER TABLE public.sales ENABLE ROW LEVEL SECURITY");
|
||||
DB::statement("
|
||||
CREATE POLICY rbac_policy ON public.sales
|
||||
FOR ALL TO PUBLIC
|
||||
USING (public.c77_rbac_can_access('view_sales_page', public.c77_rbac_laravel_auth_id(), 'campus', campus))
|
||||
");
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
DB::statement("DROP TABLE public.sales CASCADE");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
### Querying
|
||||
|
||||
```php
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
public function index()
|
||||
{
|
||||
$sales = DB::table('sales')->get();
|
||||
return view('sales.index', ['sales' => $sales]);
|
||||
}
|
||||
```
|
||||
|
||||
Logged-in users see only authorized rows (e.g., `chicago` for user ID `1`).
|
||||
3. **Initialize RBAC**: During database setup, create your roles and assign features:
|
||||
```sql
|
||||
SELECT public.c77_rbac_assign_subject('1', 'editor', 'department', 'marketing');
|
||||
SELECT public.c77_rbac_grant_feature('editor', 'edit_content');
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- **Security**: `c77_rbac_*` tables are protected; only `SECURITY DEFINER` functions access them. The application user (`app_user`) needs `EXECUTE` on functions and `SELECT/INSERT/UPDATE/DELETE` on application tables (e.g., `sales`).
|
||||
- **Materialized Views**: PostgreSQL materialized views don’t support RLS. Use regular views for dynamic filtering:
|
||||
- **Security**: `c77_rbac_*` tables are protected; only `SECURITY DEFINER` functions access them. The application user (`app_user`) needs `EXECUTE` on functions and `SELECT/INSERT/UPDATE/DELETE` on application tables.
|
||||
- **Materialized Views**: PostgreSQL materialized views don't support RLS. Use regular views for dynamic filtering:
|
||||
```sql
|
||||
CREATE VIEW public.sales_view AS SELECT * FROM public.sales;
|
||||
```
|
||||
- **Future Enhancements**: Planned features include dynamic policy management and support for other frameworks (Django, Rails).
|
||||
- **Framework Integration**: While designed to be framework-agnostic, you'll need to ensure your application sets the `c77_rbac.external_id` session variable appropriately.
|
||||
|
||||
## Testing
|
||||
|
||||
@ -272,9 +160,8 @@ Verify the extension:
|
||||
CREATE DATABASE c77_rbac_test;
|
||||
\c c77_rbac_test
|
||||
CREATE EXTENSION c77_rbac;
|
||||
CREATE EXTENSION c77_rbac_laravel;
|
||||
|
||||
SELECT public.c77_rbac_laravel_assign_user(1, 'sales_manager', 'campus', 'chicago');
|
||||
SELECT public.c77_rbac_assign_subject('1', 'sales_manager', 'campus', 'chicago');
|
||||
SELECT public.c77_rbac_grant_feature('sales_manager', 'view_sales_page');
|
||||
|
||||
CREATE TABLE public.sales (
|
||||
@ -285,13 +172,22 @@ CREATE TABLE public.sales (
|
||||
INSERT INTO public.sales (campus, amount) VALUES ('chicago', 1000), ('miami', 2000);
|
||||
ALTER TABLE public.sales ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY rbac_policy ON public.sales FOR ALL TO PUBLIC USING (
|
||||
public.c77_rbac_can_access('view_sales_page', public.c77_rbac_laravel_auth_id(), 'campus', campus)
|
||||
public.c77_rbac_can_access('view_sales_page', current_setting('c77_rbac.external_id', true), 'campus', campus)
|
||||
);
|
||||
|
||||
SET "c77_rbac.external_id" TO '1';
|
||||
SELECT * FROM public.sales;
|
||||
```
|
||||
|
||||
## Planned Features
|
||||
|
||||
- Revocation functions (`c77_rbac_revoke_feature`, `c77_rbac_unassign_subject`)
|
||||
- Functions to list all features available to a subject within a scope
|
||||
- Role hierarchy support (inheritance)
|
||||
- Timestamped assignments for auditing
|
||||
- Framework-specific integration extensions
|
||||
- Performance optimization for deeply nested scopes
|
||||
|
||||
## License
|
||||
|
||||
MIT License. See `LICENSE` for details.
|
||||
@ -303,6 +199,4 @@ Issues and pull requests are welcome on [GitHub](#) (replace with your repo if a
|
||||
## Authors
|
||||
|
||||
- Your Name (or leave blank for now)
|
||||
|
||||
---
|
||||
Generated with help from Grok 3, built by xAI.
|
||||
-
|
195
USAGE.md
195
USAGE.md
@ -1,25 +1,21 @@
|
||||
# c77_rbac Usage Guide
|
||||
|
||||
This guide provides detailed instructions on using the `c77_rbac` PostgreSQL extension for Role-Based Access Control (RBAC) with Row-Level Security (RLS). The `c77_rbac` extension is an agnostic RBAC system that works with any application, while `c77_rbac_laravel` provides seamless integration for Laravel. This document assumes the extensions are installed (see `README.md` for setup) and focuses on practical usage, examples, and best practices.
|
||||
This guide provides detailed instructions on using the `c77_rbac` PostgreSQL extension for Role-Based Access Control (RBAC) with Row-Level Security (RLS). The `c77_rbac` extension is an agnostic RBAC system that works with any application. This document assumes the extension is installed (see `README.md` for setup) and focuses on practical usage, examples, and best practices.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Core Concepts](#core-concepts)
|
||||
3. [Using `c77_rbac`](#using-c77_rbac)
|
||||
- [Assigning Subjects](#assigning-subjects)
|
||||
- [Granting Features](#granting-features)
|
||||
- [Checking Access](#checking-access)
|
||||
- [Applying RLS Policies](#applying-rls-policies)
|
||||
- [Admin Role](#admin-role)
|
||||
4. [Using `c77_rbac_laravel`](#using-c77_rbac_laravel)
|
||||
- [Assigning Laravel Users](#assigning-laravel-users)
|
||||
- [Setting External ID](#setting-external-id)
|
||||
- [Laravel Queries with RLS](#laravel-queries-with-rls)
|
||||
5. [Best Practices](#best-practices)
|
||||
6. [Edge Cases](#edge-cases)
|
||||
7. [Testing and Debugging](#testing-and-debugging)
|
||||
8. [Limitations](#limitations)
|
||||
- [Assigning Subjects](#assigning-subjects)
|
||||
- [Granting Features](#granting-features)
|
||||
- [Checking Access](#checking-access)
|
||||
- [Applying RLS Policies](#applying-rls-policies)
|
||||
- [Admin Role](#admin-role)
|
||||
4. [Best Practices](#best-practices)
|
||||
5. [Edge Cases](#edge-cases)
|
||||
6. [Testing and Debugging](#testing-and-debugging)
|
||||
7. [Limitations](#limitations)
|
||||
|
||||
## Overview
|
||||
|
||||
@ -27,9 +23,8 @@ This guide provides detailed instructions on using the `c77_rbac` PostgreSQL ext
|
||||
- **Agnostic Design**: Uses `external_id` (TEXT) to identify subjects, compatible with any framework.
|
||||
- **Scoped Permissions**: Roles can be tied to scopes (e.g., `campus/chicago`) for granular control.
|
||||
- **Secure Execution**: `SECURITY DEFINER` functions protect RBAC metadata, requiring no direct table access.
|
||||
- **Laravel Support**: `c77_rbac_laravel` maps Laravel user IDs to RBAC subjects and integrates via middleware.
|
||||
|
||||
This guide uses `app_user` as the database user for all operations, assuming a single-user setup typical for applications like Laravel.
|
||||
This guide uses `app_user` as the database user for all operations, assuming a single-user setup typical for applications.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
@ -98,7 +93,7 @@ SELECT public.c77_rbac_can_access('view_sales_page', '1', 'campus', 'chicago');
|
||||
|
||||
- **Parameters**:
|
||||
- `p_feature_name`: Feature to check.
|
||||
- `p_external_id`: Subject’s identifier.
|
||||
- `p_external_id`: Subject's identifier.
|
||||
- `p_scope_type`: Scope category (optional).
|
||||
- `p_scope_id`: Scope value (optional).
|
||||
- **Returns**: `TRUE` if access is granted, `FALSE` otherwise.
|
||||
@ -133,12 +128,12 @@ CREATE TABLE public.sales (
|
||||
INSERT INTO public.sales (campus, amount) VALUES ('chicago', 1000), ('miami', 2000);
|
||||
ALTER TABLE public.sales ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY rbac_policy ON public.sales FOR ALL TO PUBLIC USING (
|
||||
public.c77_rbac_can_access('view_sales_page', public.c77_rbac_laravel_auth_id(), 'campus', campus)
|
||||
public.c77_rbac_can_access('view_sales_page', current_setting('c77_rbac.external_id', true), 'campus', campus)
|
||||
);
|
||||
```
|
||||
|
||||
- **Effect**: Only rows where `c77_rbac_can_access` returns `TRUE` are accessible.
|
||||
- **With `c77_rbac_laravel_auth_id`**: Uses the session variable `c77_rbac.external_id` set by the application.
|
||||
- **Note**: Uses the session variable `c77_rbac.external_id` set by the application.
|
||||
|
||||
Test:
|
||||
```sql
|
||||
@ -169,7 +164,7 @@ SELECT * FROM public.sales;
|
||||
|
||||
### Admin Role
|
||||
|
||||
The `admin` role bypasses scope restrictions for features it’s granted:
|
||||
The `admin` role bypasses scope restrictions for features it's granted:
|
||||
|
||||
```sql
|
||||
SELECT public.c77_rbac_assign_subject('999', 'admin', NULL, NULL);
|
||||
@ -190,114 +185,36 @@ SELECT * FROM public.sales;
|
||||
- **Use Case**: Assign `admin` to superusers who need full access.
|
||||
- **Note**: `admin` only bypasses scope checks, not feature checks (must still have `view_sales_page`).
|
||||
|
||||
## Using `c77_rbac_laravel`
|
||||
|
||||
### Assigning Laravel Users
|
||||
|
||||
Use `c77_rbac_laravel_assign_user` to assign Laravel user IDs to roles:
|
||||
|
||||
```sql
|
||||
SELECT public.c77_rbac_laravel_assign_user(1, 'sales_manager', 'campus', 'chicago');
|
||||
```
|
||||
|
||||
- **Parameters**:
|
||||
- `p_laravel_id`: Laravel user ID (cast to TEXT for `external_id`).
|
||||
- Others same as `c77_rbac_assign_subject`.
|
||||
- **Effect**: Calls `c77_rbac_assign_subject` internally.
|
||||
|
||||
Typically done in a migration:
|
||||
|
||||
```php
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SetupRbacSubjects extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$users = DB::table('users')->get();
|
||||
foreach ($users as $user) {
|
||||
DB::statement("SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)", [
|
||||
$user->id, 'sales_manager', 'campus', 'chicago'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Setting External ID
|
||||
|
||||
The Laravel middleware sets `c77_rbac.external_id`:
|
||||
|
||||
```php
|
||||
namespace App\Http\Middleware;
|
||||
use Closure;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class SetRbacExternalId
|
||||
{
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if (Auth::check()) {
|
||||
DB::statement('SET "c77_rbac.external_id" TO ?', [Auth::id()]);
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Effect**: Sets `external_id` to the logged-in user’s ID (e.g., `'1'`).
|
||||
- **Without Middleware**: Queries return no rows (RLS blocks access if `external_id` is unset).
|
||||
|
||||
Manual setting for jobs or scripts:
|
||||
```php
|
||||
DB::statement('SET "c77_rbac.external_id" TO ?', [1]);
|
||||
```
|
||||
|
||||
### Laravel Queries with RLS
|
||||
|
||||
Once the middleware is active, standard queries respect RLS:
|
||||
|
||||
```php
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
public function index()
|
||||
{
|
||||
$sales = DB::table('sales')->get();
|
||||
return view('sales.index', ['sales' => $sales]);
|
||||
}
|
||||
```
|
||||
|
||||
- **For User ID `1`**: Returns only `chicago` rows.
|
||||
- **For User ID `999` (admin)**: Returns all rows.
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Single User**: Use one PostgreSQL user (e.g., `app_user`) for migrations and runtime to avoid permission issues.
|
||||
- **Secure Functions**: All `c77_rbac` functions are `SECURITY DEFINER`, so don’t grant direct `SELECT` on `c77_rbac_*` tables.
|
||||
- **Secure Functions**: All `c77_rbac` functions are `SECURITY DEFINER`, so don't grant direct `SELECT` on `c77_rbac_*` tables.
|
||||
- **Scoped Roles**: Always define scopes (`scope_type`, `scope_id`) for non-admin roles to enforce granular access.
|
||||
- **Admin Sparingly**: Reserve the `admin` role for superusers, and audit its assignments.
|
||||
- **RLS on All Tables**: Apply RLS policies to any table with sensitive data, using `c77_rbac_can_access`.
|
||||
- **Test Policies**: Verify RLS behavior with different `external_id` values before deploying.
|
||||
- **Session Management**: Ensure your application correctly sets `c77_rbac.external_id` for each database session.
|
||||
|
||||
## Edge Cases
|
||||
|
||||
- **No `external_id` Set**:
|
||||
- Queries return no rows (RLS policy fails).
|
||||
- Fix: Ensure middleware or manual `SET` is in place.
|
||||
- Fix: Ensure your application sets the session variable with `SET "c77_rbac.external_id" TO 'your_user_id';`.
|
||||
- **Invalid `external_id`**:
|
||||
- If `external_id` doesn’t exist in `c77_rbac_subjects`, `c77_rbac_can_access` returns `FALSE`.
|
||||
- Fix: Sync users to `c77_rbac_subjects` in migrations.
|
||||
- If `external_id` doesn't exist in `c77_rbac_subjects`, `c77_rbac_can_access` returns `FALSE`.
|
||||
- Fix: Ensure subjects are assigned roles.
|
||||
- **Missing Role/Feature**:
|
||||
- `c77_rbac_can_access` returns `FALSE` if the role or feature isn’t assigned.
|
||||
- `c77_rbac_can_access` returns `FALSE` if the role or feature isn't assigned.
|
||||
- Fix: Use `c77_rbac_assign_subject` and `c77_rbac_grant_feature` to set up.
|
||||
- **Scope Mismatch**:
|
||||
- Access denied if `scope_type`/`scope_id` don’t match exactly.
|
||||
- Example: User with `campus/chicago` can’t access `campus/miami`.
|
||||
- Access denied if `scope_type`/`scope_id` don't match exactly.
|
||||
- Example: User with `campus/chicago` can't access `campus/miami`.
|
||||
- **Admin Overreach**:
|
||||
- `admin` bypasses scope but needs explicit feature grants.
|
||||
- Example: `admin` without `view_sales_page` gets `FALSE`.
|
||||
- **Database Connection Pooling**:
|
||||
- If using connection pooling, ensure `c77_rbac.external_id` is reset or set for each request.
|
||||
- Consider using a connection interceptor to set `external_id` based on the current user.
|
||||
|
||||
## Testing and Debugging
|
||||
|
||||
@ -305,9 +222,9 @@ To test the setup:
|
||||
|
||||
```sql
|
||||
-- Setup
|
||||
SELECT public.c77_rbac_laravel_assign_user(1, 'sales_manager', 'campus', 'chicago');
|
||||
SELECT public.c77_rbac_assign_subject('1', 'sales_manager', 'campus', 'chicago');
|
||||
SELECT public.c77_rbac_grant_feature('sales_manager', 'view_sales_page');
|
||||
SELECT public.c77_rbac_laravel_assign_user(999, 'admin', NULL, NULL);
|
||||
SELECT public.c77_rbac_assign_subject('999', 'admin', NULL, NULL);
|
||||
SELECT public.c77_rbac_grant_feature('admin', 'view_sales_page');
|
||||
|
||||
CREATE TABLE public.sales (
|
||||
@ -318,7 +235,7 @@ CREATE TABLE public.sales (
|
||||
INSERT INTO public.sales (campus, amount) VALUES ('chicago', 1000), ('miami', 2000);
|
||||
ALTER TABLE public.sales ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY rbac_policy ON public.sales FOR ALL TO PUBLIC USING (
|
||||
public.c77_rbac_can_access('view_sales_page', public.c77_rbac_laravel_auth_id(), 'campus', campus)
|
||||
public.c77_rbac_can_access('view_sales_page', current_setting('c77_rbac.external_id', true), 'campus', campus)
|
||||
);
|
||||
|
||||
-- Test regular user
|
||||
@ -357,16 +274,64 @@ Debug tips:
|
||||
- **No Rows Returned**: Check if `external_id` is set (`SELECT current_setting('c77_rbac.external_id', true);`).
|
||||
- **Access Denied**: Verify subject, role, and feature assignments in `c77_rbac_*` tables (requires superuser).
|
||||
- **RLS Issues**: Test `c77_rbac_can_access` directly with known inputs (as above).
|
||||
- **Transaction Isolation**: Be aware that changes to RBAC assignments need to be visible in the current transaction.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Materialized Views**: PostgreSQL materialized views don’t support RLS. Use regular views:
|
||||
- **Materialized Views**: PostgreSQL materialized views don't support RLS. Use regular views:
|
||||
```sql
|
||||
CREATE VIEW public.sales_view AS SELECT * FROM public.sales;
|
||||
```
|
||||
- **Dynamic Policies**: Policies are hardcoded in migrations. Future versions may add dynamic policy management.
|
||||
- **Single `external_id`**: Only one `external_id` per session. Multi-user contexts require separate connections.
|
||||
- **Performance**: Complex scopes may impact query performance. Index `c77_rbac_*` tables if needed.
|
||||
- **Session Variables**: Relies on session variables, which require careful management in connection pooling scenarios.
|
||||
|
||||
---
|
||||
Generated with help from Grok 3, built by xAI.
|
||||
## Application Integration Examples
|
||||
|
||||
### Setting External ID
|
||||
|
||||
Most applications will need to set the external ID session variable for each database connection:
|
||||
|
||||
```sql
|
||||
-- Set the external ID to the current user's ID
|
||||
SET "c77_rbac.external_id" TO '1';
|
||||
```
|
||||
|
||||
For web applications, this is typically done at the start of each request.
|
||||
|
||||
### Transaction Handling
|
||||
|
||||
When using transactions, ensure RBAC changes are committed before checking access:
|
||||
|
||||
```sql
|
||||
BEGIN;
|
||||
SELECT public.c77_rbac_assign_subject('2', 'reporter', 'department', 'finance');
|
||||
SELECT public.c77_rbac_grant_feature('reporter', 'view_reports');
|
||||
COMMIT;
|
||||
|
||||
-- Now in a new transaction
|
||||
BEGIN;
|
||||
SET "c77_rbac.external_id" TO '2';
|
||||
SELECT * FROM reports; -- Will use the new permissions
|
||||
```
|
||||
|
||||
### Multi-tenant Systems
|
||||
|
||||
For multi-tenant systems, you can use scopes to separate data by tenant:
|
||||
|
||||
```sql
|
||||
-- Assign users to tenant-specific roles
|
||||
SELECT public.c77_rbac_assign_subject('101', 'tenant_user', 'tenant', 'acme_corp');
|
||||
SELECT public.c77_rbac_assign_subject('102', 'tenant_user', 'tenant', 'globex');
|
||||
|
||||
-- Grant features to the role
|
||||
SELECT public.c77_rbac_grant_feature('tenant_user', 'view_data');
|
||||
|
||||
-- Create RLS policy using tenant scope
|
||||
CREATE POLICY tenant_isolation ON customer_data FOR ALL TO PUBLIC USING (
|
||||
public.c77_rbac_can_access('view_data', current_setting('c77_rbac.external_id', true), 'tenant', tenant_id)
|
||||
);
|
||||
```
|
||||
|
||||
This ensures users from one tenant cannot see data from another tenant.
|
Loading…
x
Reference in New Issue
Block a user