Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions packages/agent/src/routes/access/chart.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
Caller,
Collection,
ConditionTreeBranch,
DateOperation,
Filter,
Expand Down Expand Up @@ -29,6 +30,7 @@ import { DateTime } from 'luxon';
import { v1 as uuidv1 } from 'uuid';

import ContextFilterFactory from '../../utils/context-filter-factory';
import FieldPathUtils from '../../utils/field-path';
import QueryStringParser from '../../utils/query-string';
import CollectionRoute from '../collection-route';

Expand Down Expand Up @@ -67,6 +69,11 @@ export default class ChartRoute extends CollectionRoute {
chartRequest,
});

await this.services.authorization.assertCanReadQueryFields(context, this.collection, [
'filter',
'search',
]);

switch (chartRequest.type) {
case ChartType.Value:
return this.makeValueChart(context);
Expand Down Expand Up @@ -120,6 +127,11 @@ export default class ChartRoute extends CollectionRoute {
aggregateFieldName: aggregateField,
} = <PieChart>context.request.body;

await this.assertCanReadAggregatedFields(context, this.collection, [
['group a chart by', groupByField],
['aggregate a chart on', aggregateField],
]);

const rows = await this.collection.aggregate(
QueryStringParser.parseCaller(context),
await this.getFilter(context),
Expand All @@ -144,6 +156,11 @@ export default class ChartRoute extends CollectionRoute {
timeRange,
} = <LineChart>context.request.body;

await this.assertCanReadAggregatedFields(context, this.collection, [
['group a chart by', groupByDateField],
['aggregate a chart on', aggregateField],
]);

const filter = await this.getFilter(context);
const filterOnlyWithValues = filter.override({
conditionTree: ConditionTreeFactory.intersect(
Expand Down Expand Up @@ -233,9 +250,25 @@ export default class ChartRoute extends CollectionRoute {
}

if (collection && filter && aggregation) {
const rows = await this.dataSource
.getCollection(collection)
.aggregate(QueryStringParser.parseCaller(context), filter, aggregation, Number(body.limit));
const aggregatedCollection = this.dataSource.getCollection(collection);

await this.assertCanReadAggregatedFields(context, aggregatedCollection, [
['group a leaderboard by', aggregation.groups[0].field],
['aggregate a leaderboard on', aggregation.field],
]);

// A count exposes the cardinality of the relation, which `/relationships/<name>/count` puts
// behind `browse`.
if (!aggregation.field) {
await this.services.authorization.assertCanBrowse(context, field.foreignCollection);
Comment thread
hercemer42 marked this conversation as resolved.
}

const rows = await aggregatedCollection.aggregate(
QueryStringParser.parseCaller(context),
filter,
aggregation,
Number(body.limit),
);

return rows.map(row => ({
key: row.group[aggregation.groups[0].field] as string,
Expand All @@ -254,6 +287,10 @@ export default class ChartRoute extends CollectionRoute {
);
const aggregation = new Aggregation({ operation: aggregator, field: aggregateField });

await this.assertCanReadAggregatedFields(context, this.collection, [
['aggregate a chart on', aggregateField],
]);

const rows = await this.collection.aggregate(
QueryStringParser.parseCaller(context),
filter,
Expand All @@ -263,6 +300,24 @@ export default class ChartRoute extends CollectionRoute {
return rows.length ? (rows[0].value as number) : 0;
}

private async assertCanReadAggregatedFields(
context: Context,
pathCollection: Collection,
fields: Array<[action: string, path: string]>,
): Promise<void> {
await this.services.authorization.assertCanReadUsages(
context,
this.collection.name,
fields
.filter(([, path]) => path)
.map(([action, path]) => ({
action,
path,
collectionName: FieldPathUtils.getLeafCollection(pathCollection, path).name,
})),
);
}

private async getFilter(context: Context): Promise<Filter> {
const scope = await this.services.authorization.getScope(this.collection, context);

Expand Down
5 changes: 5 additions & 0 deletions packages/agent/src/routes/access/count-related.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export default class CountRelatedRoute extends RelationRoute {
await this.services.authorization.assertCanBrowse(context, this.foreignCollection.name);

if (this.foreignCollection.schema.countable) {
await this.services.authorization.assertCanReadQueryFields(context, this.foreignCollection, [
'filter',
'search',
]);

const parentId = IdUtils.unpackId(this.collection.schema, context.params.parentId);
const scope = await this.services.authorization.getScope(this.foreignCollection, context);
const caller = QueryStringParser.parseCaller(context);
Expand Down
5 changes: 5 additions & 0 deletions packages/agent/src/routes/access/count.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ export default class CountRoute extends CollectionRoute {
await this.services.authorization.assertCanBrowse(context, this.collection.name);

if (this.collection.schema.countable) {
await this.services.authorization.assertCanReadQueryFields(context, this.collection, [
'filter',
'search',
]);

const scope = await this.services.authorization.getScope(this.collection, context);
const caller = QueryStringParser.parseCaller(context);
let filter = ContextFilterFactory.build(this.collection, context, scope);
Expand Down
11 changes: 9 additions & 2 deletions packages/agent/src/routes/access/csv-related.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@ export default class CsvRelatedRoute extends RelationRoute {
async handleRelatedCsv(context: Context): Promise<void> {
await this.services.authorization.assertCanBrowse(context, this.foreignCollection.name);
await this.services.authorization.assertCanExport(context, this.foreignCollection.name);
await this.services.authorization.assertCanReadQueryFields(context, this.foreignCollection);

const { header } = context.request.query as Record<string, string>;
const { header: requestedHeader } = context.request.query as Record<string, string>;

const projection = QueryStringParser.parseProjectionFromHeaderOrQuery(
const requested = QueryStringParser.parseProjectionFromHeaderOrQuery(
this.foreignCollection,
context,
);
const projection = await this.services.authorization.redactProjection(
context,
this.foreignCollection,
requested,
);
const header = CsvGenerator.filterHeader(requestedHeader, requested.projection, projection);
const scope = await this.services.authorization.getScope(this.foreignCollection, context);
const caller = QueryStringParser.parseCaller(context);
const filter = ContextFilterFactory.buildPaginated(this.foreignCollection, context, scope);
Expand Down
11 changes: 9 additions & 2 deletions packages/agent/src/routes/access/csv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ export default class CsvRoute extends CollectionRoute {
async handleCsv(context: Context): Promise<void> {
await this.services.authorization.assertCanBrowse(context, this.collection.name);
await this.services.authorization.assertCanExport(context, this.collection.name);
await this.services.authorization.assertCanReadQueryFields(context, this.collection);

const { header } = context.request.query as Record<string, string>;
const { header: requestedHeader } = context.request.query as Record<string, string>;

const projection = QueryStringParser.parseProjectionFromHeaderOrQuery(this.collection, context);
const requested = QueryStringParser.parseProjectionFromHeaderOrQuery(this.collection, context);
const projection = await this.services.authorization.redactProjection(
context,
this.collection,
requested,
);
const header = CsvGenerator.filterHeader(requestedHeader, requested.projection, projection);
const scope = await this.services.authorization.getScope(this.collection, context);
const caller = QueryStringParser.parseCaller(context);
const filter = ContextFilterFactory.buildPaginated(this.collection, context, scope);
Expand Down
6 changes: 5 additions & 1 deletion packages/agent/src/routes/access/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export default class GetRoute extends CollectionRoute {
),
});

const projection = QueryStringParser.parseProjectionFromHeaderOrQuery(this.collection, context);
const projection = await this.services.authorization.redactProjection(
context,
this.collection,
QueryStringParser.parseProjectionFromHeaderOrQuery(this.collection, context),
);

const records = await this.collection.list(
QueryStringParser.parseCaller(context),
Expand Down
6 changes: 4 additions & 2 deletions packages/agent/src/routes/access/list-related.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default class ListRelatedRoute extends RelationRoute {

public async handleListRelated(context: Context): Promise<void> {
await this.services.authorization.assertCanBrowse(context, this.foreignCollection.name);
await this.services.authorization.assertCanReadQueryFields(context, this.foreignCollection);

const parentId = IdUtils.unpackId(this.collection.schema, context.params.parentId);
const scope = await this.services.authorization.getScope(this.foreignCollection, context);
Expand All @@ -27,9 +28,10 @@ export default class ListRelatedRoute extends RelationRoute {
scope,
);

const projection = QueryStringParser.parseProjectionFromHeaderOrQuery(
this.foreignCollection,
const projection = await this.services.authorization.redactProjection(
context,
this.foreignCollection,
QueryStringParser.parseProjectionFromHeaderOrQuery(this.foreignCollection, context),
);

const records = await CollectionUtils.listRelation(
Expand Down
7 changes: 6 additions & 1 deletion packages/agent/src/routes/access/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class ListRoute extends CollectionRoute {

public async handleList(context: Context) {
await this.services.authorization.assertCanBrowse(context, this.collection.name);
await this.services.authorization.assertCanReadQueryFields(context, this.collection);

const scope = await this.services.authorization.getScope(this.collection, context);
let paginatedFilter = ContextFilterFactory.buildPaginated(this.collection, context, scope);
Expand All @@ -20,7 +21,11 @@ export default class ListRoute extends CollectionRoute {
paginatedFilter,
);

const projection = QueryStringParser.parseProjectionFromHeaderOrQuery(this.collection, context);
const projection = await this.services.authorization.redactProjection(
context,
this.collection,
QueryStringParser.parseProjectionFromHeaderOrQuery(this.collection, context),
);

const records = await this.collection.list(
QueryStringParser.parseCaller(context),
Expand Down
5 changes: 4 additions & 1 deletion packages/agent/src/routes/modification/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export default class UpdateRoute extends CollectionRoute {
const [updateResult] = await this.collection.list(
caller,
new Filter({ conditionTree }),
ProjectionFactory.all(this.collection),
await this.services.authorization.redactProjection(context, this.collection, {
Comment thread
hercemer42 marked this conversation as resolved.
projection: ProjectionFactory.all(this.collection),
namedByCaller: false,
}),
);

context.response.body = this.services.serializer.serialize(this.collection, updateResult);
Expand Down
Loading
Loading