Declarative authorization checks

Purpose

Allow for flexible configuration of authorization logic within the framework. The authorization checks should be well exposed (i.e. visible). It should be straight forward to add new checks, or modify existing ones. It should be possible to use GaP as the authorization engine, alternative engines and their combination.

Implementation

The implementation uses ACEGI declarative authorization proxy factories, providing a voter class implementation (PermissionVoter) that talks to GaP back-end. PermissionVoter is configured with a permission target resolver (a bean whose task is to identify permission target from either configuration attributes or the arguments of the intercepted method call).

Example

Here's an example of how ACEGI constructs can be used together with a PermissionVoter to protect a mock portlet definition registry:

<!-- the registry being protected -->
<bean id="mockPortletDefinitionRegistry" class="org.jasig.portal.security.acegi.MockPortletDefinitionRegistry"/>

<!-- the security interceptor that in this case is configured to protect portlet definition regisrty calls -->
<bean id="portletDefinitionSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
    <property name="authenticationManager"><ref local="authentication"/></property>
    <property name="accessDecisionManager"><ref local="portletDefinitionAccessDecisionManager"/></property>
    <property name="objectDefinitionSource">
        <!-- Below is a mapping of intercepted methods to configuration attributes that will be passed to the voter
             In this case "remove*" and "create*" methods are configured with the activity attribute and the target
             (portlet definition id) is picked up by the resolver from the method call arguments. The update* method
             is configured with both activity and target (prefix for targets is PORT_ID in this case, see target resolver
             configuration below) - in this case target resolver will pick up the target from the config attribute
             instead of looking at the arguments passed to the method.
        -->
        <value> 
            org.jasig.portal.portlet.registry.general.IPortletDefinitionRegistry.update*=ACTIVITY_UPDATE,PORT_ID.*
            org.jasig.portal.portlet.registry.general.IPortletDefinitionRegistry.remove*=ACTIVITY_DELETE
            org.jasig.portal.portlet.registry.general.IPortletDefinitionRegistry.create*=ACTIVITY_CREATE
        </value>
    </property>
</bean>

<!-- decision manager for portlet definition access -->
<bean id="portletDefinitionAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
    <property name="allowIfAllAbstainDecisions">
        <value>false</value>
    </property>
    <property name="decisionVoters">
        <list>
            <!-- GAP voter -->
            <bean class="org.jasig.portal.security.acegi.PermissionVoter">
                <property name="targetResolver">
                    <ref local="portletDefinitionTargetResolver"/>
                </property>
                <property name="permissionOwner" value="UP_FRAMEWORK"/>
                <property name="authorizationService">
                    <ref local="authorizationService"/>
                </property>
                <property name="userController">
                    <ref local="userController"/>
                </property>
                <property name="activityPrefix" value="ACTIVITY_"/>
            </bean>
        </list>
    </property>
</bean>

<!-- permission target resolver for portlet definition id targets -->
<bean id="portletDefinitionTargetResolver" class="org.jasig.portal.security.acegi.PortletDefinitionIdResolver">
    <property name="targetPrefix" value="PORT_ID."/>
    <property name="allowMissingTarget" value="true"/>
</bean>

<!-- proxy configuration: this can be used to protect multiple beans, but in this case only mock registry is proxied -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <value>mockPortletDefinitionRegistry</value>
    </property>
    <property name="interceptorNames">
        <list>
            <value>portletDefinitionSecurityInterceptor</value>
        </list>
    </property>
    <property name="proxyTargetClass" value="true"/>
</bean>

Suggested use pattern

The PermissionVoter is configuredwith the permissionOwner property, and a helper bean that can resolve permission targets (targetResolver). The example above suggests that a given access decision manager should be associated with a single owner and a single target type. This should make configuration more transparent and more efficient. For instance, a different decision manager, portletDeploymentDecisionManager, would be created to check authorization to publish portlets.

Mixed configurations

The described approach can be very flexible considering that additional permission voters can be configured for the same decision manager. For instance, if the installation maintains alternative set of permission information (let's say a role-based permission information that's entirely separate from GaP), then a second voter, RoleVoter could be configured in parallel with the existing PermissionVoter (this would require PermissionVoter to abstain in situations when it doesn't have enough information to make a decision - right now it defaults to denying access).