Revision 1.0.0 c77_rbac_laravel

This commit is contained in:
trogers1884 2025-04-13 07:24:32 -05:00
parent 181c245b62
commit 39976f1500
5 changed files with 940 additions and 0 deletions

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
# MIT License
Copyright (c) 2025 c77_rbac_laravel 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.

217
README.md Normal file
View File

@ -0,0 +1,217 @@
# c77_rbac_laravel
A PostgreSQL extension that provides Laravel integration for the `c77_rbac` Role-Based Access Control system. This extension simplifies the use of `c77_rbac` in Laravel applications by providing Laravel-specific functions and utilities.
## Prerequisites
- PostgreSQL 13 or later (tested on 17)
- `c77_rbac` extension must be installed
- Laravel 9.0 or later
## Installation
### Step 1: Install the Extension
Copy the extension files to your PostgreSQL extension directory:
```bash
sudo cp c77_rbac_laravel--1.0.sql c77_rbac_laravel.control /usr/share/postgresql/17/extension/
```
### Step 2: Create/Connect to Your Database
Ensure you have a database with the `c77_rbac` extension already installed:
```sql
-- Connect to your database
\c your_database_name
-- Verify c77_rbac is installed
SELECT * FROM pg_extension WHERE extname = 'c77_rbac';
```
### Step 3: Enable the Laravel Extension
```sql
CREATE EXTENSION c77_rbac_laravel;
```
## Features
The extension provides two key functions:
1. **c77_rbac_laravel_auth_id()**
- Retrieves the current Laravel user ID from the session variable
- Used in RLS policies to identify the current user
- Returns NULL if the session variable is not set
2. **c77_rbac_laravel_assign_user()**
- Assigns a Laravel user to a role with optional scope
- Automatically converts Laravel's integer IDs to the text format used by `c77_rbac`
## Integration with Laravel
### Middleware Setup
Create a middleware to set the current user's ID as the `c77_rbac.external_id` session variable:
```php
<?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 this middleware in your `app/Http/Kernel.php`:
```php
protected $middlewareGroups = [
'web' => [
// Other middleware...
\App\Http\Middleware\SetRbacExternalId::class,
],
];
```
### Setting Up RBAC in Migrations
```php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
class SetupRbacRoles extends Migration
{
public function up()
{
// Assign an admin user
DB::statement("SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)", [
1, // User ID
'admin',
NULL,
NULL
]);
// Grant features to the admin role
DB::statement("SELECT public.c77_rbac_grant_feature(?, ?)", [
'admin', 'view_dashboard'
]);
// Assign a regular user to a scoped role
DB::statement("SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)", [
2, // User ID
'editor',
'department',
'marketing'
]);
// Grant features to the editor role
DB::statement("SELECT public.c77_rbac_grant_feature(?, ?)", [
'editor', 'edit_content'
]);
}
public function down()
{
// Cleanup code if needed
}
}
```
### Creating RLS Policies
Apply RLS policies that use the Laravel user ID:
```php
DB::statement("
CREATE POLICY department_access_policy ON documents
FOR ALL
TO PUBLIC
USING (public.c77_rbac_can_access('edit_content', public.c77_rbac_laravel_auth_id(), 'department', department))
");
```
## Usage Examples
### Checking Access in Queries
Once the middleware is active, your Laravel queries will automatically respect RLS policies:
```php
// This query will only return rows the user has access to based on RLS
$documents = DB::table('documents')->get();
```
### Manual Access Checks
You can also perform manual access checks in your code:
```php
$userId = Auth::id();
$canAccess = DB::selectOne("
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS result
", ['edit_content', $userId, 'department', 'marketing'])->result;
if ($canAccess) {
// Allow the action
} else {
// Deny the action
}
```
## Connection Pooling Considerations
If you're using connection pooling (e.g., PgBouncer), be aware that session variables like `c77_rbac.external_id` persist for the duration of the database connection. For session pooling to work correctly:
1. Ensure your pooling is configured in "Session" mode, not "Transaction" mode
2. Set the external ID at the beginning of each request via middleware
3. Consider using a cleanup middleware for long-lived connections
## Troubleshooting
### No Rows Returned
If your queries return no rows when you expect data:
1. Verify the middleware is setting the external ID:
```php
$externalId = DB::selectOne("SELECT current_setting('c77_rbac.external_id', true) AS id")->id;
```
2. Check if your user has the required role and feature:
```php
$hasAccess = DB::selectOne("
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS result
", ['required_feature', Auth::id(), 'scope_type', 'scope_id'])->result;
```
### Database Errors
For "undefined_object" errors related to `c77_rbac.external_id`, ensure your middleware is running and setting the variable correctly.
## License
MIT License. See `LICENSE` file for details.
## Related Projects
- [c77_rbac](https://github.com/yourusername/c77_rbac) - The core RBAC extension for PostgreSQL
---
For more detailed information on the underlying RBAC system, please refer to the documentation for the `c77_rbac` extension.

675
USEAGE.md Normal file
View File

@ -0,0 +1,675 @@
# c77_rbac_laravel Usage Guide
This guide provides detailed instructions for using the `c77_rbac_laravel` PostgreSQL extension, which integrates the `c77_rbac` Role-Based Access Control system with Laravel applications. The guide assumes you have already installed both the `c77_rbac` and `c77_rbac_laravel` extensions.
## Table of Contents
1. [Extension Functions](#extension-functions)
2. [Laravel Integration Setup](#laravel-integration-setup)
3. [Common Usage Patterns](#common-usage-patterns)
4. [Advanced Usage](#advanced-usage)
5. [Best Practices](#best-practices)
6. [Troubleshooting](#troubleshooting)
## Extension Functions
The `c77_rbac_laravel` extension provides two key functions that bridge Laravel's authentication system with PostgreSQL's RBAC implementation:
### c77_rbac_laravel_auth_id()
Retrieves the current Laravel user ID from the session variable.
```sql
SELECT public.c77_rbac_laravel_auth_id();
```
- **Returns**: TEXT value of the current user's ID, or NULL if not set
- **Use Case**: Used within RLS policies to identify the current user
- **Example**:
```sql
CREATE POLICY user_data_policy ON user_data
FOR ALL TO PUBLIC
USING (user_id::text = public.c77_rbac_laravel_auth_id());
```
### c77_rbac_laravel_assign_user()
Assigns a Laravel user to a role with optional scope.
```sql
SELECT public.c77_rbac_laravel_assign_user(
p_laravel_id BIGINT, -- Laravel user ID (integer)
p_role_name TEXT, -- Role name to assign
p_scope_type TEXT, -- Optional scope type
p_scope_id TEXT -- Optional scope ID
);
```
- **Parameters**:
- `p_laravel_id`: Laravel's integer user ID (will be converted to TEXT)
- `p_role_name`: Name of the role to assign
- `p_scope_type`: Scope category (can be NULL)
- `p_scope_id`: Scope value (can be NULL)
- **Example**:
```sql
-- Assign user ID 1 to the 'editor' role for the 'marketing' department
SELECT public.c77_rbac_laravel_assign_user(1, 'editor', 'department', 'marketing');
-- Assign user ID 2 to the global 'admin' role
SELECT public.c77_rbac_laravel_assign_user(2, 'admin', NULL, NULL);
```
## Laravel Integration Setup
### Setting Up the Middleware
Create a middleware to set the `c77_rbac.external_id` session variable for each request:
```php
<?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()]);
} else {
// Optional: Reset the external_id if no user is logged in
DB::statement('RESET "c77_rbac.external_id"');
}
return $next($request);
}
}
```
Register this middleware in your `app/Http/Kernel.php`:
```php
protected $middlewareGroups = [
'web' => [
// Other middleware...
\App\Http\Middleware\SetRbacExternalId::class,
],
];
```
### Database Migrations
Create migrations to set up your RBAC rules:
```php
<?php
// database/migrations/2025_04_12_000001_setup_rbac_roles.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
class SetupRbacRoles extends Migration
{
public function up()
{
// Create roles
$roles = ['admin', 'editor', 'viewer'];
foreach ($roles as $role) {
DB::statement("
INSERT INTO public.c77_rbac_roles (name)
VALUES (?) ON CONFLICT (name) DO NOTHING
", [$role]);
}
// Define features
$features = [
'view_dashboard', 'edit_content', 'publish_content',
'manage_users', 'view_reports'
];
foreach ($features as $feature) {
DB::statement("
INSERT INTO public.c77_rbac_features (name)
VALUES (?) ON CONFLICT (name) DO NOTHING
", [$feature]);
}
// Grant features to roles
$roleFeatures = [
'admin' => ['view_dashboard', 'edit_content', 'publish_content', 'manage_users', 'view_reports'],
'editor' => ['view_dashboard', 'edit_content', 'publish_content', 'view_reports'],
'viewer' => ['view_dashboard', 'view_reports']
];
foreach ($roleFeatures as $role => $features) {
foreach ($features as $feature) {
DB::statement("
SELECT public.c77_rbac_grant_feature(?, ?)
", [$role, $feature]);
}
}
}
public function down()
{
// Clean up if needed
DB::statement("TRUNCATE public.c77_rbac_role_features CASCADE");
}
}
```
```php
<?php
// database/migrations/2025_04_12_000002_assign_initial_users.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use App\Models\User;
class AssignInitialUsers extends Migration
{
public function up()
{
// Find admin user(s)
$adminUsers = User::where('is_admin', true)->get();
foreach ($adminUsers as $admin) {
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$admin->id, 'admin', NULL, NULL]);
}
// Assign department editors
$editors = User::where('role', 'editor')->get();
foreach ($editors as $editor) {
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$editor->id, 'editor', 'department', $editor->department]);
}
// Assign viewers
$viewers = User::where('role', 'viewer')->get();
foreach ($viewers as $viewer) {
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$viewer->id, 'viewer', 'department', $viewer->department]);
}
}
public function down()
{
// Clean up if needed
DB::statement("TRUNCATE public.c77_rbac_subject_roles CASCADE");
}
}
```
### Applying RLS to Tables
Create migrations to enable RLS on your tables:
```php
<?php
// database/migrations/2025_04_12_000003_enable_rls_policies.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class EnableRlsPolicies extends Migration
{
public function up()
{
// Enable RLS on content table
DB::statement("ALTER TABLE content ENABLE ROW LEVEL SECURITY");
// Create policy for content access
DB::statement("
CREATE POLICY content_access_policy ON content
FOR ALL
TO PUBLIC
USING (
public.c77_rbac_can_access(
'edit_content',
public.c77_rbac_laravel_auth_id(),
'department',
department
)
)
");
// Enable RLS on users table
DB::statement("ALTER TABLE users ENABLE ROW LEVEL SECURITY");
// Create policy for user management
DB::statement("
CREATE POLICY user_management_policy ON users
FOR ALL
TO PUBLIC
USING (
public.c77_rbac_can_access(
'manage_users',
public.c77_rbac_laravel_auth_id(),
NULL,
NULL
)
)
");
}
public function down()
{
// Drop policies
DB::statement("DROP POLICY IF EXISTS content_access_policy ON content");
DB::statement("DROP POLICY IF EXISTS user_management_policy ON users");
// Disable RLS
DB::statement("ALTER TABLE content DISABLE ROW LEVEL SECURITY");
DB::statement("ALTER TABLE users DISABLE ROW LEVEL SECURITY");
}
}
```
## Common Usage Patterns
### User Registration
When registering new users, assign them appropriate roles:
```php
public function register(Request $request)
{
// Validate request and create user
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'department' => $request->department,
]);
// Assign default role with department scope
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$user->id, 'viewer', 'department', $user->department]);
// Continue with registration process...
}
```
### Role Management
Create a controller for managing user roles:
```php
public function assignRole(User $user, Request $request)
{
$request->validate([
'role' => 'required|string|exists:c77_rbac_roles,name',
'scope_type' => 'nullable|string',
'scope_id' => 'nullable|string',
]);
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [
$user->id,
$request->role,
$request->scope_type,
$request->scope_id
]);
return back()->with('success', 'Role assigned successfully');
}
```
### Checking Permissions in Controllers
Check if a user has a specific permission:
```php
public function edit(Content $content)
{
$canEdit = DB::selectOne("
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS result
", ['edit_content', Auth::id(), 'department', $content->department])->result;
if (!$canEdit) {
abort(403, 'Unauthorized action.');
}
return view('content.edit', compact('content'));
}
```
### Blade Directives
Create custom Blade directives for permission checks:
```php
// In AppServiceProvider.php
public function boot()
{
Blade::directive('can', function ($expression) {
list($feature, $scopeType, $scopeId) = explode(',', $expression);
return "<?php if(DB::selectOne(\"
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS result
\", [{$feature}, auth()->id(), {$scopeType}, {$scopeId}])->result): ?>";
});
Blade::directive('endcan', function () {
return "<?php endif; ?>";
});
}
```
Usage in Blade:
```blade
@can('edit_content', 'department', $content->department)
<a href="{{ route('content.edit', $content) }}" class="btn btn-primary">Edit</a>
@endcan
```
## Advanced Usage
### Batch User Assignments
For batch operations, use a transaction to improve performance:
```php
DB::transaction(function () use ($users, $role, $scopeType, $scopeId) {
foreach ($users as $user) {
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$user->id, $role, $scopeType, $scopeId]);
}
});
```
### Dynamic Scope Resolution
Create a helper for dynamic scope resolution:
```php
// In a helper file or service
function resolveScope($model)
{
switch (get_class($model)) {
case 'App\Models\Department':
return ['department', $model->id];
case 'App\Models\Project':
return ['project', $model->id];
case 'App\Models\Team':
return ['team', $model->id];
default:
return [null, null];
}
}
// Usage
list($scopeType, $scopeId) = resolveScope($department);
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$user->id, 'manager', $scopeType, $scopeId]);
```
### Command-Line Role Management
Create an Artisan command for role management:
```php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class AssignUserRole extends Command
{
protected $signature = 'rbac:assign-role {user} {role} {--scope-type=} {--scope-id=}';
protected $description = 'Assign a role to a user with optional scope';
public function handle()
{
$user = User::findOrFail($this->argument('user'));
$role = $this->argument('role');
$scopeType = $this->option('scope-type');
$scopeId = $this->option('scope-id');
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$user->id, $role, $scopeType, $scopeId]);
$this->info("Role '$role' assigned to user #{$user->id}");
return 0;
}
}
```
Usage:
```bash
php artisan rbac:assign-role 1 editor --scope-type=department --scope-id=marketing
```
## Best Practices
### Connection Management
1. **Connection Pooling**: If using connection pooling (like PgBouncer), always reset the `c77_rbac.external_id` variable at the end of each request:
```php
// In a middleware or terminating callback
public function terminate($request, $response)
{
DB::statement('RESET "c77_rbac.external_id"');
}
```
2. **Long-running Jobs**: For queued jobs, set the external ID at the beginning of the job:
```php
public function handle()
{
// Set the RBAC external ID for this job
DB::statement('SET "c77_rbac.external_id" TO ?', [$this->user_id]);
// Job logic...
// Reset when done
DB::statement('RESET "c77_rbac.external_id"');
}
```
### Security
1. **Middleware Priority**: Ensure your RBAC middleware runs early in the request cycle to protect all database queries.
2. **Authorization Wrapper**: Create a service for all authorization checks:
```php
// app/Services/RbacService.php
class RbacService
{
public function can($feature, $scopeType = null, $scopeId = null)
{
$userId = auth()->id();
if (!$userId) return false;
return DB::selectOne("
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS result
", [$feature, $userId, $scopeType, $scopeId])->result;
}
public function assignRole($userId, $role, $scopeType = null, $scopeId = null)
{
DB::statement("
SELECT public.c77_rbac_laravel_assign_user(?, ?, ?, ?)
", [$userId, $role, $scopeType, $scopeId]);
}
}
```
3. **Policy Integration**: Integrate with Laravel's policy system:
```php
// app/Policies/ContentPolicy.php
class ContentPolicy
{
protected $rbac;
public function __construct(RbacService $rbac)
{
$this->rbac = $rbac;
}
public function update(User $user, Content $content)
{
return $this->rbac->can('edit_content', 'department', $content->department);
}
}
```
### Performance
1. **Eager Loading**: When loading related models that will be filtered by RLS, use eager loading with care:
```php
// This will load ALL departments first, then filter
$user->load('departments');
// Better approach for large datasets
$departments = Department::all(); // Already filtered by RLS
```
2. **Cache Role Assignments**: Cache role assignments for frequently accessed permissions:
```php
public function can($feature, $scopeType = null, $scopeId = null)
{
$cacheKey = "rbac:{$feature}:{$scopeType}:{$scopeId}:" . auth()->id();
return Cache::remember($cacheKey, now()->addMinutes(10), function () use ($feature, $scopeType, $scopeId) {
return DB::selectOne("
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS result
", [$feature, auth()->id(), $scopeType, $scopeId])->result;
});
}
```
## Troubleshooting
### Common Issues
1. **No Data Returned**
**Symptom**: Queries return empty results when you expect data.
**Possible Causes**:
- External ID not set
- User doesn't have required permissions
- RLS policy incorrectly configured
**Solution**:
```php
// Check if external ID is set
$externalId = DB::selectOne("SELECT current_setting('c77_rbac.external_id', true) AS id")->id;
// Check direct permissions
$hasPermission = DB::selectOne("
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS result
", ['required_feature', auth()->id(), 'scope_type', 'scope_id'])->result;
// Verify RLS policy
$policyExists = DB::selectOne("
SELECT EXISTS (
SELECT 1 FROM pg_policies
WHERE tablename = 'your_table' AND policyname = 'your_policy'
) AS exists
")->exists;
```
2. **Permission Denied Errors**
**Symptom**: PostgreSQL returns permission denied errors.
**Possible Causes**:
- Database user doesn't have execute permission on functions
- RLS preventing access to tables
**Solution**:
```sql
-- Grant execute permission
GRANT EXECUTE ON FUNCTION public.c77_rbac_laravel_auth_id() TO app_user;
GRANT EXECUTE ON FUNCTION public.c77_rbac_laravel_assign_user(BIGINT, TEXT, TEXT, TEXT) TO app_user;
-- Check for explicit denials in RLS
SELECT tablename, policyname, cmd, qual
FROM pg_policies
WHERE tablename = 'your_table';
```
3. **Middleware Not Working**
**Symptom**: RBAC checks fail despite user being logged in.
**Possible Causes**:
- Middleware not registered or running
- Auth system not initialized when middleware runs
**Solution**:
- Check middleware registration in Kernel.php
- Add debug code to verify middleware execution:
```php
// In your middleware
public function handle($request, Closure $next)
{
\Log::debug('RBAC Middleware - User: ' . (Auth::check() ? Auth::id() : 'Guest'));
if (Auth::check()) {
DB::statement('SET "c77_rbac.external_id" TO ?', [Auth::id()]);
\Log::debug('Set c77_rbac.external_id to ' . Auth::id());
}
return $next($request);
}
```
4. **Role Assignment Issues**
**Symptom**: User assignments aren't taking effect.
**Possible Causes**:
- Transaction issues
- Scopes not matching exactly
**Solution**:
- Ensure transactions are committed
- Check for exact scope matches:
```sql
SELECT * FROM c77_rbac_subject_roles
WHERE subject_id = (
SELECT subject_id FROM c77_rbac_subjects
WHERE external_id = '1'
);
```
For persistent issues, you can enable PostgreSQL query logging to see exactly what's happening:
```php
// In a debug route or controller
DB::listen(function ($query) {
\Log::debug($query->sql, [
'bindings' => $query->bindings,
'time' => $query->time
]);
});
```

View File

@ -0,0 +1,21 @@
-- /usr/share/postgresql/17/extension/c77_rbac_laravel--1.0.sql
\echo Use "CREATE EXTENSION c77_rbac_laravel" to load this file. \quit
CREATE OR REPLACE FUNCTION public.c77_rbac_laravel_auth_id() RETURNS TEXT AS $$
BEGIN
RETURN current_setting('c77_rbac.external_id', true);
EXCEPTION WHEN OTHERS THEN
RETURN NULL;
END;
$$ LANGUAGE plpgsql STABLE;
CREATE OR REPLACE FUNCTION public.c77_rbac_laravel_assign_user(
p_laravel_id BIGINT,
p_role_name TEXT,
p_scope_type TEXT,
p_scope_id TEXT
) RETURNS VOID AS $$
BEGIN
PERFORM public.c77_rbac_assign_subject(p_laravel_id::TEXT, p_role_name, p_scope_type, p_scope_id);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

6
c77_rbac_laravel.control Normal file
View File

@ -0,0 +1,6 @@
# /usr/share/postgresql/17/extension/c77_rbac_laravel.control
comment = 'Laravel integration for c77_rbac'
default_version = '1.0'
module_pathname = '$libdir/c77_rbac_laravel'
relocatable = true
requires = 'c77_rbac'