Skip to content

Commit

Permalink
fix(cms): bring back old permissions checking-related utils (#3904)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrians5j authored Feb 27, 2024
1 parent 16af3b7 commit 9125c57
Show file tree
Hide file tree
Showing 5 changed files with 217 additions and 2 deletions.
30 changes: 30 additions & 0 deletions packages/api-headless-cms/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { createModelsCrud } from "~/crud/contentModel.crud";
import { createContentEntryCrud } from "~/crud/contentEntry.crud";
import { StorageOperationsCmsModelPlugin } from "~/plugins";
import { createCmsModelFieldConvertersAttachFactory } from "~/utils/converters/valueKeyStorageConverter";
import { ModelsPermissions } from "~/utils/permissions/ModelsPermissions";
import { ModelGroupsPermissions } from "./utils/permissions/ModelGroupsPermissions";
import { EntriesPermissions } from "./utils/permissions/EntriesPermissions";
import { createExportCrud } from "~/export";
import { createImportCrud } from "~/export/crud/importing";

Expand Down Expand Up @@ -60,6 +63,25 @@ export const createContextPlugin = ({ storageOperations }: CrudParams) => {
await context.benchmark.measure("headlessCms.createContext", async () => {
await storageOperations.beforeInit(context);

const modelGroupsPermissions = new ModelGroupsPermissions({
getIdentity: context.security.getIdentity,
getPermissions: () => context.security.getPermissions("cms.contentModelGroup"),
fullAccessPermissionName: "cms.*"
});

const modelsPermissions = new ModelsPermissions({
getIdentity: context.security.getIdentity,
getPermissions: () => context.security.getPermissions("cms.contentModel"),
fullAccessPermissionName: "cms.*",
modelGroupsPermissions
});

const entriesPermissions = new EntriesPermissions({
getIdentity: context.security.getIdentity,
getPermissions: () => context.security.getPermissions("cms.contentEntry"),
fullAccessPermissionName: "cms.*"
});

const accessControl = new AccessControl({
getIdentity: context.security.getIdentity,
getGroupsPermissions: () =>
Expand All @@ -81,6 +103,14 @@ export const createContextPlugin = ({ storageOperations }: CrudParams) => {
PREVIEW: type === "preview",
MANAGE: type === "manage",
storageOperations,

// TODO: remove with 5.40 release.
permissions: {
groups: modelGroupsPermissions,
models: modelsPermissions,
entries: entriesPermissions
},

accessControl,
...createSystemCrud({
context,
Expand Down
20 changes: 18 additions & 2 deletions packages/api-headless-cms/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,18 @@ import { Topic } from "@webiny/pubsub/types";
import { CmsModelConverterCallable } from "~/utils/converters/ConverterCollection";
import { HeadlessCmsExport, HeadlessCmsImport } from "~/export/types";
import { AccessControl } from "~/crud/AccessControl/AccessControl";
import { ModelGroupsPermissions } from "~/utils/permissions/ModelGroupsPermissions";
import { ModelsPermissions } from "~/utils/permissions/ModelsPermissions";
import { EntriesPermissions } from "~/utils/permissions/EntriesPermissions";

export type ApiEndpoint = "manage" | "preview" | "read";

interface HeadlessCmsPermissions {
groups: ModelGroupsPermissions;
models: ModelsPermissions;
entries: EntriesPermissions;
}

export interface HeadlessCms
extends CmsSystemContext,
CmsGroupContext,
Expand Down Expand Up @@ -48,12 +57,19 @@ export interface HeadlessCms
* The storage operations loaded for current context.
*/
storageOperations: HeadlessCmsStorageOperations;

/**
* Use to ensure perform authorization and ensure identities have access to the groups, models and entries.
*/
accessControl: AccessControl;

/**
* Permissions for groups, models and entries.
*
* @internal
* @deprecated Will be removed with the 5.40.0 release. Use `accessControl` instead.
*/
accessControl: AccessControl;
permissions: HeadlessCmsPermissions;

/**
* Export operations.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { CmsEntryPermission } from "~/types";
import { AppPermissions } from "@webiny/api-security";

export class EntriesPermissions extends AppPermissions<CmsEntryPermission> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { AppPermissions, NotAuthorizedError } from "@webiny/api-security";
import { CmsGroup, CmsGroupPermission } from "~/types";

export interface CanAccessGroupParams {
group: Pick<CmsGroup, "id" | "locale">;
}

export class ModelGroupsPermissions extends AppPermissions<CmsGroupPermission> {
async canAccessGroup({ group }: CanAccessGroupParams) {
if (await this.hasFullAccess()) {
return true;
}

const permissions = await this.getPermissions();

const locale = group.locale;

for (const permission of permissions) {
const { groups } = permission;

// When no groups defined on permission it means user has access to everything.
if (!groups) {
return true;
}

// when there is no locale in groups, it means that no access was given
// this happens when access control was set but no models or groups were added
if (
Array.isArray(groups[locale]) === false ||
groups[locale].includes(group.id) === false
) {
continue;
}
return true;
}

return false;
}

async ensureCanAccessGroup(params: CanAccessGroupParams) {
const canAccessModel = await this.canAccessGroup(params);
if (canAccessModel) {
return;
}

throw new NotAuthorizedError({
data: {
reason: `Not allowed to access group "${params.group.id}".`
}
});
}
}
113 changes: 113 additions & 0 deletions packages/api-headless-cms/src/utils/permissions/ModelsPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { AppPermissions, AppPermissionsParams, NotAuthorizedError } from "@webiny/api-security";
import {
CmsGroupPermission,
CmsModel as BaseCmsModel,
CmsModelGroup as BaseCmsModelGroup,
CmsModelPermission
} from "~/types";
import { ModelGroupsPermissions } from "~/utils/permissions/ModelGroupsPermissions";

export interface ModelsPermissionsParams extends AppPermissionsParams<CmsGroupPermission> {
modelGroupsPermissions: ModelGroupsPermissions;
}

interface PickedCmsModel extends Pick<BaseCmsModel, "modelId" | "locale"> {
group: Pick<BaseCmsModelGroup, "id">;
}

export interface CanAccessModelParams {
model: PickedCmsModel;
}

export interface EnsureModelAccessParams {
model: PickedCmsModel;
}

export class ModelsPermissions extends AppPermissions<CmsModelPermission> {
private readonly modelGroupsPermissions: ModelGroupsPermissions;

public constructor(params: ModelsPermissionsParams) {
super(params);
this.modelGroupsPermissions = params.modelGroupsPermissions;
}

public async canAccessModel({ model }: CanAccessModelParams) {
if (await this.hasFullAccess()) {
return true;
}

const modelGroupsPermissions = this.modelGroupsPermissions;

// eslint-disable-next-line
const modelsPermissions = this;

const canReadGroups = await modelGroupsPermissions.ensure({ rwd: "r" }, { throw: false });
if (!canReadGroups) {
return false;
}

const canReadModels = await modelsPermissions.ensure({ rwd: "r" }, { throw: false });
if (!canReadModels) {
return false;
}

const modelGroupsPermissionsList = await modelGroupsPermissions.getPermissions();
const modelsPermissionsList = await this.getPermissions();

const locale = model.locale;

for (let i = 0; i < modelGroupsPermissionsList.length; i++) {
const modelGroupPermission = modelGroupsPermissionsList[i];

const { groups } = modelGroupPermission;

for (let j = 0; j < modelsPermissionsList.length; j++) {
const modelPermission = modelsPermissionsList[j];

const { models } = modelPermission;
// when no models or groups defined on permission
// it means user has access to everything
if (!models && !groups) {
return true;
}

// Does the model belong to a group for which user has permission?
if (groups) {
if (
Array.isArray(groups[locale]) === false ||
groups[locale].includes(model.group.id) === false
) {
continue;
}
}

// Does the user have access to the specific model?
if (models) {
if (
Array.isArray(models[locale]) === false ||
models[locale].includes(model.modelId) === false
) {
continue;
}
}

return true;
}
}

return false;
}

public async ensureCanAccessModel(params: EnsureModelAccessParams) {
const canAccessModel = await this.canAccessModel(params);
if (canAccessModel) {
return;
}

throw new NotAuthorizedError({
data: {
reason: `Not allowed to access model "${params.model.modelId}".`
}
});
}
}

0 comments on commit 9125c57

Please sign in to comment.