search-backend: deduplicate authorize calls
Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user