Jump to: navigation, search

Scout/HowTo/3.7/Use permissions in your Scout application

< Scout‎ | HowTo‎ | 3.7
Revision as of 04:39, 4 May 2012 by Mvi.bsiag.com (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)


Scout
Wiki Home
Website
DownloadGit
Community
ForumsBlogTwitter
Bugzilla
Bugzilla


Note.png
TODO
Check and Merge of Scout/HowTo/Create Permissions. Possibility: This How-to is more "how can I store and load the permission", the other How To is more on "how should I handle Permissions". This separation makes maybe no sense.


How to use permissions in your Scout application

1. Create AccessControlService to load your permissions

First of all, create a custom access control service which inherits from AbstractAccessControlService. This is typically created by Scout SDK itself. Furthermore, this service has to be registered in plugin.xml similar to:

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

2. Load permissions from database

Add some logic for the LoadPermissions event of your AccessControlService (by overwriting the execLoadPermissions method): there you load all the permissions that belong to the user of the given context.

Let us say you have a role based security concept with the 2 database tables USER_ROLE AND ROLE_PERMISSION. In the table USER_ROLE, there you link the user to several roles whereas the table ROLE_PERMISSION holds all the permissions that belong to those roles.

Note.png
TODO
Add an ER Diagram


By running the following SQL statement, you get all the permission that belong to the given user:

Object[][] permissionData = SQL.select("" +
   "SELECT    P.PERMISSION_NAME, " +
   "          P.PERMISSION_LEVEL " +
   "FROM      USER_ROLE R " +
   "LEFT JOIN ROLE_PERMISSION P " +
   "          ON P.ROLE_ID = R.ROLE_ID " +
   "WHERE     R.USER_ID = :userId");

In turn, you call AccessControlUtility.createPermissions(permissionData) to create the associated Java permissions.

Last but not least, you add the permission RemoteServiceAccessPermission to the collection of permissions to allow the user to call backend services. The two arguments allow you to constrain the kind of services this user is allowed to call. You can use the wilcard character * to specify a matching pattern.

All in all, your AccessControlService looks as follows:

public class AccessControlService extends AbstractAccessControlService {
 
  @Override
  protected Permissions execLoadPermissions() {
    Object[][] permissionData = SQL.select("" +
      "SELECT    P.PERMISSION_NAME, " +
      "          P.PERMISSION_LEVEL " +
      "FROM      USER_ROLE R " +
      "LEFT JOIN ROLE_PERMISSION P " +
      "          ON P.ROLE_ID = R.ROLE_ID " +
      "WHERE     R.USER_ID = :userId");
 
    Permissions p = AccessControlUtility.createPermissions(permissionData);
    p.add(new RemoteServiceAccessPermission("*.shared.*", "*"));
    return p;
  }
}

Please note: There exists the permission AllPermission which implies all other permission. For full-access, please add this permission to the collection.

3. Create java.security.Permission representatives

All the permissions you like to have available in your application must be represented by a 'java.security.Permission' permission. 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");
  }
}

The name you provide in the constructor is the name of the permission. Among other things, its 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).

4. Populate database tables ROLE_PERMISSION AND USER_ROLE

Put an entry into the table ROLE_PERMISSION to represent this permission. Also, create a role entry to associate this permission with a role the user belongs to. Please note, that in the example above, the PERMISSION_NAME would be x.y.shared.security.ReadCompanyPermission. The level is not of interest for this kind of permission, so simply put PERMISSION_LEVEL=100. More on the topic 'level' will follow further down.

5. Protect resources

Finally, you're done. In your business logic, you now can check for that permission. This is done as follows:

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.

In the following, please find some more information about the topic.

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:

  • The class must be of the type java.security.Permission
  • The type must be a public concrete class, meaning not an interface nor an abstract class
  • Class must have the token Permission in its name
  • 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. Furthermore, PERMISSION_LEVEL in the database table ROLE_PERMISSION comes into play, meaning that in there, you specify the access level on its behalf the user can access protected resources.

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 anymore, the user's permission level must be changed to 10 in ROLE_PERMISSION.PERMISSION_LEVEL for the permission ReadCompanyPermission.

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.