mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-18 01:28:58 +00:00
Implement opt-in guest access system allowing unauthenticated users to view forum content with configurable permissions. Features Added: - Guest access toggle in admin settings for forum-wide control - Guest role with configurable category-level permissions (view/post/reply) - Role type system (admin, moderator, default, guest, custom) for enhanced access control - Public page support in routing with automatic redirect to login when disabled - Public settings API endpoint for unauthenticated access to forum metadata - Guest role permissions UI in admin panel with clear capability restrictions - Database migration with automatic role type assignment and guest role seeding Security & Permission Improvements: - Permission middleware now validates permissions on public pages instead of skipping checks - Admin/moderator roles have full access; guest/default roles restricted from moderation - Guest role cannot be assigned to authenticated users Breaking Changes: - Forum title/subtitle moved from system config to app config (auto-migrated) - Permission middleware behavior changed for PublicPage routes (now checks permissions)
133 lines
3.5 KiB
PHP
133 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
namespace OCA\Forum\Service;
|
|
|
|
use OCA\Forum\Db\UserRole;
|
|
use OCA\Forum\Db\UserRoleMapper;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
/**
|
|
* Service for managing user role assignments
|
|
*/
|
|
class UserRoleService {
|
|
public function __construct(
|
|
private UserRoleMapper $userRoleMapper,
|
|
private LoggerInterface $logger,
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Assign a role to a user
|
|
*
|
|
* @param string $userId The user ID
|
|
* @param int $roleId The role ID to assign
|
|
* @param bool $skipIfExists If true, silently skip if user already has the role. If false, log a warning.
|
|
* @return UserRole|null The created UserRole, or null if already exists and skipIfExists is true
|
|
*/
|
|
public function assignRole(string $userId, int $roleId, bool $skipIfExists = true): ?UserRole {
|
|
// Check if user already has this role
|
|
if ($this->hasRole($userId, $roleId)) {
|
|
if ($skipIfExists) {
|
|
return null;
|
|
}
|
|
|
|
$this->logger->warning('User {userId} already has role {roleId}', [
|
|
'userId' => $userId,
|
|
'roleId' => $roleId,
|
|
]);
|
|
return null;
|
|
}
|
|
|
|
// Create and insert the new user role
|
|
$userRole = new UserRole();
|
|
$userRole->setUserId($userId);
|
|
$userRole->setRoleId($roleId);
|
|
$userRole->setCreatedAt(time());
|
|
|
|
try {
|
|
$createdRole = $this->userRoleMapper->insert($userRole);
|
|
$this->logger->info('Assigned role {roleId} to user {userId}', [
|
|
'userId' => $userId,
|
|
'roleId' => $roleId,
|
|
]);
|
|
return $createdRole;
|
|
} catch (\Exception $ex) {
|
|
$this->logger->error('Failed to assign role {roleId} to user {userId}: {error}', [
|
|
'userId' => $userId,
|
|
'roleId' => $roleId,
|
|
'error' => $ex->getMessage(),
|
|
]);
|
|
throw $ex;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if a user has a specific role
|
|
*
|
|
* @param string $userId The user ID
|
|
* @param int $roleId The role ID to check
|
|
* @return bool True if user has the role, false otherwise
|
|
*/
|
|
public function hasRole(string $userId, int $roleId): bool {
|
|
$userRoles = $this->userRoleMapper->findByUserId($userId);
|
|
foreach ($userRoles as $userRole) {
|
|
if ($userRole->getRoleId() === $roleId) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Remove a role from a user
|
|
*
|
|
* @param string $userId The user ID
|
|
* @param int $roleId The role ID to remove
|
|
* @return bool True if role was removed, false if user didn't have the role
|
|
*/
|
|
public function removeRole(string $userId, int $roleId): bool {
|
|
$userRoles = $this->userRoleMapper->findByUserId($userId);
|
|
foreach ($userRoles as $userRole) {
|
|
if ($userRole->getRoleId() === $roleId) {
|
|
try {
|
|
$this->userRoleMapper->delete($userRole);
|
|
$this->logger->info('Removed role {roleId} from user {userId}', [
|
|
'userId' => $userId,
|
|
'roleId' => $roleId,
|
|
]);
|
|
return true;
|
|
} catch (\Exception $ex) {
|
|
$this->logger->error('Failed to remove role {roleId} from user {userId}: {error}', [
|
|
'userId' => $userId,
|
|
'roleId' => $roleId,
|
|
'error' => $ex->getMessage(),
|
|
]);
|
|
throw $ex;
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->logger->debug('User {userId} does not have role {roleId}, nothing to remove', [
|
|
'userId' => $userId,
|
|
'roleId' => $roleId,
|
|
]);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get all role IDs for a user
|
|
*
|
|
* @param string $userId The user ID
|
|
* @return array<int> Array of role IDs
|
|
*/
|
|
public function getUserRoleIds(string $userId): array {
|
|
$userRoles = $this->userRoleMapper->findByUserId($userId);
|
|
return array_map(fn ($userRole) => $userRole->getRoleId(), $userRoles);
|
|
}
|
|
}
|