Add secure TrizRis access control for OHIF toolbar
Build and Deploy Docs / build-and-deploy-docs (push) Waiting to run

This commit is contained in:
Kenza committed 2026-10-13 19:23:10 +02:00
1 parent f00379a672
commit 1546a4c81a
4 files changed
+168 -8

No files matched your search

@@ -9,6 +9,7 @@ import HeaderPatientInfo from './HeaderPatientInfo';
import { PatientInfoVisibility } from './HeaderPatientInfo/HeaderPatientInfo';
import { preserveQueryParameters } from '@ohif/app';
import { Types } from '@ohif/core';
import { checkTrizRisAccess } from '../services/TrizRisAccessApi';
function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }>) {
const { servicesManager, extensionManager, commandsManager } = useSystem();
@@ -17,6 +18,22 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
const navigate = useNavigate();
const location = useLocation();
const [toolbarDisabled, setToolbarDisabled] = React.useState(true);
React.useEffect(() => {
const chargerAcces = async () => {
const access = await checkTrizRisAccess();
console.log('### ACCESS TOOLBAR ###', access);
console.log('### canUseToolbar ###', access?.canUseToolbar);
console.log('### canConsulter ###', access?.canConsulter);
console.log('### isMedecin ###', access?.isMedecin);
setToolbarDisabled(access?.canUseToolbar !== true);
};
chargerAcces();
}, []);
const onClickReturnButton = () => {
const { pathname } = location;
const dataSourceIdx = pathname.indexOf('/', 1);
@@ -104,7 +121,21 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
isReturnEnabled={!!appConfig.showStudyList}
onClickReturnButton={onClickReturnButton}
WhiteLabeling={appConfig.whiteLabeling}
Secondary={<Toolbar buttonSection="secondary" />}
Secondary={
<div
style={
toolbarDisabled
? {
pointerEvents: 'none',
opacity: 0.45,
cursor: 'not-allowed',
}
: undefined
}
>
<Toolbar buttonSection="secondary" />
</div>
}
PatientInfo={
appConfig.showPatientInfo !== PatientInfoVisibility.DISABLED && (
<HeaderPatientInfo
@@ -138,7 +169,14 @@ function ViewerHeader({ appConfig }: withAppTypes<{ appConfig: AppTypes.Config }
</div>
}
>
<div className="relative flex justify-center gap-[4px]">
<div
className="relative flex justify-center gap-[4px]"
style={{
pointerEvents: toolbarDisabled ? 'none' : 'auto',
opacity: toolbarDisabled ? 0.45 : 1,
cursor: toolbarDisabled ? 'not-allowed' : 'default',
}}
>
<Toolbar buttonSection="primary" />
</div>
</Header>
@@ -2,7 +2,15 @@ import { TrizRisConfig } from '@ohif/core/src/config/TrizRisConfig';
const ACCESS_URL = `${TrizRisConfig.apiBaseUrl}/ohif/access`;
export async function checkTrizRisAccess(): Promise<boolean> {
export interface TrizRisAccess {
authorized: boolean;
isMedecin: boolean;
canVisualiser: boolean;
canConsulter: boolean;
canUseToolbar: boolean;
}
export async function checkTrizRisAccess(): Promise<TrizRisAccess | null> {
try {
const response = await fetch(ACCESS_URL, {
method: 'GET',
@@ -15,15 +23,22 @@ export async function checkTrizRisAccess(): Promise<boolean> {
});
if (!response.ok) {
return false;
return null;
}
const result = await response.json();
console.log('### REPONSE ACCESS TRIZRIS ###', result);
return result.authorized === true;
return {
authorized: result.authorized === true,
isMedecin: result.isMedecin === true,
canVisualiser: result.canVisualiser === true,
canConsulter: result.canConsulter === true,
canUseToolbar: result.canUseToolbar === true,
};
} catch (error) {
console.error('### ERREUR VERIFICATION ACCES TRIZRIS ###', error);
return false;
return null;
}
}
+2 -1
View File
@@ -9,6 +9,7 @@ const webpackBase = require('./../../../.webpack/webpack.base.js');
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
const PUBLIC_DIR = path.join(__dirname, '../public');
const { ORTHANC_BASE_URL } = require('../config/trizris.shared');
// Ignore node_modules except @cornerstonejs (symlinked local development).
const WATCH_IGNORED = /node_modules[\\/](?!@cornerstonejs(?:[\\/]|$))/;
@@ -212,7 +213,7 @@ module.exports = (env, argv) => {
{
context: ['/orthanc'],
target: 'http://localhost:8042',
target: ORTHANC_BASE_URL,
changeOrigin: true,
pathRewrite: {
'^/orthanc': '',
+107 -1
View File
@@ -66,12 +66,96 @@ export default function ModeRoute({
userAuthenticationService,
customizationService,
} = servicesManager.services;
console.log('displaySetService =', displaySetService);
const { extensions, sopClassHandlers, hangingProtocol } = mode;
const runTimeHangingProtocolId = lowerCaseSearchParams.get('hangingprotocolid');
const runTimeStageId = lowerCaseSearchParams.get('stageid');
const token = lowerCaseSearchParams.get('token');
// ==================== TRIZRIS : SOP INITIAL ====================
const initialSopInstanceUID = new URLSearchParams(window.location.search).get(
'initialSopInstanceUID'
);
useEffect(() => {
if (!initialSopInstanceUID) {
return;
}
const { viewportGridService, cornerstoneViewportService } = servicesManager.services;
const essayerNavigation = () => {
const displaySet = displaySetService.getDisplaySetForSOPInstanceUID(
initialSopInstanceUID,
null
);
console.log('DISPLAY SET DU SOP APRES CHARGEMENT =', displaySet);
if (!displaySet) {
return;
}
const imageIndex = displaySet.instances.findIndex(
instance => instance.SOPInstanceUID === initialSopInstanceUID
);
console.log('INDEX DU SOP =', imageIndex);
if (imageIndex < 0) {
return;
}
const { viewports } = viewportGridService.getState();
let targetViewport = null;
viewports.forEach((viewport, viewportId) => {
console.log('VIEWPORT', viewportId, 'displaySets =', viewport.displaySetInstanceUIDs);
if (viewport.displaySetInstanceUIDs?.includes(displaySet.displaySetInstanceUID)) {
targetViewport = {
id: viewportId,
...viewport,
};
}
});
console.log('TARGET VIEWPORT =', targetViewport);
if (!targetViewport) {
console.log('### VIEWPORT PAS ENCORE PRET -> ATTENTE ###');
return;
}
console.log('### VIEWPORT TROUVE POUR KEY IMAGE ###', targetViewport.id);
commandsManager.run('jumpToImage', {
imageIndex,
viewport: targetViewport,
});
};
// 1. essai immédiat
essayerNavigation();
// 2. attendre les display sets
const { unsubscribe: unsubscribeDisplaySets } = displaySetService.subscribe(
displaySetService.EVENTS.DISPLAY_SETS_ADDED,
essayerNavigation
);
// 3. attendre que le viewport ait réellement reçu ses données
const { unsubscribe: unsubscribeViewport } = cornerstoneViewportService.subscribe(
cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED,
essayerNavigation
);
return () => {
unsubscribeDisplaySets();
unsubscribeViewport();
};
}, [initialSopInstanceUID, displaySetService, servicesManager]); // ===============================================================
if (token) {
updateAuthServiceAndCleanUrl(token, location, userAuthenticationService);
@@ -232,6 +316,28 @@ export default function ModeRoute({
// Add SOPClassHandlers to a new SOPClassManager.
displaySetService.init(extensionManager, sopClassHandlers);
const displaySets = displaySetService.getActiveDisplaySets
? displaySetService.getActiveDisplaySets()
: displaySetService.getDisplaySets();
console.log('DISPLAY SETS =', displaySets);
if (displaySets && displaySets.length > 0) {
displaySets.forEach((displaySet, index) => {
console.log('================ DISPLAY SET', index, '================');
console.log('SeriesInstanceUID =', displaySet.SeriesInstanceUID);
console.log('displaySetInstanceUID =', displaySet.displaySetInstanceUID);
console.log('CHAMPS =', Object.keys(displaySet));
console.log('images =', displaySet.images);
console.log('instances =', displaySet.instances);
});
}
extensionManager.onModeEnter({
servicesManager,
extensionManager,