Access Control
Access Control provides enterprise-grade permissions management through integration with Nextcloud RBAC (Role-Based Access Control) and Keycloak.
Overview
The access control system integrates with:
- ADFS (Active Directory Federation Services) for user and group management via Keycloak
- Nextcloud RBAC for role-based permissions
- FCS (Federal Cloud Services) compliance requirements
- Verwerkingen registers for process tracking
Permission Levels
Access can be controlled at multiple levels:
- Register level - Control access to entire registers
- Schema level - Manage permissions for specific register/schema combinations
- Object level - Set permissions on individual objects
- Property level - Fine-grained control over specific object properties
Permission Types
Permissions are granted through:
-
User Rights
- CRUD (Create, Read, Update, Delete) operations
- Inherited from ADFS groups via Keycloak
- Role-based access control through Nextcloud
-
Contract Rights
- Application-level permissions
- Process-specific authorizations
- Compliance with FCS requirements
- Integration with verwerkingen registers
Implementation
Access control is implemented through:
-
User Authentication
- Direct integration with Keycloak for identity management
- ADFS synchronization for user and group information
- Single Sign-On (SSO) capabilities
-
Permission Management
- CRUD-level permissions for all system entities
- Hierarchical permission inheritance
- Fine-grained access control at multiple levels
-
Process Integration
- Compliance with FCS guidelines
- Integration with verwerkingen registers for process tracking
- Application-specific permission contracts
Technical Implementation
Architecture Overview
Authorization Flow
Schema Authorization Configuration
Authorization Exception System
RBAC Query Filtering Process
Database Schema
Authorization Exception Table: oc_openregister_authorization_exceptions
| Column | Type | Description |
|---|---|---|
id | INTEGER | Primary key |
uuid | VARCHAR(36) | Unique identifier |
type | VARCHAR(20) | Exception type: inclusion or exclusion |
subject_type | VARCHAR(20) | Subject type: user or group |
subject_id | VARCHAR(255) | User ID or Group ID |
schema_uuid | VARCHAR(36) | Schema UUID (nullable for global) |
register_uuid | VARCHAR(36) | Register UUID (nullable) |
organization_uuid | VARCHAR(36) | Organisation UUID (nullable) |
action | VARCHAR(20) | CRUD action: create, read, update, delete |
priority | INTEGER | Priority for resolution (higher = more important) |
active | BOOLEAN | Whether exception is active |
description | TEXT | Human-readable description |
created_by | VARCHAR(255) | User who created the exception |
created_at | DATETIME | Creation timestamp |
updated_at | DATETIME | Last update timestamp |
Schema Authorization Field:
- Stored in
oc_openregister_schemas.authorization(JSON) - Format:
{
"create": ["admin", "editors"],
"read": ["admin", "editors", "viewers", "public"],
"update": ["admin", "editors"],
"delete": ["admin"]
}
Object Authorization Field:
- Stored in
oc_openregister_objects.authorization(JSON) - Inherits from schema but can be overridden per-object
- Same format as schema authorization
Permission Resolution Algorithm
Code Examples
Schema Authorization Configuration
// Setting authorization on a schema
$schema->setAuthorization([
'create' => ['admin', 'editors'],
'read' => ['admin', 'editors', 'viewers', 'public'],
'update' => ['admin', 'editors'],
'delete' => ['admin']
]);
// Checking if a user has permission
$hasPermission = $schema->hasPermission('read', $userGroups);
Creating Authorization Exceptions
use OCA\OpenRegister\Db\AuthorizationException;
// Create an inclusion exception (grant extra permission)
$inclusion = new AuthorizationException();
$inclusion->setType(AuthorizationException::TYPE_INCLUSION);
$inclusion->setSubjectType(AuthorizationException::SUBJECT_TYPE_USER);
$inclusion->setSubjectId('user123');
$inclusion->setSchemaUuid($schemaUuid);
$inclusion->setAction(AuthorizationException::ACTION_UPDATE);
$inclusion->setPriority(10);
$inclusion->setActive(true);
$inclusion->setDescription('Allow user123 to update objects in this schema');
// Create an exclusion exception (deny permission)
$exclusion = new AuthorizationException();
$exclusion->setType(AuthorizationException::TYPE_EXCLUSION);
$exclusion->setSubjectType(AuthorizationException::SUBJECT_TYPE_GROUP);
$exclusion->setSubjectId('restricted_group');
$exclusion->setRegisterUuid($registerUuid);
$exclusion->setAction(AuthorizationException::ACTION_DELETE);
$exclusion->setPriority(20);
$exclusion->setActive(true);
$exclusion->setDescription('Prevent restricted_group from deleting objects in this register');
// Check if an exception matches criteria
$matches = $exception->matches(
subjectType: 'user',
subjectId: 'user123',
action: 'update',
schemaUuid: $schemaUuid
);
RBAC Query Filtering (MagicMapper)
use OCA\OpenRegister\Service\MagicMapperHandlers\MagicRbacHandler;
// Apply RBAC filters to a dynamic table query
$rbacHandler->applyRbacFilters(
qb: $queryBuilder,
register: $register,
schema: $schema,
tableAlias: 't',
userId: $currentUserId,
rbac: true
);
// Check if current user is admin
$isAdmin = $rbacHandler->isCurrentUserAdmin();
// Get current user's groups
$userGroups = $rbacHandler->getCurrentUserGroups();
Object-Level Authorization
// Get object authorization (inherits from schema if not set)
$objectAuth = $object->getAuthorization();
// Override schema authorization for specific object
$object->setAuthorization([
'read' => ['admin', 'special_viewers'],
'update' => ['admin']
]);
RBAC Configuration
RBAC can be configured in Nextcloud app settings:
{
"enabled": true,
"adminOverride": true
}
enabled: Master switch for RBAC systemadminOverride: Allow users in 'admin' group to bypass all RBAC checks
Performance Optimizations
-
Lazy Group Loading
- User groups are only fetched when RBAC is enabled
- Results are cached within the request lifecycle
-
Admin Fast Path
- Admin users bypass permission checks entirely when override is enabled
- Reduces database queries for administrative operations
-
Query-Level Filtering
- RBAC filters are applied at the database query level
- Prevents loading unauthorized objects into memory
-
Authorization Config Caching
- Schema authorization configs are cached with schema entities
- Reduces redundant JSON parsing
-
Exception Priority Indexing
- Database index on
priorityfield for fast exception sorting - Composite index on
(subject_type, subject_id, action, active)for fast matching
- Database index on
Integration Points
1. ObjectEntityMapper
- Applies RBAC filters to all object queries via
findAll(),find(),count() - Respects
rbacparameter (default: true)
2. MagicMapper (Dynamic Tables)
- Uses
MagicRbacHandlerfor schema-specific table filtering - Consistent security across schema-generated tables
3. Solr Search
- RBAC filtering applied via
applyAdditionalFilters()inGuzzleSolrService - Currently logs RBAC application but full implementation pending
4. API Controllers
- RBAC checks in
ObjectsController,RegistersController,SchemasController - Validates permissions before CRUD operations
Best Practices
-
Use Schema-Level Authorization
- Define authorization at the schema level for consistency
- Only override at object level when necessary
-
Leverage Group-Based Permissions
- Use Nextcloud groups for role management
- Avoid user-specific permissions unless absolutely required
-
Authorization Exceptions as Last Resort
- Use exceptions sparingly for edge cases
- Document the reason for each exception
- Set appropriate priorities to avoid conflicts
-
Test Permission Scenarios
- Test unauthenticated access
- Test group membership changes
- Test admin override behavior
- Test exception priority resolution
-
Monitor Authorization Exceptions
- Regularly audit active exceptions
- Deactivate or delete obsolete exceptions
- Review exception conflicts (overlapping priorities)
Debugging & Monitoring
Enable Debug Logging
// In GuzzleSolrService
$this->logger->debug('[SOLR] RBAC filtering applied');
// In MagicRbacHandler
$this->logger->debug('Applying RBAC filters', [
'user_id' => $userId,
'user_groups' => $userGroups,
'schema_uuid' => $schema->getUuid()
]);
Check Authorization Config
# Query schema authorization
docker exec -u 33 master-nextcloud-1 php -r "
\$schema = \OC::$server->get(\OCA\OpenRegister\Db\SchemaMapper::class)->find(1);
var_dump(\$schema->getAuthorization());
"
Query Authorization Exceptions
# List active exceptions
docker exec -it master-database-mysql-1 mysql -u nextcloud -pnextcloud nextcloud -e "
SELECT type, subject_type, subject_id, action, priority, description
FROM oc_openregister_authorization_exceptions
WHERE active = 1
ORDER BY priority DESC;
"
Security Considerations
-
Default Deny
- When authorization is configured, default behavior is to deny access
- Explicitly configure 'public' in read permissions for public access
-
Admin Override
- Admin override can be disabled for high-security environments
- When disabled, even admins must have explicit permissions
-
Authorization Inheritance
- Objects inherit authorization from schemas
- Object-level overrides take precedence
-
Exception Priority
- Exclusions should have higher priority than inclusions to ensure security
- Use priority > 50 for security-critical exclusions
-
Unauthenticated Access
- Unauthenticated users only see objects with 'public' in read permissions
- Fallback to published object filtering can be enabled (currently disabled)
Authorization Exceptions
The Authorization Exception System provides a flexible way to override the standard Role-Based Access Control (RBAC) system. It allows for fine-grained control over permissions by defining specific inclusions and exclusions that take precedence over normal authorization rules.
Exception Types
- Inclusions: Grant additional permissions to users or groups that they wouldn't normally have
- Exclusions: Deny permissions to users or groups even if they would normally have access through RBAC
Subject Types
- User: Exceptions that apply to specific individual users
- Group: Exceptions that apply to all members of a specific group
Priority System
Authorization exceptions use a priority system to resolve conflicts:
- Exclusions (highest priority) - Always deny access if applicable
- Inclusions (medium priority) - Grant access if no exclusions apply
- Normal RBAC (lowest priority) - Default system behavior
Within each type, exceptions with higher numerical priority values take precedence.
Database Schema
The oc_openregister_authorization_exceptions table stores authorization exceptions with the following key fields:
type: 'inclusion' or 'exclusion'subject_type: 'user' or 'group'subject_id: The actual user ID or group IDaction: CRUD operation ('create', 'read', 'update', 'delete')schema_uuid: Optional - limits exception to specific schemaregister_uuid: Optional - limits exception to specific registerorganization_uuid: Optional - limits exception to specific organizationpriority: Integer priority for conflict resolutionactive: Boolean to enable/disable exceptionsdescription: Human-readable description
Usage Examples
Example 1: Group Cross-Organization Access
Allow users in the 'ambtenaar' group to read 'gebruik' objects from all organizations:
$exception = new AuthorizationException();
$exception->setType(AuthorizationException::TYPE_INCLUSION);
$exception->setSubjectType(AuthorizationException::SUBJECT_TYPE_GROUP);
$exception->setSubjectId('ambtenaar');
$exception->setAction(AuthorizationException::ACTION_READ);
$exception->setSchemaUuid('gebruik-schema-uuid');
$exception->setPriority(20); // High priority to override multi-tenancy
$exception->setDescription('Allow ambtenaar group to read gebruik objects from all organizations');
$authService->createException(/* parameters */);
Example 2: User Exclusion
Deny a specific user update access despite group membership:
$exception = new AuthorizationException();
$exception->setType(AuthorizationException::TYPE_EXCLUSION);
$exception->setSubjectType(AuthorizationException::SUBJECT_TYPE_USER);
$exception->setSubjectId('problematic-user');
$exception->setAction(AuthorizationException::ACTION_UPDATE);
$exception->setSchemaUuid('sensitive-schema-uuid');
$exception->setPriority(15);
$exception->setDescription('Deny user update access due to security concerns');
API Usage
Creating Exceptions
# Create user inclusion
curl -X POST http://localhost/index.php/apps/openregister/api/authorization-exceptions \
-u 'admin:admin' \
-H 'Content-Type: application/json' \
-H 'OCS-APIREQUEST: true' \
-d '{
"type": "inclusion",
"subject_type": "user",
"subject_id": "special-user",
"action": "read",
"schema_uuid": "confidential-schema-uuid",
"priority": 10,
"description": "Allow special user to read confidential data"
}'
Listing Exceptions
# List all exceptions
curl http://localhost/index.php/apps/openregister/api/authorization-exceptions \
-u 'admin:admin' \
-H 'OCS-APIREQUEST: true'
# Filter by criteria
curl 'http://localhost/index.php/apps/openregister/api/authorization-exceptions?type=inclusion&active=true' \
-u 'admin:admin' \
-H 'OCS-APIREQUEST: true'
Integration with RBAC
The authorization exception system integrates seamlessly with the existing RBAC system:
- Query Level: The
ObjectEntityMapper::applyRbacFilters()method checks for exceptions before applying normal RBAC rules - Object Level: The
ObjectEntityMapper::checkObjectPermission()method evaluates exceptions first, then falls back to standard permission checks - Evaluation Order: Exclusions → Inclusions → Normal RBAC → Object ownership → Publication status
Best Practices
1. Use Specific Scope
Always limit exceptions to the most specific scope possible:
// Good - specific to schema and organization
$exception->setSchemaUuid('specific-schema');
$exception->setOrganizationUuid('specific-org');
// Avoid - too broad, affects everything
// (leaving schema_uuid and organization_uuid as null)
2. Set Appropriate Priorities
Use priority levels consistently:
- 1-10: Low priority inclusions
- 11-20: Medium priority inclusions
- 21-30: High priority inclusions
- 31-40: Low priority exclusions
- 41-50: Medium priority exclusions
- 51+: High priority exclusions
3. Document Exceptions
Always provide clear descriptions explaining why the exception exists:
$exception->setDescription('Allow support team to read customer data for troubleshooting purposes - ticket #12345');
4. Regular Audits
Regularly review authorization exceptions to ensure they're still needed:
// Find old exceptions
$oldExceptions = $mapper->findByCriteria([
'created_at' => '<' . (new DateTime('-6 months'))->format('Y-m-d'),
'active' => true
]);
Troubleshooting
Exception Not Working
-
Check if exception is active:
$exception = $mapper->findByUuid($uuid);
var_dump($exception->getActive()); -
Verify priority is high enough:
$exceptions = $service->getUserExceptions($userId);
usort($exceptions, fn($a, $b) => $b->getPriority() <=> $a->getPriority()); -
Check scope matching:
$result = $exception->matches($subjectType, $subjectId, $action, $schemaUuid);
Security Considerations
- Audit Trail: All exception creation/modification is logged with user information
- Admin Only: Only administrators should create/modify authorization exceptions
- Regular Review: Exceptions should be reviewed quarterly to ensure they're still appropriate
- Principle of Least Privilege: Use the most restrictive scope possible for each exception
Future Enhancements
-
Field-Level Authorization
- Control access to specific object properties
- Redact sensitive fields based on user permissions
-
Time-Based Permissions
- Temporary permission grants with expiration
- Scheduled permission changes
-
Audit Logging
- Log all authorization decisions
- Track permission changes over time
-
Permission Testing Tool
- UI for testing user permissions
- Visualize effective permissions for users/groups
-
RBAC Analytics
- Permission usage statistics
- Identify over-privileged users
- Suggest permission optimizations