Skip to content

Commit 53694da

Browse files
luissantosHCITwayfarer3130sedghi
authored
[Ready] fix 5288 removal of accept header options in retrieve metadata (#5293)
* chore: Minor adjustment to generateAcceptHeader function signature to reflect expected return type. Signed-off-by: Luis M. Santos <[email protected]> * fix: Added switch to skip the Accept header generation when requesting metadata. WADO metadata request is more likely to return JSON and some VNAs do not like the extra options in the Accept header. Also, passing an empty array is not sufficient because somewhere we still include a comma that breaks the Accept header. It's better to omit it for the metadata request only. Signed-off-by: Luis M. Santos <[email protected]> * chore: Added new HeadersInterface interface type docstrings. Signed-off-by: Luis M. Santos <[email protected]> * chore: Refactored getAuthorizationHeader() function signature so it is typed checked. Of course, I upgraded the module to a TypeScript module. Moved the request header interfaces into its own TypeScript module (RequestHeaders.ts) in the core types. Signed-off-by: Luis M. Santos <[email protected]> * chore: Refactored the user logic to include a TypeScript interface. As a result, upgraded the source file to TypeScript. Removed the User interface from RequestHeaders module and moved them to the user module. Signed-off-by: Luis M. Santos <[email protected]> * chore: Updated function signatures, user import, and confirmed unit tests are passing. Signed-off-by: Luis M. Santos <[email protected]> * chore: Minor stylistic adjustment to getAuthorizationHeader.test.ts Signed-off-by: Luis M. Santos <[email protected]> * chore: Added missing comments. Signed-off-by: Luis M. Santos <[email protected]> * chore adjusted generateWadoHeader parameter's name and added comment about the expected default header. Signed-off-by: Luis M. Santos <[email protected]> * Just tweaking the interface a bit to be more consistent. * fix authorization header signature change --------- Signed-off-by: Luis M. Santos <[email protected]> Co-authored-by: Bill Wallace <[email protected]> Co-authored-by: Alireza <[email protected]>
1 parent 9d68b6e commit 53694da

File tree

9 files changed

+135
-47
lines changed

9 files changed

+135
-47
lines changed

extensions/default/src/DicomWebDataSource/index.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { retrieveStudyMetadata, deleteStudyMetadataPromise } from './retrieveStu
1616
import StaticWadoClient from './utils/StaticWadoClient';
1717
import getDirectURL from '../utils/getDirectURL';
1818
import { fixBulkDataURI } from './utils/fixBulkDataURI';
19+
import {HeadersInterface} from '@ohif/core/src/types/RequestHeaders';
1920

2021
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
2122

@@ -97,6 +98,20 @@ export type BulkDataURIConfig = {
9798
relativeResolution?: 'studies' | 'series';
9899
};
99100

101+
/**
102+
* The header options are the options passed into the generateWadoHeader
103+
* command. This takes an extensible set of attributes to allow future enhancements.
104+
*/
105+
export interface HeaderOptions {
106+
includeTransferSyntax?: boolean;
107+
}
108+
109+
/**
110+
* Metadata and some other requests don't permit the transfer syntax to be included,
111+
* so pass in the excludeTransferSyntax parameter.
112+
*/
113+
export const excludeTransferSyntax: HeaderOptions = { includeTransferSyntax: false };
114+
100115
/**
101116
* Creates a DICOM Web API based on the provided configuration.
102117
*
@@ -128,27 +143,41 @@ function createDicomWebApi(dicomWebConfig: DicomWebConfig, servicesManager) {
128143
dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig));
129144

130145
getAuthorizationHeader = () => {
131-
const xhrRequestHeaders = {};
146+
const xhrRequestHeaders: HeadersInterface = {};
132147
const authHeaders = userAuthenticationService.getAuthorizationHeader();
133148
if (authHeaders && authHeaders.Authorization) {
134149
xhrRequestHeaders.Authorization = authHeaders.Authorization;
135150
}
136151
return xhrRequestHeaders;
137152
};
138153

139-
generateWadoHeader = () => {
154+
/**
155+
* Generates the wado header for requesting resources from DICOMweb.
156+
* These are classified into those that are dependent on the transfer syntax
157+
* and those that aren't, as defined by the include transfer syntax attribute.
158+
*/
159+
generateWadoHeader = (options: HeaderOptions): HeadersInterface => {
140160
const authorizationHeader = getAuthorizationHeader();
141-
//Generate accept header depending on config params
142-
const formattedAcceptHeader = utils.generateAcceptHeader(
143-
dicomWebConfig.acceptHeader,
144-
dicomWebConfig.requestTransferSyntaxUID,
145-
dicomWebConfig.omitQuotationForMultipartRequest
146-
);
147-
148-
return {
149-
...authorizationHeader,
150-
Accept: formattedAcceptHeader,
151-
};
161+
if (options?.includeTransferSyntax!==false) {
162+
//Generate accept header depending on config params
163+
const formattedAcceptHeader = utils.generateAcceptHeader(
164+
dicomWebConfig.acceptHeader,
165+
dicomWebConfig.requestTransferSyntaxUID,
166+
dicomWebConfig.omitQuotationForMultipartRequest
167+
);
168+
return {
169+
...authorizationHeader,
170+
Accept: formattedAcceptHeader,
171+
};
172+
} else {
173+
// The base header will be included in the request. We simply skip customization options around
174+
// transfer syntaxes and whether the request is multipart. In other words, a request in
175+
// which the server expects Accept: application/dicom+json will still include that in the
176+
// header.
177+
return {
178+
...authorizationHeader
179+
};
180+
}
152181
};
153182

154183
qidoConfig = {
@@ -410,7 +439,7 @@ function createDicomWebApi(dicomWebConfig: DicomWebConfig, servicesManager) {
410439
madeInClient
411440
) => {
412441
const enableStudyLazyLoad = false;
413-
wadoDicomWebClient.headers = generateWadoHeader();
442+
wadoDicomWebClient.headers = generateWadoHeader(excludeTransferSyntax);
414443
// data is all SOPInstanceUIDs
415444
const data = await retrieveStudyMetadata(
416445
wadoDicomWebClient,
@@ -484,7 +513,7 @@ function createDicomWebApi(dicomWebConfig: DicomWebConfig, servicesManager) {
484513
returnPromises = false
485514
) => {
486515
const enableStudyLazyLoad = true;
487-
wadoDicomWebClient.headers = generateWadoHeader();
516+
wadoDicomWebClient.headers = generateWadoHeader(excludeTransferSyntax);
488517
// Get Series
489518
const { preLoadData: seriesSummaryMetadata, promises: seriesPromises } =
490519
await retrieveStudyMetadata(

platform/core/src/DICOMWeb/getAuthorizationHeader.test.js renamed to platform/core/src/DICOMWeb/getAuthorizationHeader.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import getAuthorizationHeader from './getAuthorizationHeader';
22
import user from './../user';
3+
import { HeadersInterface, RequestOptions } from '../types/RequestHeaders';
34

4-
jest.mock('./../user.js');
5+
jest.mock('../user');
56

67
describe('getAuthorizationHeader', () => {
78
it('should return a HTTP Basic Auth when server contains requestOptions.auth', () => {
@@ -15,7 +16,7 @@ describe('getAuthorizationHeader', () => {
1516
Authorization: `Basic ${btoa(validServer.requestOptions.auth)}`,
1617
};
1718

18-
const authentication = getAuthorizationHeader(validServer);
19+
const authentication: HeadersInterface = getAuthorizationHeader(validServer);
1920

2021
expect(authentication).toEqual(expectedAuthorizationHeader);
2122
});
@@ -31,13 +32,13 @@ describe('getAuthorizationHeader', () => {
3132
Authorization: `Basic ${btoa(validServerWithoutPassword.requestOptions.auth)}`,
3233
};
3334

34-
const authentication = getAuthorizationHeader(validServerWithoutPassword);
35+
const authentication: HeadersInterface = getAuthorizationHeader(validServerWithoutPassword);
3536

3637
expect(authentication).toEqual(expectedAuthorizationHeader);
3738
});
3839

3940
it('should return a HTTP Basic Auth when server contains requestOptions.auth custom function', () => {
40-
const validServerCustomAuth = {
41+
const validServerCustomAuth: RequestOptions = {
4142
requestOptions: {
4243
auth: options => `Basic ${options.token}`,
4344
token: 'ZHVtbXlfdXNlcjpkdW1teV9wYXNzd29yZA==',
@@ -48,21 +49,21 @@ describe('getAuthorizationHeader', () => {
4849
Authorization: `Basic ${validServerCustomAuth.requestOptions.token}`,
4950
};
5051

51-
const authentication = getAuthorizationHeader(validServerCustomAuth);
52+
const authentication: HeadersInterface = getAuthorizationHeader(validServerCustomAuth);
5253

5354
expect(authentication).toEqual(expectedAuthorizationHeader);
5455
});
5556

5657
it('should return an empty object when there is no either server.requestOptions.auth or accessToken', () => {
57-
const authentication = getAuthorizationHeader({});
58+
const authentication: HeadersInterface = getAuthorizationHeader({});
5859

5960
expect(authentication).toEqual({});
6061
});
6162

6263
it('should return an Authorization with accessToken when server is not defined and there is an accessToken', () => {
6364
user.getAccessToken.mockImplementationOnce(() => 'MOCKED_TOKEN');
6465

65-
const authentication = getAuthorizationHeader({}, user);
66+
const authentication: HeadersInterface = getAuthorizationHeader({}, user);
6667
const expectedHeaderBasedOnUserAccessToken = {
6768
Authorization: 'Bearer MOCKED_TOKEN',
6869
};

platform/core/src/DICOMWeb/getAuthorizationHeader.js renamed to platform/core/src/DICOMWeb/getAuthorizationHeader.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
11
import 'isomorphic-base64';
2-
import user from '../user';
2+
import {UserAccountInterface} from '../user';
3+
import { HeadersInterface, RequestOptions } from '../types/RequestHeaders';
34

45
/**
56
* Returns the Authorization header as part of an Object.
67
*
78
* @export
89
* @param {Object} [server={}]
9-
* @param {Object} [server.requestOptions]
10-
* @param {string|function} [server.requestOptions.auth]
10+
* @param {Object} [requestOptions]
11+
* @param {string|function} [requestOptions.auth]
12+
* @param {Object} [user]
13+
* @param {function} [user.getAccessToken]
1114
* @returns {Object} { Authorization }
1215
*/
13-
export default function getAuthorizationHeader({ requestOptions } = {}, user) {
14-
const headers = {};
16+
export default function getAuthorizationHeader(
17+
{requestOptions}: RequestOptions = {},
18+
user: UserAccountInterface = {}): HeadersInterface
19+
{
20+
const headers: HeadersInterface = {};
1521

1622
// Check for OHIF.user since this can also be run on the server
1723
const accessToken = user && user.getAccessToken && user.getAccessToken();
1824

1925
// Auth for a specific server
20-
if (requestOptions && requestOptions.auth) {
26+
if (requestOptions?.auth) {
2127
if (typeof requestOptions.auth === 'function') {
2228
// Custom Auth Header
2329
headers.Authorization = requestOptions.auth(requestOptions);
2430
} else {
2531
// HTTP Basic Auth (user:password)
2632
headers.Authorization = `Basic ${btoa(requestOptions.auth)}`;
2733
}
28-
}
29-
// Auth for the user's default
30-
else if (accessToken) {
34+
} else if (accessToken) {
35+
// Auth for the user's default
3136
headers.Authorization = `Bearer ${accessToken}`;
3237
}
3338

platform/core/src/DICOMWeb/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import getAttribute from './getAttribute.js';
2-
import getAuthorizationHeader from './getAuthorizationHeader.js';
2+
import getAuthorizationHeader from './getAuthorizationHeader';
33
import getModalities from './getModalities.js';
44
import getName from './getName.js';
55
import getNumber from './getNumber.js';

platform/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import errorHandler from './errorHandler.js';
99
import log from './log.js';
1010
import object from './object.js';
1111
import string from './string.js';
12-
import user from './user.js';
12+
import user from './user';
1313
import utils from './utils';
1414
import defaults from './defaults';
1515
import * as Types from './types';
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Interface to clearly present the expected fields to linters when building a request header.
3+
*/
4+
export interface HeadersInterface {
5+
/**
6+
* Request Accept options. For example,
7+
* `['multipart/related; type=application/octet-stream; transfer-syntax=1.2.840.10008.1.2.1.99',]`.
8+
*
9+
* Defines to the server the formats it can use to deliver data to us.
10+
*/
11+
Accept?: string[];
12+
/**
13+
* Request Authorization field. It can be overridden with the `requestOptions.auth` config item.
14+
* Contains the authorization credentials or tokens necessary to authorize the request with the
15+
* server.
16+
*/
17+
Authorization?: string;
18+
}
19+
20+
/**
21+
* Interface to clearly present the expected fields to linters when passing the configuration's
22+
* requestOptions struct.
23+
*/
24+
export interface RequestOptions {
25+
requestOptions?: {
26+
/**
27+
* Authentication options to include. Can be a function.
28+
*/
29+
auth?: Function | string;
30+
/**
31+
* Authentication token. Satisfies the test requirement?
32+
*/
33+
token?: string;
34+
}
35+
}

platform/core/src/user.js

Lines changed: 0 additions & 13 deletions
This file was deleted.

platform/core/src/user.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Global user information, to be replaced with a specific version which
3+
* applies the methods.
4+
*/
5+
export let user = {
6+
userLoggedIn: (): boolean => false,
7+
getUserId: () => null,
8+
getName: () => null,
9+
getAccessToken: () => null,
10+
login: () => new Promise((resolve, reject) => reject()),
11+
logout: () => new Promise((resolve, reject) => reject()),
12+
getData: key => null,
13+
setData: (key, value) => null,
14+
};
15+
16+
/**
17+
* Interface to clearly present the expected fields to linters when passing the user account
18+
* struct.
19+
*/
20+
export interface UserAccountInterface {
21+
userLoggedIn?: () => boolean;
22+
getUserId?: () => null;
23+
getName?: () => null;
24+
getAccessToken?: () => null;
25+
login?: () => Promise<any>;
26+
logout?: () => Promise<any>;
27+
getData?: (key: any) => null;
28+
setData?: (key: any, value: any) => null;
29+
}
30+
31+
export default user;

platform/core/src/utils/generateAcceptHeader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const generateAcceptHeader = (
2-
configAcceptHeader = [],
2+
configAcceptHeader: string[] = [],
33
requestTransferSyntaxUID = '*', //default to accept all transfer syntax
44
omitQuotationForMultipartRequest = false
55
): string[] => {

0 commit comments

Comments
 (0)