org.obiba.opal.core.runtime.security.SpatialRealm.java Source code

Java tutorial

Introduction

Here is the source code for org.obiba.opal.core.runtime.security.SpatialRealm.java

Source

/*******************************************************************************
 * Copyright 2008(c) The OBiBa Consortium. All rights reserved.
 *
 * This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package org.obiba.opal.core.runtime.security;

import java.util.Collection;

import javax.annotation.PostConstruct;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.permission.RolePermissionResolver;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.obiba.opal.core.runtime.security.support.SubjectPermissionsConverterRegistry;
import org.obiba.opal.core.service.SubjectAclService;
import org.obiba.opal.core.service.SubjectAclService.Subject;
import org.obiba.opal.core.service.SubjectAclService.SubjectAclChangeCallback;
import org.obiba.opal.core.service.SubjectAclService.SubjectType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;

import eu.flatwhite.shiro.spatial.SingleSpaceRelationProvider;
import eu.flatwhite.shiro.spatial.SingleSpaceResolver;
import eu.flatwhite.shiro.spatial.Spatial;
import eu.flatwhite.shiro.spatial.SpatialPermissionResolver;
import eu.flatwhite.shiro.spatial.finite.Node;
import eu.flatwhite.shiro.spatial.finite.NodeRelationProvider;
import eu.flatwhite.shiro.spatial.finite.NodeResolver;
import eu.flatwhite.shiro.spatial.finite.NodeSpace;

@Component
public class SpatialRealm extends AuthorizingRealm implements RolePermissionResolver {

    private final SubjectAclService subjectAclService;

    private final RolePermissionResolver rolePermissionResolver;

    private final SubjectPermissionsConverterRegistry subjectPermissionsConverterRegistry;

    private Cache<Subject, Collection<Permission>> rolePermissionCache;

    @Autowired
    public SpatialRealm(SubjectAclService subjectAclService,
            SubjectPermissionsConverterRegistry subjectPermissionsConverterRegistry) {
        if (subjectAclService == null)
            throw new IllegalArgumentException("subjectAclService cannot be null");
        this.subjectAclService = subjectAclService;
        this.subjectPermissionsConverterRegistry = subjectPermissionsConverterRegistry;

        setPermissionResolver(new SpatialPermissionResolver(new SingleSpaceResolver(new RestSpace()),
                new NodeResolver(), new SingleSpaceRelationProvider(new NodeRelationProvider())));
        rolePermissionResolver = new GroupPermissionResolver();
    }

    @Override
    public boolean supports(AuthenticationToken token) {
        // This realm is not used for authentication
        return false;
    }

    @PostConstruct
    public void registerListener() {
        if (isAuthorizationCachingEnabled()) {
            subjectAclService.addListener(new SubjectAclChangeCallback() {

                @Override
                public void onSubjectAclChanged(Subject subject) {
                    getAuthorizationCache().remove(subject);
                    getRolePermissionCache().remove(subject);
                }
            });
        }
    }

    @Override
    public Collection<Permission> resolvePermissionsInRole(String roleString) {
        return getRolePermissionResolver().resolvePermissionsInRole(roleString);
    }

    /**
     * Overridden because the OpalSecurityManager sets {@code this} as the {@code RolePermissionResolver} on all configured
     * realms. This results the following object graph:
     * <p/>
     * <pre>
     * AuthorizingReam.rolePermissionResolver -> SpatialRealm (this)
     *      ^
     *      |
     * SpatialRealm.rolePermissionResolver -> GroupPermissionResolver
     *
     * <pre>
     * By overriding this method, we prevent an infinite loop from occurring when
     * {@code getRolePermissionResolver().resolvePermissionsInRole()} is called.
     */
    @Override
    public RolePermissionResolver getRolePermissionResolver() {
        return rolePermissionResolver;
    }

    protected Cache<Subject, Collection<Permission>> getRolePermissionCache() {
        return rolePermissionCache;
    }

    @Override
    protected void afterCacheManagerSet() {
        super.afterCacheManagerSet();
        if (isAuthorizationCachingEnabled()) {
            CacheManager cacheManager = getCacheManager();
            rolePermissionCache = cacheManager.getCache(getAuthorizationCacheName() + "_role");
        }
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Iterable<String> perms = loadSubjectPermissions(principals);
        if (perms != null) {
            SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
            sai.setStringPermissions(ImmutableSet.copyOf(perms));
            return sai;
        }
        return null;
    }

    @Override
    protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
        return getSubject(principals);
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        return null;
    }

    private Iterable<String> loadSubjectPermissions(SubjectAclService.Subject subject) {
        return subjectPermissionsConverterRegistry.convert(subjectAclService.getSubjectPermissions(subject));
    }

    private Iterable<String> loadSubjectPermissions(PrincipalCollection principals) {
        return loadSubjectPermissions(getSubject(principals));
    }

    private SubjectAclService.Subject getSubject(PrincipalCollection principals) {
        return SubjectType.USER.subjectFor(principals.getPrimaryPrincipal().toString());
    }

    private final class GroupPermissionResolver implements RolePermissionResolver {

        @Override
        public Collection<Permission> resolvePermissionsInRole(String roleString) {
            Subject group = SubjectType.GROUP.subjectFor(roleString);
            if (isAuthorizationCachingEnabled() && getRolePermissionCache() != null) {
                Collection<Permission> cached = getRolePermissionCache().get(group);
                if (cached != null) {
                    return cached;
                }
                cached = doGetGroupPermissions(group);
                getRolePermissionCache().put(group, cached);
                return cached;
            } else {
                return doGetGroupPermissions(group);
            }
        }

        private Collection<Permission> doGetGroupPermissions(Subject group) {
            return ImmutableList
                    .copyOf(Iterables.transform(loadSubjectPermissions(group), new Function<String, Permission>() {

                        @Override
                        public Permission apply(String from) {
                            return getPermissionResolver().resolvePermission(from);
                        }
                    }));
        }

    }

    /**
     * Overriden to make plural form resources part of non-plural form resources.
     * <p/>
     * That is, this space considers plural form sub-resources as related to non-plural form sub-resources:
     * <p/>
     * <pre>
     * /parent/kids
     * /parent/kid/1
     * </pre>
     */
    static class RestSpace extends NodeSpace {
        @Override
        protected double calculateDistance(Spatial s1, Spatial s2) {

            Double d = super.calculateDistance(s1, s2);
            if (Double.isNaN(d)) {
                // Check for plural form relation
                Node n1 = (Node) s1;

                Node n2 = (Node) s2;

                int nodes = Math.min(n1.getPath().size(), n2.getPath().size());
                for (int i = 0; i < nodes; i++) {
                    Node lhs = n1.getPath().get(i);
                    Node rhs = n2.getPath().get(i);
                    if (!lhs.getPathElem().equals(rhs.getPathElem())) {
                        // Check for plural form
                        return isPluralForm(lhs, rhs) ? 1 : d;
                    }
                }

            }
            return d;

        }

        /**
         * Returns true when lhs is the plural form of rhs (or vice-versa), that is their node element text is identical
         * except for an additional 's' in the other node.
         *
         * @param lhs
         * @param rhs
         */
        private boolean isPluralForm(Node lhs, Node rhs) {
            String a = lhs.getPathElem();
            String b = rhs.getPathElem();
            return a.equals(b + 's') || b.equals(a + 's');
        }
    }
}