In this section you will configure the application to implement Single Sign On (SSO) for security: the worklist displays protected health information (PHI) so it should ensure only authorized personnel can view this data. SSO integration is included in the harbinger-rails-extensions gem (documented here, but you can also use the SDK methods directly.

Setup

If you have been following the tutorial or are using the vanilla application as instructed, you can skip this setup, as it has already been done.

If not you'll need to add the harbinger-rails-extensions gem to your Gemfile (updating the version to be current):

gem 'harbinger-rails-extensions', '~> 0.0.20', source: 'http://gem.analytical.info:9292'

Fundamentals

Bridge SSO integrates with an existing directory service or SSO federation, so the platform does not have its own usernames or passwords. The SSO integration via the SDK can be used to provide authentication, by linking a users directory login with an Employee record in the data model.

Authentication only validates that a user is who they claim to be, it does not validate if they have permission to use a particular resource. The authorization in Bridge authorization is determined by clinical role associated with the employee record. Retrieve an employee record in the console:

me = Java::HarbingerSdkData::Employee.withUserName("myusername")
# Test if you are in the 'it-staff' clinical role
me.authorizedAs("it-staff")

Best Practice - Static query methods (such as createQuery) take an optional entity manager as the last argument. While debugging or testing on the console you don't need pass an entity manager, but you should always pass in an entity manager when using these methods in an application.

Employee records have methods for checking if a user is in given list of clinical roles:

A detailed list of the current clinical roles and their intended use is available in the Service Tools application under the navbar Warehouse > Clinical Roles (under the heading Reference)

Some clinical roles are meta- or modifier roles. An example is the supervisor role. The combination of supervisor and radiologist would be a section chief type role. To check that these roles exist together you can use the authorizedForAll method or you can group them together in the authorizedForAny method. Here is an example of doing both.

# Check for a supervisor radiologist or a it-staff
me.authorizedForAll(["radiologist","supervisor"]) or me.authorizedAs("it-staff")
# Do the same thing in one function
me.authorizedForAny([["radiologist","supervisor"],"it-staff"])

To implement authorization in an application without harbinger-rails-extensions, you'd need to create equivalent methods using the functions provided in Java::HarbingerSdk::SSO to get the cookie name, retrieve the data using the application framework, then check the validity of the token (SSO.get_cookie_name,SSO.validToken). All of these functions, redirection methods, and the username in set session[:username] are implemented in harbinger-rails-extensions for convenience.

Page Authorization

The tutorial application implements a before_filter to create an entity manager for controllers. A before_filter can also be used to redirect unauthenticated and unauthorized requests with the method authenticate_and_authorized (provided by harbinger-rails-extensions to controllers). Edit app/controllers/application_controller.rb to add a new method:

def general_authentication
  authenticate_and_authorize(["ai-staff","it-staff","radiologist"])
end

Add that method in a before_filter to the WorklistController:

class WorklistController < ApplicationController
  before_filter :general_authentication
  #...
end

After these changes, navigation to /worklist will prompt an SSO login. If you successfully log in, but you are not in the list of authorized clinical roles, you will be redirected to a page that doesn't currently exist in the application. The redirect is to the index method of the UnauthorizedController. Create that controller and a route to the index method, starting in config/routes.rb:

get 'unauthorized' => "unauthorized#index"

Next, create the controller (app/controllers/unauthorized_controller.rb):

class UnauthorizedController < ApplicationController

  def index
  end

end

Finally, create the view by making the app/views/unauthorized directory and creating the file index.html.erb:

<div class="jumbotron">
  <h1>Unauthorized</h1>
  <p>You are not in the required user roles to view this page.</p>
</div>

authenticate_and_authorize behaves like authorizedForAll (used above). The additional functionality is that it checks if the client has a valid SSO token, then checks to see if the logged in user also has the appropriate role(s). If the user does not have the appropriate role(s), it redirects the user to the UnauthorizedController with an action of index.

Troubleshooting - If you are redirected to the unauthorized page, check to see that your employee is in the proper clinical roles in the Employees section of the Warehouse view of the Service Tools application. Once the employee is configured correctly, try clicking the "Worklist" link in the navigation to test again (a common mistake is refreshing the unauthorized page after fixing the employee-clinical role mappings, which will continue to show the unauthorized page instead of resubmitting the request for the Worklist).

Hiding page components by role

In addition to redirecting an unauthorized user, it is possible to show different fragments of a page dependent on employee-clinical role mappings. This can be done using if statements and the SDK authorization methods (like above), but you can also use the block helpers provided by harbinger-rails-extensions. Edit app/views/worklist/index.html.erb to conditionally add an extra column to the table (this column will be used in Part 7):

<div class="container-fluid">
  <div class="row">
    <div class="col-xs-12">
      <h1>Ready to read worklist</h1>
      <table class="table table-bordered table-striped">
    <thead>
      <tr>
        <th>Accession #</th>
        <th>Patient MRN</th>
        <th>Patient Name</th>
        <th>Completed At</th>
        <th>Age</th>
        <% authorized(["radiologist"]) do %>
        <th></th>
        <% end %>
      </tr>
    </thead>
    <tbody>
      <% @exams.each do |exam| %>
      <tr>
        <td><%= exam.accession %></td>
        <td><%= exam.patientMrn.mrn %></td>
        <td><%= exam.patientMrn.patient.name %></td>
        <td><%= formatd(exam.radExamTime.endExam) %></td>
        <td><%= age(exam.radExamTime.endExam) %></td>
        <% authorized(["radiologist","ai-staff"]) do %>
        <td><button class="btn btn-xs btn-primary">View Images</button></td>
            <% end %>
      </tr>
      <% end %>
    </tbody>
      </table>
    </div>
  </div>
</div>

Note - The authorized block helper is using a concat-based method to add the contents of the block to the page. Do not put an = in the erb tag, similar to the each expression.

This creates a button on the page that only "radiologists" and "ai-staff"-mapped employees will see. authorized works just like authorizedForAny. You can also use the authorize_all, or the more verbose name for the same behavior as authorized: any_authorized. In addition to the block helpers, there are other helpers that return booleans (like the SDK). They have the same names as the block helpers with a ? suffix (e.g. authorized?(["radiologist"])).

Logout button

Apps should always give the user a way to log out. This is done with a link that has the href set to the value returned by the SDK method Java::HarbingerSdk::SSO.logoutUrl(). Edit app/views/layouts/application.html.erb to add the logout link in the primary navigation, under the "Worklist" link:

<li><a href="<%= Java::HarbingerSdk::SSO.logoutUrl() %>">Logout</a></li>