Skip to main content
Glama
onbehalfof.test.ts17.7 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { ContentType, forbidden, getReferenceString } from '@medplum/core'; import type { Patient } from '@medplum/fhirtypes'; import { randomUUID } from 'crypto'; import express from 'express'; import request from 'supertest'; import { initApp, shutdownApp } from '../app'; import { loadTestConfig } from '../config/loader'; import { addTestUser, createTestProject, withTestContext } from '../test.setup'; describe('On Behalf Of', () => { const app = express(); beforeAll(async () => { const config = await loadTestConfig(); await initApp(app, config); }); afterAll(async () => { await shutdownApp(); }); test('Set meta onBehalfOf with basic auth', () => withTestContext(async () => { // Create a single project and single admin client const adminAccount = await createTestProject({ withClient: true, withAccessToken: true, membership: { admin: true }, }); const { client, project } = adminAccount; // Setup basic auth for the admin client // This client will be the "primary" user for all HTTP requests const basicAuth = 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'); // Create two test accounts // These represent 2 "normal" users // They will be the "onBehalfOf" users for all HTTP requests const org1 = 'Organization/' + randomUUID(); const testAccount1 = await addTestUser(project, { resourceType: 'AccessPolicy', compartment: { reference: org1 }, resource: [{ resourceType: 'Patient', criteria: 'Patient?_compartment=' + org1 }], }); const org2 = 'Organization/' + randomUUID(); const testAccount2 = await addTestUser(project, { resourceType: 'AccessPolicy', compartment: { reference: org2 }, resource: [{ resourceType: 'Patient', criteria: 'Patient?_compartment=' + org2 }], }); // Create a patient on behalf of test account 1 const res1 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount1.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient' }); expect(res1.status).toBe(201); expect(res1.body.resourceType).toStrictEqual('Patient'); expect(res1.headers.location).toContain('Patient'); expect(res1.headers.location).toContain(res1.body.id); const patient1 = res1.body; expect(patient1.resourceType).toBe('Patient'); expect(patient1.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient1.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount1.profile)); // Read the patient on behalf of test account 1 const res2 = await request(app) .get(`/fhir/R4/Patient/` + patient1.id) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount1.profile)); expect(res2.status).toBe(200); const patient2 = res2.body; expect(patient2.resourceType).toBe('Patient'); expect(patient2.id).toBe(patient1.id); expect(patient2.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient2.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount1.profile)); // Create a patient on behalf of test account 2 const res3 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount2.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient' }); expect(res3.status).toBe(201); expect(res3.body.resourceType).toStrictEqual('Patient'); expect(res3.headers.location).toContain('Patient'); expect(res3.headers.location).toContain(res3.body.id); const patient3 = res3.body; expect(patient3.resourceType).toBe('Patient'); expect(patient3.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient3.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount2.profile)); // Read the patient on behalf of test account 2 const res4 = await request(app) .get(`/fhir/R4/Patient/` + patient3.id) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount2.profile)); expect(res4.status).toBe(200); const patient4 = res4.body; expect(patient4.resourceType).toBe('Patient'); expect(patient4.id).toBe(patient3.id); expect(patient4.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient4.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount2.profile)); // Try to read the first patient on behalf of test account 2 // This should fail because the patient was created on behalf of test account 1 const res5 = await request(app) .get(`/fhir/R4/Patient/` + patient1.id) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount2.profile)); expect(res5.status).toBe(404); // Try to read the second patient on behalf of test account 1 // This should fail because the patient was created on behalf of test account 2 const res6 = await request(app) .get(`/fhir/R4/Patient/` + patient3.id) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount1.profile)); expect(res6.status).toBe(404); })); test('Set meta onBehalfOf with client credentials', () => withTestContext(async () => { // Create a single project and single admin client const adminAccount = await createTestProject({ withClient: true, withAccessToken: true, membership: { admin: true }, }); const { accessToken, project } = adminAccount; // Create two test accounts // These represent 2 "normal" users // They will be the "onBehalfOf" users for all HTTP requests const org1 = 'Organization/' + randomUUID(); const testAccount1 = await addTestUser(project, { resourceType: 'AccessPolicy', compartment: { reference: org1 }, resource: [{ resourceType: 'Patient', criteria: 'Patient?_compartment=' + org1 }], }); const org2 = 'Organization/' + randomUUID(); const testAccount2 = await addTestUser(project, { resourceType: 'AccessPolicy', compartment: { reference: org2 }, resource: [{ resourceType: 'Patient', criteria: 'Patient?_compartment=' + org2 }], }); // Create a patient on behalf of test account 1 const res1 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount1.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient' }); expect(res1.status).toBe(201); expect(res1.body.resourceType).toStrictEqual('Patient'); expect(res1.headers.location).toContain('Patient'); expect(res1.headers.location).toContain(res1.body.id); const patient1 = res1.body; expect(patient1.resourceType).toBe('Patient'); expect(patient1.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient1.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount1.profile)); // Read the patient on behalf of test account 1 const res2 = await request(app) .get(`/fhir/R4/Patient/` + patient1.id) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount1.profile)); expect(res2.status).toBe(200); const patient2 = res2.body; expect(patient2.resourceType).toBe('Patient'); expect(patient2.id).toBe(patient1.id); expect(patient2.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient2.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount1.profile)); // Create a patient on behalf of test account 2 const res3 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount2.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient' }); expect(res3.status).toBe(201); expect(res3.body.resourceType).toStrictEqual('Patient'); expect(res3.headers.location).toContain('Patient'); expect(res3.headers.location).toContain(res3.body.id); const patient3 = res3.body; expect(patient3.resourceType).toBe('Patient'); expect(patient3.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient3.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount2.profile)); // Read the patient on behalf of test account 2 const res4 = await request(app) .get(`/fhir/R4/Patient/` + patient3.id) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount2.profile)); expect(res4.status).toBe(200); const patient4 = res4.body; expect(patient4.resourceType).toBe('Patient'); expect(patient4.id).toBe(patient3.id); expect(patient4.meta.author.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient4.meta.onBehalfOf.reference).toStrictEqual(getReferenceString(testAccount2.profile)); // Try to read the first patient on behalf of test account 2 // This should fail because the patient was created on behalf of test account 1 const res5 = await request(app) .get(`/fhir/R4/Patient/` + patient1.id) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount2.profile)); expect(res5.status).toBe(404); // Try to read the second patient on behalf of test account 1 // This should fail because the patient was created on behalf of test account 2 const res6 = await request(app) .get(`/fhir/R4/Patient/` + patient3.id) .set('Authorization', 'Bearer ' + accessToken) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount1.profile)); expect(res6.status).toBe(404); })); test('Forbidden for non-admin', () => withTestContext(async () => { // Create a project with a non-admin user const testAccount1 = await createTestProject({ withClient: true, withAccessToken: true, }); const { client, project } = testAccount1; const basicAuth = 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'); const testAccount2 = await addTestUser(project); // Try to use onBehalfOf without admin rights // This should fail const res1 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount2.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient' }); expect(res1.status).toBe(403); expect(res1.body).toMatchObject(forbidden); })); test('Forbidden for cross project', () => withTestContext(async () => { const adminAccount1 = await createTestProject({ withClient: true, withAccessToken: true, membership: { admin: true }, }); const adminAccount2 = await createTestProject({ withClient: true, withAccessToken: true, membership: { admin: true }, }); const { client } = adminAccount1; const basicAuth = 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'); // Try to use onBehalfOf for a different project // This should fail const res1 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(adminAccount2.client)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient' }); expect(res1.status).toBe(403); expect(res1.body).toMatchObject(forbidden); })); test('Consistent meta.account behavior', () => withTestContext(async () => { // Create a single project and single admin client const adminAccount = await createTestProject({ withClient: true, withAccessToken: true, membership: { admin: true }, }); const { client, project } = adminAccount; // Setup basic auth for the admin client // This client will be the "primary" user for all HTTP requests const basicAuth = 'Basic ' + Buffer.from(client.id + ':' + client.secret).toString('base64'); // Create non-admin test account // They will be the "onBehalfOf" users for all HTTP requests const account = 'Organization/' + randomUUID(); const testAccount = await addTestUser(project, { resourceType: 'AccessPolicy', compartment: { reference: account }, resource: [{ resourceType: 'Patient', criteria: 'Patient?_compartment=' + account }], }); // Create a patient on behalf of test account 1 const res1 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient' }); expect(res1.status).toBe(201); expect(res1.body.resourceType).toStrictEqual('Patient'); const patient1 = res1.body as Patient; expect(patient1.resourceType).toBe('Patient'); expect(patient1.meta?.author?.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient1.meta?.onBehalfOf?.reference).toStrictEqual(getReferenceString(testAccount.profile)); expect(patient1.meta?.account?.reference).toStrictEqual(account); expect(patient1.meta?.accounts?.length).toStrictEqual(1); expect(patient1.meta?.accounts?.[0]?.reference).toStrictEqual(account); // Pass in empty meta. // This should be ignored, because the on-behalf-of user is not a project admin const res2 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient', meta: {}, }); expect(res2.status).toBe(201); expect(res2.body.resourceType).toStrictEqual('Patient'); const patient2 = res2.body as Patient; expect(patient2.resourceType).toBe('Patient'); expect(patient2.id).not.toBe(patient1.id); expect(patient2.meta?.author?.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient2.meta?.onBehalfOf?.reference).toStrictEqual(getReferenceString(testAccount.profile)); expect(patient2.meta?.account?.reference).toStrictEqual(account); expect(patient2.meta?.accounts?.length).toStrictEqual(1); expect(patient2.meta?.accounts?.[0]?.reference).toStrictEqual(account); // Try to manually set the meta.account // This should be ignored, because the on-behalf-of user is not a project admin const res3 = await request(app) .post(`/fhir/R4/Patient`) .set('Authorization', basicAuth) .set('X-Medplum', 'extended') .set('X-Medplum-On-Behalf-Of', getReferenceString(testAccount.profile)) .set('Content-Type', ContentType.FHIR_JSON) .send({ resourceType: 'Patient', meta: { account: { reference: 'Organization/' + randomUUID() }, }, }); expect(res3.status).toBe(201); expect(res3.body.resourceType).toStrictEqual('Patient'); const patient3 = res3.body as Patient; expect(patient3.resourceType).toBe('Patient'); expect(patient3.id).not.toBe(patient1.id); expect(patient3.meta?.author?.reference).toStrictEqual(getReferenceString(adminAccount.client)); expect(patient3.meta?.onBehalfOf?.reference).toStrictEqual(getReferenceString(testAccount.profile)); expect(patient3.meta?.account?.reference).toStrictEqual(account); expect(patient3.meta?.accounts?.length).toStrictEqual(1); expect(patient3.meta?.accounts?.[0]?.reference).toStrictEqual(account); })); });

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