212 lines
5.8 KiB
Markdown
212 lines
5.8 KiB
Markdown
# Salesforce-Style Authorization System
|
|
|
|
## Overview
|
|
Implemented a comprehensive authorization system based on Salesforce's model with:
|
|
- **Org-Wide Defaults (OWD)** for record visibility
|
|
- **Role-based permissions** for object and field access
|
|
- **Record sharing** for granular access control
|
|
- **CASL** for flexible permission evaluation
|
|
|
|
## Architecture
|
|
|
|
### 1. Org-Wide Defaults (OWD)
|
|
Controls baseline record visibility for each object:
|
|
- `private`: Only owner can see records
|
|
- `public_read`: Everyone can see, only owner can edit/delete
|
|
- `public_read_write`: Everyone can see and modify all records
|
|
|
|
### 2. Role-Based Object Permissions
|
|
Table: `role_object_permissions`
|
|
- `canCreate`: Can create new records
|
|
- `canRead`: Can read records (subject to OWD)
|
|
- `canEdit`: Can edit records (subject to OWD)
|
|
- `canDelete`: Can delete records (subject to OWD)
|
|
- `canViewAll`: Override OWD to see ALL records
|
|
- `canModifyAll`: Override OWD to edit ALL records
|
|
|
|
### 3. Field-Level Security
|
|
Table: `role_field_permissions`
|
|
- `canRead`: Can view field value
|
|
- `canEdit`: Can modify field value
|
|
|
|
### 4. Record Sharing
|
|
Table: `record_shares`
|
|
Grants specific users access to individual records with:
|
|
```json
|
|
{
|
|
"canRead": boolean,
|
|
"canEdit": boolean,
|
|
"canDelete": boolean
|
|
}
|
|
```
|
|
|
|
## Permission Evaluation Flow
|
|
|
|
```
|
|
1. Check role_object_permissions
|
|
├─ Does user have canCreate/Read/Edit/Delete?
|
|
│ └─ NO → Deny
|
|
│ └─ YES → Continue
|
|
│
|
|
2. Check canViewAll / canModifyAll
|
|
├─ Does user have special "all" permissions?
|
|
│ └─ YES → Grant access
|
|
│ └─ NO → Continue
|
|
│
|
|
3. Check OWD (orgWideDefault)
|
|
├─ public_read_write → Grant access
|
|
├─ public_read → Grant read, check ownership for write
|
|
└─ private → Check ownership or sharing
|
|
|
|
4. Check Ownership
|
|
├─ Is user the record owner?
|
|
│ └─ YES → Grant access
|
|
│ └─ NO → Continue
|
|
│
|
|
5. Check Record Shares
|
|
└─ Is record explicitly shared with user?
|
|
└─ Check accessLevel permissions
|
|
```
|
|
|
|
## Field-Level Security
|
|
|
|
Fields are filtered after record access is granted:
|
|
1. User queries records → Apply record-level scope
|
|
2. System filters readable fields based on `role_field_permissions`
|
|
3. User updates records → System filters editable fields
|
|
|
|
## Key Features
|
|
|
|
### Multiple Role Support
|
|
- Users can have multiple roles
|
|
- Permissions are **unioned** (any role grants = user has it)
|
|
- More flexible than Salesforce's single profile model
|
|
|
|
### Active Share Detection
|
|
- Shares can expire (`expiresAt`)
|
|
- Shares can be revoked (`revokedAt`)
|
|
- Only active shares are evaluated
|
|
|
|
### CASL Integration
|
|
- Dynamic ability building per request
|
|
- Condition-based rules
|
|
- Field-level permission support
|
|
|
|
## Usage Example
|
|
|
|
```typescript
|
|
// In a controller/service
|
|
constructor(
|
|
private authService: AuthorizationService,
|
|
private tenantDbService: TenantDatabaseService,
|
|
) {}
|
|
|
|
async getRecords(tenantId: string, objectApiName: string, userId: string) {
|
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
|
|
|
// Get user with roles
|
|
const user = await User.query(knex)
|
|
.findById(userId)
|
|
.withGraphFetched('[roles.[objectPermissions, fieldPermissions]]');
|
|
|
|
// Get object definition
|
|
const objectDef = await ObjectDefinition.query(knex)
|
|
.findOne({ apiName: objectApiName });
|
|
|
|
// Build query with authorization scope
|
|
let query = knex(objectApiName.toLowerCase());
|
|
query = await this.authService.applyScopeToQuery(
|
|
query,
|
|
objectDef,
|
|
user,
|
|
'read',
|
|
knex,
|
|
);
|
|
|
|
const records = await query;
|
|
|
|
// Get field definitions
|
|
const fields = await FieldDefinition.query(knex)
|
|
.where('objectDefinitionId', objectDef.id);
|
|
|
|
// Filter fields user can read
|
|
const filteredRecords = await Promise.all(
|
|
records.map(record =>
|
|
this.authService.filterReadableFields(record, fields, user)
|
|
)
|
|
);
|
|
|
|
return filteredRecords;
|
|
}
|
|
|
|
async updateRecord(tenantId: string, objectApiName: string, recordId: string, data: any, userId: string) {
|
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
|
|
|
const user = await User.query(knex)
|
|
.findById(userId)
|
|
.withGraphFetched('[roles.[objectPermissions, fieldPermissions]]');
|
|
|
|
const objectDef = await ObjectDefinition.query(knex)
|
|
.findOne({ apiName: objectApiName });
|
|
|
|
// Get existing record
|
|
const record = await knex(objectApiName.toLowerCase())
|
|
.where({ id: recordId })
|
|
.first();
|
|
|
|
if (!record) {
|
|
throw new NotFoundException('Record not found');
|
|
}
|
|
|
|
// Check if user can update this record
|
|
await this.authService.assertCanPerformAction(
|
|
'update',
|
|
objectDef,
|
|
record,
|
|
user,
|
|
knex,
|
|
);
|
|
|
|
// Get field definitions
|
|
const fields = await FieldDefinition.query(knex)
|
|
.where('objectDefinitionId', objectDef.id);
|
|
|
|
// Filter to only editable fields
|
|
const editableData = await this.authService.filterEditableFields(
|
|
data,
|
|
fields,
|
|
user,
|
|
);
|
|
|
|
// Perform update
|
|
await knex(objectApiName.toLowerCase())
|
|
.where({ id: recordId })
|
|
.update(editableData);
|
|
|
|
return knex(objectApiName.toLowerCase())
|
|
.where({ id: recordId })
|
|
.first();
|
|
}
|
|
```
|
|
|
|
## Migration
|
|
|
|
Run the migration to add authorization tables:
|
|
```bash
|
|
npm run knex migrate:latest
|
|
```
|
|
|
|
The migration creates:
|
|
- `orgWideDefault` column in `object_definitions`
|
|
- `role_object_permissions` table
|
|
- `role_field_permissions` table
|
|
- `record_shares` table
|
|
|
|
## Next Steps
|
|
|
|
1. **Migrate existing data**: Set default `orgWideDefault` values for existing objects
|
|
2. **Create default roles**: Create Admin, Standard User, etc. with appropriate permissions
|
|
3. **Update API endpoints**: Integrate authorization service into all CRUD operations
|
|
4. **UI for permission management**: Build admin interface to manage role permissions
|
|
5. **Sharing UI**: Build interface for users to share records with others
|