Skip to main content
Glama
smart.test.ts8.69 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { AccessPolicy } from '@medplum/fhirtypes'; import { randomUUID } from 'node:crypto'; import type { PopulatedAccessPolicy } from './accesspolicy'; import { applySmartScopes, parseSmartScopes } from './smart'; describe('SMART on FHIR', () => { test('Parse empty', () => { expect(parseSmartScopes(undefined)).toStrictEqual([]); expect(parseSmartScopes(null as unknown as string)).toStrictEqual([]); expect(parseSmartScopes('')).toStrictEqual([]); expect(parseSmartScopes('openid')).toStrictEqual([]); expect(parseSmartScopes('x/y.z')).toStrictEqual([]); expect(parseSmartScopes('patient/Observation.chum')).toStrictEqual([]); expect(parseSmartScopes('patient/Observation.sdurc')).toStrictEqual([]); expect(parseSmartScopes('patient/Observation.c*')).toStrictEqual([]); expect(parseSmartScopes('patient/Observation.*c')).toStrictEqual([]); }); test('Parse scopes', () => { // Patient-specific scopes // https://build.fhir.org/ig/HL7/smart-app-launch/scopes-and-launch-context.html#patient-specific-scopes expect(parseSmartScopes('patient/Observation.rs')).toMatchObject([ { permissionType: 'patient', resourceType: 'Observation', scope: 'rs' }, ]); expect(parseSmartScopes('patient/Patient.r')).toMatchObject([ { permissionType: 'patient', resourceType: 'Patient', scope: 'r' }, ]); expect(parseSmartScopes('patient/Observation.c')).toMatchObject([ { permissionType: 'patient', resourceType: 'Observation', scope: 'c' }, ]); expect(parseSmartScopes('patient/*.cruds')).toMatchObject([ { permissionType: 'patient', resourceType: '*', scope: 'cruds' }, ]); expect(parseSmartScopes('patient/*.*')).toMatchObject([ { permissionType: 'patient', resourceType: '*', scope: 'cruds' }, ]); // User-level scopes // https://build.fhir.org/ig/HL7/smart-app-launch/scopes-and-launch-context.html#user-level-scopes expect(parseSmartScopes('user/Observation.rs')).toMatchObject([ { permissionType: 'user', resourceType: 'Observation', scope: 'rs' }, ]); expect(parseSmartScopes('user/Appointment.cruds')).toMatchObject([ { permissionType: 'user', resourceType: 'Appointment', scope: 'cruds' }, ]); expect(parseSmartScopes('user/*.cruds')).toMatchObject([ { permissionType: 'user', resourceType: '*', scope: 'cruds' }, ]); expect(parseSmartScopes('user/Patient.rs')).toMatchObject([ { permissionType: 'user', resourceType: 'Patient', scope: 'rs' }, ]); // System-level scopes // https://build.fhir.org/ig/HL7/smart-app-launch/scopes-and-launch-context.html#system-level-scopes expect(parseSmartScopes('system/Observation.rs')).toMatchObject([ { permissionType: 'system', resourceType: 'Observation', scope: 'rs' }, ]); expect(parseSmartScopes('system/*.rs')).toMatchObject([ { permissionType: 'system', resourceType: '*', scope: 'rs' }, ]); expect(parseSmartScopes('system/Encounter.cud')).toMatchObject([ { permissionType: 'system', resourceType: 'Encounter', scope: 'cud' }, ]); // SMART v1 scope formats // https://hl7.org/fhir/smart-app-launch/scopes-and-launch-context.html#scopes-for-requesting-fhir-resources expect(parseSmartScopes('system/Observation.*')).toMatchObject([ { permissionType: 'system', resourceType: 'Observation', scope: 'cruds' }, ]); expect(parseSmartScopes('system/*.read')).toMatchObject([ { permissionType: 'system', resourceType: '*', scope: 'rs' }, ]); expect(parseSmartScopes('system/Encounter.write')).toMatchObject([ { permissionType: 'system', resourceType: 'Encounter', scope: 'cud' }, ]); }); test('Do not change access policy', () => { const startAccessPolicy: PopulatedAccessPolicy = { resourceType: 'AccessPolicy', resource: [ { resourceType: 'Observation', }, { resourceType: 'Patient', }, { resourceType: 'VisionPrescription', }, ], }; const scope = 'openid'; expect(applySmartScopes(startAccessPolicy, scope)).toMatchObject({ resourceType: 'AccessPolicy', resource: [ { resourceType: 'Observation', }, { resourceType: 'Patient', }, { resourceType: 'VisionPrescription', }, ], }); }); test('Generate access policy', () => { expect( applySmartScopes( { resourceType: 'AccessPolicy', resource: [{ resourceType: '*' }] }, 'patient/Observation.cruds patient/Patient.cruds' ) ).toMatchObject({ resourceType: 'AccessPolicy', resource: [ { resourceType: 'Observation', }, { resourceType: 'Patient', }, ], }); }); test('Intersect access policy', () => { const startAccessPolicy: PopulatedAccessPolicy = { resourceType: 'AccessPolicy', resource: [ { resourceType: 'Observation', }, { resourceType: 'Patient', }, { resourceType: 'VisionPrescription', }, ], }; const scope = 'patient/Patient.cruds patient/ServiceRequest.cruds'; expect(applySmartScopes(startAccessPolicy, scope)).toMatchObject({ resourceType: 'AccessPolicy', resource: [ { resourceType: 'Patient', }, ], }); }); test('Intersect with wildcard access policy', () => { const startAccessPolicy: PopulatedAccessPolicy = { resourceType: 'AccessPolicy', resource: [ { resourceType: 'StructureDefinition', readonly: true }, { resourceType: 'SearchParameter', readonly: true }, { resourceType: '*' }, ], }; const scope = 'patient/Patient.rs patient/StructureDefinition.* patient/Practitioner.rus'; expect(applySmartScopes(startAccessPolicy, scope)).toMatchObject<AccessPolicy>({ resourceType: 'AccessPolicy', resource: [ { resourceType: 'StructureDefinition', readonly: true }, { resourceType: 'Patient', readonly: true }, { resourceType: 'StructureDefinition' }, // Expanded from * { resourceType: 'Practitioner' }, ], }); }); test('Intersect with granular scopes and criteria', () => { const id = randomUUID(); const compartment = `Patient/${id}`; const startAccessPolicy: PopulatedAccessPolicy = { resourceType: 'AccessPolicy', resource: [ { resourceType: 'Patient', readonly: true, criteria: 'Patient?_id=' + id }, { resourceType: 'Practitioner', readonly: true }, { resourceType: 'Goal', criteria: 'Goal?identifier=http://example.com/patientVisible|true&_compartment=' + compartment, }, { resourceType: '*', criteria: '*?_compartment=' + compartment }, ], }; const scope = 'patient/Patient.* patient/Practitioner.rus?identifier=http://hl7.org/fhir/sid/us-npi|1234567893 patient/Goal.rs?category=nursing patient/Condition.rus?category=encounter-diagnosis patient/Condition.rus?category=health-concern'; expect(applySmartScopes(startAccessPolicy, scope)).toMatchObject<AccessPolicy>({ resourceType: 'AccessPolicy', resource: [ { resourceType: 'Patient', readonly: true, criteria: 'Patient?_id=' + id }, { resourceType: 'Practitioner', readonly: true, criteria: 'Practitioner?identifier=http://hl7.org/fhir/sid/us-npi|1234567893', }, { resourceType: 'Goal', readonly: true, criteria: `Goal?identifier=http://example.com/patientVisible|true&_compartment=${compartment}&category=nursing`, }, { resourceType: 'Patient', criteria: `Patient?_compartment=${compartment}`, }, { resourceType: 'Practitioner', criteria: `Practitioner?_compartment=${compartment}&identifier=http://hl7.org/fhir/sid/us-npi|1234567893`, }, { resourceType: 'Goal', readonly: true, criteria: `Goal?_compartment=${compartment}&category=nursing`, }, { resourceType: 'Condition', criteria: `Condition?_compartment=${compartment}&category=encounter-diagnosis`, }, { resourceType: 'Condition', criteria: `Condition?_compartment=${compartment}&category=health-concern`, }, ], }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server