Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Scout/Concepts/Security"

m (Authentication)
Line 16: Line 16:
  
 
== Authentication ==
 
== Authentication ==
 +
{{note|The following sections apply to Scout OSGi only (i.e. version <5.0)}}
 +
 
Let us have a look on how the system prompts for 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.
 
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.

Revision as of 10:38, 27 April 2017

The Scout documentation has been moved to https://eclipsescout.github.io/. 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.

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 (The Scout documentation has been moved to https://eclipsescout.github.io/.) 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

Note.png
The following sections apply to Scout OSGi only (i.e. version <5.0)


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).

The 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 IAccessControlService.

"Your" IAccessControlService means you need to have a custom AccessControlService extending AbstractAccessControlService in your project.

How permissions are discovered

Permission are discovered by IPermissionService. The default implementation looks for permission classes in all modules. 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

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

Fine-grained access control

In contrast to the BasicPermission mentioned above, which allows access control based on users permissions, Scout also provided means to implement an access control based on a concret resource. For example a user can only update his own companies and not just all.

Fine-grained access permissions must be of 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 {
  private static final long serialVersionUID = 1L;
 
  public static final int LEVEL_OWN = 10;
 
  private final long m_companyId;
 
  /**
   * Constructor used to check access for projectId
   */
  public ReadCompanyPermission(long companyId) {
    super("ReadCompany");
    m_companyId = companyId;
  }
 
  /**
   * Constructor used in {@link AbstractAccessControlService#execLoadPermissions}.
   */
  public ReadCompanyPermission(int level) {
    super(ReadCompanyPermission.class.getSimpleName(), level);
    this.m_companyId = 0;
  }
 
  public long getCompanyId() {
    return m_companyId;
  }
 
  @Override
  protected int execCalculateLevel(BasicHierarchyPermission other) {
    int result = LEVEL_ALL; // default implementation requires level ALL; Therefore permission is only granted if user has level ALL.
    if (other instanceof ReadCompanyPermission) {
      boolean ownCompany = BEANS.get(ICompanyService.class).isOwnCompany(((ReadCompanyPermission) other).getCompanyId());
      if (ownCompany) {
        // if company for which the permission check evaluate is a own company then the user only requires the level OWN.
        return LEVEL_OWN;
      }
    }
    return result;
  }
 
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime * result + (int) (m_companyId ^ (m_companyId >>> 32));
    return result;
  }
 
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (!super.equals(obj)) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    ReadCompanyPermission other = (ReadCompanyPermission) obj;
    if (m_companyId != other.m_companyId) {
      return false;
    }
    return true;
  }
}

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:

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

Calling ACCESS.getLevel(Permission p) will return the level of access a user has.

Back to the top