Files
nextcloud-forum/lib/Db/RoleMapper.php
Chen Asraf d2baf2a813 feat: add optional guest access with role-based permissions
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)
2025-11-25 02:02:46 +02:00

164 lines
4.2 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\Db;
use OCA\Forum\AppInfo\Application;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @template-extends QBMapper<Role>
*/
class RoleMapper extends QBMapper {
public function __construct(
IDBConnection $db,
) {
parent::__construct($db, Application::tableName('forum_roles'), Role::class);
}
/**
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find(int $id): Role {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()
->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
);
return $this->findEntity($qb);
}
/**
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function findByName(string $name): Role {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()
->eq('name', $qb->createNamedParameter($name, IQueryBuilder::PARAM_STR))
);
return $this->findEntity($qb);
}
/**
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function findByNameCaseInsensitive(string $name): Role {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()
->eq(
$qb->func()->lower('name'),
$qb->func()->lower($qb->createNamedParameter($name, IQueryBuilder::PARAM_STR))
)
);
return $this->findEntity($qb);
}
/**
* Find multiple roles by IDs at once
*
* @param array<int> $ids
* @return array<Role>
*/
public function findByIds(array $ids): array {
if (empty($ids)) {
return [];
}
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->in('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))
);
return $this->findEntities($qb);
}
/**
* @return array<Role>
*/
public function findAll(): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')->from($this->getTableName());
return $this->findEntities($qb);
}
/**
* Find all roles assigned to a user using JOIN
* More efficient than fetching user roles and then querying each role
*
* @param string $userId
* @return array<Role>
*/
public function findByUserId(string $userId): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('r.*')
->from($this->getTableName(), 'r')
->innerJoin('r', Application::tableName('forum_user_roles'), 'ur', 'r.id = ur.role_id')
->where(
$qb->expr()->eq('ur.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
);
return $this->findEntities($qb);
}
/**
* Find the default role (for assigning to new users)
*
* @return Role
* @throws DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
*/
public function findDefaultRole(): Role {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('role_type', $qb->createNamedParameter(Role::ROLE_TYPE_DEFAULT, IQueryBuilder::PARAM_STR))
);
return $this->findEntity($qb);
}
/**
* Find a role by its type
*
* @param string $roleType One of: admin, moderator, default, guest, custom
* @return Role
* @throws DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
*/
public function findByRoleType(string $roleType): Role {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('role_type', $qb->createNamedParameter($roleType, IQueryBuilder::PARAM_STR))
);
return $this->findEntity($qb);
}
}