mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
feat(admin): split role permissions for each section
This commit is contained in:
@@ -125,8 +125,7 @@ class AdminController extends OCSController {
|
||||
* 200: Users list returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[RequirePermission('canManageUsers')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/admin/users')]
|
||||
public function users(): DataResponse {
|
||||
try {
|
||||
@@ -246,7 +245,7 @@ class AdminController extends OCSController {
|
||||
* 200: Roles list returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canManageUsers', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/admin/roles')]
|
||||
public function getRoles(): DataResponse {
|
||||
@@ -274,7 +273,7 @@ class AdminController extends OCSController {
|
||||
* 200: Role assigned successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canEditRoles')]
|
||||
#[RequirePermission('canManageUsers')]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/admin/users/{userId}/roles')]
|
||||
public function assignRole(string $userId, int $roleId): DataResponse {
|
||||
try {
|
||||
@@ -332,7 +331,7 @@ class AdminController extends OCSController {
|
||||
* 200: Role removed successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canEditRoles')]
|
||||
#[RequirePermission('canManageUsers')]
|
||||
#[ApiRoute(verb: 'DELETE', url: '/api/admin/users/{userId}/roles/{roleId}')]
|
||||
public function removeRole(string $userId, int $roleId): DataResponse {
|
||||
try {
|
||||
|
||||
@@ -38,7 +38,7 @@ class BBCodeController extends OCSController {
|
||||
* 200: BBCodes returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canEditBbcodes')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/bbcodes')]
|
||||
public function index(int $limit = 100, int $offset = 0): DataResponse {
|
||||
try {
|
||||
@@ -101,7 +101,7 @@ class BBCodeController extends OCSController {
|
||||
* 200: BBCode returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canEditBbcodes')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/bbcodes/{id}')]
|
||||
public function show(int $id): DataResponse {
|
||||
try {
|
||||
@@ -135,7 +135,7 @@ class BBCodeController extends OCSController {
|
||||
* 201: BBCode created
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canEditBbcodes')]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/bbcodes')]
|
||||
public function create(string $tag, string $replacement, string $example, ?string $description = null, bool $enabled = true, bool $parseInner = true): DataResponse {
|
||||
try {
|
||||
@@ -173,7 +173,7 @@ class BBCodeController extends OCSController {
|
||||
* 200: BBCode updated
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canEditBbcodes')]
|
||||
#[ApiRoute(verb: 'PUT', url: '/api/bbcodes/{id}')]
|
||||
public function update(int $id, ?string $tag = null, ?string $replacement = null, ?string $example = null, ?string $description = null, ?bool $enabled = null, ?bool $parseInner = null): DataResponse {
|
||||
try {
|
||||
@@ -223,7 +223,7 @@ class BBCodeController extends OCSController {
|
||||
* 200: BBCode deleted
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canEditBbcodes')]
|
||||
#[ApiRoute(verb: 'DELETE', url: '/api/bbcodes/{id}')]
|
||||
public function destroy(int $id): DataResponse {
|
||||
try {
|
||||
|
||||
@@ -394,8 +394,7 @@ class CategoryController extends OCSController {
|
||||
* 200: Permissions returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditCategories', orGroup: 'access')]
|
||||
#[RequirePermission('canEditCategories')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/categories/{id}/permissions')]
|
||||
public function getPermissions(int $id, int $limit = 100, int $offset = 0): DataResponse {
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,7 @@ declare(strict_types=1);
|
||||
namespace OCA\Forum\Controller;
|
||||
|
||||
use OCA\Forum\Db\ForumUserMapper;
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCA\Forum\Service\UserService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Http;
|
||||
@@ -29,6 +30,7 @@ class ForumUserController extends OCSController {
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private ForumUserMapper $forumUserMapper,
|
||||
private RoleMapper $roleMapper,
|
||||
private UserService $userService,
|
||||
private IUserSession $userSession,
|
||||
private LoggerInterface $logger,
|
||||
@@ -98,8 +100,10 @@ class ForumUserController extends OCSController {
|
||||
#[ApiRoute(verb: 'GET', url: '/api/users/{userId}')]
|
||||
public function show(string $userId): DataResponse {
|
||||
try {
|
||||
$isMe = $userId === 'me';
|
||||
|
||||
// Handle "me" as special case for current user
|
||||
if ($userId === 'me') {
|
||||
if ($isMe) {
|
||||
$currentUser = $this->userSession->getUser();
|
||||
if (!$currentUser) {
|
||||
// Guest users have no forum profile - return 404 like a user who hasn't posted yet
|
||||
@@ -108,10 +112,21 @@ class ForumUserController extends OCSController {
|
||||
$userId = $currentUser->getUID();
|
||||
}
|
||||
|
||||
$forumUser = $this->forumUserMapper->find($userId);
|
||||
return new DataResponse($forumUser->jsonSerialize());
|
||||
} catch (DoesNotExistException $e) {
|
||||
return new DataResponse(['error' => 'Forum user not found'], Http::STATUS_NOT_FOUND);
|
||||
try {
|
||||
$forumUser = $this->forumUserMapper->find($userId);
|
||||
$data = $forumUser->jsonSerialize();
|
||||
} catch (DoesNotExistException $e) {
|
||||
if (!$isMe) {
|
||||
return new DataResponse(['error' => 'Forum user not found'], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
// For "me", return a minimal response so roles are still included
|
||||
$data = ['userId' => $userId];
|
||||
}
|
||||
|
||||
$roles = $this->roleMapper->findByUserId($userId);
|
||||
$data['roles'] = array_map(fn ($role) => $role->jsonSerialize(), $roles);
|
||||
|
||||
return new DataResponse($data);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error fetching forum user: ' . $e->getMessage());
|
||||
return new DataResponse(['error' => 'Failed to fetch forum user'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
|
||||
@@ -42,7 +42,7 @@ class RoleController extends OCSController {
|
||||
* 200: Roles returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canManageUsers', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/roles')]
|
||||
public function index(int $limit = 100, int $offset = 0): DataResponse {
|
||||
@@ -64,7 +64,7 @@ class RoleController extends OCSController {
|
||||
* 200: Role returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canManageUsers', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/roles/{id}')]
|
||||
public function show(int $id): DataResponse {
|
||||
@@ -87,8 +87,10 @@ class RoleController extends OCSController {
|
||||
* @param string|null $colorLight Light mode color
|
||||
* @param string|null $colorDark Dark mode color
|
||||
* @param bool $canAccessAdminTools Can access admin tools
|
||||
* @param bool $canManageUsers Can manage users
|
||||
* @param bool $canEditRoles Can edit roles
|
||||
* @param bool $canEditCategories Can edit categories
|
||||
* @param bool $canEditBbcodes Can edit BBCodes
|
||||
* @return DataResponse<Http::STATUS_CREATED, array<string, mixed>, array{}>
|
||||
*
|
||||
* 201: Role created
|
||||
@@ -102,8 +104,10 @@ class RoleController extends OCSController {
|
||||
?string $colorLight = null,
|
||||
?string $colorDark = null,
|
||||
bool $canAccessAdminTools = false,
|
||||
bool $canManageUsers = false,
|
||||
bool $canEditRoles = false,
|
||||
bool $canEditCategories = false,
|
||||
bool $canEditBbcodes = false,
|
||||
): DataResponse {
|
||||
try {
|
||||
$role = new \OCA\Forum\Db\Role();
|
||||
@@ -112,8 +116,10 @@ class RoleController extends OCSController {
|
||||
$role->setColorLight($colorLight);
|
||||
$role->setColorDark($colorDark);
|
||||
$role->setCanAccessAdminTools($canAccessAdminTools);
|
||||
$role->setCanManageUsers($canManageUsers);
|
||||
$role->setCanEditRoles($canEditRoles);
|
||||
$role->setCanEditCategories($canEditCategories);
|
||||
$role->setCanEditBbcodes($canEditBbcodes);
|
||||
$role->setCreatedAt(time());
|
||||
|
||||
/** @var \OCA\Forum\Db\Role */
|
||||
@@ -134,8 +140,10 @@ class RoleController extends OCSController {
|
||||
* @param string|null $colorLight Light mode color
|
||||
* @param string|null $colorDark Dark mode color
|
||||
* @param bool|null $canAccessAdminTools Can access admin tools
|
||||
* @param bool|null $canManageUsers Can manage users
|
||||
* @param bool|null $canEditRoles Can edit roles
|
||||
* @param bool|null $canEditCategories Can edit categories
|
||||
* @param bool|null $canEditBbcodes Can edit BBCodes
|
||||
* @return DataResponse<Http::STATUS_OK, array<string, mixed>, array{}>
|
||||
*
|
||||
* 200: Role updated
|
||||
@@ -150,8 +158,10 @@ class RoleController extends OCSController {
|
||||
?string $colorLight = null,
|
||||
?string $colorDark = null,
|
||||
?bool $canAccessAdminTools = null,
|
||||
?bool $canManageUsers = null,
|
||||
?bool $canEditRoles = null,
|
||||
?bool $canEditCategories = null,
|
||||
?bool $canEditBbcodes = null,
|
||||
): DataResponse {
|
||||
try {
|
||||
$role = $this->roleMapper->find($id);
|
||||
@@ -169,26 +179,36 @@ class RoleController extends OCSController {
|
||||
$role->setColorDark($colorDark);
|
||||
}
|
||||
|
||||
// Admin role always has all permissions - cannot be changed
|
||||
// Admin role always has all permissions — cannot be changed
|
||||
if ($role->getRoleType() === Role::ROLE_TYPE_ADMIN) {
|
||||
$role->setCanAccessAdminTools(true);
|
||||
$role->setCanManageUsers(true);
|
||||
$role->setCanEditRoles(true);
|
||||
$role->setCanEditCategories(true);
|
||||
$role->setCanEditBbcodes(true);
|
||||
} elseif ($role->getRoleType() === Role::ROLE_TYPE_GUEST) {
|
||||
// Guest role never has admin permissions - cannot be changed
|
||||
// Guest role never has management permissions — cannot be changed
|
||||
$role->setCanAccessAdminTools(false);
|
||||
$role->setCanManageUsers(false);
|
||||
$role->setCanEditRoles(false);
|
||||
$role->setCanEditCategories(false);
|
||||
$role->setCanEditBbcodes(false);
|
||||
} else {
|
||||
if ($canAccessAdminTools !== null) {
|
||||
$role->setCanAccessAdminTools($canAccessAdminTools);
|
||||
}
|
||||
if ($canManageUsers !== null) {
|
||||
$role->setCanManageUsers($canManageUsers);
|
||||
}
|
||||
if ($canEditRoles !== null) {
|
||||
$role->setCanEditRoles($canEditRoles);
|
||||
}
|
||||
if ($canEditCategories !== null) {
|
||||
$role->setCanEditCategories($canEditCategories);
|
||||
}
|
||||
if ($canEditBbcodes !== null) {
|
||||
$role->setCanEditBbcodes($canEditBbcodes);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var \OCA\Forum\Db\Role */
|
||||
@@ -247,8 +267,7 @@ class RoleController extends OCSController {
|
||||
* 200: Permissions returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/roles/{id}/permissions')]
|
||||
public function getPermissions(int $id, int $limit = 100, int $offset = 0): DataResponse {
|
||||
try {
|
||||
|
||||
@@ -7,13 +7,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Forum\Controller;
|
||||
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCA\Forum\Migration\SeedHelper;
|
||||
use OCA\Forum\Service\StatsService;
|
||||
use OCA\Forum\Service\UserRoleService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Migration\IOutput;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
@@ -22,12 +25,90 @@ class ServerAdminController extends OCSController {
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
private RoleMapper $roleMapper,
|
||||
private UserRoleService $userRoleService,
|
||||
private IUserManager $userManager,
|
||||
private StatsService $statsService,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available roles (for server admin panel)
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{roles: list<array<string, mixed>>}, array{}>
|
||||
*
|
||||
* 200: Roles list returned
|
||||
*/
|
||||
#[ApiRoute(verb: 'GET', url: '/api/server-admin/roles')]
|
||||
public function getRoles(): DataResponse {
|
||||
try {
|
||||
$roles = $this->roleMapper->findAll();
|
||||
$rolesData = array_map(fn ($role) => [
|
||||
'id' => $role->getId(),
|
||||
'name' => $role->getName(),
|
||||
'roleType' => $role->getRoleType(),
|
||||
], $roles);
|
||||
return new DataResponse(['roles' => $rolesData]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error fetching roles: ' . $e->getMessage());
|
||||
return new DataResponse(['error' => 'Failed to fetch roles'], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a role to a user (from server admin panel)
|
||||
*
|
||||
* @param string $userId The user ID
|
||||
* @param int $roleId The role ID to assign
|
||||
* @return DataResponse<Http::STATUS_OK, array{success: bool, message: string}, array{}>
|
||||
*
|
||||
* 200: Role assigned successfully
|
||||
*/
|
||||
#[ApiRoute(verb: 'POST', url: '/api/server-admin/users/{userId}/roles')]
|
||||
public function assignRole(string $userId, int $roleId): DataResponse {
|
||||
try {
|
||||
$user = $this->userManager->get($userId);
|
||||
if ($user === null) {
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => "User '$userId' does not exist.",
|
||||
], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$role = $this->roleMapper->find($roleId);
|
||||
} catch (\OCP\AppFramework\Db\DoesNotExistException $e) {
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => "Role with ID '$roleId' does not exist.",
|
||||
], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($this->userRoleService->hasRole($userId, $roleId)) {
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'message' => "User '$userId' already has the role '{$role->getName()}'.",
|
||||
]);
|
||||
}
|
||||
|
||||
$this->userRoleService->assignRole($userId, $roleId, skipIfExists: false);
|
||||
$this->logger->info("Assigned role '{$role->getName()}' to user '$userId'");
|
||||
|
||||
return new DataResponse([
|
||||
'success' => true,
|
||||
'message' => "Successfully assigned role '{$role->getName()}' to user '$userId'.",
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Error assigning role: ' . $e->getMessage());
|
||||
return new DataResponse([
|
||||
'success' => false,
|
||||
'message' => 'Failed to assign role: ' . $e->getMessage(),
|
||||
], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the repair seeds command to restore default forum data
|
||||
*
|
||||
|
||||
@@ -24,10 +24,14 @@ use OCP\AppFramework\Db\Entity;
|
||||
* @method void setColorDark(?string $value)
|
||||
* @method bool getCanAccessAdminTools()
|
||||
* @method void setCanAccessAdminTools(bool $value)
|
||||
* @method bool getCanManageUsers()
|
||||
* @method void setCanManageUsers(bool $value)
|
||||
* @method bool getCanEditRoles()
|
||||
* @method void setCanEditRoles(bool $value)
|
||||
* @method bool getCanEditCategories()
|
||||
* @method void setCanEditCategories(bool $value)
|
||||
* @method bool getCanEditBbcodes()
|
||||
* @method void setCanEditBbcodes(bool $value)
|
||||
* @method bool getIsSystemRole()
|
||||
* @method void setIsSystemRole(bool $value)
|
||||
* @method string getRoleType()
|
||||
@@ -48,8 +52,10 @@ class Role extends Entity implements JsonSerializable {
|
||||
protected $colorLight;
|
||||
protected $colorDark;
|
||||
protected $canAccessAdminTools;
|
||||
protected $canManageUsers;
|
||||
protected $canEditRoles;
|
||||
protected $canEditCategories;
|
||||
protected $canEditBbcodes;
|
||||
protected $isSystemRole;
|
||||
protected $roleType;
|
||||
protected $createdAt;
|
||||
@@ -61,8 +67,10 @@ class Role extends Entity implements JsonSerializable {
|
||||
$this->addType('colorLight', 'string');
|
||||
$this->addType('colorDark', 'string');
|
||||
$this->addType('canAccessAdminTools', 'boolean');
|
||||
$this->addType('canManageUsers', 'boolean');
|
||||
$this->addType('canEditRoles', 'boolean');
|
||||
$this->addType('canEditCategories', 'boolean');
|
||||
$this->addType('canEditBbcodes', 'boolean');
|
||||
$this->addType('isSystemRole', 'boolean');
|
||||
$this->addType('roleType', 'string');
|
||||
$this->addType('createdAt', 'integer');
|
||||
@@ -87,8 +95,10 @@ class Role extends Entity implements JsonSerializable {
|
||||
'colorLight' => $this->getColorLight(),
|
||||
'colorDark' => $this->getColorDark(),
|
||||
'canAccessAdminTools' => $this->getCanAccessAdminTools(),
|
||||
'canManageUsers' => $this->getCanManageUsers(),
|
||||
'canEditRoles' => $this->getCanEditRoles(),
|
||||
'canEditCategories' => $this->getCanEditCategories(),
|
||||
'canEditBbcodes' => $this->getCanEditBbcodes(),
|
||||
'isSystemRole' => $this->getIsSystemRole(),
|
||||
'roleType' => $this->getRoleType(),
|
||||
'createdAt' => $this->getCreatedAt(),
|
||||
|
||||
@@ -363,8 +363,10 @@ class SeedHelper {
|
||||
'color_light' => '#dc2626',
|
||||
'color_dark' => '#f87171',
|
||||
'can_access_admin_tools' => true,
|
||||
'can_manage_users' => true,
|
||||
'can_edit_roles' => true,
|
||||
'can_edit_categories' => true,
|
||||
'can_edit_bbcodes' => true,
|
||||
'is_system_role' => true,
|
||||
'role_type' => \OCA\Forum\Db\Role::ROLE_TYPE_ADMIN,
|
||||
],
|
||||
@@ -374,8 +376,10 @@ class SeedHelper {
|
||||
'color_light' => '#2563eb',
|
||||
'color_dark' => '#60a5fa',
|
||||
'can_access_admin_tools' => true,
|
||||
'can_manage_users' => true,
|
||||
'can_edit_roles' => false,
|
||||
'can_edit_categories' => false,
|
||||
'can_edit_bbcodes' => true,
|
||||
'is_system_role' => true,
|
||||
'role_type' => \OCA\Forum\Db\Role::ROLE_TYPE_MODERATOR,
|
||||
],
|
||||
@@ -385,8 +389,10 @@ class SeedHelper {
|
||||
'color_light' => '#059669',
|
||||
'color_dark' => '#34d399',
|
||||
'can_access_admin_tools' => false,
|
||||
'can_manage_users' => false,
|
||||
'can_edit_roles' => false,
|
||||
'can_edit_categories' => false,
|
||||
'can_edit_bbcodes' => false,
|
||||
'is_system_role' => true,
|
||||
'role_type' => \OCA\Forum\Db\Role::ROLE_TYPE_DEFAULT,
|
||||
],
|
||||
@@ -396,8 +402,10 @@ class SeedHelper {
|
||||
'color_light' => '#868e96',
|
||||
'color_dark' => '#adb5bd',
|
||||
'can_access_admin_tools' => false,
|
||||
'can_manage_users' => false,
|
||||
'can_edit_roles' => false,
|
||||
'can_edit_categories' => false,
|
||||
'can_edit_bbcodes' => false,
|
||||
'is_system_role' => true,
|
||||
'role_type' => \OCA\Forum\Db\Role::ROLE_TYPE_GUEST,
|
||||
],
|
||||
@@ -414,8 +422,10 @@ class SeedHelper {
|
||||
'color_light' => $qb->createNamedParameter($roleData['color_light'] ?? null),
|
||||
'color_dark' => $qb->createNamedParameter($roleData['color_dark'] ?? null),
|
||||
'can_access_admin_tools' => $qb->createNamedParameter($roleData['can_access_admin_tools'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
|
||||
'can_manage_users' => $qb->createNamedParameter($roleData['can_manage_users'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
|
||||
'can_edit_roles' => $qb->createNamedParameter($roleData['can_edit_roles'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
|
||||
'can_edit_categories' => $qb->createNamedParameter($roleData['can_edit_categories'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
|
||||
'can_edit_bbcodes' => $qb->createNamedParameter($roleData['can_edit_bbcodes'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
|
||||
'is_system_role' => $qb->createNamedParameter($roleData['is_system_role'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL),
|
||||
'role_type' => $qb->createNamedParameter($roleData['role_type'], \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_STR),
|
||||
'created_at' => $qb->createNamedParameter($timestamp, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_INT),
|
||||
|
||||
73
lib/Migration/Version27Date20260325000000.php
Normal file
73
lib/Migration/Version27Date20260325000000.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// SPDX-FileCopyrightText: Chen Asraf <contact@casraf.dev>
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
namespace OCA\Forum\Migration;
|
||||
|
||||
use Closure;
|
||||
use OCP\DB\ISchemaWrapper;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\Migration\IOutput;
|
||||
use OCP\Migration\SimpleMigrationStep;
|
||||
|
||||
/**
|
||||
* Version 10 Migration:
|
||||
* - Add can_manage_users and can_edit_bbcodes columns to forum_roles
|
||||
* - Backfill: can_manage_users = can_access_admin_tools OR can_edit_roles
|
||||
* - Backfill: can_edit_bbcodes = can_access_admin_tools
|
||||
*/
|
||||
class Version27Date20260325000000 extends SimpleMigrationStep {
|
||||
public function __construct(
|
||||
private IDBConnection $db,
|
||||
) {
|
||||
}
|
||||
|
||||
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
|
||||
/** @var ISchemaWrapper $schema */
|
||||
$schema = $schemaClosure();
|
||||
|
||||
if ($schema->hasTable('forum_roles')) {
|
||||
$table = $schema->getTable('forum_roles');
|
||||
|
||||
if (!$table->hasColumn('can_manage_users')) {
|
||||
$table->addColumn('can_manage_users', 'boolean', [
|
||||
'notnull' => false,
|
||||
'default' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$table->hasColumn('can_edit_bbcodes')) {
|
||||
$table->addColumn('can_edit_bbcodes', 'boolean', [
|
||||
'notnull' => false,
|
||||
'default' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void {
|
||||
// Backfill can_manage_users: anyone who had canAccessAdminTools OR canEditRoles
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->update('forum_roles')
|
||||
->set('can_manage_users', $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL))
|
||||
->where($qb->expr()->orX(
|
||||
$qb->expr()->eq('can_access_admin_tools', $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL)),
|
||||
$qb->expr()->eq('can_edit_roles', $qb->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL)),
|
||||
));
|
||||
$qb->executeStatement();
|
||||
|
||||
// Backfill can_edit_bbcodes: anyone who had canAccessAdminTools
|
||||
$qb2 = $this->db->getQueryBuilder();
|
||||
$qb2->update('forum_roles')
|
||||
->set('can_edit_bbcodes', $qb2->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL))
|
||||
->where($qb2->expr()->eq('can_access_admin_tools', $qb2->createNamedParameter(true, \OCP\DB\QueryBuilder\IQueryBuilder::PARAM_BOOL)));
|
||||
$qb2->executeStatement();
|
||||
|
||||
$output->info('Backfilled can_manage_users and can_edit_bbcodes from existing permissions');
|
||||
}
|
||||
}
|
||||
@@ -463,6 +463,297 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/server-admin/roles": {
|
||||
"get": {
|
||||
"operationId": "server_admin-get-roles",
|
||||
"summary": "Get all available roles (for server admin panel)",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"server_admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Roles list returned",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"roles"
|
||||
],
|
||||
"properties": {
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/server-admin/users/{userId}/roles": {
|
||||
"post": {
|
||||
"operationId": "server_admin-assign-role",
|
||||
"summary": "Assign a role to a user (from server admin panel)",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"server_admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"roleId"
|
||||
],
|
||||
"properties": {
|
||||
"roleId": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "The role ID to assign"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
"description": "The user ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Role assigned successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"success",
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/server-admin/repair-seeds": {
|
||||
"post": {
|
||||
"operationId": "server_admin-repair-seeds",
|
||||
|
||||
@@ -7445,6 +7445,11 @@
|
||||
"default": false,
|
||||
"description": "Can access admin tools"
|
||||
},
|
||||
"canManageUsers": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Can manage users"
|
||||
},
|
||||
"canEditRoles": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -7454,6 +7459,11 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Can edit categories"
|
||||
},
|
||||
"canEditBbcodes": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Can edit BBCodes"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7689,6 +7699,12 @@
|
||||
"default": null,
|
||||
"description": "Can access admin tools"
|
||||
},
|
||||
"canManageUsers": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Can manage users"
|
||||
},
|
||||
"canEditRoles": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
@@ -7700,6 +7716,12 @@
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Can edit categories"
|
||||
},
|
||||
"canEditBbcodes": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Can edit BBCodes"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12383,6 +12405,297 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/server-admin/roles": {
|
||||
"get": {
|
||||
"operationId": "server_admin-get-roles",
|
||||
"summary": "Get all available roles (for server admin panel)",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"server_admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Roles list returned",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"roles"
|
||||
],
|
||||
"properties": {
|
||||
"roles": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/server-admin/users/{userId}/roles": {
|
||||
"post": {
|
||||
"operationId": "server_admin-assign-role",
|
||||
"summary": "Assign a role to a user (from server admin panel)",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"server_admin"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"roleId"
|
||||
],
|
||||
"properties": {
|
||||
"roleId": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "The role ID to assign"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
"description": "The user ID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Role assigned successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"success",
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/server-admin/repair-seeds": {
|
||||
"post": {
|
||||
"operationId": "server_admin-repair-seeds",
|
||||
|
||||
22
openapi.json
22
openapi.json
@@ -7445,6 +7445,11 @@
|
||||
"default": false,
|
||||
"description": "Can access admin tools"
|
||||
},
|
||||
"canManageUsers": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Can manage users"
|
||||
},
|
||||
"canEditRoles": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
@@ -7454,6 +7459,11 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Can edit categories"
|
||||
},
|
||||
"canEditBbcodes": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Can edit BBCodes"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7689,6 +7699,12 @@
|
||||
"default": null,
|
||||
"description": "Can access admin tools"
|
||||
},
|
||||
"canManageUsers": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Can manage users"
|
||||
},
|
||||
"canEditRoles": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
@@ -7700,6 +7716,12 @@
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Can edit categories"
|
||||
},
|
||||
"canEditBbcodes": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "Can edit BBCodes"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ export default {
|
||||
try {
|
||||
this.rolesLoading = true
|
||||
this.rolesError = null
|
||||
const resp = await ocs.get('/admin/roles')
|
||||
const resp = await ocs.get('/server-admin/roles')
|
||||
this.roles = resp.data.roles
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch roles', e)
|
||||
@@ -248,7 +248,7 @@ export default {
|
||||
this.assignRole,
|
||||
async (task) => {
|
||||
const resp = await ocs.post(
|
||||
`/admin/users/${encodeURIComponent(this.userId.trim())}/roles`,
|
||||
`/server-admin/users/${encodeURIComponent(this.userId.trim())}/roles`,
|
||||
{ roleId: this.selectedRole.id },
|
||||
)
|
||||
task.success = resp.data.success
|
||||
|
||||
@@ -140,7 +140,7 @@
|
||||
</NcAppNavigationItem>
|
||||
|
||||
<NcAppNavigationItem
|
||||
v-if="canEditRoles"
|
||||
v-if="canManageUsers"
|
||||
:name="strings.navAdminUsers"
|
||||
:to="{ path: '/admin/users' }"
|
||||
:active="isPathActive('/admin/users', true)"
|
||||
@@ -173,7 +173,7 @@
|
||||
</NcAppNavigationItem>
|
||||
|
||||
<NcAppNavigationItem
|
||||
v-if="canAccessAdminTools"
|
||||
v-if="canEditBbcodes"
|
||||
:name="strings.navAdminBBCodes"
|
||||
:to="{ path: '/admin/bbcodes' }"
|
||||
:active="isPathActive('/admin/bbcodes', true)"
|
||||
@@ -266,8 +266,14 @@ export default defineComponent({
|
||||
setup() {
|
||||
const { categoryHeaders, fetchCategories } = useCategories()
|
||||
const { userId, displayName, fetchCurrentUser } = useCurrentUser()
|
||||
const { canAccessAdmin, canAccessAdminTools, canEditRoles, canEditCategories, fetchUserRoles } =
|
||||
useUserRole()
|
||||
const {
|
||||
canAccessAdmin,
|
||||
canAccessAdminTools,
|
||||
canManageUsers,
|
||||
canEditRoles,
|
||||
canEditCategories,
|
||||
canEditBbcodes,
|
||||
} = useUserRole()
|
||||
const { categoryId: currentThreadCategoryId, fetchThread, clearThread } = useCurrentThread()
|
||||
const { isGuest, guestDisplayName, fetchGuestIdentity } = useGuestSession()
|
||||
|
||||
@@ -275,13 +281,14 @@ export default defineComponent({
|
||||
categoryHeaders,
|
||||
fetchCategories,
|
||||
fetchCurrentUser,
|
||||
fetchUserRoles,
|
||||
userId,
|
||||
displayName,
|
||||
canAccessAdmin,
|
||||
canAccessAdminTools,
|
||||
canManageUsers,
|
||||
canEditRoles,
|
||||
canEditCategories,
|
||||
canEditBbcodes,
|
||||
currentThreadCategoryId,
|
||||
fetchThread,
|
||||
clearThread,
|
||||
@@ -329,13 +336,8 @@ export default defineComponent({
|
||||
this.fetchCurrentUser(),
|
||||
])
|
||||
|
||||
// If user was fetched successfully, also fetch their roles
|
||||
if (userResult.status === 'fulfilled' && userResult.value) {
|
||||
// Wait for roles to load before showing the sidebar
|
||||
await this.fetchUserRoles(userResult.value.userId).catch((e) => {
|
||||
console.error('Failed to load user roles:', e)
|
||||
})
|
||||
} else if (this.isGuest) {
|
||||
// Roles are included in the /users/me response and populated automatically
|
||||
if (userResult.status !== 'fulfilled' && this.isGuest) {
|
||||
// Fetch guest identity for sidebar display
|
||||
await this.fetchGuestIdentity().catch((e) => {
|
||||
console.error('Failed to load guest identity:', e)
|
||||
@@ -465,10 +467,14 @@ export default defineComponent({
|
||||
// Navigate to the first available management item
|
||||
if (this.canAccessAdminTools) {
|
||||
this.$router.push({ path: '/admin' })
|
||||
} else if (this.canEditRoles) {
|
||||
} else if (this.canManageUsers) {
|
||||
this.$router.push({ path: '/admin/users' })
|
||||
} else if (this.canEditRoles) {
|
||||
this.$router.push({ path: '/admin/roles' })
|
||||
} else if (this.canEditCategories) {
|
||||
this.$router.push({ path: '/admin/categories' })
|
||||
} else if (this.canEditBbcodes) {
|
||||
this.$router.push({ path: '/admin/bbcodes' })
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ref, computed, type Ref } from 'vue'
|
||||
import { ocs } from '@/axios'
|
||||
import type { ForumUser } from '@/types'
|
||||
import type { ForumUser, Role } from '@/types'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { useUserRole } from '@/composables/useUserRole'
|
||||
|
||||
const currentForumUser = ref<ForumUser | null>(null)
|
||||
const loading = ref<boolean>(false)
|
||||
@@ -9,6 +10,8 @@ const error = ref<string | null>(null)
|
||||
const loaded = ref<boolean>(false)
|
||||
|
||||
export function useCurrentUser() {
|
||||
const { setRoles } = useUserRole()
|
||||
|
||||
const fetchCurrentUser = async (force = false): Promise<ForumUser | null> => {
|
||||
// Don't refetch if already loaded unless forced
|
||||
if (loaded.value && !force) {
|
||||
@@ -19,19 +22,21 @@ export function useCurrentUser() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
const response = await ocs.get<ForumUser>('/users/me')
|
||||
currentForumUser.value = response.data
|
||||
const response = await ocs.get<ForumUser & { roles?: Role[] }>('/users/me')
|
||||
const { roles, ...forumUser } = response.data ?? {}
|
||||
currentForumUser.value = forumUser as ForumUser
|
||||
loaded.value = true
|
||||
|
||||
// Extract roles from the response and populate the role composable
|
||||
if (roles) {
|
||||
const uid = nextcloudUser.value?.uid
|
||||
if (uid) {
|
||||
setRoles(uid, roles)
|
||||
}
|
||||
}
|
||||
|
||||
return currentForumUser.value
|
||||
} catch (e: unknown) {
|
||||
// If 404, forum user doesn't exist yet (user hasn't posted) - this is OK
|
||||
const err = e as { response?: { status?: number } }
|
||||
if (err?.response?.status === 404) {
|
||||
console.debug('Forum user not found - user has not posted yet')
|
||||
currentForumUser.value = null
|
||||
loaded.value = true
|
||||
return null
|
||||
}
|
||||
console.error('Failed to fetch current forum user', e)
|
||||
error.value = (e as Error).message || 'Failed to load user information'
|
||||
return null
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { ocs } from '@/axios'
|
||||
import { isAdminRole, isModeratorRole } from '@/constants'
|
||||
import type { Role } from '@/types'
|
||||
|
||||
@@ -10,26 +9,13 @@ const loaded = ref<boolean>(false)
|
||||
const currentUserId = ref<string | null>(null)
|
||||
|
||||
export function useUserRole() {
|
||||
const fetchUserRoles = async (userId: string, force = false): Promise<Role[]> => {
|
||||
if (loaded.value && !force && currentUserId.value === userId) {
|
||||
return userRoles.value
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
const response = await ocs.get<Role[]>(`/users/${userId}/roles`)
|
||||
userRoles.value = response.data || []
|
||||
currentUserId.value = userId
|
||||
loaded.value = true
|
||||
return userRoles.value
|
||||
} catch (e) {
|
||||
error.value = (e as Error).message || 'Failed to fetch user roles'
|
||||
console.error('Failed to fetch user roles:', e)
|
||||
return []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
/**
|
||||
* Set roles directly (called by useCurrentUser after fetching /users/me)
|
||||
*/
|
||||
const setRoles = (userId: string, roles: Role[]): void => {
|
||||
userRoles.value = roles
|
||||
currentUserId.value = userId
|
||||
loaded.value = true
|
||||
}
|
||||
|
||||
const isAdmin = computed<boolean>(() => {
|
||||
@@ -44,8 +30,8 @@ export function useUserRole() {
|
||||
return userRoles.value.some((role) => role.canAccessAdminTools)
|
||||
})
|
||||
|
||||
const canAccessAdmin = computed<boolean>(() => {
|
||||
return canAccessAdminTools.value || canEditRoles.value || canEditCategories.value
|
||||
const canManageUsers = computed<boolean>(() => {
|
||||
return userRoles.value.some((role) => role.canManageUsers)
|
||||
})
|
||||
|
||||
const canEditRoles = computed<boolean>(() => {
|
||||
@@ -56,12 +42,19 @@ export function useUserRole() {
|
||||
return userRoles.value.some((role) => role.canEditCategories)
|
||||
})
|
||||
|
||||
const refresh = () => {
|
||||
if (currentUserId.value) {
|
||||
loaded.value = false
|
||||
return fetchUserRoles(currentUserId.value, true)
|
||||
}
|
||||
}
|
||||
const canEditBbcodes = computed<boolean>(() => {
|
||||
return userRoles.value.some((role) => role.canEditBbcodes)
|
||||
})
|
||||
|
||||
const canAccessAdmin = computed<boolean>(() => {
|
||||
return (
|
||||
canAccessAdminTools.value ||
|
||||
canManageUsers.value ||
|
||||
canEditRoles.value ||
|
||||
canEditCategories.value ||
|
||||
canEditBbcodes.value
|
||||
)
|
||||
})
|
||||
|
||||
const clear = () => {
|
||||
userRoles.value = []
|
||||
@@ -79,10 +72,11 @@ export function useUserRole() {
|
||||
isModerator,
|
||||
canAccessAdmin,
|
||||
canAccessAdminTools,
|
||||
canManageUsers,
|
||||
canEditRoles,
|
||||
canEditCategories,
|
||||
fetchUserRoles,
|
||||
refresh,
|
||||
canEditBbcodes,
|
||||
setRoles,
|
||||
clear,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +83,13 @@ router.beforeEach(async (to, from, next) => {
|
||||
|
||||
// Check if the route is an admin route
|
||||
if (to.path.startsWith('/admin')) {
|
||||
const { canAccessAdmin, fetchUserRoles, loaded } = useUserRole()
|
||||
const { canAccessAdmin, loaded } = useUserRole()
|
||||
|
||||
// Fetch user and roles if not already loaded
|
||||
// Roles are populated by fetchCurrentUser (/users/me includes roles).
|
||||
// On direct page load the guard may run before AppNavigation, so fetch now.
|
||||
if (!loaded.value && userId.value) {
|
||||
await fetchUserRoles(userId.value)
|
||||
const { fetchCurrentUser } = useCurrentUser()
|
||||
await fetchCurrentUser()
|
||||
}
|
||||
|
||||
// Redirect users without admin access to home
|
||||
|
||||
@@ -139,8 +139,10 @@ export interface Role {
|
||||
colorLight: string | null
|
||||
colorDark: string | null
|
||||
canAccessAdminTools: boolean
|
||||
canManageUsers: boolean
|
||||
canEditRoles: boolean
|
||||
canEditCategories: boolean
|
||||
canEditBbcodes: boolean
|
||||
isSystemRole: boolean
|
||||
roleType: 'admin' | 'moderator' | 'default' | 'guest' | 'custom'
|
||||
createdAt: number
|
||||
|
||||
@@ -121,6 +121,15 @@
|
||||
<span class="checkbox-desc muted">{{ strings.canAccessAdminToolsDesc }}</span>
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<NcCheckboxRadioSwitch
|
||||
v-model="formData.canManageUsers"
|
||||
:disabled="isAdmin || isGuest"
|
||||
class="permission-switch"
|
||||
>
|
||||
<strong>{{ strings.canManageUsers }}</strong>
|
||||
<span class="checkbox-desc muted">{{ strings.canManageUsersDesc }}</span>
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<NcCheckboxRadioSwitch
|
||||
v-model="formData.canEditRoles"
|
||||
:disabled="isAdmin || isGuest"
|
||||
@@ -138,6 +147,15 @@
|
||||
<strong>{{ strings.canEditCategories }}</strong>
|
||||
<span class="checkbox-desc muted">{{ strings.canEditCategoriesDesc }}</span>
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<NcCheckboxRadioSwitch
|
||||
v-model="formData.canEditBbcodes"
|
||||
:disabled="isAdmin || isGuest"
|
||||
class="permission-switch"
|
||||
>
|
||||
<strong>{{ strings.canEditBbcodes }}</strong>
|
||||
<span class="checkbox-desc muted">{{ strings.canEditBbcodesDesc }}</span>
|
||||
</NcCheckboxRadioSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
||||
@@ -245,8 +263,10 @@ export default defineComponent({
|
||||
colorLight: '#000000',
|
||||
colorDark: '#ffffff',
|
||||
canAccessAdminTools: false,
|
||||
canManageUsers: false,
|
||||
canEditRoles: false,
|
||||
canEditCategories: false,
|
||||
canEditBbcodes: false,
|
||||
},
|
||||
darkColorModified: false,
|
||||
permissions: {} as Record<number, CategoryPermission>,
|
||||
@@ -274,15 +294,22 @@ export default defineComponent({
|
||||
reset: t('forum', 'Reset'),
|
||||
rolePermissions: t('forum', 'Role permissions'),
|
||||
rolePermissionsDesc: t('forum', 'Set global permissions for this role'),
|
||||
canAccessAdminTools: t('forum', 'Can access management tools'),
|
||||
canAccessAdminTools: t('forum', 'Dashboard and forum settings'),
|
||||
canAccessAdminToolsDesc: t(
|
||||
'forum',
|
||||
'Allow access to the management dashboard, forum settings, and BBCode management',
|
||||
'Allow access to the management dashboard and forum settings',
|
||||
),
|
||||
canEditRoles: t('forum', 'Can edit roles'),
|
||||
canEditRolesDesc: t('forum', 'Allow creating, editing and deleting roles'),
|
||||
canEditCategories: t('forum', 'Can edit categories'),
|
||||
canManageUsers: t('forum', 'Account management'),
|
||||
canManageUsersDesc: t('forum', 'Allow viewing accounts and assigning roles'),
|
||||
canEditRoles: t('forum', 'Roles and teams management'),
|
||||
canEditRolesDesc: t(
|
||||
'forum',
|
||||
'Allow creating, editing and deleting roles and team permissions',
|
||||
),
|
||||
canEditCategories: t('forum', 'Category management'),
|
||||
canEditCategoriesDesc: t('forum', 'Allow creating, editing and deleting categories'),
|
||||
canEditBbcodes: t('forum', 'BBCode management'),
|
||||
canEditBbcodesDesc: t('forum', 'Allow creating, editing and deleting custom BBCodes'),
|
||||
categoryPermissions: t('forum', 'Category permissions'),
|
||||
categoryPermissionsDesc: t('forum', 'Set which categories this role can access'),
|
||||
adminAllRolePermissions: t('forum', 'Admin role must have all permissions enabled'),
|
||||
@@ -411,28 +438,36 @@ export default defineComponent({
|
||||
this.formData.colorLight = this.role.colorLight || fallback?.light || '#000000'
|
||||
this.formData.colorDark = this.role.colorDark || fallback?.dark || '#ffffff'
|
||||
this.formData.canAccessAdminTools = this.role.canAccessAdminTools || false
|
||||
this.formData.canManageUsers = this.role.canManageUsers || false
|
||||
this.formData.canEditRoles = this.role.canEditRoles || false
|
||||
this.formData.canEditCategories = this.role.canEditCategories || false
|
||||
this.formData.canEditBbcodes = this.role.canEditBbcodes || false
|
||||
|
||||
// Admin role always has all permissions
|
||||
if (this.isAdmin) {
|
||||
this.formData.canAccessAdminTools = true
|
||||
this.formData.canManageUsers = true
|
||||
this.formData.canEditRoles = true
|
||||
this.formData.canEditCategories = true
|
||||
this.formData.canEditBbcodes = true
|
||||
}
|
||||
|
||||
// Guest role never has admin permissions
|
||||
// Guest role never has management permissions
|
||||
if (this.isGuest) {
|
||||
this.formData.canAccessAdminTools = false
|
||||
this.formData.canManageUsers = false
|
||||
this.formData.canEditRoles = false
|
||||
this.formData.canEditCategories = false
|
||||
this.formData.canEditBbcodes = false
|
||||
}
|
||||
|
||||
// Default role never has admin permissions (same as guest)
|
||||
// Default role never has management permissions (same as guest)
|
||||
if (this.isDefault) {
|
||||
this.formData.canAccessAdminTools = false
|
||||
this.formData.canManageUsers = false
|
||||
this.formData.canEditRoles = false
|
||||
this.formData.canEditCategories = false
|
||||
this.formData.canEditBbcodes = false
|
||||
}
|
||||
|
||||
// If colors are different, mark dark as modified
|
||||
@@ -521,22 +556,18 @@ export default defineComponent({
|
||||
try {
|
||||
this.submitting = true
|
||||
|
||||
const perm = (value: boolean) => (this.isAdmin ? true : this.isGuest ? false : value)
|
||||
|
||||
const roleData = {
|
||||
name: this.formData.name.trim(),
|
||||
description: this.formData.description.trim() || null,
|
||||
colorLight: this.formData.colorLight || null,
|
||||
colorDark: this.formData.colorDark || null,
|
||||
canAccessAdminTools: this.isAdmin
|
||||
? true
|
||||
: this.isGuest
|
||||
? false
|
||||
: this.formData.canAccessAdminTools,
|
||||
canEditRoles: this.isAdmin ? true : this.isGuest ? false : this.formData.canEditRoles,
|
||||
canEditCategories: this.isAdmin
|
||||
? true
|
||||
: this.isGuest
|
||||
? false
|
||||
: this.formData.canEditCategories,
|
||||
canAccessAdminTools: perm(this.formData.canAccessAdminTools),
|
||||
canManageUsers: perm(this.formData.canManageUsers),
|
||||
canEditRoles: perm(this.formData.canEditRoles),
|
||||
canEditCategories: perm(this.formData.canEditCategories),
|
||||
canEditBbcodes: perm(this.formData.canEditBbcodes),
|
||||
}
|
||||
|
||||
let roleId: number
|
||||
|
||||
@@ -8,6 +8,7 @@ use OCA\Forum\AppInfo\Application;
|
||||
use OCA\Forum\Controller\ForumUserController;
|
||||
use OCA\Forum\Db\ForumUser;
|
||||
use OCA\Forum\Db\ForumUserMapper;
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCA\Forum\Service\UserService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Http;
|
||||
@@ -22,6 +23,8 @@ class ForumUserControllerTest extends TestCase {
|
||||
private ForumUserController $controller;
|
||||
/** @var ForumUserMapper&MockObject */
|
||||
private ForumUserMapper $forumUserMapper;
|
||||
/** @var RoleMapper&MockObject */
|
||||
private RoleMapper $roleMapper;
|
||||
/** @var UserService&MockObject */
|
||||
private UserService $userService;
|
||||
/** @var IUserSession&MockObject */
|
||||
@@ -34,6 +37,8 @@ class ForumUserControllerTest extends TestCase {
|
||||
protected function setUp(): void {
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->forumUserMapper = $this->createMock(ForumUserMapper::class);
|
||||
$this->roleMapper = $this->createMock(RoleMapper::class);
|
||||
$this->roleMapper->method('findByUserId')->willReturn([]);
|
||||
$this->userService = $this->createMock(UserService::class);
|
||||
$this->userSession = $this->createMock(IUserSession::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
@@ -42,6 +47,7 @@ class ForumUserControllerTest extends TestCase {
|
||||
Application::APP_ID,
|
||||
$this->request,
|
||||
$this->forumUserMapper,
|
||||
$this->roleMapper,
|
||||
$this->userService,
|
||||
$this->userSession,
|
||||
$this->logger
|
||||
@@ -124,7 +130,7 @@ class ForumUserControllerTest extends TestCase {
|
||||
$this->assertEquals(['error' => 'Forum user not found'], $response->getData());
|
||||
}
|
||||
|
||||
public function testShowWithMeReturnsNotFoundWhenForumUserDoesNotExist(): void {
|
||||
public function testShowWithMeReturnsMinimalResponseWhenForumUserDoesNotExist(): void {
|
||||
$nextcloudUserId = 'user-without-forum-profile';
|
||||
|
||||
$user = $this->createMock(IUser::class);
|
||||
@@ -136,10 +142,17 @@ class ForumUserControllerTest extends TestCase {
|
||||
->with($nextcloudUserId)
|
||||
->willThrowException(new DoesNotExistException('Forum user not found'));
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with($nextcloudUserId)
|
||||
->willReturn([]);
|
||||
|
||||
$response = $this->controller->show('me');
|
||||
|
||||
$this->assertEquals(Http::STATUS_NOT_FOUND, $response->getStatus());
|
||||
$this->assertEquals(['error' => 'Forum user not found'], $response->getData());
|
||||
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||
$data = $response->getData();
|
||||
$this->assertEquals($nextcloudUserId, $data['userId']);
|
||||
$this->assertEquals([], $data['roles']);
|
||||
}
|
||||
|
||||
public function testCreateForumUserSuccessfully(): void {
|
||||
|
||||
@@ -57,8 +57,10 @@ class RoleControllerTest extends TestCase {
|
||||
// Verify Admin role always has all permissions
|
||||
$this->assertEquals($adminRoleId, $role->getId());
|
||||
$this->assertTrue($role->getCanAccessAdminTools());
|
||||
$this->assertTrue($role->getCanManageUsers());
|
||||
$this->assertTrue($role->getCanEditRoles());
|
||||
$this->assertTrue($role->getCanEditCategories());
|
||||
$this->assertTrue($role->getCanEditBbcodes());
|
||||
return $role;
|
||||
});
|
||||
|
||||
@@ -71,14 +73,18 @@ class RoleControllerTest extends TestCase {
|
||||
'#ff0000',
|
||||
false, // Try to disable - should be forced to true
|
||||
false, // Try to disable - should be forced to true
|
||||
false // Try to disable - should be forced to true
|
||||
false, // Try to disable - should be forced to true
|
||||
false, // Try to disable - should be forced to true
|
||||
false, // Try to disable - should be forced to true
|
||||
);
|
||||
|
||||
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||
$data = $response->getData();
|
||||
$this->assertTrue($data['canAccessAdminTools']);
|
||||
$this->assertTrue($data['canManageUsers']);
|
||||
$this->assertTrue($data['canEditRoles']);
|
||||
$this->assertTrue($data['canEditCategories']);
|
||||
$this->assertTrue($data['canEditBbcodes']);
|
||||
}
|
||||
|
||||
public function testUpdateNonAdminRoleAllowsPermissionChanges(): void {
|
||||
@@ -107,9 +113,11 @@ class RoleControllerTest extends TestCase {
|
||||
'Moderator role',
|
||||
null,
|
||||
null,
|
||||
false, // Changed from true
|
||||
true, // Kept true
|
||||
false // Changed from true
|
||||
false, // canAccessAdminTools — changed from true
|
||||
null, // canManageUsers — unchanged
|
||||
true, // canEditRoles — kept true
|
||||
false, // canEditCategories — changed from true
|
||||
null, // canEditBbcodes — unchanged
|
||||
);
|
||||
|
||||
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||
@@ -250,8 +258,10 @@ class RoleControllerTest extends TestCase {
|
||||
$this->assertEquals($roleId, $data['id']);
|
||||
$this->assertEquals('Moderator', $data['name']);
|
||||
$this->assertTrue($data['canAccessAdminTools']);
|
||||
$this->assertFalse($data['canManageUsers']);
|
||||
$this->assertFalse($data['canEditRoles']);
|
||||
$this->assertTrue($data['canEditCategories']);
|
||||
$this->assertFalse($data['canEditBbcodes']);
|
||||
}
|
||||
|
||||
public function testShowReturnsNotFoundWhenRoleDoesNotExist(): void {
|
||||
@@ -283,8 +293,10 @@ class RoleControllerTest extends TestCase {
|
||||
$this->assertEquals($colorLight, $role->getColorLight());
|
||||
$this->assertEquals($colorDark, $role->getColorDark());
|
||||
$this->assertTrue($role->getCanAccessAdminTools());
|
||||
$this->assertFalse($role->getCanManageUsers());
|
||||
$this->assertFalse($role->getCanEditRoles());
|
||||
$this->assertTrue($role->getCanEditCategories());
|
||||
$this->assertFalse($role->getCanEditBbcodes());
|
||||
|
||||
// Simulate DB setting ID
|
||||
$role->setId(4);
|
||||
@@ -297,8 +309,10 @@ class RoleControllerTest extends TestCase {
|
||||
$colorLight,
|
||||
$colorDark,
|
||||
true, // canAccessAdminTools
|
||||
false, // canManageUsers
|
||||
false, // canEditRoles
|
||||
true // canEditCategories
|
||||
true, // canEditCategories
|
||||
false, // canEditBbcodes
|
||||
);
|
||||
|
||||
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
|
||||
@@ -478,11 +492,13 @@ class RoleControllerTest extends TestCase {
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('update')
|
||||
->willReturnCallback(function ($role) use ($guestRoleId) {
|
||||
// Verify Guest role never has admin permissions
|
||||
// Verify Guest role never has management permissions
|
||||
$this->assertEquals($guestRoleId, $role->getId());
|
||||
$this->assertFalse($role->getCanAccessAdminTools());
|
||||
$this->assertFalse($role->getCanManageUsers());
|
||||
$this->assertFalse($role->getCanEditRoles());
|
||||
$this->assertFalse($role->getCanEditCategories());
|
||||
$this->assertFalse($role->getCanEditBbcodes());
|
||||
return $role;
|
||||
});
|
||||
|
||||
@@ -495,14 +511,18 @@ class RoleControllerTest extends TestCase {
|
||||
'#cccccc',
|
||||
true, // Try to enable - should be forced to false
|
||||
true, // Try to enable - should be forced to false
|
||||
true // Try to enable - should be forced to false
|
||||
true, // Try to enable - should be forced to false
|
||||
true, // Try to enable - should be forced to false
|
||||
true, // Try to enable - should be forced to false
|
||||
);
|
||||
|
||||
$this->assertEquals(Http::STATUS_OK, $response->getStatus());
|
||||
$data = $response->getData();
|
||||
$this->assertFalse($data['canAccessAdminTools']);
|
||||
$this->assertFalse($data['canManageUsers']);
|
||||
$this->assertFalse($data['canEditRoles']);
|
||||
$this->assertFalse($data['canEditCategories']);
|
||||
$this->assertFalse($data['canEditBbcodes']);
|
||||
}
|
||||
|
||||
public function testUpdateGuestPermissionsEnforcesNoModerate(): void {
|
||||
@@ -573,13 +593,15 @@ class RoleControllerTest extends TestCase {
|
||||
$this->assertTrue($data['success']);
|
||||
}
|
||||
|
||||
private function createRole(int $id, string $name, bool $canAccessAdminTools, bool $canEditRoles, bool $canEditCategories, bool $isSystemRole = false, string $roleType = Role::ROLE_TYPE_CUSTOM): Role {
|
||||
private function createRole(int $id, string $name, bool $canAccessAdminTools, bool $canEditRoles, bool $canEditCategories, bool $isSystemRole = false, string $roleType = Role::ROLE_TYPE_CUSTOM, bool $canManageUsers = false, bool $canEditBbcodes = false): Role {
|
||||
$role = new Role();
|
||||
$role->setId($id);
|
||||
$role->setName($name);
|
||||
$role->setCanAccessAdminTools($canAccessAdminTools);
|
||||
$role->setCanManageUsers($canManageUsers);
|
||||
$role->setCanEditRoles($canEditRoles);
|
||||
$role->setCanEditCategories($canEditCategories);
|
||||
$role->setCanEditBbcodes($canEditBbcodes);
|
||||
$role->setIsSystemRole($isSystemRole);
|
||||
$role->setRoleType($roleType);
|
||||
$role->setCreatedAt(time());
|
||||
|
||||
@@ -6,8 +6,11 @@ namespace OCA\Forum\Tests\Controller;
|
||||
|
||||
use OCA\Forum\AppInfo\Application;
|
||||
use OCA\Forum\Controller\ServerAdminController;
|
||||
use OCA\Forum\Db\RoleMapper;
|
||||
use OCA\Forum\Service\StatsService;
|
||||
use OCA\Forum\Service\UserRoleService;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -15,6 +18,12 @@ use Psr\Log\LoggerInterface;
|
||||
class ServerAdminControllerTest extends TestCase {
|
||||
private ServerAdminController $controller;
|
||||
|
||||
/** @var RoleMapper&MockObject */
|
||||
private RoleMapper $roleMapper;
|
||||
/** @var UserRoleService&MockObject */
|
||||
private UserRoleService $userRoleService;
|
||||
/** @var IUserManager&MockObject */
|
||||
private IUserManager $userManager;
|
||||
/** @var StatsService&MockObject */
|
||||
private StatsService $statsService;
|
||||
/** @var LoggerInterface&MockObject */
|
||||
@@ -24,12 +33,18 @@ class ServerAdminControllerTest extends TestCase {
|
||||
|
||||
protected function setUp(): void {
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->roleMapper = $this->createMock(RoleMapper::class);
|
||||
$this->userRoleService = $this->createMock(UserRoleService::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->statsService = $this->createMock(StatsService::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->controller = new ServerAdminController(
|
||||
Application::APP_ID,
|
||||
$this->request,
|
||||
$this->roleMapper,
|
||||
$this->userRoleService,
|
||||
$this->userManager,
|
||||
$this->statsService,
|
||||
$this->logger
|
||||
);
|
||||
|
||||
@@ -328,8 +328,10 @@ class UserRoleControllerTest extends TestCase {
|
||||
$role->setRoleType($roleType);
|
||||
$role->setIsSystemRole($roleType !== Role::ROLE_TYPE_CUSTOM);
|
||||
$role->setCanAccessAdminTools($roleType === Role::ROLE_TYPE_ADMIN);
|
||||
$role->setCanManageUsers($roleType === Role::ROLE_TYPE_ADMIN);
|
||||
$role->setCanEditRoles($roleType === Role::ROLE_TYPE_ADMIN);
|
||||
$role->setCanEditCategories($roleType === Role::ROLE_TYPE_ADMIN);
|
||||
$role->setCanEditBbcodes($roleType === Role::ROLE_TYPE_ADMIN);
|
||||
$role->setCreatedAt(time());
|
||||
return $role;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,14 @@ class TestPermissionController extends Controller {
|
||||
#[RequirePermission('canEditCategories')]
|
||||
public function methodWithOrGroupAndUngrouped(): void {
|
||||
}
|
||||
|
||||
#[RequirePermission('canManageUsers')]
|
||||
public function methodWithManageUsersPermission(): void {
|
||||
}
|
||||
|
||||
#[RequirePermission('canEditBbcodes')]
|
||||
public function methodWithEditBBCodesPermission(): void {
|
||||
}
|
||||
}
|
||||
|
||||
class PermissionMiddlewareTest extends TestCase {
|
||||
@@ -532,6 +540,74 @@ class PermissionMiddlewareTest extends TestCase {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCanManageUsersPermissionAllowsAccess(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
$this->config->method('getAppValueBool')
|
||||
->with('allow_guest_access', false, true)
|
||||
->willReturn(false);
|
||||
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('hasGlobalPermission')
|
||||
->with('user1', 'canManageUsers')
|
||||
->willReturn(true);
|
||||
|
||||
$this->middleware->beforeController($this->controller, 'methodWithManageUsersPermission');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCanManageUsersPermissionDeniesAccess(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
$this->config->method('getAppValueBool')
|
||||
->with('allow_guest_access', false, true)
|
||||
->willReturn(false);
|
||||
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('hasGlobalPermission')
|
||||
->with('user1', 'canManageUsers')
|
||||
->willReturn(false);
|
||||
|
||||
$this->expectException(OCSForbiddenException::class);
|
||||
$this->middleware->beforeController($this->controller, 'methodWithManageUsersPermission');
|
||||
}
|
||||
|
||||
public function testCanEditBBCodesPermissionAllowsAccess(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
$this->config->method('getAppValueBool')
|
||||
->with('allow_guest_access', false, true)
|
||||
->willReturn(false);
|
||||
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('hasGlobalPermission')
|
||||
->with('user1', 'canEditBbcodes')
|
||||
->willReturn(true);
|
||||
|
||||
$this->middleware->beforeController($this->controller, 'methodWithEditBBCodesPermission');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testCanEditBBCodesPermissionDeniesAccess(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
$this->userSession->method('getUser')->willReturn($user);
|
||||
$this->config->method('getAppValueBool')
|
||||
->with('allow_guest_access', false, true)
|
||||
->willReturn(false);
|
||||
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('hasGlobalPermission')
|
||||
->with('user1', 'canEditBbcodes')
|
||||
->willReturn(false);
|
||||
|
||||
$this->expectException(OCSForbiddenException::class);
|
||||
$this->middleware->beforeController($this->controller, 'methodWithEditBBCodesPermission');
|
||||
}
|
||||
|
||||
public function testAuthenticatedUserBypassesGuestRestrictions(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
@@ -127,6 +127,43 @@ class PermissionServiceTest extends TestCase {
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testHasGlobalPermissionCanManageUsers(): void {
|
||||
$userId = 'user1';
|
||||
$role = $this->createRole(1, 'Manager', false, false, false, false, Role::ROLE_TYPE_CUSTOM, true, false);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->assertTrue($this->service->hasGlobalPermission($userId, 'canManageUsers'));
|
||||
$this->assertFalse($role->getCanEditBbcodes());
|
||||
}
|
||||
|
||||
public function testHasGlobalPermissionCanEditBbcodes(): void {
|
||||
$userId = 'user1';
|
||||
$role = $this->createRole(1, 'BBCodeEditor', false, false, false, false, Role::ROLE_TYPE_CUSTOM, false, true);
|
||||
|
||||
$this->roleMapper->expects($this->once())
|
||||
->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->assertTrue($this->service->hasGlobalPermission($userId, 'canEditBbcodes'));
|
||||
$this->assertFalse($role->getCanManageUsers());
|
||||
}
|
||||
|
||||
public function testHasGlobalPermissionReturnsFalseForNewPermissionsWhenNotSet(): void {
|
||||
$userId = 'user1';
|
||||
$role = $this->createRole(1, 'Basic', false, false, false, false, Role::ROLE_TYPE_CUSTOM);
|
||||
|
||||
$this->roleMapper->method('findByUserId')
|
||||
->with($userId)
|
||||
->willReturn([$role]);
|
||||
|
||||
$this->assertFalse($this->service->hasGlobalPermission($userId, 'canManageUsers'));
|
||||
}
|
||||
|
||||
public function testHasGlobalPermissionHandlesException(): void {
|
||||
$userId = 'user1';
|
||||
$permission = 'canEditRoles';
|
||||
@@ -776,13 +813,15 @@ class PermissionServiceTest extends TestCase {
|
||||
return $userRole;
|
||||
}
|
||||
|
||||
private function createRole(int $id, string $name, bool $canAccessAdminTools, bool $canEditRoles, bool $canEditCategories, bool $isSystemRole = false, string $roleType = Role::ROLE_TYPE_CUSTOM): Role {
|
||||
private function createRole(int $id, string $name, bool $canAccessAdminTools, bool $canEditRoles, bool $canEditCategories, bool $isSystemRole = false, string $roleType = Role::ROLE_TYPE_CUSTOM, bool $canManageUsers = false, bool $canEditBbcodes = false): Role {
|
||||
$role = new Role();
|
||||
$role->setId($id);
|
||||
$role->setName($name);
|
||||
$role->setCanAccessAdminTools($canAccessAdminTools);
|
||||
$role->setCanManageUsers($canManageUsers);
|
||||
$role->setCanEditRoles($canEditRoles);
|
||||
$role->setCanEditCategories($canEditCategories);
|
||||
$role->setCanEditBbcodes($canEditBbcodes);
|
||||
$role->setIsSystemRole($isSystemRole);
|
||||
$role->setRoleType($roleType);
|
||||
$role->setCreatedAt(time());
|
||||
|
||||
Reference in New Issue
Block a user