org.obiba.mica.security.realm.MicaAuthorizingRealm.java Source code

Java tutorial

Introduction

Here is the source code for org.obiba.mica.security.realm.MicaAuthorizingRealm.java

Source

/*
 * Copyright (c) 2018 OBiBa. 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.mica.security.realm;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.inject.Inject;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
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.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.PermissionUtils;
import org.obiba.mica.security.PermissionsUtils;
import org.obiba.mica.security.Roles;
import org.obiba.mica.security.domain.SubjectAcl;
import org.obiba.mica.security.event.SubjectAclUpdatedEvent;
import org.obiba.mica.security.service.SubjectAclService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.Subscribe;

@Component
public class MicaAuthorizingRealm extends AuthorizingRealm implements RolePermissionResolver {

    private static final String[] ALL_RESOURCES = { "network", "individual-study", "harmonization-study",
            "collected-dataset", "harmonized-dataset", "project" };

    private static final Logger logger = LoggerFactory.getLogger(MicaAuthorizingRealm.class);

    @Inject
    private SubjectAclService subjectAclService;

    private final RolePermissionResolver rolePermissionResolver = new GroupPermissionResolver();

    private Cache<String, Collection<Permission>> rolePermissionsCache = CacheBuilder.newBuilder()
            .maximumSize(10000).expireAfterWrite(1, TimeUnit.MINUTES).build();

    private Cache<String, List<String>> subjectPermissionsCache = CacheBuilder.newBuilder().maximumSize(10000)
            .expireAfterWrite(1, TimeUnit.MINUTES).build();

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

    /**
     * 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 -> MicaAuthorizingRealm (this)
     *      ^
     *      |
     * MicaAuthorizingRealm.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;
    }

    //
    // AuthorizingRealm
    //

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        List<String> perms = loadUserPermissions(principals);
        if (perms == null || perms.isEmpty())
            return null;
        SimpleAuthorizationInfo sai = new SimpleAuthorizationInfo();
        sai.setStringPermissions(ImmutableSet.copyOf(perms));
        return sai;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // This realm is not used for authentication
        return null;
    }

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

    @Override
    protected Object getAuthorizationCacheKey(PrincipalCollection principals) {
        return SubjectAcl.Type.USER.subjectFor(principals.getPrimaryPrincipal().toString());
    }

    //
    // RolePermissionResolver
    //

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

    //
    // Events
    //

    @Subscribe
    public void onSubjectAclUpdate(SubjectAclUpdatedEvent event) {
        invalidateCache();
    }

    //
    // Private methods
    //

    private void invalidateCache() {
        logger.warn("Invalidating authorization cache");
        if (isCachingEnabled()) {
            getAuthorizationCache().clear();

            subjectPermissionsCache.invalidateAll();
            subjectPermissionsCache.cleanUp();

            rolePermissionsCache.invalidateAll();
            rolePermissionsCache.cleanUp();
        }
    }

    private List<String> loadUserPermissions(PrincipalCollection principals) {
        return loadSubjectPermissions(principals.getPrimaryPrincipal().toString(), SubjectAcl.Type.USER);
    }

    /**
     * Get permissions that apply to the subject and to any subjects of the same type.
     *
     * @param name
     * @param type
     * @return
     */
    private List<String> loadSubjectPermissions(String name, SubjectAcl.Type type) {
        String key = name + ":" + type;
        try {
            return subjectPermissionsCache.get(key, () -> doLoadSubjectPermissions(name, type));
        } catch (ExecutionException e) {
            return doLoadSubjectPermissions(name, type);
        }
    }

    private List<String> doLoadSubjectPermissions(String name, SubjectAcl.Type type) {
        return Stream
                .concat(subjectAclService.findBySubject("*", type).stream(),
                        subjectAclService.findBySubject(name, type).stream())
                .map(SubjectAcl::getPermission).collect(Collectors.toList());
    }

    //
    // Inner classes
    //

    private final class GroupPermissionResolver implements RolePermissionResolver {

        @Override
        public Collection<Permission> resolvePermissionsInRole(String roleString) {
            Collection<Permission> cachedPerms = rolePermissionsCache.getIfPresent(roleString);
            if (cachedPerms != null)
                return cachedPerms;

            List<String> permissions = loadSubjectPermissions(roleString, SubjectAcl.Type.GROUP);
            // built-in permissions
            Collection<Permission> perms;
            switch (roleString) {
            case Roles.MICA_ADMIN:
                perms = mergePermissions("*", permissions);
                break;
            case Roles.MICA_REVIEWER:
                // all permissions: edition and publication on draft, view on published
                perms = mergePermissions("/files:UPLOAD", permissions);
                Arrays.stream(ALL_RESOURCES).forEach(e -> {
                    perms.addAll(toPermissions(String.format("/draft/%s", e)));
                    perms.addAll(toPermissions(String.format("/draft/file:*:/%s", e)));
                    perms.addAll(toPermissions(String.format("/%s:VIEW", e)));
                    perms.addAll(toPermissions(String.format("/file:VIEW:/%s", e)));
                });
                break;
            case Roles.MICA_EDITOR:
                // all edition permissions on draft
                perms = mergePermissions("/files:UPLOAD", permissions);
                Arrays.stream(ALL_RESOURCES).forEach(e -> PermissionsUtils.EDITOR_ACTIONS.forEach(a -> {
                    perms.addAll(toPermissions(String.format("/draft/%s:%s", e, a)));
                    perms.addAll(toPermissions(String.format("/draft/file:%s:/%s", a, e)));

                }));
                // all view permissions on published
                Arrays.stream(ALL_RESOURCES).forEach(e -> {
                    perms.addAll(toPermissions(String.format("/%s:VIEW", e)));
                    perms.addAll(toPermissions(String.format("/file:VIEW:/%s", e)));
                });
                break;
            case Roles.MICA_DAO:
                // can view and delete any project and data access requests
                perms = mergePermissions(
                        "/private-comment/data-access-request:VIEW,/private-comment/data-access-request:ADD,"
                                + "/data-access-request:ADD,/data-access-request:VIEW,/data-access-request:DELETE,"
                                + "/files:UPLOAD",
                        permissions);
                break;
            case Roles.MICA_USER:
                perms = mergePermissions("/data-access-request:ADD,/files:UPLOAD", permissions);
                break;
            default:
                // other groups
                perms = PermissionUtils.resolvePermissions(permissions, getPermissionResolver());
            }
            rolePermissionsCache.put(roleString, perms);
            return perms;
        }

        private Collection<Permission> mergePermissions(String delimitedPermissions,
                Collection<String> permissions) {
            Collection<Permission> perms = toPermissions(delimitedPermissions);
            perms.addAll(PermissionUtils.resolvePermissions(permissions, getPermissionResolver()));
            return perms;
        }

        private Collection<Permission> toPermissions(String delimitedPermissions) {
            return PermissionUtils.resolveDelimitedPermissions(delimitedPermissions, getPermissionResolver());
        }
    }

}