Scout/Concepts/Security

From Eclipsepedia

Jump to: navigation, search


Scout
Wiki Home
Website
DownloadGit
Community
ForumsBlogTwitter
Bugzilla
Bugzilla

In order to understand this chapter it is important to know the difference between the two terms "Authentication" and "Authorization".

Authentication
Authentication means identifing the user trying to access the system.
Authorization
Authorization means determing the users rights and permissions according to his identity.

Contents

Security principles of Scout

Scout security actually relies on concepts of the java’s standard and pluggable authentication and authorization service (JAAS) by representing the source of a request as a subject with principals associated. In turn, any action is performed on behalf of that subject in a respective doAs-call.

Instead of using the core JAAS classes for authentication (LoginContext, LoginModule, CallbackHandler and Callback), Scout authenticates users directly in so called SecurityFilters. Those filters are chainable, meaning that you might have several filters knowing of how to authenticate user credentials against database (DataSourceSecurityFilter), LDAP directory (LDAPSecurityFilter) or whatever you like. Those filters are registered as an extension to the extension point 'org.eclipse.scout.http.servletfilter.filters'. If a filter can authenticate the user successfully, the request is passed within a doAs call to the endpoint servlet (ServiceTunnelServlet) to dispatch the call to the requested OSGi service.

Because Scout does not use the LoginContext for authentication and is not based on the static policy configuration files for principal-based granting (Policy API), permission loading (authorization) is done by Scout AccessControlService. There are gathered all the permissions that belong to the authenticated subject and are maintained in Scout AccessControlStore. In consequence, authorization cannot be delegated to JAAS AccessController, but is done in a very convenient way by Scout AccessControlService itself.

Authentication

Let us have a look on how the system prompts for authentication. First you have to know, that all code on client is run within a doAs call with the user's account name as subject. By default, authentication is only triggered by calling the backend. That means that if you have a standalone client application without a server running, you are never prompted for authentication. So when doing your first backend call, a connection to the server is established. In the application's life cycle, this typically occurs if initializing the client session which is quite the first thing done. Thereby, the security filter on server side intercepts the call. Because no subject is associated yet, a 401 Unauthorized error code is sent back to the client. On client side, before being propagated back to the caller, this 401 is intercepted by the installed net authenticator. By default, the installed authenticator first looks in the Eclipse secure preference store for credentials. If not available, a login dialog is presented to the user to enter username and password. In turn, those are sent back to the server to continue authentication. Please note, that the authenticator mechanism can be installed in your Swing application by calling NetActivator.install().

@Override
protected Object startInSubject(IApplicationContext context) throws Exception {
   NetActivator.install();
   return super.startInSubject(context);
}

Thereby, the default Scout authenticator InternalNetAuthenticator is installed. This can be easily overwritten by registering an OSGi service with the name java.net.Authenticator and a ranking higher than -2. Alternatively, you can register an extension to the Eclipse extension point org.eclipse.core.net.authenticator that contains your custom java.net.Authenticator.

Security Filters

Scout Security Filters are responsible for the authentication of the user. They can simply assigned to on ore more servlets by adding an extension of extension point 'org.eclipse.scout.http.servletfilter.filters'. Since the filters are chainable an order can be set. Additionally it can be defined if the filter should be active or not. This behaviour can easily be overriden by using the config.ini (see #Settings_in_config.ini).

Following SecurityFilters come with scout and can be found in the package org.eclipse.scout.http.servletfilter.security. If you need a custom behaviour (like using the subject provided by an application server) just create your own filter.

AnonymousSecurityFilter

A security filter allowing anonymous access to the application

BasicSecurityFilter

The basic filter with the usernames and passwords directly configured in the configuration file (or the extension point). E.g.:

 org.eclipse.scout.http.servletfilter.security.BasicSecurityFilter#users=frank\=test,mark\=test,steve\=test 

DataSourceSecurityFilter

Uses the users and passwords defined in the database. This filter does a Base64 encryption in its default negotiate implementation (see DataSourceSecurityFilter.negotiate and DataSourceSecurityFilter.encryptPass). So the expected password in the database is also Base64 encrypted. It is possible to change this behavior: in a subclass of DataSourceSecurityFilter override one or the other implementation of the two methods.

Here an example:

Create table to store users in your database (For the default DataSourceSecurityFilter, password are expected to be Base64 encrypted):

  CREATE TABLE MYAPP_USERS (
    USER_NR DECIMAL NOT NULL,
    NAME VARCHAR(128),
    PASSWORD VARCHAR(128),
    PRIMARY KEY (USER_NR)
  );

Add DataSourceSecurityFilter to your the dependencies of your server application com.yourapp.server/plugin.xml

  <filter
        aliases="/process"
        class="org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter"
        ranking="40">
  </filter>


Add some configurations for the DataSourceSecurityFilter in the config.ini file (com.yourapp.server/producs/development/config.ini)

 org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter#active=true
 org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter#realm=TEST APPLICATION
 org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter#jdbcDriverName=org.apache.derby.jdbc.EmbeddedDriver
 org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter#jdbcMappingName=jdbc:derby:D:/Temp/scoutJuno/derbyDb 
 org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter#jdbcUsername=***
 org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter#jdbcPassword=***
 org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter#selectUserPass=SELECT LOWER(NAME) FROM MYAPP_USERS WHERE LOWER(NAME)=? AND PASSWORD=?


Ensure to have no white spaces at the end of the parameters and set all other security filters to active=false.

Ensure the bundle org.eclipse.scout.http.servletfilter is added as required bundle in the product file.

LDAPSecurityFilter

A security filter to authenticate against a ldap server.

Settings in config.ini

In order to activate or deactivate a certain filter in a specific environment you typically set the property "active" to true or false in the config.ini belonging to the environment. Lets say you would like to use the BasicSecurityFilter in development environment but not in production, instead you would like to use DataSourceSecurityFilter. Good practice is to define DataSourceSecurityFilter as active in your plugin.xml and BasicSecurityFilter as inactive. In the config.ini assigned to the development product you can override this defaults and inactivate DataSourceSecurityFilter and activate BasicSecurityFilter.

org.eclipse.scout.http.servletfilter.security.BasicSecurityFilter/process#active=true
org.eclipse.scout.http.servletfilter.security.DataSourceSecurityFilter/process#active=false

Please note: You can activate/deactive the filters for a specific servlet path. If the property for a given servlet is not explicitly set the default of the plugin.xml is used.

/process is typically assigned to the main entry point servlet called ServiceTunnelServlet. This is where all scout internals connect, i.e. the calls from the client to the server.

Another property of the servlet filter is called "failover". This property determines whether the filter should deny access, if not successfully authenticated (false) or if the next filter should try to authenticate (true).

org.eclipse.scout.http.servletfilter.security.BasicSecurityFilter#failover=true

Authorization (Granting)

Granting access to a specific resource is done with permissions. Permissions represent atomic data and operation-level security tokens. They are checked before a privileged operation is performed.

Access restriction on CRUD operations

Typically there are four different permissions per entity for performing CRUD operations on an object. With this four permissions you are able to restrict the access to that entity for create, read, update and delete operations.

Let us assume you require a permission to allow a user to access companies. Thereto, you create the permission class 'ReadCompanyPermission' in the package x.y.shared.security:

public class ReadCompanyPermission extends BasicPermission {
 
  public ReadCompanyPermission() {
    super("ReadCompany");
  }
}

As you can see ReadCompanyPermission extends from BasicPermission which is a class of the package java.security provided by java itself. Every permission in a scout project needs to extend from that class or rather from java.security.Permission directly.

The name you provide in the constructor is the name of the permission. Among other things, it is used to decide whether to grant access to a specific resource. For more detail, please refer to BasicPermission#implies(Permission p) which is evaluated in IAccessControlService#checkPermission(Permission p).

With the help of that ReadCompanyPermission you are now able to check if the user has the right to read a company entity.

if (!ACCESS.check(new ReadCompanyPermission())) {
   throw new VetoException("Authorization failed");
} 
else {
   // user is authorized, do some business logic here
}

Please note, that the class ACCESS is simply a delegate to IAccessControlService. The IAccessControlService is responsible for loading and caching the permissions as well as checking them. In order to do that it is necessary to specify how the permissions belonging to the current user can be loaded. This is done by overriding the method execLoadPermissions in your AccessControlService.

"Your" AccessControlService means you need to have a custom AccessControlService extending AbstractAccessControlService in your project. This is typically created by Scout SDK itself. Furthermore, this service has to be registered in the plugin.xml of the server project similar to:

<service class="x.y.server.services.custom.security.AccessControlService" factory="org.eclipse.scout.rt.server.services.ServerServiceFactory" session="x.y.server.ServerSession"/>

How permissions are discovered

Permission are discovered by IPermissionService. The default implementation looks for permission classes in all bundles installed in the OSGi environemnt. Thereby, the following criteria must be satisfied:

  1. The class must be of the type java.security.Permission
  2. The type must be a public concrete class, meaning not an interface nor an abstract class
  3. Class must have the token 'Permission' in its name
  4. The class must be located in a package with '.security.' in its package path

Please note: This behavior can be overwritten by writing an own implementation for IPermissionService.

Fine-grained access control

Further, it is possible to use fine-grained access permissions. This is in contrast to the BasicPermission mentioned above, which simply handles 'go' or 'no-go' situations. That is that if the user has the permission, the access to the resource is granted or rejected otherwise.

However, fine-grained access permissions must be of the type BasicHierarchyPermission. The concept is based on various levels in the range from 0 up to 100. Thereby, 0 means no-access, whereas 100 mean full-access. Basically, if the permission of the user (loaded from database) has a level higher or equals than/to the level requested, access is granted. In BasicHierarchyPermission, the following levels are defined:

LEVEL_NONE = 0
LEVEL_ALL = 100
LEVEL_UNDEFINED = -1


Again, let us elaborate a tiny example: The requirement would be that users should only access companies which they really belong to. For that purpose, we introduce a new access level LEVEL_OWN=10.

The permission ReadCompanyPermission would be changed as follows:

public class ReadCompanyPermission extends BasicHierarchyPermission {
 
  public static final int LEVEL_OWN = 10;
 
  private Long m_companyId;
 
  public ReadCompanyPermission(Long companyId) {
    super("ReadCompany" + "." + companyId, LEVEL_UNDEFINED);
    m_companyId = companyId;
  }
 
  protected boolean execCheckLevel(int userLevel) throws ProcessingException {
    if (userLevel == LEVEL_OWN) {
      return SERVICES.getService(ICompanyService.class).isOwnCompany(getCompanyId());
    }
    return false;
  }
 
  public Long getCompanyId() {
    return m_companyId;
  }
}

In order to enable fine-grained access control so the user can only access his personal company objects, the user's permission level of the permission 'ReadCompanyPermission' must be set to 10 when loading the permissions with execLoadPermissions.

The access-check to protect the company resource would be changed as follows:

if (!ACCESS.check(new ReadCompanyPermission(xy))) {
   throw new VetoException("Authorization failed");
} 
else {
   // user is authorized, do some business logic here
}

As you may have noticed, there is provided an implicit level in the ReadCompanyPermission's constructor when doing the super call. This level stands for the minimal required level the user must have in order to access the resource. If you would put LEVEL_ALL in there, the user would not be allowed to access the company resource anymore as his level is only 10 (LEVEL_OWN) which is lower than 100 (LEVEL_ALL). In difference, the level LEVEL_UNDEFINED (-1) does not represent a concrete level, but exclusively stands for fine-grained access control. As a consequence, the access controller does not decide by itself whether to grant access or not. Thereto, it delegates this decision to you by invoking execCheckLevel(int userLevel) on the permission.

In this example, the provided userLevel would be 10 (LEVEL_OWN). That indicates your code that the caller is only allowed to see companies which he belongs to. In consequence, you have to verify the user's relation to the given company and grant access accordingly.

So feel free to define some other fine-grained access levels, e.g. LEVEL_DEPARTMENT = 20;

Please note, that by calling ACCESS.getLevel(Permission p) the user specific access level on that permission can be requested.