Wednesday, April 28, 2010

Security Implementation

Our existing Security Framework is Role based. Each logged in user can have one or more roles. Example a logged in user can take on a role of a Receptionist or a Receptionist and a biller. Each role restricts the logged in user to a set of methods on a interface/class. Due to HIPAA requirements we also log out an user if session is idle for a specified amount of time. If a user is "logged out" and a user submits a request, the Security Framework presented the user with a login screen and once user logs in the request is re-queued to the server. This allows the user to not loose any data if the user entered in a lot of data. Our applications - Practice Management, Document Management etc .. are spread over different web servers and the Security Framework also allowed us a "Single Sign on". User can log in thru any application and can follow the links to other web servers without having to login again.

Our goal in moving to GWT was to ensure that all the above functionality stayed intact. Below is how we implemented similar functionality using GWT.
A Login interface provides login functionality and defined as
public interface Login extends RemoteService {
public UserSessionInfo login(String userid, String password);
public UserSessionInfo checkSession(String userid, String password);
...
public void logoff();
}

In order to implement the security framework we needed to intercept the calls onto the server implementation so we can authenticate the user role, access etc.
public class SecureRemoteServiceServlet extends RemoteServiceServlet {
 ...
public String processCall(String payload) throws SerializationException {
try {
RPCRequst rpcRequest = RPC.decodeRequest(payload, getClass());
Object parameters[] = rpcRequest.getParameters();
Method m = rpcRequest.getMethod();
authenticate(m, parameters);
return RPC.invokeAndEncodeResponse(this, m, parameters,
rpcRequest.getSerializationPolicy());
}
catch (Exception e) {
return RPC.encodeResponseForFailure(null, e);
}
}
protected HttpSession getSession() {
HttpServletRequest req = getThreadLocalRequest();
return (req=null) ? null : req.getSession());
}
protected void authenticate(Method m, Object Parameters[])
throws com.medrium.exception.SecurityException {
// Logic for verifying user login and access based on
// on user role ..
// Throw SecurityException if user is not logged in
// or user does not have access to method
..
}
..
public boolean allowAcess(String method)
throws com.medrium.exception.SecurityException {
}
}

All our server implementations derived from SecureRemoteServiceServlet.
public class PatientConditionImpl extends SecureRemoteServiceServlet
implements PatientConditon {

public void addConditions(ArrayList c) {
..
}
..
}
Note that the SecureRemoteServiceServlet may throw com.medrium.exception.SecurityException. However this exception is not defined in the interface methods. So GWT will not generate/include the serialization code for SecurityException as such you would get an exception on the client. Obviously we do not want to add throws SecurityException to each of the methods on the interface since we would like the security framework to redirect the user to login and reexecute the call (once logged in) if the security exception was a “not logged in” exception. To enable this functionality we did a couple of modifications.
Defined a new interface
public interface SecureRemoteService extends RemoteService {
public boolean allowAccess(String method)
throws com.medrium.exception.SecurityException;
}
and all our RPC interfaces derived from SecureRemoteService instead of RemoteService.
interface PatientCondition extends SecureRemoteService {
void addConditions(int accountID, ArrayList list);
PatientConditionInfo get(int accountID, int PatientConditionID);
ArrayList search(int accountID, SearchCriteria criteria);
}
This ensured that SecurityException serialization was possible in the client. The method allowAccess is implemented by SecureRemoteServiceServlet – which essentially will throw a SecurityException if user is not logged in or return true/false if user has access or no access to the given method.

One of the requirements discussed before was to ensure that the framework address the issue of directing the user to login if user session timedout (or user bookmarked a link) and once logged in automatically reexecuting the initial request. The Command pattern allows us to do this seemlessly and is done by the base Action class and a SecurityManager class as follows.
public class SecurityManager {
private UserSessionInfo _user = null;
private ArrayList<Action<?>> _actionQueue =
new ArrayList<Action<?>>();
private Provider<Login_checkSession> _loginCheckSessionProvider;
// We use Gin to get instances of Action classes although you can do a new ...
protected SecurityManager() {}
@Inject
public SecurityManager(... ) {
..
}
...
public UserSessionInfo getUser() {
return _user;
}
public void checkSession() {
Login_checkSession action = _loginCheckSessionProvider.get();
action.initialize(
new AsyncCallback<UserSessionInfo>() {
public void onSucess(UserSessionInfo user) {
if ( !user.loggedIn() .. ) { // User not logged in
displayLogin();
}
else {
setUser(userSession);
...
executeActions();
}
}
public void onFailure(Throwable t) {
...
}
});
action.execute();
}
private void displayLogin() {
// Binds the Login Presenter – displays login screen
}
public void reloginAndExecute(Action<?> action) {
_actionQueue.add(action);
// Only display login for the first action in queue
if ( _actionQueue.size() = 1) {
displayLogin();
}
}
private void executeActions() {
for (Action<?> action: _actionQueue) {
action.execute();
}
_actionQueue.clear();
}
}

Below are some changes made to the Command Pattern base Action class.
public abstract class Action<T> implements AsyncCallback<T> {
// Inject SecurityManager
@Inject static protected SecurityManager _securityManager;
...
public void onFailure(Throwable t) {
// If user not logged in redirect to SecurityManager so security manager
// can display login screen, execute Login RPC and once logged in
// reexecute the action.
if ( t instance of com.medrium.exception.SecurityException &&
((SecurityException)t).getReasonCode() == ..
SecurityException.NOT_LOGGED_IN ) {
_securityManager.reloginAndExceute(this);
}
else {
_callback.onFailure(t);
}
}
...
}

Given the nature of the application and a user role determining the functionallity available the client code needed a way to determine if a certain functionality (link) is available to the logged in user. It is not desirable to always enable a link and then issue an alert that the user does not have access. The client can make a allowAccess call on an interface to determine if a method is accessible to the logged in user before enabling a link. This however is not desirable since this would require additional RPC calls.

A model described below was used so we can “statically” generate some access control code.
Medrum's model for authenticating is based on tieing each interface name and method access to a role and optionally to a set of services enrolled by the account. The link between a role and interface.method is “static” - i.e. it cannot be changed dynamically. A new release may change these attributes. The link between enrolled service and interface.method however can change dynamically. i.e. the user may enroll a new service which would enable new functionality. For example an account administrator can enroll to a “Patient statement service” which would enable Statement printing functionality etc. The Command pattern code generator was modified so that it would generate the access masks for roles and services that are needed to perform an operation. Essentially, the code generator looked up the database tables contain the access defintions and generated an additional function isAccessAllowed.
public Class PatientConditionImpl extends SecureRemoteServiceServlet
implements PatientCondition {
public void addConditions(ArrayList conditions) {
...
}
public boolean isAcessAllowed() {
// Call security manager with roles mask, service mask ...
return _securityManager.isAccessAllowed(...);
}
}

Security manager changes are
public class SecurityManager {
public boolean isAccessAllowed(int roleMask, int servicesMask) {
return (rolesMask & _user.getRoleMask()) > 0
&& (serviceMask & _user.getServiceMask()) > 0;
}
}
One of the features we wanted was a "Single Sign On". This feature was easily implemented outside of GWT by using domain cookies and Servlet Filter and HttpSessionListener. When a user logs in, a domain cookie "SharedLogin" cookie was created. The Cookie value contained the host name and session id. When the user went to our other application web server, this "domain" cookie was passed by the browser. A servlet filter parsed the "SharedLogin" cookie and did a verify session - a simple Rest Https call to the hostname in the cookie value along with the session id. The servlet filter also updated the "SharedLogin" cookie value with it's own hostname, session id .. so that the "SharedLogin" contained the host and session information of the server that was accessed last.

In my next post, I will explain how we implemented Cross Site GWT RPC calls.

No comments:

Post a Comment