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/HowTo/3.8/Create Permissions"

< Scout‎ | HowTo‎ | 3.8
(New page: {{ScoutPage=Scout HowTo 3.8}} Permissions are needed to control: # Who is allowed to do what action # Who is allowed to see which records == Permission to do certain actions == To enfo...)
 
(Permission to do certain Actions)
 
(16 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{ScoutPage=Scout HowTo 3.8}}
+
{{ScoutPage|cat=HowTo 3.8}}
  
 +
== Introduction ==
 
Permissions are needed to control:
 
Permissions are needed to control:
  
Line 6: Line 7:
 
# Who is allowed to see which records
 
# Who is allowed to see which records
  
== Permission to do certain actions ==
+
== Permission to do certain Actions ==
  
To enforce that not everybody can do everything, permission are created. A permission has a name, e.g. ReadCompanyPermission. The permissions are grouped into roles, e.g. Standard role or Administrator role. Each user is assigned to one or more roles.
+
To enforce that not everybody can do everything, permission are created. A permission has a name, e.g. <code>ReadCompanyPermission</code>. The permissions are grouped into roles, e.g. Standard role or Administrator role. Each user is assigned to one or more roles.
  
'''Create a permission'''
+
===Create a Permission===
  
To create a permission in Scout, use the menu "New Permission..." on folder Permissions below folder shared. This creates a class that extends BasicHierarchyPermission.
+
To create a permission in Scout, use the menu "New Permission..." on folder "Permissions" below folder shared. This creates a class that extends <code>BasicPermission</code> or <code>BasicHierarchyPermission</code>.<br>
 +
When creating e.g. a new Scout Form or Process Service using the Scout SDK, corresponding permissions are created by default as well.
  
'''Use permissions (client side)'''
+
===Use Permissions (Client Side)===
  
 
On a table page, the following methods checks whether the logged in user has the necessary permission and depending on it sets the visibility of the table page to true or false.
 
On a table page, the following methods checks whether the logged in user has the necessary permission and depending on it sets the visibility of the table page to true or false.
 +
<source lang="java">
 +
@Override
 +
public void execInitPage() throws ProcessingException{
 +
  setVisibleGranted(ACCESS.getLevel(new ReadCompanyPermission()) > BasicHierarchyPermission.LEVEL_NONE);
 +
}
 +
</source>
  
  @Override
+
===Use Permissions (Server Side)===
  public void execInitPage() throws ProcessingException{
+
 
    setVisibleGranted(ACCESS.getLevel(new ReadCompanyPermission())>BasicHierarchyPermission.LEVEL_NONE);
+
When creating a process service with Scout SDK, permission classes are created with it. For the methods <code>prepareCreate</code>, <code>create</code>, <code>load</code> and <code>store</code>, the following code is added (on the example of a <code>CompanyProcessService</code>):
 +
<source lang="java">
 +
public CompanyFormData prepareCreate(CompanyFormData  formData) throws ProcessingException{
 +
  if(!ACCESS.check(new CreateCompanyPermission())){
 +
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
 
   }
 
   }
 +
  // TODO business logic here
 +
  return formData;
 +
}
  
'''Use permissions (server side)'''
+
public CompanyFormData create(CompanyFormData  formData) throws ProcessingException{
 +
  if(!ACCESS.check(new CreateCompanyPermission())){
 +
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
 +
  }
 +
  // TODO business logic here
 +
  return formData;
 +
}
  
When creating a process service with Scout, permission classes are created with it. For the methods prepareCreate, create, load and store, the following code is added (on the example of a CompanyProcessService):
+
public CompanyFormData load(CompanyFormData  formData) throws ProcessingException{
 
+
  if(!ACCESS.check(new ReadCompanyPermission())){
  public CompanyFormData prepareCreate(CompanyFormData  formData) throws ProcessingException{
+
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
    if(!ACCESS.check(new CreateCompanyPermission())){
+
      throw new VetoException(Texts.get("YouAreNotAuthorizedToRegisterformData"));
+
    }
+
    // TODO business logic here
+
    return formData;
+
 
   }
 
   }
 +
  // TODO business logic here
 +
  return formData;
 +
}
  
  public CompanyFormData create(CompanyFormData  formData) throws ProcessingException{
+
public CompanyFormData store(CompanyFormData  formData) throws ProcessingException{
    if(!ACCESS.check(new CreateCompanyPermission())){
+
  if(!ACCESS.check(new UpdateCompanyPermission())){
      throw new VetoException(Texts.get("YouAreNotAuthorizedToRegisterformData"));
+
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
    }
+
    // TODO business logic here
+
    return formData;
+
 
   }
 
   }
 +
  // TODO business logic here
 +
  return formData;
 +
}
 +
</source>
  
  public CompanyFormData load(CompanyFormData  formData) throws ProcessingException{
+
The <code>ACCESS.check(Permission p)</code> checks, whether the logged in user has the permission p. If not, an exception is thrown, which results in a message box popping up saying that the user is not authorized to do this action.
     if(!ACCESS.check(new ReadCompanyPermission())){
+
 
      throw new VetoException(Texts.get("YouAreNotAuthorizedToRegisterformData"));
+
== Assigning Permissions to Users ==
     }
+
The permissons are assigned to a user in the <code>AccessControlService</code> [http://wiki.eclipse.org/Image:Scout.3.8.howto.permissions.01.png]. This service is typically created and registered by the Scout SDK when creating a new Scout Project.
     // TODO business logic here
+
 
     return formData;
+
===Example: Load permissions from a database===
 +
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. Then you can select the available permission levels for the current user and then convert it to a permission list using <code>AccessControlUtility.createPermissions(permissionData)</code>.
 +
 
 +
Furthermore you have to add the builtin permission <code>RemoteServiceAccessPermission</code> 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.
 +
 
 +
Your <code>AccessControlService</code> could look like this:
 +
<source lang="java">
 +
@Override
 +
protected Permissions execLoadPermissions() {
 +
  try {
 +
     Object[][] permissionData = SQL.select(
 +
            "SELECT  P.PERMISSION_NAME, " +
 +
            "        MAX(P.PERMISSION_LEVEL) " +
 +
            "FROM    USER_ROLE R, ROLE_PERMISSION P " +
 +
            "WHERE    R.ROLE_UID=P.ROLE_UID " +
 +
            "AND      P.PERMISSION_LEVEL>0 " +
 +
            "AND      R.USER_NR = :userNr " +
 +
            "GROUP BY P.PERMISSION_NAME"
 +
    );
 +
 
 +
    Permissions p = AccessControlUtility.createPermissions(permissionData);
 +
    p.add(new RemoteServiceAccessPermission("*.shared.*", "*"));
 +
     return p;
 +
  }
 +
  catch (ProcessingException t) {
 +
     //TODO: add logging
 +
     return null;
 
   }
 
   }
 +
}
 +
</source>
  
   public CompanyFormData store(CompanyFormData  formData) throws ProcessingException{
+
If your permissions are stored elsewhere, no problem. Just prepare the permissionData matrix as required: First column with the name of the class, second column with a numerical "level".
     if(!ACCESS.check(new UpdateCompanyPermission())){
+
 
       throw new VetoException(Texts.get("YouAreNotAuthorizedToRegisterformData"));
+
{{Note|Full Access|There exists the permission <code>AllPermission</code> which implies all other permissions. For full-access, please add this permission to the collection.}}
 +
 
 +
==== Fill database tables ROLE_PERMISSION AND USER_ROLE ====
 +
Put an entry into the table '''ROLE_PERMISSION''' to represent the 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 <code>x.y.shared.security.ReadCompanyPermission</code>. The level is not of interest for this kind of permission, so simply put <code>PERMISSION_LEVEL=100</code>. More on the topic 'level' will follow further down.
 +
 
 +
==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 name
 +
 
 +
{{Note|Change Permission Discovery|This behavior can be overwritten by writing an own implementation of <code>IPermissionService</code> and giving the own implementation a higher priority.}}
 +
 
 +
==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.
 +
 
 +
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 <code>ReadCompanyPermission</code> would be changed as follows:
 +
 
 +
<source lang="java">
 +
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());
 
     }
 
     }
    // TODO business logic here
+
     return false;
     return formData;
+
 
   }
 
   }
 +
 
 +
  public Long getCompanyId() {
 +
  return m_companyId;
 +
  }
 +
}
 +
</source>
  
The ACCESS.check(Permission p) checks, whether the logged in user has the permission p. If not, an exception is thrown, which results in a message box popping up saying that the user is not authorized to do this action.
+
In order to enable fine-grained access control so the user can only access her/his personal company objects, the user's permission level must be changed to 10 in the table '''ROLE_PERMISSION.PERMISSION_LEVEL''' for the permission '''ReadCompanyPermission'''.
  
== Assigning permissions when a server session is created ==
+
The access-check to protect the company resource would be changed as follows:
  
Here's an example of how to load permissions from a database. If your permissions are stored elsewhere, no problem. Just prepare the permissionData matrix as required: First column with the name of the class, second column with a numerical "level".
+
<source lang="java">
 +
if (!ACCESS.check(new ReadCompanyPermission(xy))) {
 +
  throw new VetoException("Authorization failed");
 +
}
 +
// user is authorized, do some business logic here
 +
</source>
  
    Object[][] permissionData=SQL.select(
+
As you may have noticed, there is provided an implicit level in the <code>ReadCompanyPermission</code>'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 her/his level is only 10 ('''LEVEL_OWN''') which is lower than 100 ('''LEVEL_ALL''').
        "SELECT  P.PERMISSION_NAME, "+
+
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 <code>execCheckLevel(int userLevel)</code> on the permission.
        "        MAX(P.PERMISSION_LEVEL) "+
+
 
        "FROM    ORS_USER_ROLE R, ORS_ROLE_PERMISSION P "+
+
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.
        "WHERE  R.ROLE_UID=P.ROLE_UID "+
+
In consequence, you have to verify the user's relation to the given company and grant access accordingly.
        "AND    P.PERMISSION_LEVEL>0 "+
+
 
        "AND    R.USER_NR IN( "+
+
So feel free to define some other fine-grained access levels, e.g. '''LEVEL_DEPARTMENT''' = 20;
        "        SELECT  S.USER_NR "+
+
        "        FROM    ORS_USER_SUBSTITUTE S "+
+
        "        WHERE  S.SUBSTITUTE_NR=:userNr "+
+
        "        UNION ALL "+
+
        "        SELECT  TO_NUMBER(:userNr) FROM DUAL) "+
+
        "GROUP BY P.PERMISSION_NAME"
+
    );
+
   
+
    SERVICES.getService(IAccessControlService.class).
+
      setPermissions(permissionData, BasicCrmPermission.PERMISSION_PACKAGE_NAME);
+
  
In the above example, the BasicCrmPermission.PERMISSION_PACKAGE_NAME is simply "com.bsiag.crm.shared.security." and used to fully qualify the permissions.
+
Please note, that by calling <code>ACCESS.getLevel(Permission p)</code> the user specific access level on that permission can be requested.
Obviously that's exactly where the framework will create these permission classes
+
as mentioned above.
+
  
== Permission to see certain records AKA row-level granting ==
+
== Permission to see certain Records (Row-Level Granting) ==
  
To control who is allowed to see certain records, this must be implemented directly in the sql statement that selects the data from the database into the application. Records for which the logged in user has no right to see, are not returned by the sql statement. This is only possible if the users, the roles and the permissions are stored in the database.
+
To control who is allowed to see certain records, this must be implemented directly in the SQL statement that selects the data from the database into the application. Records for which the logged in user has no right to see, are not returned by the SQL statement. This is only possible if the users, the roles and the permissions are stored in the database.

Latest revision as of 08:38, 7 May 2012

The Scout documentation has been moved to https://eclipsescout.github.io/.

Introduction

Permissions are needed to control:

  1. Who is allowed to do what action
  2. Who is allowed to see which records

Permission to do certain Actions

To enforce that not everybody can do everything, permission are created. A permission has a name, e.g. ReadCompanyPermission. The permissions are grouped into roles, e.g. Standard role or Administrator role. Each user is assigned to one or more roles.

Create a Permission

To create a permission in Scout, use the menu "New Permission..." on folder "Permissions" below folder shared. This creates a class that extends BasicPermission or BasicHierarchyPermission.
When creating e.g. a new Scout Form or Process Service using the Scout SDK, corresponding permissions are created by default as well.

Use Permissions (Client Side)

On a table page, the following methods checks whether the logged in user has the necessary permission and depending on it sets the visibility of the table page to true or false.

@Override
public void execInitPage() throws ProcessingException{
  setVisibleGranted(ACCESS.getLevel(new ReadCompanyPermission()) > BasicHierarchyPermission.LEVEL_NONE);
}

Use Permissions (Server Side)

When creating a process service with Scout SDK, permission classes are created with it. For the methods prepareCreate, create, load and store, the following code is added (on the example of a CompanyProcessService):

public CompanyFormData prepareCreate(CompanyFormData  formData) throws ProcessingException{
  if(!ACCESS.check(new CreateCompanyPermission())){
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
  }
  // TODO business logic here
  return formData;
}
 
public CompanyFormData create(CompanyFormData  formData) throws ProcessingException{
  if(!ACCESS.check(new CreateCompanyPermission())){
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
  }
  // TODO business logic here
  return formData;
}
 
public CompanyFormData load(CompanyFormData  formData) throws ProcessingException{
  if(!ACCESS.check(new ReadCompanyPermission())){
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
  }
  // TODO business logic here
  return formData;
}
 
public CompanyFormData store(CompanyFormData  formData) throws ProcessingException{
  if(!ACCESS.check(new UpdateCompanyPermission())){
    throw new VetoException(TEXTS.get("AuthorizationFailed"));
  }
  // TODO business logic here
  return formData;
}

The ACCESS.check(Permission p) checks, whether the logged in user has the permission p. If not, an exception is thrown, which results in a message box popping up saying that the user is not authorized to do this action.

Assigning Permissions to Users

The permissons are assigned to a user in the AccessControlService [1]. This service is typically created and registered by the Scout SDK when creating a new Scout Project.

Example: Load permissions from a database

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. Then you can select the available permission levels for the current user and then convert it to a permission list using AccessControlUtility.createPermissions(permissionData).

Furthermore you have to add the builtin 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.

Your AccessControlService could look like this:

@Override
protected Permissions execLoadPermissions() {
  try {
    Object[][] permissionData = SQL.select(
            "SELECT   P.PERMISSION_NAME, " +
            "         MAX(P.PERMISSION_LEVEL) " +
            "FROM     USER_ROLE R, ROLE_PERMISSION P " +
            "WHERE    R.ROLE_UID=P.ROLE_UID " +
            "AND      P.PERMISSION_LEVEL>0 " +
            "AND      R.USER_NR = :userNr " +
            "GROUP BY P.PERMISSION_NAME"
    );
 
    Permissions p = AccessControlUtility.createPermissions(permissionData);
    p.add(new RemoteServiceAccessPermission("*.shared.*", "*"));
    return p;
  }
  catch (ProcessingException t) {
    //TODO: add logging
    return null;
  }
}

If your permissions are stored elsewhere, no problem. Just prepare the permissionData matrix as required: First column with the name of the class, second column with a numerical "level".

Note.png
Full Access
There exists the permission AllPermission which implies all other permissions. For full-access, please add this permission to the collection.


Fill database tables ROLE_PERMISSION AND USER_ROLE

Put an entry into the table ROLE_PERMISSION to represent the 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.

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 name
Note.png
Change Permission Discovery
This behavior can be overwritten by writing an own implementation of IPermissionService and giving the own implementation a higher priority.


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.

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 her/his personal company objects, the user's permission level must be changed to 10 in the table 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");
}
// 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 her/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.

Permission to see certain Records (Row-Level Granting)

To control who is allowed to see certain records, this must be implemented directly in the SQL statement that selects the data from the database into the application. Records for which the logged in user has no right to see, are not returned by the SQL statement. This is only possible if the users, the roles and the permissions are stored in the database.

Back to the top