diff --git a/OHIFViewer/.meteor/packages b/OHIFViewer/.meteor/packages
index db08c3217..5dcf596e4 100644
--- a/OHIFViewer/.meteor/packages
+++ b/OHIFViewer/.meteor/packages
@@ -25,7 +25,9 @@ reactive-var@1.0.11
reactive-dict@1.2.0
standard-minifier-css@1.4.1
standard-minifier-js@2.3.4
-
+accounts-base
+accounts-oauth
+oauth-encryption
# OHIF Packages
ohif:polyfill
@@ -42,6 +44,8 @@ ohif:dicom-services
ohif:dicomweb-client
ohif:hanging-protocols
ohif:metadata
+ohif:user
+ohif:user-keycloak
fortawesome:fontawesome
momentjs:moment@2.15.1
diff --git a/OHIFViewer/.meteor/versions b/OHIFViewer/.meteor/versions
index 0f7df10e8..c5ffbc870 100644
--- a/OHIFViewer/.meteor/versions
+++ b/OHIFViewer/.meteor/versions
@@ -1,4 +1,6 @@
accounts-base@1.4.2
+accounts-oauth@1.1.15
+accounts-password@1.5.1
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
@@ -38,6 +40,7 @@ ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.7.1
ecmascript-runtime-server@0.7.0
ejson@1.1.0
+email@1.2.3
es5-shim@4.8.0
fastclick@1.0.13
fortawesome:fontawesome@4.7.0
@@ -77,8 +80,15 @@ momentjs:moment@2.22.2
mongo@1.5.1
mongo-dev-server@1.1.0
mongo-id@1.0.7
+mxab:accounts-keycloak@0.0.2
+mxab:keycloak-loader@0.0.2
+mxab:keycloak-oauth@0.0.2
natestrauser:select2@4.0.3
+npm-bcrypt@0.9.3
npm-mongo@3.0.7
+oauth@1.2.3
+oauth-encryption@1.3.0
+oauth2@1.2.0
observe-sequence@1.0.16
ohif:commands@0.0.1
ohif:core@0.0.1
@@ -98,6 +108,8 @@ ohif:studies@0.0.1
ohif:study-list@0.0.1
ohif:themes@0.0.1
ohif:themes-common@0.0.1
+ohif:user@0.0.1
+ohif:user-keycloak@0.0.1
ohif:viewerbase@0.0.1
ohif:wadoproxy@0.0.1
ordered-dict@1.1.0
@@ -113,11 +125,13 @@ retry@1.1.0
routepolicy@1.0.13
service-configuration@1.0.11
session@1.1.7
+sha@1.0.9
shell-server@0.3.1
silentcicero:jszip@0.0.4
socket-stream-client@0.2.2
spacebars@1.0.15
spacebars-compiler@1.1.3
+srp@1.0.10
standard-app-packages@1.0.9
standard-minifier-css@1.4.1
standard-minifier-js@2.3.4
diff --git a/OHIFViewer/client/components/ohifViewer/ohifViewer.js b/OHIFViewer/client/components/ohifViewer/ohifViewer.js
index 3a4c2b6bd..459946798 100644
--- a/OHIFViewer/client/components/ohifViewer/ohifViewer.js
+++ b/OHIFViewer/client/components/ohifViewer/ohifViewer.js
@@ -1,3 +1,4 @@
+import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { Session } from 'meteor/session';
import { Router } from 'meteor/clinical:router';
@@ -8,7 +9,7 @@ Template.ohifViewer.onCreated(() => {
const instance = Template.instance();
instance.headerClasses = new ReactiveVar('');
- OHIF.header.dropdown.setItems([{
+ const headerItems = [{
action: () => OHIF.ui.showDialog('serverInformationModal'),
text: 'Server Information',
icon: 'fa fa-server fa-lg',
@@ -28,7 +29,18 @@ Template.ohifViewer.onCreated(() => {
action: () => OHIF.ui.showDialog('aboutModal'),
text: 'About',
icon: 'fa fa-info'
- }]);
+ }];
+
+ if (Meteor.user()) {
+ items.push({
+ action: OHIF.user.logout,
+ text: 'Logout',
+ iconClasses: 'logout',
+ iconSvgUse: 'packages/ohif_viewerbase/assets/user-menu-icons.svg#logout'
+ });
+ }
+
+ OHIF.header.dropdown.setItems(headerItems);
instance.autorun(() => {
const currentRoute = Router.current();
diff --git a/OHIFViewer/client/config.js b/OHIFViewer/client/config.js
index 0b1d9e0b5..49c7c9ce3 100644
--- a/OHIFViewer/client/config.js
+++ b/OHIFViewer/client/config.js
@@ -1,6 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { OHIF } from 'meteor/ohif:core';
import { cornerstoneWADOImageLoader } from 'meteor/ohif:cornerstone';
+import { Accounts } from "meteor/accounts-base";
Meteor.startup(function() {
const maxWebWorkers = Math.max(navigator.hardwareConcurrency - 1, 1);
@@ -19,4 +20,21 @@ Meteor.startup(function() {
};
cornerstoneWADOImageLoader.webWorkerManager.initialize(config);
+
+ cornerstoneWADOImageLoader.configure({
+ beforeSend: function(xhr) {
+ const userId = Meteor.userId();
+ const accessToken = OHIF.user.getAccessToken();
+
+ if (accessToken) {
+ xhr.setRequestHeader("Authorization", `Bearer ${accessToken}`);
+ } else {
+ const loginToken = Accounts._storedLoginToken();
+ if (userId && loginToken) {
+ xhr.setRequestHeader("x-user-id", userId);
+ xhr.setRequestHeader("x-auth-token", loginToken);
+ }
+ }
+ }
+ });
});
diff --git a/Packages/ohif-dicomweb-client/src/getAccessToken.js b/Packages/ohif-dicomweb-client/src/getAccessToken.js
new file mode 100644
index 000000000..b1da5273a
--- /dev/null
+++ b/Packages/ohif-dicomweb-client/src/getAccessToken.js
@@ -0,0 +1,7 @@
+export default function getAccessToken() {
+ if (!global.window || !window.sessionStorage || !sessionStorage) {
+ return;
+ }
+
+ return sessionStorage.token;
+}
diff --git a/Packages/ohif-dicomweb-client/src/index.js b/Packages/ohif-dicomweb-client/src/index.js
index c7a0c1d78..cfa9358d5 100644
--- a/Packages/ohif-dicomweb-client/src/index.js
+++ b/Packages/ohif-dicomweb-client/src/index.js
@@ -6,6 +6,7 @@ import getModalities from './getModalities.js';
import getName from './getName.js';
import getNumber from './getNumber.js';
import getString from './getString.js';
+import getAccessToken from './getAccessToken.js';
import makeRequest from './makeRequest.js';
const DICOMWeb = {
@@ -17,6 +18,7 @@ const DICOMWeb = {
getName,
getNumber,
getString,
+ getAccessToken,
makeRequest
};
diff --git a/Packages/ohif-dicomweb-client/src/makeRequest.js b/Packages/ohif-dicomweb-client/src/makeRequest.js
index 0d59d860a..9d6f5ec1e 100644
--- a/Packages/ohif-dicomweb-client/src/makeRequest.js
+++ b/Packages/ohif-dicomweb-client/src/makeRequest.js
@@ -1,6 +1,7 @@
import { Meteor } from "meteor/meteor";
import URL from 'url-parse';
import 'isomorphic-fetch';
+import getAccessToken from './getAccessToken.js';
async function makeRequest(url, options) {
const parsed = new URL(url);
@@ -10,8 +11,7 @@ async function makeRequest(url, options) {
headers: {}
};
- // TODO: Clean this up
- const accessToken = false //Meteor.user().services.keycloak.accessToken;
+ const accessToken = getAccessToken();
if (accessToken) {
requestOpt.headers = {
Authorization: `Bearer ${accessToken}`
diff --git a/Packages/ohif-servers/both/schema/servers.js b/Packages/ohif-servers/both/schema/servers.js
index aa90dd1d5..71988598d 100644
--- a/Packages/ohif-servers/both/schema/servers.js
+++ b/Packages/ohif-servers/both/schema/servers.js
@@ -255,6 +255,11 @@ export const PublicServerConfig = new SimpleSchema({
label: 'Creates demo user on startup and show TestDrive button',
defaultValue: true
},
+ userAuthenticationRoutesEnabled: {
+ type: Boolean,
+ label: 'Enables routing to /login page.',
+ defaultValue: false,
+ },
ui: {
type: UISettings,
label: 'UI Settings'
diff --git a/Packages/ohif-studies/imports/both/services/qido/studies.js b/Packages/ohif-studies/imports/both/services/qido/studies.js
index 24b39e1ae..b7ff4f97b 100644
--- a/Packages/ohif-studies/imports/both/services/qido/studies.js
+++ b/Packages/ohif-studies/imports/both/services/qido/studies.js
@@ -101,8 +101,6 @@ OHIF.studies.services.QIDO.Studies = (server, filter) => {
const url = filterToQIDOURL(server, filter);
return new Promise((resolve, reject) => {
- console.warn(DICOMWeb);
-
DICOMWeb.getJSON(url, server.requestOptions).then(result => {
const studies = resultDataToStudies(result);
diff --git a/Packages/ohif-user-keycloak/client/main.js b/Packages/ohif-user-keycloak/client/main.js
new file mode 100644
index 000000000..aacc070bd
--- /dev/null
+++ b/Packages/ohif-user-keycloak/client/main.js
@@ -0,0 +1,10 @@
+import { Meteor } from 'meteor/meteor';
+
+if (!Meteor.settings.public ||
+ !Meteor.settings.public.custom ||
+ !Meteor.settings.public.custom.keycloak) {
+
+ console.log('To use the ohif-user-keycloak package, you must add relevant Keycloak settings to Meteor.settings.public.custom.keycloak (client-side).');
+} else {
+ require('../imports/client/index.js')
+}
diff --git a/Packages/ohif-user-keycloak/imports/client/components/keycloakLoginButton.html b/Packages/ohif-user-keycloak/imports/client/components/keycloakLoginButton.html
new file mode 100644
index 000000000..3dfdfdba5
--- /dev/null
+++ b/Packages/ohif-user-keycloak/imports/client/components/keycloakLoginButton.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Packages/ohif-user-keycloak/imports/client/components/keycloakLoginButton.js b/Packages/ohif-user-keycloak/imports/client/components/keycloakLoginButton.js
new file mode 100644
index 000000000..cba5b75d5
--- /dev/null
+++ b/Packages/ohif-user-keycloak/imports/client/components/keycloakLoginButton.js
@@ -0,0 +1,18 @@
+import { Meteor } from 'meteor/meteor';
+import { Template } from 'meteor/templating';
+import { Router } from 'meteor/clinical:router';
+import { OHIF } from 'meteor/ohif:core';
+
+import './keycloakLoginButton.html';
+
+Template.keycloakLoginButton.events({
+ 'click .js-login-keycloak'() {
+ Meteor.loginWithMeteorKeycloak({}, function(error) {
+ if (error) {
+ throw new Error(error);
+ }
+
+ Router.go('/studylist');
+ });
+ }
+});
diff --git a/Packages/ohif-user-keycloak/imports/client/handlers.js b/Packages/ohif-user-keycloak/imports/client/handlers.js
new file mode 100644
index 000000000..9d42ffd8a
--- /dev/null
+++ b/Packages/ohif-user-keycloak/imports/client/handlers.js
@@ -0,0 +1,25 @@
+import { Accounts } from 'meteor/accounts-base';
+import { Meteor } from "meteor/meteor";
+
+OHIF.user.getAccessToken = () => {
+ const user = Meteor.user();
+ if (!user) {
+ return;
+ }
+
+ return user.services.keycloak.accessToken;
+};
+
+Accounts.onLogin(() => {
+ Meteor.subscribe('user.services.keycloak', () => {
+ sessionStorage.token = OHIF.user.getAccessToken();
+ });
+});
+
+Accounts.onLogout(() => {
+ const authServerUrl = Meteor.settings.public.custom.keycloak.authServerUrl;
+ const realm = Meteor.settings.public.custom.keycloak.realmName;
+ const redirectUri = Meteor.absoluteUrl('login');
+ const logoutUrl = `${authServerUrl}/realms/${realm}/protocol/openid-connect/logout?redirect_uri=${redirectUri}`;
+ window.location = logoutUrl;
+});
diff --git a/Packages/ohif-user-keycloak/imports/client/index.js b/Packages/ohif-user-keycloak/imports/client/index.js
new file mode 100644
index 000000000..fb5f07023
--- /dev/null
+++ b/Packages/ohif-user-keycloak/imports/client/index.js
@@ -0,0 +1,3 @@
+import './handlers';
+import './setup.js';
+import './components/keycloakLoginButton.js';
diff --git a/Packages/ohif-user-keycloak/imports/client/setup.js b/Packages/ohif-user-keycloak/imports/client/setup.js
new file mode 100644
index 000000000..0d67f0d66
--- /dev/null
+++ b/Packages/ohif-user-keycloak/imports/client/setup.js
@@ -0,0 +1,30 @@
+import { Meteor } from 'meteor/meteor';
+import { Router } from 'meteor/clinical:router';
+import { OHIF } from 'meteor/ohif:core';
+
+Router.waitOn(function() {
+ return [
+ Meteor.subscribe('user.services.keycloak'),
+ ];
+}, { except: ['userLogin'] });
+
+Router.onBeforeAction(function() {
+ // Check if user is signed in
+ if (!Meteor.userId() && !Meteor.loggingIn()) {
+ this.redirect('userLogin');
+ } else {
+ this.next();
+ }
+}, {
+ except: ['userLogin', 'entrySignUp', 'forgotPassword', 'resetPassword']
+});
+
+OHIF.user.additionalLoginButtons = OHIF.user.additionalLoginButtons || [];
+
+OHIF.user.additionalLoginButtons.push({
+ template: 'keycloakLoginButton'
+});
+
+if (!Meteor.settings.public.userAuthenticationRoutesEnabled) {
+ OHIF.log.error('Please set Meteor.settings.public.userAuthenticationRoutesEnabled=true');
+}
diff --git a/Packages/ohif-user-keycloak/imports/server/setup.js b/Packages/ohif-user-keycloak/imports/server/setup.js
new file mode 100644
index 000000000..fbc95baaa
--- /dev/null
+++ b/Packages/ohif-user-keycloak/imports/server/setup.js
@@ -0,0 +1,51 @@
+import { Meteor } from 'meteor/meteor';
+import { ServiceConfiguration } from 'meteor/service-configuration';
+import { Accounts } from "meteor/accounts-base";
+
+console.log('Keycloak settings were found! Enabling Keycloak integration.');
+
+ServiceConfiguration.configurations.upsert(
+ { service: 'keycloak' },
+ {
+ $set: {
+ "realm": Meteor.settings.public.custom.keycloak.realmName,
+ "auth-server-url": Meteor.settings.public.custom.keycloak.authServerUrl,
+ "auth_redirect_uri": Meteor.settings.keycloak.authRedirectUri,
+ "ssl-required": Meteor.settings.keycloak.sslRequired,
+ "resource": Meteor.settings.keycloak.clientId,
+ "client_id": Meteor.settings.keycloak.clientId,
+ "loginStyle": Meteor.settings.keycloak.loginStyle,
+ "secret": Meteor.settings.keycloak.clientSecret,
+ "realm-public-key": Meteor.settings.keycloak.realmPublicKey,
+ "public-client": false,
+ "use-resource-role-mappings": false,
+ "bearer-only": false,
+ }
+ }
+);
+
+Meteor.publish('user.services.keycloak', function() {
+ const userId = this.userId;
+ if (!userId) {
+ return [];
+ }
+
+ return Meteor.users.find(userId, {
+ fields: {
+ 'services.keycloak': 1
+ }
+ });
+});
+
+Accounts.onLogout(({ user }) => {
+ if (!user) {
+ return;
+ }
+
+ // Erase any Keycloak token that exists
+ Meteor.users.update(user._id, {
+ $unset: {
+ 'services.keycloak': 1
+ }
+ });
+});
diff --git a/Packages/ohif-user-keycloak/package.js b/Packages/ohif-user-keycloak/package.js
new file mode 100644
index 000000000..4a115a262
--- /dev/null
+++ b/Packages/ohif-user-keycloak/package.js
@@ -0,0 +1,26 @@
+Package.describe({
+ name: 'ohif:user-keycloak',
+ summary: 'OHIF Integration with Keycloak for Identity and Access Management',
+ version: '0.0.1'
+});
+
+Package.onUse(function(api) {
+ api.versionsFrom('1.6');
+
+ api.use('templating');
+ api.use('ecmascript');
+ api.use('service-configuration');
+ api.use('accounts-base');
+
+ // Our custom packages
+ api.use('ohif:core');
+ api.use('ohif:user');
+
+ api.use('mxab:keycloak-oauth@0.0.2');
+ api.use('mxab:keycloak-loader@0.0.2');
+ api.use('mxab:accounts-keycloak');
+
+ // Client imports
+ api.mainModule('client/main.js', 'client');
+ api.mainModule('server/main.js', 'server');
+});
diff --git a/Packages/ohif-user-keycloak/server/main.js b/Packages/ohif-user-keycloak/server/main.js
new file mode 100644
index 000000000..5965f9c25
--- /dev/null
+++ b/Packages/ohif-user-keycloak/server/main.js
@@ -0,0 +1,11 @@
+import { Meteor } from 'meteor/meteor';
+
+if (!Meteor.settings.public ||
+ !Meteor.settings.public.custom ||
+ !Meteor.settings.public.custom.keycloak ||
+ !Meteor.settings.keycloak) {
+
+ console.log('To use the ohif-user-keycloak package, you must add relevant Keycloak settings to Meteor.settings.keycloak (server-side).');
+} else {
+ require('../imports/server/setup.js')
+}
diff --git a/Packages/ohif-user/client/components/login/userLogin.html b/Packages/ohif-user/client/components/login/userLogin.html
index 02a9cc544..d9edf9a33 100644
--- a/Packages/ohif-user/client/components/login/userLogin.html
+++ b/Packages/ohif-user/client/components/login/userLogin.html
@@ -12,6 +12,10 @@
{{>section 'userLoginFieldsAfter'}}