org.openiot.security.client.AuthorizationManager.java Source code

Java tutorial

Introduction

Here is the source code for org.openiot.security.client.AuthorizationManager.java

Source

/**
 * Copyright (c) 2011-2014, OpenIoT
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL"). If you do not alter this
 * notice, a recipient may use your version of this file under the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL  for
 * the specific language governing rights and limitations.
 *
 * Contact: OpenIoT mailto: info@openiot.eu
 */

package org.openiot.security.client;

import static org.openiot.security.client.SecurityConstants.CALLER_ACCESS_TOKEN;
import static org.openiot.security.client.SecurityConstants.CALLER_CLIENT_ID;
import static org.openiot.security.client.SecurityConstants.ERROR;
import static org.openiot.security.client.SecurityConstants.ROLE_PERMISSIONS;
import static org.openiot.security.client.SecurityConstants.TARGET_CLIENT_ID;
import static org.openiot.security.client.SecurityConstants.USER_ACCESS_TOKEN;
import static org.openiot.security.client.SecurityConstants.*;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.PermissionResolver;
import org.apache.shiro.authz.permission.WildcardPermissionResolver;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.pac4j.core.exception.HttpCommunicationException;
import org.pac4j.oauth.client.BaseOAuth20Client;
import org.pac4j.oauth.profile.JsonHelper;
import org.pac4j.oauth.profile.casoauthwrapper.CasOAuthWrapperProfile;
import org.scribe.model.OAuthConstants;
import org.scribe.model.ProxyOAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Verb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;

/**
 * This class provides authorization information to the clients. The permissions and roles are
 * obtained for the tokens on services as follows:
 * <ul>
 * <li>If OAuthorizationCredentials does not have caller credentials, then the authorization
 * information is retrieved and consulted for its token on its service (specified by clientId)</li>
 * <li>If OAuthorizationCredentials has caller credentials, but the caller does not have a caller
 * (i.e., chain of 1 credentials), then the authorization information is retrieved and consulted for
 * the caller's token on the current service (specified by clientId) unless the target service is
 * explicitly specified</li>
 * <li>If OAuthorizationCredentials has a caller credentials, which also has a caller (i.e., chain
 * of 3 credentials), then the authorization information is retrieved and consulted for the the last
 * caller's token on the current service (specified by clientId) unless the target service is
 * explicitly specified</li>
 * </ul>
 * If the target service is different than the current service, the user must have the required
 * permission for retrieving authorization information on the target service.
 * 
 * @author Mehdi Riahi
 * 
 */
public class AuthorizationManager implements ClearCacheListener {

    // TODO: use the subject's session for caching authorization data and back the session store by
    // Ehcache.

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

    private Cache<CacheKey, Map<String, Set<Permission>>> cacheManager;

    private BaseOAuth20Client<?> client;

    private boolean cachingEnabled = false;

    private String permissionsURL;

    private PermissionResolver permissionResolver = new WildcardPermissionResolver();

    public AuthorizationManager() {

    }

    public void setCacheManager(CacheManager cacheManager) {
        if (cacheManager != null) {
            logger.debug("Setting the cache manager to {}", cacheManager.getClass().getCanonicalName());
            this.cacheManager = cacheManager
                    .<CacheKey, Map<String, Set<Permission>>>getCache("AuthorizationManager-Cache");
            cachingEnabled = true;
        }
    }

    public void setPermissionsURL(String permissionsURL) {
        this.permissionsURL = permissionsURL;
    }

    public boolean isCachingEnabled() {
        return cachingEnabled;
    }

    public void setClient(BaseOAuth20Client<?> client) {
        this.client = client;
    }

    /**
     * Sends a request to the server to check if the token is expired.
     * 
     * @param credentials
     * @return the expired token or <code>null</code> if non of the tokens in
     *         <code>credentials</code> are expired
     */
    public String getExpiredAccessToken(OAuthorizationCredentials credentials) {
        if (cachingEnabled)
            cacheManager.remove(new CacheKey(credentials.getClientId(), credentials));
        try {
            getAuthorizationInfo(credentials, credentials.getClientId());
        } catch (AccessTokenExpiredException e) {
            return e.getToken();
        }
        return null;
    }

    void reset(String token) {
        clearCacheForToken(token);
    }

    public boolean hasPermission(String permStr, OAuthorizationCredentials credentials) {
        if (credentials == null)
            return false;
        return hasPermission(permStr, credentials.getClientId(), credentials);
    }

    public boolean hasPermission(String permStr, String targetClientId, OAuthorizationCredentials credentials) {
        if (credentials == null)
            return false;
        Permission perm = permissionResolver.resolvePermission(permStr);

        Map<String, Set<Permission>> authorizationInfo = getAuthorizationInfo(credentials,
                targetClientId == null ? credentials.getClientId() : targetClientId);
        boolean hasPerm = false;
        for (Set<Permission> permSet : authorizationInfo.values()) {
            if (hasPerm)
                break;
            for (Permission permission : permSet)
                if (permission.implies(perm)) {
                    hasPerm = true;
                    break;
                }
        }
        return hasPerm;
    }

    public boolean hasRole(String role, OAuthorizationCredentials credentials) {
        if (credentials == null)
            return false;
        return hasRole(role, credentials.getClientId(), credentials);
    }

    public boolean hasRole(String role, String targetClientId, OAuthorizationCredentials credentials) {
        if (credentials == null)
            return false;
        Map<String, Set<Permission>> authorizationInfo = getAuthorizationInfo(credentials,
                targetClientId == null ? credentials.getClientId() : targetClientId);
        return authorizationInfo.containsKey(role);
    }

    protected Map<String, Set<Permission>> getAuthorizationInfo(final OAuthorizationCredentials credentials,
            final String targetClientId) {
        Map<String, Set<Permission>> authorizationInfo = null;

        CacheKey key = new CacheKey(targetClientId, credentials);
        if (cachingEnabled)
            authorizationInfo = cacheManager.get(key);

        if (authorizationInfo == null) {
            authorizationInfo = getAuthorizationInfoInternal(credentials, targetClientId);

            if (cachingEnabled)
                cacheManager.put(key, authorizationInfo);
        }

        return authorizationInfo;
    }

    protected Map<String, Set<Permission>> getAuthorizationInfoInternal(final OAuthorizationCredentials credentials,
            final String targetClientId) throws AccessTokenExpiredException {
        Map<String, Set<Permission>> map = new HashMap<String, Set<Permission>>();

        final String body = sendRequestForPermissions(credentials, targetClientId);
        JsonNode json = JsonHelper.getFirstNode(body);
        if (json != null) {
            JsonNode errorNode = json.get(ERROR);
            if (errorNode != null) {
                String errorMsg = errorNode.asText();
                logger.info("Error returned: {}", errorMsg);
                if (errorMsg.startsWith(EXPIRED_ACCESS_TOKEN)) {
                    String token = credentials.getAccessToken();
                    if (errorMsg.endsWith("_for_caller"))
                        token = credentials.getCallerCredentials().getAccessToken();
                    else if (errorMsg.endsWith("_for_user"))
                        if (credentials.getCallerCredentials().getCallerCredentials() == null)
                            token = credentials.getCallerCredentials().getAccessToken();
                        else
                            token = credentials.getCallerCredentials().getCallerCredentials().getAccessToken();
                    throw new AccessTokenExpiredException(token, errorMsg);
                }
            } else {
                json = json.get(ROLE_PERMISSIONS);
                if (json != null) {
                    final Iterator<JsonNode> nodes = json.iterator();
                    while (nodes.hasNext()) {
                        for (Iterator<Entry<String, JsonNode>> fields = nodes.next().fields(); fields.hasNext();) {
                            Entry<String, JsonNode> next = fields.next();

                            logger.debug("next role: {}", next.getKey());

                            final HashSet<Permission> permissionsSet = new HashSet<Permission>();
                            map.put(next.getKey(), permissionsSet);
                            Iterator<JsonNode> permIter = next.getValue().iterator();
                            while (permIter.hasNext()) {
                                json = permIter.next();
                                String permission = json.asText();
                                permissionsSet.add(permissionResolver.resolvePermission(permission));
                                logger.debug("next permission: {}", permission);
                            }
                        }
                    }
                }
            }
        }

        return map;
    }

    protected String sendRequestForPermissions(final OAuthorizationCredentials credentials,
            final String targetClientId) {
        logger.debug("accessToken : {} / permissionsUrl : {}", credentials.getAccessToken(), permissionsURL);
        final long t0 = System.currentTimeMillis();
        final ProxyOAuthRequest request = new ProxyOAuthRequest(Verb.GET, permissionsURL,
                client.getConnectTimeout(), client.getReadTimeout(), client.getProxyHost(), client.getProxyPort());

        request.addQuerystringParameter(OAuthConstants.CLIENT_ID, credentials.getClientId());
        request.addQuerystringParameter(OAuthConstants.ACCESS_TOKEN, credentials.getAccessToken());

        String userToken = credentials.getAccessToken();
        final OAuthorizationCredentials callerCredentials = credentials.getCallerCredentials();

        if (callerCredentials != null) {
            final OAuthorizationCredentials userCredentials = callerCredentials.getCallerCredentials();
            if (userCredentials != null) {
                userToken = userCredentials.getAccessToken();
                request.addQuerystringParameter(USER_CLIENT_ID, userCredentials.getClientId());

                request.addQuerystringParameter(CALLER_CLIENT_ID, callerCredentials.getClientId());
                request.addQuerystringParameter(CALLER_ACCESS_TOKEN, callerCredentials.getAccessToken());
            } else {
                userToken = callerCredentials.getAccessToken();
                request.addQuerystringParameter(USER_CLIENT_ID, callerCredentials.getClientId());
            }
        }

        request.addQuerystringParameter(USER_ACCESS_TOKEN, userToken);
        request.addQuerystringParameter(TARGET_CLIENT_ID, targetClientId);

        final Response response = request.send();
        final int code = response.getCode();
        final String body = response.getBody();
        final long t1 = System.currentTimeMillis();
        logger.debug("Request took : " + (t1 - t0) + " ms for : " + permissionsURL);
        logger.debug("response code : {} / response body : {}", code, body);
        if (code != 200) {
            logger.error("Failed to get permissions, code : " + code + " / body : " + body);
            throw new HttpCommunicationException(code, body);
        }
        return body;
    }

    @Override
    public void clearCache(PrincipalCollection principals) {
        if (cachingEnabled) {
            if (principals == null) {
                logger.debug("Clearing cache");
                cacheManager.clear();
            } else {
                final CasOAuthWrapperProfile profile = principals.oneByType(CasOAuthWrapperProfile.class);
                String accessToken = profile.getAccessToken();
                logger.debug("Clearing cache for accessToken: {} ", accessToken);
                clearCacheForToken(accessToken);
            }
        }
    }

    private void clearCacheForToken(String accessToken) {
        Set<CacheKey> keys = cacheManager.keys();
        for (CacheKey key : keys) {
            if (key.credentials.containsToken(accessToken))
                cacheManager.remove(key);
        }
    }

    private static class CacheKey {
        String targetClientId;
        OAuthorizationCredentials credentials;

        public CacheKey(String targetClientId, OAuthorizationCredentials credentials) {
            this.targetClientId = targetClientId;
            this.credentials = credentials;
        }

        @Override
        public int hashCode() {
            HashCodeBuilder builder = new HashCodeBuilder();
            builder.append(targetClientId);
            builder.append(credentials);
            return builder.build();
        }

        @Override
        public boolean equals(Object obj) {
            return EqualsBuilder.reflectionEquals(this, obj);
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }

    }

}