Friday, October 26, 2012

Allowing Read-Only Access to your application

We recently had a requirement to allow user logins with any assigned role but with read only access to the application.   User should be able to login and see all of the pages associated with the assigned role but cannot do anything that will add or change data in the database.    This turned out to be fairly simple to implement.   We created a new role "readOnlyAccessAllowed".  We already have a custom base Application Module class that all of our Application Modules extend.    So the change was simply to override the beforeCommit method, check if the user has the read only access role and throw an exception if so.  We also display a tag on the home page to assure the user that they have read only access.

If you do not already have a base class defined for your application modules, this is done by editing the Model.jpr  project properties.  Under Business Components/Base Classes you define a base class for your View Objects and Application Modules.   If you do this after your application modules have been defined and your app module has a java implementation, you will need to edit those java implementations and verify that they extend your new base app module.  Newly created app modules will automatically extend your base app module.

Below is sample code showing the implementation. The were 2 or 3 app modules used for storing user preferences, saved searches and login history that we still wanted to be allowed so I excluded those.  I also added a method allowOneTimeCommitForReadOnlyUser that can be called in the code immediately before calling commit() to allow for other exceptions to the rule.



 
    /* overriden in order to prevent commiting data when user has read only user role
     * beforeCommit is called for both the root app module and the app module
     * containing the view object being commited.
     */
    @Override
    public void beforeCommit(TransactionEvent e) { 
        boolean isReadOnlyAccess = getDBTransaction().getSession().isUserInRole("readOnlyAccessAllowed");
        String appModuleName = this.getRootApplicationModule().getName();
        // allow commits for login history, performance monitorng and user preferences
        boolean allowCommit=(!isReadOnlyAccess || 
                             "AppCtrlAM".equals(appModuleName) || 
                             "SearchAM".equals(appModuleName)   ); // allow readOnlyAccess users to save searches criteria
        // this is used to allow writing the account audit info for USER_ACTIVITY table       
        if (allowOneTimeCommitForReadOnlyUser){
          allowCommit=true;
          allowOneTimeCommitForReadOnlyUser=false;
        } 
            
        if(!allowCommit){ 
            displayReadOnlyMessage();
            throw new ReadOnlyAttrException(0,"","",this.getName(),"");
        }
       
        super.beforeCommit(e);
    }
    
    public static void displayReadOnlyMessage(){
      FacesContext context = FacesContext.getCurrentInstance();
      FacesMessage fm = new FacesMessage(FacesMessage.SEVERITY_ERROR,"Read Only","You are allowed read only access.  Data cannot be added or changed.");
      context.addMessage(null, fm);
    }

    public void allowOneTimeCommitForReadOnlyUser() {
        ApplicationModule am = this.getRootApplicationModule();
        if (am instanceof CAAppModuleImpl){
          ((CAAppModuleImpl)am).allowOneTimeCommitForReadOnlyUser = true;
        }
        this.allowOneTimeCommitForReadOnlyUser = true;
    }

Here's a link to another solution.  That solution makes input fields display as read-only output fields.
We didn't do that because we didn't want to change the Application look in any way because we use Selenium non-invasive tests to regression test our pages in production so we want the page to look the same as it does for a normal user but still prevent the test or user from committing data.