Skip to content

Commit

Permalink
fix: HCMS Model / Security (FLP) Adjustments and Improvements (#3865)
Browse files Browse the repository at this point in the history
Co-authored-by: Pavel Denisjuk <[email protected]>
  • Loading branch information
adrians5j and Pavel910 authored Feb 23, 2024
1 parent 5678056 commit 63f0673
Show file tree
Hide file tree
Showing 49 changed files with 2,213 additions and 848 deletions.
4 changes: 2 additions & 2 deletions packages/api-aco/__tests__/flp.fm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ const identityA: SecurityIdentity = { id: "1", type: "admin", displayName: "A" }
const identityB: SecurityIdentity = { id: "2", type: "admin", displayName: "B" };
const identityC: SecurityIdentity = { id: "3", type: "admin", displayName: "C" };

const expectNotAuthorized = async (promise: Promise<any>) => {
const expectNotAuthorized = async (promise: Promise<any>, data: Record<string, any> = null) => {
await expect(promise).resolves.toEqual({
data: null,
error: {
code: "SECURITY_NOT_AUTHORIZED",
data: null,
data,
message: "Not authorized!"
}
});
Expand Down
7 changes: 7 additions & 0 deletions packages/api-aco/src/folder/folder.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ export const createFolderModel = () => {
return createPrivateModel({
name: "ACO - Folder",
modelId: FOLDER_MODEL_ID,
authorization: {
// Disables base permission checks, but leaves FLP checks enabled.
permissions: false

// We're leaving FLP enabled (no need to set `flp: true`).
// flp: true
},
titleFieldId: "title",
fields: [titleField(), slugField(), typeField(), parentIdField(), permissionsField()]
});
Expand Down
7 changes: 7 additions & 0 deletions packages/api-aco/src/record/record.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ export const createSearchModel = (params?: CreateSearchModelParams) => {
name: "ACO - Search Record",
modelId: SEARCH_RECORD_MODEL_ID,
titleFieldId: "title",
authorization: {
// Disables base permission checks, but leaves FLP checks enabled.
permissions: false

// We're leaving FLP enabled (no need to set `flp: true`).
// flp: true
},
fields: [
typeField(),
titleField(),
Expand Down
169 changes: 74 additions & 95 deletions packages/api-aco/src/record/record.so.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ENTRY_META_FIELDS } from "@webiny/api-headless-cms/constants";
export const createSearchRecordOperations = (
params: CreateAcoStorageOperationsParams
): AcoSearchRecordStorageOperations => {
const { cms, security } = params;
const { cms } = params;

const getRecord = async (model: CmsModel, id: string) => {
/**
Expand All @@ -33,119 +33,98 @@ export const createSearchRecordOperations = (

return {
async getRecord(model, { id }) {
return security.withoutAuthorization(async () => {
const record = await getRecord(model, id);
return pickEntryFieldValues<SearchRecord<any>>(record);
});
const record = await getRecord(model, id);
return pickEntryFieldValues<SearchRecord<any>>(record);
},
listRecords(model, params) {
return security.withoutAuthorization(async () => {
const { sort, where } = params;

const [entries, meta] = await cms.listLatestEntries(model, {
...params,
sort,
where: {
...(where || {})
}
});

return [entries.map(pickEntryFieldValues<SearchRecord<any>>), meta];
async listRecords(model, params) {
const { sort, where } = params;
const [entries, meta] = await cms.listLatestEntries(model, {
...params,
sort,
where: {
...(where || {})
}
});

return [entries.map(pickEntryFieldValues<SearchRecord<any>>), meta];
},
listTags(model, params) {
return security.withoutAuthorization(async () => {
const { where } = params;
async listTags(model, params) {
const { where } = params;
const items = await cms.getUniqueFieldValues(model, {
where: {
...(where || {}),
latest: true
},
fieldId: "tags"
});

const items = await cms.getUniqueFieldValues(model, {
where: {
...(where || {}),
latest: true
},
fieldId: "tags"
});
const meta = {
hasMoreItems: false,
totalCount: items.length,
cursor: null
};

const meta = {
hasMoreItems: false,
totalCount: items.length,
cursor: null
const tags = items.map(item => {
return {
tag: item.value,
count: item.count
};

const tags = items.map(item => {
return {
tag: item.value,
count: item.count
};
});

return [tags, meta];
});
},
createRecord(model, { data: SearchRecordData }) {
return security.withoutAuthorization(async () => {
const { tags = [], data = {}, ...rest } = SearchRecordData;
const entry = await cms.createEntry(model, {
tags,
data,
...rest,
id: attachAcoRecordPrefix(rest.id)
});

return pickEntryFieldValues<SearchRecord<any>>(entry);
});
return [tags, meta];
},
updateRecord(this: AcoSearchRecordStorageOperations, model, { id, data }) {
return security.withoutAuthorization(async () => {
const original = await this.getRecord(model, { id });
async createRecord(model, { data: SearchRecordData }) {
const { tags = [], data = {}, ...rest } = SearchRecordData;
const entry = await cms.createEntry(model, {
tags,
data,
...rest,
id: attachAcoRecordPrefix(rest.id)
});

const input = {
/**
* We are omitting the standard entry meta fields:
* we don't want to override them with the ones coming from the `original` entry.
*/
...omit(original, ENTRY_META_FIELDS),
...data
};
return pickEntryFieldValues<SearchRecord<any>>(entry);
},
async updateRecord(this: AcoSearchRecordStorageOperations, model, { id, data }) {
const original = await this.getRecord(model, { id });
const input = {
/**
* We are omitting the standard entry meta fields:
* we don't want to override them with the ones coming from the `original` entry.
*/
...omit(original, ENTRY_META_FIELDS),
...data
};

const entry = await cms.updateEntry(
model,
attachAcoRecordPrefix(original.id),
input
);
const entry = await cms.updateEntry(model, attachAcoRecordPrefix(original.id), input);

return pickEntryFieldValues<SearchRecord<any>>(entry);
});
return pickEntryFieldValues<SearchRecord<any>>(entry);
},
moveRecord(this: AcoSearchRecordStorageOperations, model, params) {
async moveRecord(this: AcoSearchRecordStorageOperations, model, params) {
const { id, folderId } = params;
return security.withoutAuthorization(async () => {
const original = await this.getRecord(model, { id });
const original = await this.getRecord(model, { id });

const input: UpdateCmsEntryInput = {
wbyAco_location: {
folderId
}
};
/**
* We only apply the location to the search record model as we do not want to override the users data.
*/
const lookFor = `${SEARCH_RECORD_MODEL_ID}-`;
if (model.modelId.substring(0, lookFor.length) === lookFor) {
input.location = {
folderId
};
const input: UpdateCmsEntryInput = {
wbyAco_location: {
folderId
}
};
/**
* We only apply the location to the search record model as we do not want to override the users data.
*/
const lookFor = `${SEARCH_RECORD_MODEL_ID}-`;
if (model.modelId.substring(0, lookFor.length) === lookFor) {
input.location = {
folderId
};
}

await cms.updateEntry(model, attachAcoRecordPrefix(original.id), input);
await cms.updateEntry(model, attachAcoRecordPrefix(original.id), input);

return true;
});
return true;
},
deleteRecord(model, { id }) {
return security.withoutAuthorization(async () => {
await cms.deleteEntry(model, attachAcoRecordPrefix(id));
return true;
});
async deleteRecord(model, { id }) {
await cms.deleteEntry(model, attachAcoRecordPrefix(id));
return true;
}
};
};
8 changes: 0 additions & 8 deletions packages/api-aco/src/utils/FolderLevelPermissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,6 @@ export class FolderLevelPermissions {
this.canUseFolderLevelPermissions = params.canUseFolderLevelPermissions;

this.isAuthorizationEnabled = params.isAuthorizationEnabled;

// TODO: resolve this issue.
// We immediately enable authorization, because, at the moment, the rest of the system
// requires us to have FLP always enabled. We must now disable it, even if the security's
// `isAuthorizationEnabled` is set to false. To resolve this, we'll need to refactor CMS-based
// CRUD files and have them use CMS storage operations instead of CMS CRUD methods.
// We'll be handling this in the near future.
this.isAuthorizationEnabled = () => true;
}

async listAllFolders(folderType: string): Promise<Folder[]> {
Expand Down
23 changes: 7 additions & 16 deletions packages/api-aco/src/utils/createOperationsWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,19 @@ interface CreateOperationsWrapperParams extends CreateAcoStorageOperationsParams
}

export const createOperationsWrapper = (params: CreateOperationsWrapperParams) => {
const { cms, security, modelName } = params;
const { cms, modelName } = params;

const withModel = async <TResult>(
cb: (model: CmsModel) => Promise<TResult>
): Promise<TResult> => {
return security.withoutAuthorization(async () => {
const model = await cms.getModel(modelName);
const model = await cms.getModel(modelName);

if (!model) {
throw new WebinyError(
`Could not find "${modelName}" model.`,
"MODEL_NOT_FOUND_ERROR"
);
}
if (!model) {
throw new WebinyError(`Could not find "${modelName}" model.`, "MODEL_NOT_FOUND_ERROR");
}

const result = await cb(model);

return result;
});
return cb(model);
};

return {
withModel
};
return { withModel };
};
Loading

0 comments on commit 63f0673

Please sign in to comment.