MCCodes - Chapter 8: Staff Panel & Permissions

MCCodes Walkthrough Tutorial

Welcome to Chapter 8! In Chapter 7: Cron Job System, we learned how MCCodes automates background tasks like refilling energy and processing jail times using scheduled jobs. Now, let's switch gears and look at how game administrators (or "staff") manage the game itself using the Staff Panel.

The Problem: Who Runs the Game?

Imagine a game with no one in charge. Who fixes problems? Who adds new items or features? Who makes sure players aren't cheating? A game needs administrators or "Staff Members" with special tools to manage players, items, settings, and more.

But we can't just give everyone these powerful tools! We need a separate, secure area for staff and a way to control which staff members can do which actions. For example, maybe a junior moderator can only read player reports, while a senior admin can edit any player's account.

The Solution: MCCodes provides a dedicated Staff Panel -- a private administrative backend -- and a Permissions System to control access within it.

The Control Room: staff.php and sglobals.php

The main entry point to the staff area is typically staff.php. When a staff member accesses this page or any other page within the staff panel (like staff_users.php to manage users, or staff_items.php to manage items), a special setup file called sglobals.php runs first.

sglobals.php: The Staff Setup

Remember globals.php from Chapter 2: Global Setup & Session Management? It sets up everything for regular players. sglobals.php is very similar, but it's specifically for staff pages. It does things like:

  1. Session & Basic Setup: Starts the session, connects to the database ($db), loads site settings ($set), and loads the logged-in user's data ($ir).
  2. Staff Check: Crucially, it verifies that the current user is actually a staff member. If not, it stops loading the page immediately. This prevents regular players from accessing staff tools. It uses a helper function, is_staff(), for this check.
  3. Staff Page Structure: It initializes the page builder object ($h) from Chapter 1: Page Rendering & Structure and uses a special method, $h->smenuarea(), to display the staff menu (smenu.php) instead of the regular game menu.

Here's a highly simplified conceptual look at part of sglobals.php:

<?php
// File: sglobals.php (Simplified Concept)

// ... (Session start, DB connection, load $set, load $ir) ...
require 'global_func.php'; // Contains is_staff() and check_access()

// *** Staff Check ***
// is_staff() checks if the user has any role assigned in the users_roles table
if (!is_staff())
{
    echo 'This page cannot be accessed.<br />&gt; <a href="index.php">Go Home</a>';
    die; // Stop execution if not staff
}

// ... (Check level, etc.) ...

// --- Initialize Page Builder for Staff ---
require 'header.php';
$h = new headers();
if (!isset($nohdr) || !$nohdr) {
    $h->startheaders();
    // ... (Display user data) ...
    $h->smenuarea(); // <--- Loads the STAFF menu (smenu.php)
}
?>

The key difference from globals.php is the is_staff() check and loading the smenu.php navigation.

staff.php: The Gatekeeper

The staff.php file itself often acts as the main dashboard or router for the staff panel. It might display some general information (like in the default MCCodes setup) or check the action parameter in the URL (staff.php?action=some_action) to include the correct staff_*.php file to handle that specific task.

For example, a request to staff_users.php?action=edituser might be handled directly by staff_users.php, or it might be routed through staff.php which then includes the relevant part of staff_users.php. The core idea is that sglobals.php runs first to secure the area and set up the environment.

Keycards and Permissions: staff_roles and check_access()

Okay, so sglobals.php makes sure only staff can get into the control room. But how do we control which buttons they can press once inside?

MCCodes uses a Role-Based Access Control (RBAC) system:

  1. Roles (staff_roles table): This table defines the different "keycards" or job titles available (e.g., "Moderator", "Assistant", "Administrator"). More importantly, it defines what permissions each role grants. Each permission is usually a simple Yes/No (boolean) column in the table.

    • Example staff_roles columns:
      • id (Unique Role ID)
      • name (e.g., "Moderator")
      • manage_users (Boolean: 1 for Yes, 0 for No)
      • manage_items (Boolean: 1 for Yes, 0 for No)
      • view_logs (Boolean: 1 for Yes, 0 for No)
      • manage_punishments (Boolean: 1 for Yes, 0 for No)
      • administrator (Boolean: If 1, grants ALL permissions)
      • ... and many more specific permissions.
  2. User Roles (users_roles table): This table links users to roles. It simply stores pairs of userid and staff_role (the ID from the staff_roles table). A user can have multiple roles.

    • Example users_roles entry: userid=5, staff_role=3 (User 5 has the role with ID 3).
  3. The Bouncer (check_access() function): This crucial function, usually found in global_func.php, acts like the security guard checking your keycard at each section door.

    • Input: It takes the permission needed (e.g., 'manage_users') and optionally the user ID to check (defaults to the currently logged-in user).
    • Process:
      1. It looks up the user's role IDs from the users_roles table.
      2. For each role the user has, it fetches the permissions granted by that role from the staff_roles table.
      3. It checks if any of the user's roles grant the specific permission requested (i.e., if the corresponding column is 1 or if the administrator column is 1).
    • Output: Returns true if access is granted, false otherwise.

Here's a simplified conceptual look at check_access():

<?php
// File: global_func.php (Simplified Concept)

/**
 * Checks if a user has a specific permission based on their assigned roles.
 * @param string|array $permissions The permission name(s) required (e.g., 'manage_users').
 * @param int|null $target_id The user ID to check (null for current user).
 * @return bool True if access granted, False otherwise.
 */
function check_access(string|array $permissions, ?int $target_id = null): bool
{
    global $db, $userid;
    $target_id ??= (int)$userid; // Use current user if not specified

    // 1. Get the user's role IDs from users_roles table
    $q_user_roles = $db->query("SELECT staff_role FROM users_roles WHERE userid = {$target_id}");
    $user_role_ids = [];
    while ($row = $db->fetch_row($q_user_roles)) {
        $user_role_ids[] = $row['staff_role'];
    }
    $db->free_result($q_user_roles);

    if (empty($user_role_ids)) {
        return false; // No roles assigned
    }

    // 2. Get all permissions granted by these roles from staff_roles table
    $q_role_perms = $db->query("SELECT * FROM staff_roles WHERE id IN (" . implode(',', $user_role_ids) . ")");
    $granted_permissions = [];
    while ($role_data = $db->fetch_row($q_role_perms)) {
        if ($role_data['administrator']) {
            return true; // Admins have all permissions!
        }
        foreach ($role_data as $perm_name => $is_granted) {
            // Add permission if granted (value is 1) and not already added
            if ($is_granted && !in_array($perm_name, $granted_permissions) && !in_array($perm_name, ['id', 'name'])) {
                 $granted_permissions[] = $perm_name;
            }
        }
    }
    $db->free_result($q_role_perms);

    // 3. Check if the required permission(s) are in the granted list
    if (is_string($permissions)) {
        $permissions = [$permissions]; // Make it an array if single string
    }
    // Convert requested permissions to DB column format (lowercase, underscores)
    $requested_db_perms = array_map(fn($p) => strtolower(str_replace([' ', '-'], '_', $p)), $permissions);

    $matches = array_intersect($granted_permissions, $requested_db_perms);

    return !empty($matches); // Access granted if there's at least one match
}

/**
 * Checks if the current user has any staff role.
 * @return bool True if staff, False otherwise.
 */
function is_staff(): bool
{
    global $db, $userid;
    $q = $db->query("SELECT COUNT(*) FROM users_roles WHERE staff_role > 0 AND userid = {$userid}");
    return $db->fetch_single($q) > 0;
}
?>

How It Works: Accessing a Staff Feature

Let's tie it all together. Imagine a staff member with the "Moderator" role (which grants manage_punishments but not manage_items) tries to access the "Add Item" page.

  1. Navigate: The Moderator clicks a link in the staff menu (smenu.php). Maybe they manually type staff_items.php?action=newitem into the address bar.
  2. Setup: The server starts running staff_items.php. The first line includes sglobals.php.
  3. Staff Check (sglobals.php): sglobals.php runs. It checks the session, connects to the DB, loads the Moderator's data ($ir), and calls is_staff(). Since the Moderator is staff, this check passes. sglobals.php finishes, and the staff menu is displayed.
  4. Permission Check (staff_items.php): Control returns to staff_items.php. Inside the code block that handles the newitem action, the first thing it does is check the specific permission required:

    <?php
    // File: staff_items.php (Simplified Concept for 'newitem' action)
    require_once('sglobals.php'); // Already ran, user is verified as staff
    
    // ... other code ...
    
    // Specific action: Adding a new item
    if ($_GET['action'] == 'newitem') {
    
        // *** Permission Check ***
        if (!check_access('manage_items')) {
            // Moderator does NOT have 'manage_items' permission
            echo 'You cannot access this area.<br />&gt; <a href="staff.php">Go Back</a>';
            $h->endpage();
            exit; // Stop execution
        }
    
        // --- If check passes, show the form ---
        echo "<h3>Adding an item...</h3>";
        // ... (code to display the 'Add Item' form) ...
    
    }
    // ... other actions like edititem, etc. ...
    ?>
    
  5. Access Denied: The check_access('manage_items') function is called for the Moderator.

    • It finds the Moderator's role ID(s).
    • It fetches the permissions for the "Moderator" role from staff_roles.
    • It sees that the manage_items column for the Moderator role is 0 (or false).
    • check_access() returns false.
  6. Stop Execution: The if condition (!check_access(...)) becomes true. The script prints the "You cannot access this area" message, calls $h->endpage(), and uses exit to stop running immediately. The Moderator never sees the "Add Item" form.

If an Administrator (whose role has administrator=1 or manage_items=1) accessed the same page, check_access('manage_items') would return true, the if condition would be false, and the script would continue to display the form.

Sequence Diagram: Accessing a Staff Page

sequenceDiagram
    participant B as Browser (Staff User)
    participant S as Server (staff_items.php)
    participant SG as sglobals.php
    participant CA as check_access()
    participant DB as Database

    B->>S: Request staff_items.php?action=newitem
    S->>SG: require 'sglobals.php'
    SG->>DB: Connect, Load Settings, Load User ($ir)
    SG->>CA: Call is_staff()
    CA->>DB: SELECT COUNT(*) FROM users_roles WHERE userid=X AND staff_role > 0
    DB-->>CA: Return count (e.g., 1)
    CA-->>SG: Return true
    SG->>S: Finish sglobals.php setup (display staff menu)
    S->>S: Reaches code for action='newitem'
    S->>CA: Call check_access('manage_items')
    CA->>DB: Get user's roles (SELECT staff_role FROM users_roles WHERE userid=X)
    DB-->>CA: Return role IDs (e.g., [3])
    CA->>DB: Get permissions for roles (SELECT * FROM staff_roles WHERE id IN (3))
    DB-->>CA: Return role data (e.g., {'manage_users': 1, 'manage_items': 0, ...})
    CA->>CA: Check if 'manage_items' is granted (No)
    CA-->>S: Return false
    S->>S: Access denied branch executes
    S-->>B: Display "You cannot access this area." message
    S->>S: exit()

This shows how sglobals.php verifies the user is staff, and then the specific page (staff_items.php) uses check_access() to verify the specific permission needed for that action before proceeding.

Key Files & Tables

Conclusion

You've now learned how MCCodes provides a secure and flexible way for administrators to manage the game!

This system allows for granular control over staff capabilities, ensuring that different staff members have access only to the tools appropriate for their responsibilities.

Managing a game involves not just adding features and controlling access, but also ensuring the security and integrity of the system itself. How does MCCodes protect against common web vulnerabilities and handle sensitive data like passwords?


Next Up: Chapter 9: Security Functions (CSRF/Password/Validation)

Previously: Chapter 7: Cron Job System


First published April 21, 2025

Tags: MCCodes Walkthrough Tutorial