mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
fix: Access request logic for collection managers (#12579)
* fix: Access request logic for collection managers * test: Exercise collection-manager path in access request regression tests Grant the non-workspace-admin manager a collection-level Admin membership instead of a direct document-level membership, so authorization flows through the collection-manager path being tested for #12567. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { DocumentPermission } from "@shared/types";
|
||||
import { CollectionPermission, DocumentPermission } from "@shared/types";
|
||||
import { AccessRequest, UserMembership } from "@server/models";
|
||||
import { AccessRequestStatus } from "@server/models/AccessRequest";
|
||||
import {
|
||||
@@ -313,6 +313,54 @@ describe("#accessRequests.approve", () => {
|
||||
expect(membership?.permission).toEqual(DocumentPermission.ReadWrite);
|
||||
});
|
||||
|
||||
it("should allow a document manager who is not a workspace admin to approve", async () => {
|
||||
const team = await buildTeam();
|
||||
const requester = await buildUser({ teamId: team.id });
|
||||
const manager = await buildUser({ teamId: team.id });
|
||||
const collection = await buildCollection({
|
||||
teamId: team.id,
|
||||
userId: manager.id,
|
||||
permission: null,
|
||||
});
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
createdById: manager.id,
|
||||
collectionId: collection.id,
|
||||
});
|
||||
|
||||
await UserMembership.create({
|
||||
userId: manager.id,
|
||||
collectionId: collection.id,
|
||||
createdById: manager.id,
|
||||
permission: CollectionPermission.Admin,
|
||||
});
|
||||
|
||||
const accessRequest = await AccessRequest.create({
|
||||
documentId: document.id,
|
||||
userId: requester.id,
|
||||
teamId: team.id,
|
||||
status: AccessRequestStatus.Pending,
|
||||
});
|
||||
|
||||
const res = await server.post("/api/accessRequests.approve", manager, {
|
||||
body: {
|
||||
id: accessRequest.id,
|
||||
permission: DocumentPermission.Read,
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
|
||||
const membership = await UserMembership.findOne({
|
||||
where: {
|
||||
userId: requester.id,
|
||||
documentId: document.id,
|
||||
},
|
||||
});
|
||||
expect(membership).toBeTruthy();
|
||||
expect(membership?.permission).toEqual(DocumentPermission.Read);
|
||||
});
|
||||
|
||||
it("should not allow non-managers to approve requests", async () => {
|
||||
const team = await buildTeam();
|
||||
const requester = await buildUser({ teamId: team.id });
|
||||
@@ -461,6 +509,46 @@ describe("#accessRequests.dismiss", () => {
|
||||
expect(membership).toBeNull();
|
||||
});
|
||||
|
||||
it("should allow a document manager who is not a workspace admin to dismiss", async () => {
|
||||
const team = await buildTeam();
|
||||
const requester = await buildUser({ teamId: team.id });
|
||||
const manager = await buildUser({ teamId: team.id });
|
||||
const collection = await buildCollection({
|
||||
teamId: team.id,
|
||||
userId: manager.id,
|
||||
permission: null,
|
||||
});
|
||||
const document = await buildDocument({
|
||||
teamId: team.id,
|
||||
createdById: manager.id,
|
||||
collectionId: collection.id,
|
||||
});
|
||||
|
||||
await UserMembership.create({
|
||||
userId: manager.id,
|
||||
collectionId: collection.id,
|
||||
createdById: manager.id,
|
||||
permission: CollectionPermission.Admin,
|
||||
});
|
||||
|
||||
const accessRequest = await AccessRequest.create({
|
||||
documentId: document.id,
|
||||
userId: requester.id,
|
||||
teamId: team.id,
|
||||
});
|
||||
|
||||
const res = await server.post("/api/accessRequests.dismiss", manager, {
|
||||
body: {
|
||||
id: accessRequest.id,
|
||||
},
|
||||
});
|
||||
const body = await res.json();
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(body.data.status).toEqual(AccessRequestStatus.Dismissed);
|
||||
expect(body.data.responderId).toEqual(manager.id);
|
||||
});
|
||||
|
||||
it("should not allow non-managers to dismiss requests", async () => {
|
||||
const team = await buildTeam();
|
||||
const requester = await buildUser({ teamId: team.id });
|
||||
|
||||
@@ -101,8 +101,6 @@ router.post(
|
||||
transaction,
|
||||
lock: { level: transaction.LOCK.UPDATE, of: AccessRequest },
|
||||
});
|
||||
authorize(user, "update", accessRequest);
|
||||
authorize(user, "read", accessRequest.user);
|
||||
|
||||
if (accessRequest.status !== AccessRequestStatus.Pending) {
|
||||
throw InvalidRequestError("Access request has already been responded to");
|
||||
@@ -111,8 +109,10 @@ router.post(
|
||||
const document = await Document.findByPk(accessRequest.documentId, {
|
||||
userId: user.id,
|
||||
transaction,
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
authorize(user, "share", document);
|
||||
authorize(user, "manageUsers", document);
|
||||
authorize(user, "read", accessRequest.user);
|
||||
|
||||
const membership = await UserMembership.findOne({
|
||||
where: {
|
||||
@@ -157,14 +157,14 @@ router.post(
|
||||
transaction,
|
||||
lock: { level: transaction.LOCK.UPDATE, of: AccessRequest },
|
||||
});
|
||||
authorize(user, "update", accessRequest);
|
||||
authorize(user, "read", accessRequest.user);
|
||||
|
||||
const document = await Document.findByPk(accessRequest.documentId, {
|
||||
userId: user.id,
|
||||
transaction,
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
authorize(user, "share", document);
|
||||
authorize(user, "manageUsers", document);
|
||||
authorize(user, "read", accessRequest.user);
|
||||
|
||||
if (accessRequest.status === AccessRequestStatus.Pending) {
|
||||
await accessRequest.dismiss(ctx);
|
||||
|
||||
Reference in New Issue
Block a user