Skip to content

Commit

Permalink
feat: add strict mode that forbid accessing resource props
Browse files Browse the repository at this point in the history
  • Loading branch information
ruscoder committed May 27, 2024
1 parent 670bb44 commit f401de6
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 37 deletions.
80 changes: 47 additions & 33 deletions ts/server/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { Resource } from 'fhir/r4b';
import * as fhirpath_r4_model from 'fhirpath/fhir-context/r4';
import * as fhirpath from 'fhirpath';
import { FPMLValidationErrorFilter } from './app.filters';
import { FPOptions } from './utils/extract';
class Template {
context: Record<string, Resource> | Resource;
template: object;
strict?: boolean;
}

function containsQuestionnaireResponse(
Expand All @@ -23,63 +25,75 @@ export class AppController {
@Post(['parse-template', 'r4/parse-template'])
@HttpCode(200)
resolveTemplateR4(@Body() body: Template): object {
const { context, template } = body;
const { context, template, strict = false } = body;

const options: FPOptions = {
userInvocationTable: {
answers: {
fn: (inputs, linkId: string) => {
return fhirpath.evaluate(
inputs,
`repeat(item).where(linkId='${linkId}').answer.value`,
null,
fhirpath_r4_model,
null,
);
},
arity: { 0: [], 1: ['String'] },
},
},
};
if (containsQuestionnaireResponse(context)) {
return this.appService.resolveTemplate(
context.QuestionnaireResponse,
template,
context,
fhirpath_r4_model,
{
userInvocationTable: {
answers: {
fn: (inputs, linkId: string) => {
return fhirpath.evaluate(
inputs,
`repeat(item).where(linkId='${linkId}').answer.value`,
null,
fhirpath_r4_model,
);
},
arity: { 0: [], 1: ['String'] },
},
},
},
options,
strict,
);
}

return this.appService.resolveTemplate(context, template, context, fhirpath_r4_model);
return this.appService.resolveTemplate(
context,
template,
context,
fhirpath_r4_model,
options,
strict,
);
}

@Post('aidbox/parse-template')
@HttpCode(200)
resolveTemplateAidbox(@Body() body: Template): object {
const { context, template } = body;
const { context, template, strict = false } = body;

const options: FPOptions = {
userInvocationTable: {
answers: {
fn: (inputs, linkId: string) => {
return fhirpath.evaluate(
inputs,
`repeat(item).where(linkId='${linkId}').answer.value.children()`,
null,
);
},
arity: { 0: [], 1: ['String'] },
},
},
};
if (containsQuestionnaireResponse(context)) {
return this.appService.resolveTemplate(
context.QuestionnaireResponse,
template,
context,
null,
{
userInvocationTable: {
answers: {
fn: (inputs, linkId: string) => {
return fhirpath.evaluate(
inputs,
`repeat(item).where(linkId='${linkId}').answer.value.children()`,
null,
);
},
arity: { 0: [], 1: ['String'] },
},
},
},
options,
strict,
);
}

return this.appService.resolveTemplate(context, template, context);
return this.appService.resolveTemplate(context, template, context, null, options, strict);
}
}
3 changes: 2 additions & 1 deletion ts/server/src/app.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export class AppService {
context: Context,
model?: Model,
options?: FPOptions,
strict?: boolean
): object {
return resolveTemplate(resource, template, { root: resource, ...context }, model, options);
return resolveTemplate(resource, template, { root: resource, ...context }, model, options, strict);
}
}
6 changes: 6 additions & 0 deletions ts/server/src/utils/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { FPMLValidationError, resolveTemplate } from './extract';
describe('Transformation', () => {
const resource = { list: [{ key: 1 }, { key: 2 }, { key: 3 }] } as any;

test('fails on access props of resource in strict mode', () => {
expect(() =>
resolveTemplate(resource, { key: '{{ list }}' }, {}, null, null, true),
).toThrow(FPMLValidationError);
});

test('for empty object return empty object', () => {
expect(resolveTemplate(resource, {})).toStrictEqual({});
});
Expand Down
31 changes: 28 additions & 3 deletions ts/server/src/utils/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,43 @@ export interface FPOptions {
export class FPMLValidationError extends Error {
constructor(message: string, path: Path) {
const pathStr = path.filter((x) => x != rootNodeKey).join('.');
super(`${message} on path ${pathStr}`);
super(`${message}. Path '${pathStr}'`);
}
}

const guardedResource = new Proxy(
{},
{
get: (obj, prop) => {
if (prop === '__path__' || prop === 'resourceType') {
return undefined;
}

throw new Error(
`Forbidden access to resource property ${String(
prop,
)} in strict mode. Use context instead`,
);
},
},
);

export function resolveTemplate(
resource: Resource,
template: any,
context?: Context,
model?: Model,
fpOptions?: FPOptions,
strict?: boolean,
): any {
return resolveTemplateRecur([], resource, template, context, model, fpOptions);
return resolveTemplateRecur(
[],
strict ? guardedResource : resource,
template,
context,
model,
fpOptions,
);
}

function resolveTemplateRecur(
Expand Down Expand Up @@ -354,6 +379,6 @@ export function evaluateExpression(
options,
);
} catch (exc) {
throw new FPMLValidationError(`Can not evaluate "${expression}": ${exc}`, path);
throw new FPMLValidationError(`Can not evaluate '${expression}': ${exc}`, path);
}
}

0 comments on commit f401de6

Please sign in to comment.