search-backend: deduplicate authorize calls

Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
MT Lewis
2022-01-26 19:38:01 +00:00
parent 10358e8b20
commit af4864b462
3 changed files with 78 additions and 2 deletions
+1
View File
@@ -34,6 +34,7 @@
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"lodash": "^4.17.21",
"qs": "^6.10.1",
"winston": "^3.2.1",
"yn": "^4.0.0",
"zod": "^3.11.6"
@@ -257,6 +257,72 @@ describe('AuthorizedSearchEngine', () => {
);
});
it('should deduplicate authorization queries when resourceRefs match', async () => {
const searchResults = [
{
type: 'templates',
document: {
title: `doc_0_a`,
authorization: { resourceRef: `template_doc_0` },
} as IndexableDocument,
},
{
type: 'templates',
document: {
title: `doc_0_b`,
authorization: { resourceRef: `template_doc_0` },
} as IndexableDocument,
},
];
mockedQuery.mockImplementation(async () => ({
results: searchResults,
}));
mockedAuthorize.mockImplementation(async queries =>
queries.map(query => {
if (query.resourceRef) {
return {
result: AuthorizeResult.ALLOW,
};
}
return {
result: AuthorizeResult.CONDITIONAL,
} as AuthorizeDecision;
}),
);
await expect(
authorizedSearchEngine.query({ term: '', types: ['templates'] }, options),
).resolves.toEqual({ results: searchResults });
expect(mockedAuthorize).toHaveBeenCalledTimes(2);
expect(mockedAuthorize).toHaveBeenNthCalledWith(
1,
[
{
permission: expect.objectContaining({
name: 'search.templates.read',
}),
},
],
{ token: 'token' },
);
expect(mockedAuthorize).toHaveBeenNthCalledWith(
2,
[
{
permission: expect.objectContaining({
name: 'search.templates.read',
}),
resourceRef: 'template_doc_0',
},
],
{ token: 'token' },
);
});
it('should perform search until the target number of results is reached', async () => {
mockedAuthorize.mockImplementation(async queries =>
queries.map(query => {
@@ -15,6 +15,7 @@
*/
import { compact, zipObject } from 'lodash';
import qs from 'qs';
import DataLoader from 'dataloader';
import {
AuthorizeDecision,
@@ -87,8 +88,16 @@ export class AuthorizedSearchEngine implements SearchEngine {
): Promise<SearchResultSet> {
const queryStartTime = Date.now();
const authorizer = new DataLoader((requests: readonly AuthorizeQuery[]) =>
this.permissions.authorize(requests.slice(), options),
const authorizer = new DataLoader(
(requests: readonly AuthorizeQuery[]) =>
this.permissions.authorize(requests.slice(), options),
{
// Serialize the permission name and resourceRef as
// a query string to avoid collisions from overlapping
// permission names and resourceRefs.
cacheKeyFn: ({ permission: { name }, resourceRef }) =>
qs.stringify({ name, resourceRef }),
},
);
const requestedTypes = query.types || Object.keys(this.types);