Access Control
This document describes the access control system in Rondo Club.
Overview
Section titled “Overview”Rondo Club uses a mostly shared access model: authenticated users can see and edit all people and teams, with task-specific visibility rules for todos. On top of this, a role-based permission system controls access to administrative features and specific sections of the application.
Key principles:
- Authenticated users share core data - Once logged in, users can view and edit all people, teams, and dates
- Todo visibility is scoped - Users only see todos they created or todos assigned to them
- Trashed posts are hidden - Posts in the trash are not accessible via the frontend
- WP Admin is blocked - Non-admin users are redirected away from wp-admin
- Roles map from Sportlink - Sportlink “functies” are mapped to Rondo permission roles via the Functie-Capability Map
Implementation
Section titled “Implementation”The access control system is implemented in includes/class-access-control.php via the AccessControl class.
Controlled Post Types
Section titled “Controlled Post Types”Access control applies to these post types:
person- Contact recordsteam- Team/company recordsrondo_todo- Todo items
Standard WordPress posts and pages are not affected.
Hook Points
Section titled “Hook Points”The class intercepts data access at multiple levels:
| Hook | Purpose |
|---|---|
pre_get_posts | Blocks unauthenticated users from seeing any posts |
rest_{post_type}_query | Blocks unauthenticated users from REST API list queries |
rest_prepare_{post_type} | Verifies authentication for single item REST access |
Todo Visibility Rule
Section titled “Todo Visibility Rule”For post type rondo_todo, list and single-item access use this rule:
- Creator visibility:
post_author = current_user - Assignee visibility: post meta
assigned_user_id = current_user
This lets a user assign a todo to another user while still keeping the todo visible for themselves.
Access Check Methods
Section titled “Access Check Methods”Check if user can access a specific post:
$access_control = new Rondo\Core\AccessControl();$can_access = $access_control->user_can_access_post( $post_id, $user_id );// Returns false if: user not logged in, post trashed, or post doesn't existGet permission level:
$permission = $access_control->get_user_permission( $post_id, $user_id );// Returns: 'owner' (if user created the post), 'editor' (if logged in but not author), or falseBypassing Access Control
Section titled “Bypassing Access Control”Internal system code can bypass access control using suppress_filters:
$query = new WP_Query([ 'post_type' => 'person', 'suppress_filters' => true, // Bypasses pre_get_posts]);User Roles
Section titled “User Roles”Rondo Club creates a custom user role called “Rondo User” (rondo_user) on theme activation.
Capabilities:
read- Required for WordPress accessedit_posts- Create and edit postspublish_posts- Publish postsdelete_posts- Delete postsedit_published_posts- Edit published postsdelete_published_posts- Delete published postsupload_files- Upload files (photos, logos)
What Rondo Users cannot do:
- Manage other users
- Access WordPress admin settings
- Install plugins or themes
The role is removed on theme deactivation (users are reassigned to Subscriber).
WP Admin Blocking
Section titled “WP Admin Blocking”Non-admin users are blocked from accessing the WordPress admin panel (/wp-admin/). When a user without manage_options capability navigates to any wp-admin URL, they are immediately redirected to the app home page.
How It Works
Section titled “How It Works”A function hooked to admin_init checks whether the current user has the manage_options capability. If not, the user is redirected via wp_safe_redirect().
Exemptions
Section titled “Exemptions”The following request types are exempt from the redirect:
| Request Type | Detection | Why Exempt |
|---|---|---|
| AJAX | wp_doing_ajax() | admin-ajax.php serves frontend AJAX requests and lives under /wp-admin/ |
| WP-CLI | defined( 'WP_CLI' ) | CLI commands should never be redirected |
| Cron | defined( 'DOING_CRON' ) | Scheduled tasks must run unimpeded |
| Administrators | current_user_can( 'manage_options' ) | Admins need full wp-admin access |
REST API
Section titled “REST API”The WordPress REST API is not affected by admin blocking. REST requests do not go through admin_init (they use rest_api_init instead), so no exemption is needed.
Implementation
Section titled “Implementation”The blocking function is rondo_block_wp_admin() in functions.php, hooked to admin_init.
Functie-to-Role Mapping
Section titled “Functie-to-Role Mapping”Administrators can configure which Sportlink Functies (job titles from work history) automatically grant which Rondo WordPress roles. This configuration is used by Phase 206 (Capability Sync) during rondo-sync runs to grant or revoke roles.
Overview
Section titled “Overview”- Admin navigates to Settings > Beheer > Functies
- A checkbox matrix shows all known Functies as rows and all Rondo roles as columns
- Checking a cell means “this Functie grants this role”
- A Functie can grant multiple roles simultaneously
- Functies are populated automatically from
work_history.job_titledata in the database — admins never type them manually
Data Model
Section titled “Data Model”Stored in WordPress options as a nested associative array:
// Option key: rondo_functie_capability_map[ 'Trainer' => [ 'rondo_user' => true, 'rondo_fairplay' => false, 'rondo_vog' => false, 'rondo_bestuur' => false ], 'Penningmeester' => [ 'rondo_user' => true, 'rondo_fairplay' => false, 'rondo_vog' => false, 'rondo_bestuur' => true ],]Only roles checked true are considered granted — entries with false are ignored by get_roles_for_functie().
REST API
Section titled “REST API”| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET | /rondo/v1/functie-capability-map | Admin | Returns { map, roles } — current mapping and all Rondo role definitions |
POST | /rondo/v1/functie-capability-map | Admin | Accepts { map: {...} }, persists and returns updated { map, roles } |
GET response example:
{ "map": {}, "roles": [ { "slug": "rondo_user", "label": "Rondo User" }, { "slug": "rondo_fairplay", "label": "Rondo FairPlay" }, { "slug": "rondo_vog", "label": "Rondo VOG" }, { "slug": "rondo_bestuur", "label": "Rondo Bestuur" } ]}PHP Usage
Section titled “PHP Usage”The FunctieCapabilityMap class lives in includes/class-functie-capability-map.php under the Rondo\Config namespace.
// Get the full mapping$map = \Rondo\Config\FunctieCapabilityMap::get_map();
// Get role slugs granted by a specific Functie$roles = \Rondo\Config\FunctieCapabilityMap::get_roles_for_functie('Trainer');// Returns e.g. ['rondo_user', 'rondo_fairplay'] — only truthy entries
// Persist an updated mapping\Rondo\Config\FunctieCapabilityMap::update_map($map);This is the primary integration point for Phase 206 (Capability Sync): for each user’s active Functies, call get_roles_for_functie() to determine which roles they should have.
UI: Settings > Beheer > Functies
Section titled “UI: Settings > Beheer > Functies”The FunctiesTab component in src/pages/Settings/Settings.jsx renders the checkbox matrix:
- Rows: Union of Functies from
/rondo/v1/werkfuncties/availableand keys already in the saved map, sorted alphabetically - Columns: All Rondo roles from
/rondo/v1/functie-capability-mapresponse - Stale Functies: If a Functie exists in the saved map but is no longer returned by the available endpoint, the row still appears with the label
(niet meer actief)in gray italic
Stale Functies
Section titled “Stale Functies”When a Functie is removed from Sportlink (and no longer appears in work history), its row remains visible in the matrix with a (niet meer actief) label. This allows admins to review and clean up stale mappings. The mapping itself is preserved until the admin explicitly unchecks and saves.
Finance Settings Access
Section titled “Finance Settings Access”Users with the financieel role can access Financien > Instellingen (financial settings). Previously this was restricted to administrators only.
Security Considerations
Section titled “Security Considerations”- All access control is enforced server-side - Never trust client-side checks
- REST API is protected - Unauthenticated users receive 403 errors
- WP Admin is blocked - Non-admin users cannot access the WordPress dashboard
Related Documentation
Section titled “Related Documentation”- Multi-User System - User management and provisioning
- User Provisioning - Creating WordPress accounts for members
- Data Model - Post types and field definitions
- REST API - API endpoints