Update Documentation

This commit is contained in:
trogers1884 2025-04-13 06:55:41 -05:00
parent 74ae17dfbd
commit 09362f9bf5
3 changed files with 145 additions and 265 deletions

21
LICENSE.md Normal file
View 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
View File

@ -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 dont 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.
-

185
USAGE.md
View File

@ -1,6 +1,6 @@
# 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
@ -12,14 +12,10 @@ This guide provides detailed instructions on using the `c77_rbac` PostgreSQL ext
- [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)
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`: Subjects 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 its 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 users 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 dont 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` doesnt 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 isnt 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` dont match exactly.
- Example: User with `campus/chicago` cant 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 dont 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.