From 09362f9bf5386f7fef14b4899e1603552fbfca07 Mon Sep 17 00:00:00 2001 From: trogers1884 Date: Sun, 13 Apr 2025 06:55:41 -0500 Subject: [PATCH] Update Documentation --- LICENSE.md | 21 ++++++ README.md | 194 ++++++++++++---------------------------------------- USAGE.md | 195 ++++++++++++++++++++++------------------------------- 3 files changed, 145 insertions(+), 265 deletions(-) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..d2a82f1 --- /dev/null +++ b/LICENSE.md @@ -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. diff --git a/README.md b/README.md index 06a8895..9c40ae6 100644 --- a/README.md +++ b/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. +- \ No newline at end of file diff --git a/USAGE.md b/USAGE.md index dbe5c65..5537a23 100644 --- a/USAGE.md +++ b/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. \ No newline at end of file