mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
fix(admin): correctly handle nav sections access
This commit is contained in:
@@ -38,6 +38,9 @@ class RequirePermission {
|
||||
private ?string $resourceIdFromThreadId = null,
|
||||
// Derive category ID from post ID parameter
|
||||
private ?string $resourceIdFromPostId = null,
|
||||
// OR group name: attributes with the same group are OR'd together (any must pass),
|
||||
// while attributes with different groups or no group are AND'd
|
||||
private ?string $orGroup = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -64,4 +67,8 @@ class RequirePermission {
|
||||
public function getResourceIdFromPostId(): ?string {
|
||||
return $this->resourceIdFromPostId;
|
||||
}
|
||||
|
||||
public function getOrGroup(): ?string {
|
||||
return $this->orGroup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ class AdminController extends OCSController {
|
||||
* 200: Users list returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/admin/users')]
|
||||
public function users(): DataResponse {
|
||||
try {
|
||||
@@ -305,8 +306,6 @@ class AdminController extends OCSController {
|
||||
*
|
||||
* 200: Stats rebuilt successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/admin/rebuild-stats')]
|
||||
public function rebuildStats(): DataResponse {
|
||||
try {
|
||||
@@ -353,6 +352,9 @@ class AdminController extends OCSController {
|
||||
*
|
||||
* 200: Roles list returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/admin/roles')]
|
||||
public function getRoles(): DataResponse {
|
||||
try {
|
||||
@@ -378,6 +380,8 @@ class AdminController extends OCSController {
|
||||
*
|
||||
* 200: Role assigned successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canEditRoles')]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/admin/users/{userId}/roles')]
|
||||
public function assignRole(string $userId, int $roleId): DataResponse {
|
||||
try {
|
||||
@@ -435,7 +439,7 @@ class AdminController extends OCSController {
|
||||
* 200: Role removed successfully
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canEditRoles')]
|
||||
#[ApiRoute(verb: 'DELETE', url: '/api/admin/users/{userId}/roles/{roleId}')]
|
||||
public function removeRole(string $userId, int $roleId): DataResponse {
|
||||
try {
|
||||
|
||||
@@ -394,7 +394,8 @@ class CategoryController extends OCSController {
|
||||
* 200: Permissions returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditCategories', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/categories/{id}/permissions')]
|
||||
public function getPermissions(int $id, int $limit = 100, int $offset = 0): DataResponse {
|
||||
try {
|
||||
|
||||
@@ -42,7 +42,8 @@ class RoleController extends OCSController {
|
||||
* 200: Roles returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/roles')]
|
||||
public function index(int $limit = 100, int $offset = 0): DataResponse {
|
||||
try {
|
||||
@@ -63,7 +64,8 @@ class RoleController extends OCSController {
|
||||
* 200: Role returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/roles/{id}')]
|
||||
public function show(int $id): DataResponse {
|
||||
try {
|
||||
@@ -245,7 +247,8 @@ class RoleController extends OCSController {
|
||||
* 200: Permissions returned
|
||||
*/
|
||||
#[NoAdminRequired]
|
||||
#[RequirePermission('canAccessAdminTools')]
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/roles/{id}/permissions')]
|
||||
public function getPermissions(int $id, int $limit = 100, int $offset = 0): DataResponse {
|
||||
try {
|
||||
|
||||
@@ -65,11 +65,7 @@ class PermissionMiddleware extends Middleware {
|
||||
if (!empty($permissionAttrs)) {
|
||||
// Check permissions using guest role (null userId)
|
||||
$this->logger->debug('Checking permissions for unauthenticated user (public page or guest access)');
|
||||
foreach ($permissionAttrs as $attr) {
|
||||
/** @var RequirePermission $permission */
|
||||
$permission = $attr->newInstance();
|
||||
$this->checkPermission(null, $permission);
|
||||
}
|
||||
$this->checkPermissions(null, $permissionAttrs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -87,12 +83,57 @@ class PermissionMiddleware extends Middleware {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check each permission requirement
|
||||
$this->checkPermissions($userId, $permissionAttrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all permission attributes, respecting OR groups.
|
||||
*
|
||||
* Attributes with the same orGroup are OR'd (any one must pass).
|
||||
* Attributes with no orGroup, or with different orGroups, are AND'd together.
|
||||
*
|
||||
* @param string|null $userId
|
||||
* @param \ReflectionAttribute[] $permissionAttrs
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
private function checkPermissions(?string $userId, array $permissionAttrs): void {
|
||||
// Separate into OR groups and ungrouped
|
||||
$orGroups = [];
|
||||
$ungrouped = [];
|
||||
|
||||
foreach ($permissionAttrs as $attr) {
|
||||
/** @var RequirePermission $permission */
|
||||
$permission = $attr->newInstance();
|
||||
$group = $permission->getOrGroup();
|
||||
if ($group !== null) {
|
||||
$orGroups[$group][] = $permission;
|
||||
} else {
|
||||
$ungrouped[] = $permission;
|
||||
}
|
||||
}
|
||||
|
||||
// Ungrouped attributes: all must pass (AND)
|
||||
foreach ($ungrouped as $permission) {
|
||||
$this->checkPermission($userId, $permission);
|
||||
}
|
||||
|
||||
// OR groups: at least one in each group must pass
|
||||
foreach ($orGroups as $group => $permissions) {
|
||||
$lastException = null;
|
||||
$passed = false;
|
||||
foreach ($permissions as $permission) {
|
||||
try {
|
||||
$this->checkPermission($userId, $permission);
|
||||
$passed = true;
|
||||
break;
|
||||
} catch (OCSForbiddenException $e) {
|
||||
$lastException = $e;
|
||||
}
|
||||
}
|
||||
if (!$passed && $lastException !== null) {
|
||||
throw $lastException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -177,142 +177,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/roles": {
|
||||
"get": {
|
||||
"operationId": "admin-get-roles",
|
||||
"summary": "Get all available roles",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"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/admin/users/{userId}/roles": {
|
||||
"/ocs/v2.php/apps/forum/api/admin/rebuild-stats": {
|
||||
"post": {
|
||||
"operationId": "admin-assign-role",
|
||||
"summary": "Assign a role to a user",
|
||||
"operationId": "admin-rebuild-stats",
|
||||
"summary": "Rebuild all forum statistics (users, categories, threads)",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"admin"
|
||||
@@ -325,36 +193,7 @@
|
||||
"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",
|
||||
@@ -368,7 +207,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Role assigned successfully",
|
||||
"description": "Stats rebuilt successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
||||
@@ -466,10 +466,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/rebuild-stats": {
|
||||
"post": {
|
||||
"operationId": "admin-rebuild-stats",
|
||||
"summary": "Rebuild all forum statistics (users, categories, threads)",
|
||||
"/ocs/v2.php/apps/forum/api/admin/roles": {
|
||||
"get": {
|
||||
"operationId": "admin-get-roles",
|
||||
"summary": "Get all available roles",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
@@ -495,7 +495,139 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Stats rebuilt successfully",
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/users/{userId}/roles": {
|
||||
"post": {
|
||||
"operationId": "admin-assign-role",
|
||||
"summary": "Assign a role to a user",
|
||||
"tags": [
|
||||
"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": {
|
||||
@@ -11965,142 +12097,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/roles": {
|
||||
"get": {
|
||||
"operationId": "admin-get-roles",
|
||||
"summary": "Get all available roles",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"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/admin/users/{userId}/roles": {
|
||||
"/ocs/v2.php/apps/forum/api/admin/rebuild-stats": {
|
||||
"post": {
|
||||
"operationId": "admin-assign-role",
|
||||
"summary": "Assign a role to a user",
|
||||
"operationId": "admin-rebuild-stats",
|
||||
"summary": "Rebuild all forum statistics (users, categories, threads)",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"admin"
|
||||
@@ -12113,36 +12113,7 @@
|
||||
"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",
|
||||
@@ -12156,7 +12127,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Role assigned successfully",
|
||||
"description": "Stats rebuilt successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
||||
142
openapi.json
142
openapi.json
@@ -466,10 +466,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/rebuild-stats": {
|
||||
"post": {
|
||||
"operationId": "admin-rebuild-stats",
|
||||
"summary": "Rebuild all forum statistics (users, categories, threads)",
|
||||
"/ocs/v2.php/apps/forum/api/admin/roles": {
|
||||
"get": {
|
||||
"operationId": "admin-get-roles",
|
||||
"summary": "Get all available roles",
|
||||
"tags": [
|
||||
"admin"
|
||||
],
|
||||
@@ -495,7 +495,139 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Stats rebuilt successfully",
|
||||
"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": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/forum/api/admin/users/{userId}/roles": {
|
||||
"post": {
|
||||
"operationId": "admin-assign-role",
|
||||
"summary": "Assign a role to a user",
|
||||
"tags": [
|
||||
"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": {
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
<!-- Admin sub-items -->
|
||||
<template v-if="isAdminOpen">
|
||||
<NcAppNavigationItem
|
||||
v-if="canAccessAdminTools"
|
||||
:name="strings.navAdminDashboard"
|
||||
:to="{ path: '/admin' }"
|
||||
:active="isPathActive('/admin')"
|
||||
@@ -128,6 +129,7 @@
|
||||
</NcAppNavigationItem>
|
||||
|
||||
<NcAppNavigationItem
|
||||
v-if="canAccessAdminTools"
|
||||
:name="strings.navAdminSettings"
|
||||
:to="{ path: '/admin/settings' }"
|
||||
:active="isPathActive('/admin/settings')"
|
||||
@@ -171,6 +173,7 @@
|
||||
</NcAppNavigationItem>
|
||||
|
||||
<NcAppNavigationItem
|
||||
v-if="canAccessAdminTools"
|
||||
:name="strings.navAdminBBCodes"
|
||||
:to="{ path: '/admin/bbcodes' }"
|
||||
:active="isPathActive('/admin/bbcodes', true)"
|
||||
@@ -263,7 +266,8 @@ export default defineComponent({
|
||||
setup() {
|
||||
const { categoryHeaders, fetchCategories } = useCategories()
|
||||
const { userId, displayName, fetchCurrentUser } = useCurrentUser()
|
||||
const { canAccessAdmin, canEditRoles, canEditCategories, fetchUserRoles } = useUserRole()
|
||||
const { canAccessAdmin, canAccessAdminTools, canEditRoles, canEditCategories, fetchUserRoles } =
|
||||
useUserRole()
|
||||
const { categoryId: currentThreadCategoryId, fetchThread, clearThread } = useCurrentThread()
|
||||
const { isGuest, guestDisplayName, fetchGuestIdentity } = useGuestSession()
|
||||
|
||||
@@ -275,6 +279,7 @@ export default defineComponent({
|
||||
userId,
|
||||
displayName,
|
||||
canAccessAdmin,
|
||||
canAccessAdminTools,
|
||||
canEditRoles,
|
||||
canEditCategories,
|
||||
currentThreadCategoryId,
|
||||
@@ -457,8 +462,14 @@ export default defineComponent({
|
||||
this.saveNavigationState()
|
||||
}
|
||||
|
||||
// Navigate to admin dashboard (first admin item)
|
||||
this.$router.push({ path: '/admin' })
|
||||
// Navigate to the first available management item
|
||||
if (this.canAccessAdminTools) {
|
||||
this.$router.push({ path: '/admin' })
|
||||
} else if (this.canEditRoles) {
|
||||
this.$router.push({ path: '/admin/users' })
|
||||
} else if (this.canEditCategories) {
|
||||
this.$router.push({ path: '/admin/categories' })
|
||||
}
|
||||
},
|
||||
|
||||
isCategoryActive(category: Category): boolean {
|
||||
|
||||
@@ -40,10 +40,14 @@ export function useUserRole() {
|
||||
return userRoles.value.some(isModeratorRole)
|
||||
})
|
||||
|
||||
const canAccessAdmin = computed<boolean>(() => {
|
||||
const canAccessAdminTools = computed<boolean>(() => {
|
||||
return userRoles.value.some((role) => role.canAccessAdminTools)
|
||||
})
|
||||
|
||||
const canAccessAdmin = computed<boolean>(() => {
|
||||
return canAccessAdminTools.value || canEditRoles.value || canEditCategories.value
|
||||
})
|
||||
|
||||
const canEditRoles = computed<boolean>(() => {
|
||||
return userRoles.value.some((role) => role.canEditRoles)
|
||||
})
|
||||
@@ -74,6 +78,7 @@ export function useUserRole() {
|
||||
isAdmin,
|
||||
isModerator,
|
||||
canAccessAdmin,
|
||||
canAccessAdminTools,
|
||||
canEditRoles,
|
||||
canEditCategories,
|
||||
fetchUserRoles,
|
||||
|
||||
@@ -47,6 +47,17 @@ class TestPermissionController extends Controller {
|
||||
#[RequirePermission('canView', resourceType: 'invalid', resourceIdParam: 'id')]
|
||||
public function methodWithInvalidResource(): void {
|
||||
}
|
||||
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
public function methodWithOrGroup(): void {
|
||||
}
|
||||
|
||||
#[RequirePermission('canAccessAdminTools', orGroup: 'access')]
|
||||
#[RequirePermission('canEditRoles', orGroup: 'access')]
|
||||
#[RequirePermission('canEditCategories')]
|
||||
public function methodWithOrGroupAndUngrouped(): void {
|
||||
}
|
||||
}
|
||||
|
||||
class PermissionMiddlewareTest extends TestCase {
|
||||
@@ -428,6 +439,99 @@ class PermissionMiddlewareTest extends TestCase {
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testOrGroupAllowsAccessWhenFirstPermissionPasses(): 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->method('hasGlobalPermission')
|
||||
->willReturnMap([
|
||||
['user1', 'canAccessAdminTools', true],
|
||||
]);
|
||||
|
||||
$this->middleware->beforeController($this->controller, 'methodWithOrGroup');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testOrGroupAllowsAccessWhenSecondPermissionPasses(): 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->method('hasGlobalPermission')
|
||||
->willReturnMap([
|
||||
['user1', 'canAccessAdminTools', false],
|
||||
['user1', 'canEditRoles', true],
|
||||
]);
|
||||
|
||||
$this->middleware->beforeController($this->controller, 'methodWithOrGroup');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testOrGroupDeniesAccessWhenNoPermissionPasses(): 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->method('hasGlobalPermission')
|
||||
->willReturnMap([
|
||||
['user1', 'canAccessAdminTools', false],
|
||||
['user1', 'canEditRoles', false],
|
||||
]);
|
||||
|
||||
$this->expectException(OCSForbiddenException::class);
|
||||
$this->middleware->beforeController($this->controller, 'methodWithOrGroup');
|
||||
}
|
||||
|
||||
public function testOrGroupWithUngroupedRequiresBoth(): 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);
|
||||
|
||||
// OR group passes (canEditRoles), but ungrouped (canEditCategories) fails
|
||||
$this->permissionService->method('hasGlobalPermission')
|
||||
->willReturnMap([
|
||||
['user1', 'canAccessAdminTools', false],
|
||||
['user1', 'canEditRoles', true],
|
||||
['user1', 'canEditCategories', false],
|
||||
]);
|
||||
|
||||
$this->expectException(OCSForbiddenException::class);
|
||||
$this->middleware->beforeController($this->controller, 'methodWithOrGroupAndUngrouped');
|
||||
}
|
||||
|
||||
public function testOrGroupWithUngroupedAllowsWhenBothPass(): 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);
|
||||
|
||||
// OR group passes (canEditRoles) AND ungrouped (canEditCategories) passes
|
||||
$this->permissionService->method('hasGlobalPermission')
|
||||
->willReturnMap([
|
||||
['user1', 'canAccessAdminTools', false],
|
||||
['user1', 'canEditRoles', true],
|
||||
['user1', 'canEditCategories', true],
|
||||
]);
|
||||
|
||||
$this->middleware->beforeController($this->controller, 'methodWithOrGroupAndUngrouped');
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
public function testAuthenticatedUserBypassesGuestRestrictions(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUID')->willReturn('user1');
|
||||
|
||||
Reference in New Issue
Block a user