Access to add and change pages is restricted. See:

Service definitions, such as that used by the WebTools app, can include a reference to a permission-service.

Definition of entityExportAll service
    <service name="entityExportAll" engine="java"
            location="org.apache.ofbiz.webtools.WebToolsServices" invoke="entityExportAll" auth="true" use-transaction="false">
        <description>Exports all entities into xml files</description>
        <permission-service service-name="entityMaintPermCheck" main-action="VIEW"/>
        <attribute name="outpath" type="String" mode="IN" optional="true"/>
        <attribute name="fromDate" type="Timestamp" mode="IN" optional="true"/>
        <attribute name="txTimeout" type="Integer" mode="IN" optional="true"/>
        <attribute name="results" type="List" mode="OUT" optional="false"/>

In the above example, the entityExportAll service cannot be executed unless a call to the permission service, entityMainPermCheck, is successful and returns the hasPermission boolean attribute with a value of true.

The referenced permission service can be defined anyplace where service definitions are permitted. In the case of entityMainPermCheck, it happens to be defined in the same file as the entityExportAll service.

Definition of entityMainPermCheck service
    <service name="entityMaintPermCheck" engine="java" location="org.apache.ofbiz.webtools.WebToolsServices" invoke="entityMaintPermCheck">
        <description>Performs an entity maintenance security check. Returns hasPermission=true
          if the user has the ENTITY_MAINT permission.</description>
        <implements service="permissionInterface"/>

Permission services used to protect access to other services should implement the permissionInterface interface.

The permissionInterface interface
<service name="permissionInterface" engine="interface">
    <description>Interface to describe base parameters for Permission Services</description>
    <attribute name="mainAction" type="String" mode="IN" optional="true">
        <description>The action requiring permission. Must be one of ADMIN, CREATE, UPDATE, DELETE, VIEW.</description>
    <attribute name="primaryPermission" type="String" mode="IN" optional="true">
        <description>The permission to check - typically the name of an application or entity.</description>
    <attribute name="altPermission" type="String" mode="IN" optional="true">
        <description>Optional alternate permission to check. If the primary permission check fails,
        the alternate permission will be checked.</description>
    <attribute name="resourceDescription" type="String" mode="IN" optional="true">
        <description>The name of the resource being accessed - defaults to service name.</description>
    <attribute name="hasPermission" type="Boolean" mode="OUT" optional="false">
        <description>Contains true if the requested permission has been granted.</description>
    <attribute name="failMessage" type="String" mode="OUT" optional="true">
        <description>Contains an explanation if the permission was denied.</description>

Note that the only required attribute is the hasPermission output boolean attribute. All other attributes are optional, but implementing services are free to override attribute optionality as needed. For example, the genericBasePermissionCheck service from the common component specifies a mandatory primaryPermission input attribute.

genericBasePermissionCheck service definition
<service name="genericBasePermissionCheck" engine="groovy"
         location="component://common/groovyScripts/permission/CommonPermissionServices.groovy" invoke="genericBasePermissionCheck">
    <implements service="permissionInterface"/>
    <attribute name="primaryPermission" type="String" mode="IN" optional="false"/>
    <attribute name="altPermission" type="String" mode="IN" optional="true"/>

How permission services are used to protect a service

Services are called using the ServiceDispatcher. Before the ServiceDispatcher calls a protected service's implementation it will check the current user has permission to execute the protected service based any permission-service specified in the protected service's service definition.

Service definitions are read by the ModelServiceReader to create ModelService objects. If a service definition specifies a permission-service, then the corresponding ModelService object shall be populated with a ModelPermission object.

The checkAuth method in the ServiceDispatcher calls the evalPermission method of the service's ModelPermission object, if ModelPermission exists.

evalPermission will itself call evalPermissionService to handle locating and synchronously calling the permission service.

The response from the permission service is returned to evalPermissionService which extracts the failMessage output attribute.

If the ModelPermission object is configured to return an error on failure - which is the default case and not currently overridden by any service definition in OFBiz - then it will interpret a non-empty failMessage output attribute or a false hasPermission output attribute as an indicator of failure. In this case an error response is created, using ServiceUtil.returnError, with the content of the failMessage attribute from the original response being used as the errorMessage. If failMessage is empty then a default value from ServiceErrorUILabels with key ServicePermissionErrorRefused is used instead (see below).

    <property key="ServicePermissionErrorRefused">
        <value xml:lang="en">Access refused</value>
        <value xml:lang="fr">Accés refusé</value>

If the failMessage is empty AND hasPermission is true in the response from the permission service, then that response is returned back up the call stack to the ServiceDispatcher#checkAuth method.

If the response from ModelPermission#evalPermissionService passed back to checkAuth is a success response, then any output parameters in the response that match the input parameters to the originally called service are copied to the context. This is a mechanism for a permission service to provide any inputs needed by the service it is protecting.

Finally, if the response from evalPermissionService is a failure or error, a ServiceAuthException is thrown, using the message from from ServiceErrorUILabels with key ServicePermissionErrorRefused (see below).

    <property key="ServicePermissionError">
        <value xml:lang="en">You haven't the permission for the service ${serviceName}, reason : ${failMessage}</value>
        <value xml:lang="fr">Vous n'avez pas la permission sur le service ${serviceName}, raison : ${failMessage}</value>

If the response was a success, then the updated context is returned to the ServiceDispatcher's runSync method where it is used in various evaluations of ECA rules before being used to invoke the protected service.

Whether to return Success, Failure or Error from permission service implementations

Success responses from permission service implementations represent the user having permissions needed to satisfy the service.

Failure responses represent cases where the user does not have the permissions needed to satisfy the service. In this case the optional failMessage output attribute can be populated to explain why permission to run the protected service was denied - i.e. what permissions does the current user lack.

In either the success or failure case, the hasPermission boolean output attribute MUST be set to true or false, respectively. This is needed to satisfy logic in ModelPermission to correctly identify cases where permission is denied and generate error responses.

It could be reasonably argued that a lack of necessary permissions does not constitute a failure in the execution of the permission service and that we should therefore return a success response with hasPermission set to false in those cases. The failMessage attribute could still be populated to provide information on what cause permission to be denied.

Conversely it could also be argued that the hasPermission boolean output attribute is unnecessary and instead ModelPermission#evalPermissionService should be able to determine whether permission is granted based on whether a success or failure response is received from the permission service.

Unfortunately it is the case that both approaches have been used in the implementations of ModelPermission and ServiceDispatcher. The advice on responses that should be given is designed to fit in with the expectations of both these classes.

Unless an implementation of a permission service encounters an error during execution, it should never return an error response. The case of a permission being absent should NOT be interpreted as an error condition.

When an error response is returned from a service using the Groovy engine, the GroovyBaseScript#runService implementation will throw an ExecutionServiceException with a message based on the response's errorMessage attribute.

The thrown ExecutionServiceException is later caught by the GroovyEngine#runSync method where it is converted to an error service-response with errorMessage populated from the exception's message. Any other attributes in the original response from the permission service are lost, information that could have been helpful to the ModelPermission class and other code respondible for managing access to services.

Note: There are case in OFBiz of permission services returning an error to indicate that a user does not have a permissions. This approach, although not recommended, may not be causing issues for those particular cases.

It would be worthwhile to review those cases and switch to failure responses.

  • No labels