- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
925 lines
31 KiB
PHP
925 lines
31 KiB
PHP
<?php
|
|
/*******************************************************************************************************************
|
|
| Software Name : EasyStream
|
|
| Software Description : High End YouTube Clone Script with Videos, Shorts, Streams, Images, Audio, Documents, Blogs
|
|
| Software Author : (c) Sami Ahmed
|
|
|*******************************************************************************************************************
|
|
|
|
|
|*******************************************************************************************************************
|
|
| This source file is subject to the EasyStream Proprietary License Agreement.
|
|
|
|
|
| By using this software, you acknowledge having read this Agreement and agree to be bound thereby.
|
|
|*******************************************************************************************************************
|
|
| Copyright (c) 2025 Sami Ahmed. All rights reserved.
|
|
|*******************************************************************************************************************/
|
|
|
|
defined('_ISVALID') or header('Location: /error');
|
|
|
|
/**
|
|
* Role-Based Access Control (RBAC) System
|
|
*/
|
|
class VRBAC
|
|
{
|
|
private static $instance = null;
|
|
private $db;
|
|
private $logger;
|
|
private $cache;
|
|
private $userRoles = [];
|
|
private $rolePermissions = [];
|
|
|
|
// Default system roles
|
|
const ROLE_GUEST = 'guest';
|
|
const ROLE_MEMBER = 'member';
|
|
const ROLE_VERIFIED = 'verified';
|
|
const ROLE_STREAMER = 'streamer';
|
|
const ROLE_CREATOR = 'creator';
|
|
const ROLE_MODERATOR = 'moderator';
|
|
const ROLE_ADMIN = 'admin';
|
|
const ROLE_SUPER_ADMIN = 'super_admin';
|
|
|
|
// Permission categories
|
|
const PERM_CONTENT = 'content';
|
|
const PERM_USER = 'user';
|
|
const PERM_ADMIN = 'admin';
|
|
const PERM_SYSTEM = 'system';
|
|
|
|
public static function getInstance()
|
|
{
|
|
if (self::$instance === null) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function __construct()
|
|
{
|
|
$this->db = VDatabase::getInstance();
|
|
$this->logger = VLogger::getInstance();
|
|
$this->cache = VRedis::getInstance();
|
|
|
|
$this->initializeDefaultRoles();
|
|
}
|
|
|
|
/**
|
|
* Check if user has permission
|
|
* @param int|string $userId User ID or current session user
|
|
* @param string $permission Permission name
|
|
* @param array $context Additional context for permission check
|
|
* @return bool True if user has permission
|
|
*/
|
|
/**
|
|
* Flexible permission checker supporting both legacy and new call signatures.
|
|
*
|
|
* Supported usages:
|
|
* - hasPermission($userId, $permission, $context = [])
|
|
* - hasPermission($permission, $userId = 'current', $context = [])
|
|
* - hasPermission($permission, $contextArray) // uses current session user
|
|
*
|
|
* @param mixed $arg1 User ID or permission name depending on signature
|
|
* @param mixed $arg2 Permission name, user ID, or context array
|
|
* @param mixed $arg3 Context array when using legacy signature
|
|
* @return bool
|
|
*/
|
|
public function hasPermission($arg1 = null, $arg2 = null, $arg3 = [])
|
|
{
|
|
try {
|
|
$permission = null;
|
|
$userId = null;
|
|
$context = [];
|
|
|
|
if (is_string($arg1) && ($arg2 === null || is_scalar($arg2) || is_array($arg2))) {
|
|
// New-style signature: ($permission, $userId?, $context?)
|
|
$permission = $arg1;
|
|
|
|
if (is_array($arg2) && ($arg3 === [] || $arg3 === null)) {
|
|
$context = $arg2;
|
|
$userId = 'current';
|
|
} else {
|
|
$userId = $arg2;
|
|
$context = is_array($arg3) ? $arg3 : [];
|
|
}
|
|
} else {
|
|
// Legacy signature: ($userId, $permission, $context)
|
|
$userId = $arg1;
|
|
$permission = $arg2;
|
|
$context = is_array($arg3) ? $arg3 : [];
|
|
}
|
|
|
|
if (is_array($userId)) {
|
|
// Handle accidental signature: hasPermission($permission, $context)
|
|
$context = $userId;
|
|
$userId = 'current';
|
|
}
|
|
|
|
if (!is_array($context)) {
|
|
$context = [];
|
|
}
|
|
|
|
if (!$permission || !is_string($permission)) {
|
|
$this->logger->warning('Permission check called without a valid permission name', [
|
|
'arg1' => $arg1,
|
|
'arg2' => $arg2,
|
|
'arg3' => $arg3
|
|
]);
|
|
return false;
|
|
}
|
|
|
|
// Handle current user
|
|
if ($userId === 'current' || $userId === null) {
|
|
$userId = $_SESSION['USER_ID'] ?? null;
|
|
}
|
|
|
|
if (!$userId) {
|
|
// Guest user - check guest permissions
|
|
return $this->checkGuestPermission($permission, $context);
|
|
}
|
|
|
|
// Get user roles
|
|
$userRoles = $this->getUserRoles($userId);
|
|
|
|
if (empty($userRoles)) {
|
|
// No roles assigned - treat as guest
|
|
return $this->checkGuestPermission($permission, $context);
|
|
}
|
|
|
|
// Check if any role has the permission
|
|
foreach ($userRoles as $role) {
|
|
if ($this->roleHasPermission($role, $permission, $context)) {
|
|
// Log permission grant for audit
|
|
$this->logPermissionCheck($userId, $permission, true, $role, $context);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check for resource-specific permissions
|
|
if (isset($context['resource_type']) && isset($context['resource_id'])) {
|
|
if ($this->hasResourcePermission($userId, $permission, $context['resource_type'], $context['resource_id'])) {
|
|
$this->logPermissionCheck($userId, $permission, true, 'resource_specific', $context);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Log permission denial
|
|
$this->logPermissionCheck($userId, $permission, false, implode(',', $userRoles), $context);
|
|
return false;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Permission check failed', [
|
|
'user_id' => $userId,
|
|
'permission' => $permission,
|
|
'context' => $context,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
// Fail secure - deny permission on error
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a user (or current session) has a specific role.
|
|
* Falls back to guest semantics when no authenticated user is present.
|
|
*
|
|
* @param string $role Target role name
|
|
* @param int|string|null $userId User identifier, `'current'` (default) uses session
|
|
* @return bool
|
|
*/
|
|
public function hasRole($role, $userId = 'current')
|
|
{
|
|
try {
|
|
if ($userId === 'current' || $userId === null) {
|
|
$userId = $_SESSION['USER_ID'] ?? null;
|
|
}
|
|
|
|
if (!$userId) {
|
|
return $role === self::ROLE_GUEST;
|
|
}
|
|
|
|
$roles = $this->getUserRoles($userId);
|
|
return in_array($role, $roles, true);
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Role check failed', [
|
|
'user_id' => $userId,
|
|
'role' => $role,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assign role to user
|
|
* @param int $userId User ID
|
|
* @param string $role Role name
|
|
* @param int $assignedBy User ID who assigned the role
|
|
* @param string $reason Reason for assignment
|
|
* @return bool Success status
|
|
*/
|
|
public function assignRole($userId, $role, $assignedBy = null, $reason = '')
|
|
{
|
|
try {
|
|
// Validate role exists
|
|
if (!$this->roleExists($role)) {
|
|
throw new Exception("Role does not exist: {$role}");
|
|
}
|
|
|
|
// Check if user already has this role
|
|
if ($this->userHasRole($userId, $role)) {
|
|
return true; // Already has role
|
|
}
|
|
|
|
// Insert role assignment
|
|
$assignmentData = [
|
|
'user_id' => $userId,
|
|
'role_name' => $role,
|
|
'assigned_by' => $assignedBy,
|
|
'assigned_at' => date('Y-m-d H:i:s'),
|
|
'reason' => $reason,
|
|
'status' => 'active'
|
|
];
|
|
|
|
$assignmentId = $this->db->doInsert('db_user_roles', $assignmentData);
|
|
|
|
if ($assignmentId) {
|
|
// Clear user role cache
|
|
$this->clearUserRoleCache($userId);
|
|
|
|
// Log role assignment
|
|
$this->logger->info('Role assigned to user', [
|
|
'user_id' => $userId,
|
|
'role' => $role,
|
|
'assigned_by' => $assignedBy,
|
|
'reason' => $reason
|
|
]);
|
|
|
|
// Send notification to user
|
|
$this->sendRoleNotification($userId, 'role_assigned', $role);
|
|
|
|
$this->handleRoleAssignmentSideEffects($userId, $role);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Role assignment failed', [
|
|
'user_id' => $userId,
|
|
'role' => $role,
|
|
'assigned_by' => $assignedBy,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove role from user
|
|
* @param int $userId User ID
|
|
* @param string $role Role name
|
|
* @param int $removedBy User ID who removed the role
|
|
* @param string $reason Reason for removal
|
|
* @return bool Success status
|
|
*/
|
|
public function removeRole($userId, $role, $removedBy = null, $reason = '')
|
|
{
|
|
try {
|
|
// Update role assignment status
|
|
$updateData = [
|
|
'status' => 'revoked',
|
|
'revoked_by' => $removedBy,
|
|
'revoked_at' => date('Y-m-d H:i:s'),
|
|
'revoke_reason' => $reason
|
|
];
|
|
|
|
$result = $this->db->doUpdate(
|
|
'db_user_roles',
|
|
['user_id', 'role_name', 'status'],
|
|
$updateData,
|
|
[$userId, $role, 'active']
|
|
);
|
|
|
|
if ($result) {
|
|
// Clear user role cache
|
|
$this->clearUserRoleCache($userId);
|
|
|
|
// Log role removal
|
|
$this->logger->info('Role removed from user', [
|
|
'user_id' => $userId,
|
|
'role' => $role,
|
|
'removed_by' => $removedBy,
|
|
'reason' => $reason
|
|
]);
|
|
|
|
// Send notification to user
|
|
$this->sendRoleNotification($userId, 'role_removed', $role);
|
|
|
|
$this->handleRoleRemovalSideEffects($userId, $role);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Role removal failed', [
|
|
'user_id' => $userId,
|
|
'role' => $role,
|
|
'removed_by' => $removedBy,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user roles
|
|
* @param int $userId User ID
|
|
* @return array User roles
|
|
*/
|
|
public function getUserRoles($userId)
|
|
{
|
|
if (isset($this->userRoles[$userId])) {
|
|
return $this->userRoles[$userId];
|
|
}
|
|
|
|
// Try cache first
|
|
$cacheKey = "user_roles_{$userId}";
|
|
if ($this->cache->isConnected()) {
|
|
$cachedRoles = $this->cache->get($cacheKey);
|
|
if ($cachedRoles !== false) {
|
|
$this->userRoles[$userId] = $cachedRoles;
|
|
return $cachedRoles;
|
|
}
|
|
}
|
|
|
|
// Get from database
|
|
$query = "SELECT role_name FROM db_user_roles WHERE user_id = ? AND status = 'active'";
|
|
$result = $this->db->doQuery($query, [$userId]);
|
|
|
|
$roles = [];
|
|
while ($row = $this->db->doFetch($result)) {
|
|
$roles[] = $row['role_name'];
|
|
}
|
|
|
|
// Add default member role if user has no roles
|
|
if (empty($roles) && $this->userExists($userId)) {
|
|
$roles[] = self::ROLE_MEMBER;
|
|
}
|
|
|
|
// Cache roles
|
|
$this->userRoles[$userId] = $roles;
|
|
if ($this->cache->isConnected()) {
|
|
$this->cache->set($cacheKey, $roles, 3600); // Cache for 1 hour
|
|
}
|
|
|
|
return $roles;
|
|
}
|
|
|
|
/**
|
|
* Check if role has permission
|
|
* @param string $role Role name
|
|
* @param string $permission Permission name
|
|
* @param array $context Permission context
|
|
* @return bool True if role has permission
|
|
*/
|
|
public function roleHasPermission($role, $permission, $context = [])
|
|
{
|
|
// Get role permissions
|
|
$rolePermissions = $this->getRolePermissions($role);
|
|
|
|
// Check direct permission
|
|
if (in_array($permission, $rolePermissions)) {
|
|
return true;
|
|
}
|
|
|
|
// Check wildcard permissions
|
|
$permissionParts = explode('.', $permission);
|
|
for ($i = count($permissionParts) - 1; $i > 0; $i--) {
|
|
$wildcardPerm = implode('.', array_slice($permissionParts, 0, $i)) . '.*';
|
|
if (in_array($wildcardPerm, $rolePermissions)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Check context-specific permissions
|
|
if (!empty($context)) {
|
|
return $this->checkContextualPermission($role, $permission, $context);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get role permissions
|
|
* @param string $role Role name
|
|
* @return array Role permissions
|
|
*/
|
|
public function getRolePermissions($role)
|
|
{
|
|
if (isset($this->rolePermissions[$role])) {
|
|
return $this->rolePermissions[$role];
|
|
}
|
|
|
|
// Try cache first
|
|
$cacheKey = "role_permissions_{$role}";
|
|
if ($this->cache->isConnected()) {
|
|
$cachedPermissions = $this->cache->get($cacheKey);
|
|
if ($cachedPermissions !== false) {
|
|
$this->rolePermissions[$role] = $cachedPermissions;
|
|
return $cachedPermissions;
|
|
}
|
|
}
|
|
|
|
// Get from database
|
|
$query = "SELECT permission_name FROM db_role_permissions WHERE role_name = ? AND status = 'active'";
|
|
$result = $this->db->doQuery($query, [$role]);
|
|
|
|
$permissions = [];
|
|
while ($row = $this->db->doFetch($result)) {
|
|
$permissions[] = $row['permission_name'];
|
|
}
|
|
|
|
// Add default permissions for built-in roles
|
|
$permissions = array_merge($permissions, $this->getDefaultRolePermissions($role));
|
|
|
|
// Cache permissions
|
|
$this->rolePermissions[$role] = $permissions;
|
|
if ($this->cache->isConnected()) {
|
|
$this->cache->set($cacheKey, $permissions, 3600); // Cache for 1 hour
|
|
}
|
|
|
|
return $permissions;
|
|
}
|
|
|
|
/**
|
|
* Create custom role
|
|
* @param string $roleName Role name
|
|
* @param string $displayName Display name
|
|
* @param string $description Role description
|
|
* @param array $permissions Initial permissions
|
|
* @param int $createdBy User ID who created the role
|
|
* @return bool Success status
|
|
*/
|
|
public function createRole($roleName, $displayName, $description = '', $permissions = [], $createdBy = null)
|
|
{
|
|
try {
|
|
// Check if role already exists
|
|
if ($this->roleExists($roleName)) {
|
|
throw new Exception("Role already exists: {$roleName}");
|
|
}
|
|
|
|
// Insert role
|
|
$roleData = [
|
|
'role_name' => $roleName,
|
|
'display_name' => $displayName,
|
|
'description' => $description,
|
|
'created_by' => $createdBy,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
'status' => 'active'
|
|
];
|
|
|
|
$roleId = $this->db->doInsert('db_roles', $roleData);
|
|
|
|
if ($roleId) {
|
|
// Add permissions
|
|
foreach ($permissions as $permission) {
|
|
$this->addRolePermission($roleName, $permission);
|
|
}
|
|
|
|
$this->logger->info('Custom role created', [
|
|
'role_name' => $roleName,
|
|
'display_name' => $displayName,
|
|
'permissions' => $permissions,
|
|
'created_by' => $createdBy
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Role creation failed', [
|
|
'role_name' => $roleName,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add permission to role
|
|
* @param string $role Role name
|
|
* @param string $permission Permission name
|
|
* @return bool Success status
|
|
*/
|
|
public function addRolePermission($role, $permission)
|
|
{
|
|
try {
|
|
$permissionData = [
|
|
'role_name' => $role,
|
|
'permission_name' => $permission,
|
|
'granted_at' => date('Y-m-d H:i:s'),
|
|
'status' => 'active'
|
|
];
|
|
|
|
$result = $this->db->doInsert('db_role_permissions', $permissionData);
|
|
|
|
if ($result) {
|
|
// Clear role permission cache
|
|
$this->clearRolePermissionCache($role);
|
|
|
|
$this->logger->info('Permission added to role', [
|
|
'role' => $role,
|
|
'permission' => $permission
|
|
]);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Failed to add role permission', [
|
|
'role' => $role,
|
|
'permission' => $permission,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize default system roles and permissions
|
|
*/
|
|
private function initializeDefaultRoles()
|
|
{
|
|
$defaultRoles = [
|
|
self::ROLE_GUEST => [
|
|
'display_name' => 'Guest',
|
|
'description' => 'Non-registered users',
|
|
'permissions' => [
|
|
'content.view',
|
|
'content.search',
|
|
'user.register'
|
|
]
|
|
],
|
|
self::ROLE_MEMBER => [
|
|
'display_name' => 'Member',
|
|
'description' => 'Registered users',
|
|
'permissions' => [
|
|
'content.*',
|
|
'user.profile.edit',
|
|
'user.upload.basic',
|
|
'user.comment',
|
|
'user.like',
|
|
'user.subscribe'
|
|
]
|
|
],
|
|
self::ROLE_VERIFIED => [
|
|
'display_name' => 'Verified User',
|
|
'description' => 'Email verified users',
|
|
'permissions' => [
|
|
'content.*',
|
|
'user.*',
|
|
'upload.advanced'
|
|
]
|
|
],
|
|
self::ROLE_STREAMER => [
|
|
'display_name' => 'Streamer',
|
|
'description' => 'Users allowed to broadcast live streams',
|
|
'permissions' => [
|
|
'live.stream.basic',
|
|
'live.stream.advanced',
|
|
'live.stream.record',
|
|
'live.stream.quality'
|
|
]
|
|
],
|
|
self::ROLE_CREATOR => [
|
|
'display_name' => 'Content Creator',
|
|
'description' => 'Content creators with enhanced publishing features',
|
|
'permissions' => [
|
|
'content.*',
|
|
'user.*',
|
|
'upload.*',
|
|
'analytics.view',
|
|
'monetization.basic'
|
|
]
|
|
],
|
|
self::ROLE_MODERATOR => [
|
|
'display_name' => 'Moderator',
|
|
'description' => 'Content moderators',
|
|
'permissions' => [
|
|
'content.*',
|
|
'user.*',
|
|
'moderation.*',
|
|
'admin.content.moderate',
|
|
'admin.users.moderate'
|
|
]
|
|
],
|
|
self::ROLE_ADMIN => [
|
|
'display_name' => 'Administrator',
|
|
'description' => 'Site administrators',
|
|
'permissions' => [
|
|
'content.*',
|
|
'user.*',
|
|
'admin.*',
|
|
'system.settings',
|
|
'system.logs.view'
|
|
]
|
|
],
|
|
self::ROLE_SUPER_ADMIN => [
|
|
'display_name' => 'Super Administrator',
|
|
'description' => 'Full system access',
|
|
'permissions' => [
|
|
'*' // All permissions
|
|
]
|
|
]
|
|
];
|
|
|
|
// This would typically be run during installation/setup
|
|
// For now, we'll store the default permissions in memory
|
|
foreach ($defaultRoles as $roleName => $roleData) {
|
|
$this->rolePermissions[$roleName] = $roleData['permissions'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get default permissions for built-in roles
|
|
* @param string $role Role name
|
|
* @return array Default permissions
|
|
*/
|
|
private function getDefaultRolePermissions($role)
|
|
{
|
|
$defaultPermissions = [
|
|
self::ROLE_GUEST => [
|
|
'content.view',
|
|
'content.search',
|
|
'user.register'
|
|
],
|
|
self::ROLE_MEMBER => [
|
|
'content.view',
|
|
'content.search',
|
|
'user.profile.edit',
|
|
'user.upload.basic',
|
|
'user.comment',
|
|
'user.like',
|
|
'user.subscribe'
|
|
],
|
|
self::ROLE_VERIFIED => [
|
|
'content.view',
|
|
'content.search',
|
|
'user.profile.edit',
|
|
'user.upload.basic',
|
|
'user.upload.advanced',
|
|
'user.comment',
|
|
'user.like',
|
|
'user.subscribe'
|
|
],
|
|
self::ROLE_STREAMER => [
|
|
'live.stream.basic',
|
|
'live.stream.advanced',
|
|
'live.stream.record',
|
|
'live.stream.quality'
|
|
],
|
|
self::ROLE_CREATOR => [
|
|
'content.view',
|
|
'content.search',
|
|
'user.profile.edit',
|
|
'user.upload.basic',
|
|
'user.upload.advanced',
|
|
'user.comment',
|
|
'user.like',
|
|
'user.subscribe',
|
|
'analytics.view',
|
|
'monetization.basic'
|
|
],
|
|
self::ROLE_MODERATOR => [
|
|
'content.view',
|
|
'content.search',
|
|
'content.moderate',
|
|
'user.profile.edit',
|
|
'user.moderate',
|
|
'admin.content.moderate',
|
|
'admin.users.moderate'
|
|
],
|
|
self::ROLE_ADMIN => [
|
|
'content.*',
|
|
'user.*',
|
|
'admin.*',
|
|
'system.settings',
|
|
'system.logs.view'
|
|
],
|
|
self::ROLE_SUPER_ADMIN => [
|
|
'*'
|
|
]
|
|
];
|
|
|
|
return $defaultPermissions[$role] ?? [];
|
|
}
|
|
|
|
/**
|
|
* Helper methods
|
|
*/
|
|
private function checkGuestPermission($permission, $context)
|
|
{
|
|
$guestPermissions = $this->getDefaultRolePermissions(self::ROLE_GUEST);
|
|
return in_array($permission, $guestPermissions) || in_array('*', $guestPermissions);
|
|
}
|
|
|
|
private function roleGrantsStreaming($role)
|
|
{
|
|
$streamingRoles = [
|
|
self::ROLE_STREAMER,
|
|
self::ROLE_ADMIN,
|
|
self::ROLE_SUPER_ADMIN
|
|
];
|
|
|
|
return in_array($role, $streamingRoles, true);
|
|
}
|
|
|
|
private function handleRoleAssignmentSideEffects($userId, $role)
|
|
{
|
|
if (!$this->roleGrantsStreaming($role)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
VStreamKeyManager::getInstance()->activateStreaming($userId);
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Failed to activate streaming during role assignment', [
|
|
'user_id' => $userId,
|
|
'role' => $role,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function handleRoleRemovalSideEffects($userId, $role)
|
|
{
|
|
if (!$this->roleGrantsStreaming($role)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// If the user still has streaming permission via another role, do not disable.
|
|
if ($this->hasPermission($userId, 'live.stream.basic')) {
|
|
return;
|
|
}
|
|
|
|
VStreamKeyManager::getInstance()->deactivateStreaming($userId);
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Failed to deactivate streaming during role removal', [
|
|
'user_id' => $userId,
|
|
'role' => $role,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
private function roleExists($role)
|
|
{
|
|
$query = "SELECT COUNT(*) as count FROM db_roles WHERE role_name = ? AND status = 'active'";
|
|
$result = $this->db->doQuery($query, [$role]);
|
|
$row = $this->db->doFetch($result);
|
|
|
|
return ($row['count'] > 0) || in_array($role, [
|
|
self::ROLE_GUEST, self::ROLE_MEMBER, self::ROLE_VERIFIED,
|
|
self::ROLE_CREATOR, self::ROLE_MODERATOR, self::ROLE_ADMIN, self::ROLE_SUPER_ADMIN
|
|
]);
|
|
}
|
|
|
|
private function userHasRole($userId, $role)
|
|
{
|
|
$userRoles = $this->getUserRoles($userId);
|
|
return in_array($role, $userRoles);
|
|
}
|
|
|
|
private function userExists($userId)
|
|
{
|
|
$query = "SELECT COUNT(*) as count FROM db_accountuser WHERE usr_id = ?";
|
|
$result = $this->db->doQuery($query, [$userId]);
|
|
$row = $this->db->doFetch($result);
|
|
|
|
return $row['count'] > 0;
|
|
}
|
|
|
|
private function hasResourcePermission($userId, $permission, $resourceType, $resourceId)
|
|
{
|
|
// Check if user owns the resource
|
|
if ($this->userOwnsResource($userId, $resourceType, $resourceId)) {
|
|
return true;
|
|
}
|
|
|
|
// Check resource-specific permissions
|
|
$query = "SELECT COUNT(*) as count FROM db_resource_permissions
|
|
WHERE user_id = ? AND resource_type = ? AND resource_id = ?
|
|
AND permission_name = ? AND status = 'active'";
|
|
$result = $this->db->doQuery($query, [$userId, $resourceType, $resourceId, $permission]);
|
|
$row = $this->db->doFetch($result);
|
|
|
|
return $row['count'] > 0;
|
|
}
|
|
|
|
private function userOwnsResource($userId, $resourceType, $resourceId)
|
|
{
|
|
$ownershipQueries = [
|
|
'video' => "SELECT COUNT(*) as count FROM db_videofiles WHERE file_key = ? AND usr_id = ?",
|
|
'channel' => "SELECT COUNT(*) as count FROM db_channels WHERE ch_id = ? AND usr_id = ?",
|
|
'playlist' => "SELECT COUNT(*) as count FROM db_playlists WHERE pl_id = ? AND usr_id = ?"
|
|
];
|
|
|
|
if (!isset($ownershipQueries[$resourceType])) {
|
|
return false;
|
|
}
|
|
|
|
$result = $this->db->doQuery($ownershipQueries[$resourceType], [$resourceId, $userId]);
|
|
$row = $this->db->doFetch($result);
|
|
|
|
return $row['count'] > 0;
|
|
}
|
|
|
|
private function checkContextualPermission($role, $permission, $context)
|
|
{
|
|
// Implement context-specific permission logic
|
|
// For example, moderators can only moderate content in their assigned categories
|
|
|
|
if ($role === self::ROLE_MODERATOR && isset($context['category'])) {
|
|
// Check if moderator is assigned to this category
|
|
$query = "SELECT COUNT(*) as count FROM db_moderator_categories
|
|
WHERE role_name = ? AND category = ? AND status = 'active'";
|
|
$result = $this->db->doQuery($query, [$role, $context['category']]);
|
|
$row = $this->db->doFetch($result);
|
|
|
|
return $row['count'] > 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private function clearUserRoleCache($userId)
|
|
{
|
|
unset($this->userRoles[$userId]);
|
|
if ($this->cache->isConnected()) {
|
|
$this->cache->delete("user_roles_{$userId}");
|
|
}
|
|
}
|
|
|
|
private function clearRolePermissionCache($role)
|
|
{
|
|
unset($this->rolePermissions[$role]);
|
|
if ($this->cache->isConnected()) {
|
|
$this->cache->delete("role_permissions_{$role}");
|
|
}
|
|
}
|
|
|
|
private function logPermissionCheck($userId, $permission, $granted, $role, $context)
|
|
{
|
|
$this->logger->info('Permission check', [
|
|
'user_id' => $userId,
|
|
'permission' => $permission,
|
|
'granted' => $granted,
|
|
'role' => $role,
|
|
'context' => $context,
|
|
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
|
|
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
|
|
]);
|
|
}
|
|
|
|
private function sendRoleNotification($userId, $type, $role)
|
|
{
|
|
try {
|
|
$queue = new VQueue();
|
|
|
|
$notificationData = [
|
|
'type' => 'in_app',
|
|
'recipient' => $userId,
|
|
'subject' => $type === 'role_assigned' ? 'Role Assigned' : 'Role Removed',
|
|
'message' => $type === 'role_assigned'
|
|
? "You have been assigned the role: {$role}"
|
|
: "Your role has been removed: {$role}",
|
|
'template_data' => [
|
|
'notification_type' => $type,
|
|
'data' => ['role' => $role]
|
|
]
|
|
];
|
|
|
|
$queue->enqueue('NotificationJob', $notificationData, 'notifications');
|
|
|
|
} catch (Exception $e) {
|
|
$this->logger->error('Failed to send role notification', [
|
|
'user_id' => $userId,
|
|
'type' => $type,
|
|
'role' => $role,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
}
|