HEX
Server: nginx
System: Linux pool195-106-36.bur.atomicsites.net 6.12.57+deb12-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.57-1~bpo12+1 (2025-11-17) x86_64
User: (0)
PHP: 8.3.32
Disabled: pcntl_fork
Upload Files
File: /wordpress/plugins/wp-cloud-client/beta/src/Access/RoleManager.php
<?php

declare(strict_types=1);

namespace VPlugins\WPCloudClient\Access;

use WP_User;

/**
 * SSO-driven WordPress role grant for one-click login.
 *
 * The platform's desired role (administrator vs editor) is written to the
 * `wpcc_desired_role` user meta by the one_click_login action before it hands
 * back the login URL. On every WordPress login the flag is read and the user's
 * role is reconciled to match — so a customer token following an admin token
 * downgrades the user on the next login, and rotating/removing the admin token
 * revokes admin access.
 *
 * Mirrors {@see \VPlugins\WPCloudClient\Multisite\SuperAdminManager}: only acts
 * when the platform has explicitly set the meta, leaving manually-assigned roles
 * on non-SSO installations untouched.
 */
final class RoleManager extends SsoLoginReconciler {

	/**
	 * User meta key that stores the platform's desired WordPress role.
	 */
	public const META_KEY = 'wpcc_desired_role';

	/**
	 * Roles the platform is permitted to assign via SSO.
	 *
	 * Constrained to the two roles LocalEdge maps to (admin portal -> administrator,
	 * customer portal -> editor). Anything else is rejected at the API boundary.
	 *
	 * @var string[]
	 */
	public const ALLOWED_ROLES = array( 'administrator', 'editor' );

	/**
	 * Priority 9 runs before SuperAdminManager (10) so the base role is set
	 * first and super admin is layered on top.
	 *
	 * @return int
	 */
	protected function hookPriority(): int {
		return 9;
	}

	/**
	 * Reconcile the user's WordPress role from the platform flag on login.
	 *
	 * @param WP_User $user The authenticated user object.
	 * @return void
	 */
	protected function reconcile( WP_User $user ): void {
		$role = get_user_meta( (int) $user->ID, self::META_KEY, true );

		// Absence of the meta key means the platform has no opinion — leave the
		// user's role untouched (mirrors SuperAdminManager's revoke caution).
		if ( ! is_string( $role ) || ! self::isAllowedRole( $role ) ) {
			return;
		}

		self::applyRole( $user, $role );
	}

	/**
	 * Reconcile a user's WordPress role to exactly the platform's desired role.
	 *
	 * The single authority for applying an SSO role — used both on login and at
	 * user auto-creation so the two paths can never diverge (both run the value
	 * through the `wpcc_user_role` filter and the same exact-match reconciliation).
	 *
	 * Reconciliation is by exact match, not membership: the user must end up with
	 * *only* this role. `WP_User::set_role()` is authoritative — it strips every
	 * other role — so a customer login following an admin login actually downgrades
	 * (the leftover `administrator` is removed), and an admin login promotes.
	 * The write is skipped only when the user already has exactly this one role,
	 * to avoid a redundant destructive `set_role()` on every login.
	 *
	 * @param WP_User $user The user to reconcile.
	 * @param string  $role The platform's desired role (must be an allowed role).
	 * @return void
	 */
	public static function applyRole( WP_User $user, string $role ): void {
		if ( ! self::isAllowedRole( $role ) ) {
			return;
		}

		/**
		 * Filter the desired WordPress role for an SSO login.
		 *
		 * @param string  $role The role the platform wants this user to have.
		 * @param WP_User $user The user being logged in.
		 */
		$role = (string) apply_filters( 'wpcc_user_role', $role, $user );

		if ( ! self::isAllowedRole( $role ) ) {
			return;
		}

		$currentRoles = (array) $user->roles;
		if ( 1 !== count( $currentRoles ) || ! in_array( $role, $currentRoles, true ) ) {
			$user->set_role( $role );
		}
	}

	/**
	 * Persist the platform's desired role as user meta.
	 *
	 * Intended to be called from the one_click_login flow before the login URL
	 * is handed back to the platform.
	 *
	 * @param int    $userId The WordPress user ID.
	 * @param string $role   The desired role (must be an allowed role).
	 * @return void
	 */
	public static function setRole( int $userId, string $role ): void {
		update_user_meta( $userId, self::META_KEY, $role );
	}

	/**
	 * Whether the given role may be assigned via SSO.
	 *
	 * @param string $role The role to check.
	 * @return bool
	 */
	public static function isAllowedRole( string $role ): bool {
		return in_array( $role, self::ALLOWED_ROLES, true );
	}
}