eu.eubrazilcc.lvl.storage.oauth2.security.OAuth2SecurityManager.java Source code

Java tutorial

Introduction

Here is the source code for eu.eubrazilcc.lvl.storage.oauth2.security.OAuth2SecurityManager.java

Source

/*
 * Copyright 2014 EUBrazilCC (EU?Brazil Cloud Connect)
 * 
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by 
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * 
 *   http://ec.europa.eu/idabc/eupl
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and 
 * limitations under the Licence.
 * 
 * This product combines work with different licenses. See the "NOTICE" text
 * file for details on the various modules and licenses.
 * The "NOTICE" text file is part of the distribution. Any derivative works
 * that you distribute must include a readable copy of the "NOTICE" text file.
 */

package eu.eubrazilcc.lvl.storage.oauth2.security;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Lists.newArrayList;
import static eu.eubrazilcc.lvl.core.servlet.ServletUtils.getClientAddress;
import static eu.eubrazilcc.lvl.storage.oauth2.security.OAuth2Common.AUTHORIZATION_HEADER_OAUTH2;
import static eu.eubrazilcc.lvl.storage.oauth2.security.OAuth2Common.AUTHORIZATION_QUERY_OAUTH2;
import static eu.eubrazilcc.lvl.storage.oauth2.security.OAuth2Common.HEADER_AUTHORIZATION;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
import static javax.ws.rs.core.Response.status;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static javax.ws.rs.core.Response.Status.UNAUTHORIZED;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.oltu.oauth2.common.OAuth.HeaderType.WWW_AUTHENTICATE;
import static org.apache.oltu.oauth2.common.error.OAuthError.ResourceResponse.INVALID_TOKEN;
import static org.apache.oltu.oauth2.common.message.types.ParameterStyle.HEADER;
import static org.apache.oltu.oauth2.common.message.types.ParameterStyle.QUERY;
import static org.apache.shiro.SecurityUtils.getSubject;
import static org.apache.shiro.SecurityUtils.setSecurityManager;
import static org.slf4j.LoggerFactory.getLogger;

import java.util.Collection;

import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;

import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.oltu.oauth2.common.utils.OAuthUtils;
import org.apache.oltu.oauth2.rs.request.OAuthAccessResourceRequest;
import org.apache.oltu.oauth2.rs.response.OAuthRSResponse;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;

import eu.eubrazilcc.lvl.core.servlet.OAuth2RequestWrapper;
import eu.eubrazilcc.lvl.storage.security.BaseSecurityManager;

/**
 * Extends the security manager with methods that throws Web exceptions when user authentication/authorization fails.
 * @author Erik Torres <ertorser@upv.es>
 */
public final class OAuth2SecurityManager extends BaseSecurityManager {

    private static final Logger LOGGER = getLogger(OAuth2SecurityManager.class);

    private static boolean initiated = false;

    private final String resourceName;
    private final String clientAddress;

    private OAuth2SecurityManager(final String resourceName, final String clientAddress) {
        super(getSubject());
        this.resourceName = resourceName;
        this.clientAddress = clientAddress;
    }

    private static void init() {
        // prepare Shiro security manager (including the Shiro Web extensions will do this automatically in a Servlet environment)
        final Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:lvl-shiro.ini");
        final SecurityManager securityManager = factory.getInstance();
        setSecurityManager(securityManager);
        initiated = true;
    }

    public static final OAuth2SecurityManager login(final HttpServletRequest request,
            final @Nullable MultivaluedMap<String, String> form, final @Nullable HttpHeaders headers,
            final String resourceName) {
        try {
            final String clientAddress = getClientAddress(request);
            final OAuth2SecurityManager instance = OAuth2SecurityManager.builder().build(resourceName,
                    clientAddress);
            try {
                instance.login(getOAuth2AccessToken(request, form, headers, resourceName));
            } catch (AuthenticationException ae) {
                LOGGER.error("Authentication failed", ae);
                invalidCredentialError(resourceName, clientAddress);
            }
            return instance;
        } catch (OAuthSystemException e) {
            LOGGER.error("Authorization failed", e);
            throw new WebApplicationException(status(INTERNAL_SERVER_ERROR)
                    .header("WWW-Authenticate", "Bearer realm='" + resourceName + "', error='invalid-token'")
                    .build());
        }
    }

    public OAuth2SecurityManager requiresAuthentication() {
        try {
            if (!isAuthenticated()) {
                invalidCredentialError(resourceName, clientAddress);
            }
            return this;
        } catch (OAuthSystemException e) {
            LOGGER.error("Authorization failed", e);
            throw new WebApplicationException(status(INTERNAL_SERVER_ERROR)
                    .header("WWW-Authenticate", "Bearer realm='" + resourceName + "', error='invalid-token'")
                    .build());
        }
    }

    public OAuth2SecurityManager requiresRoles(final Collection<String> roleIdentifiers) {
        try {
            if (!hasAllRoles(roleIdentifiers)) {
                invalidCredentialError(resourceName, clientAddress);
            }
            return this;
        } catch (OAuthSystemException e) {
            LOGGER.error("Authorization failed", e);
            throw new WebApplicationException(status(INTERNAL_SERVER_ERROR)
                    .header("WWW-Authenticate", "Bearer realm='" + resourceName + "', error='invalid-token'")
                    .build());
        }
    }

    public OAuth2SecurityManager requiresPermissions(final String... permissions) {
        try {
            if (!isPermittedAll(permissions)) {
                invalidCredentialError(resourceName, clientAddress);
            }
            return this;
        } catch (OAuthSystemException e) {
            LOGGER.error("Authorization failed", e);
            throw new WebApplicationException(status(INTERNAL_SERVER_ERROR)
                    .header("WWW-Authenticate", "Bearer realm='" + resourceName + "', error='invalid-token'")
                    .build());
        }
    }

    private static final String getOAuth2AccessToken(final HttpServletRequest request,
            final @Nullable MultivaluedMap<String, String> form, final @Nullable HttpHeaders headers,
            final String resourceName) throws OAuthSystemException {
        try {
            // make the OAuth request out of this request
            final OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(
                    new OAuth2RequestWrapper(request, form, headers),
                    isNotBlank(request.getParameter(AUTHORIZATION_QUERY_OAUTH2)) ? QUERY : HEADER);
            // get the access token
            return oauthRequest.getAccessToken();
        } catch (OAuthProblemException e) {
            // check if the error code has been set
            final String errorCode = e.getError();
            if (OAuthUtils.isEmpty(errorCode)) {
                // setup the OAuth error message
                final OAuthResponse oauthResponse = OAuthRSResponse.errorResponse(SC_UNAUTHORIZED)
                        .setRealm(resourceName).buildHeaderMessage();
                LOGGER.warn("Access from " + getClientAddress(request) + " is denied due to: bad request", e);
                // if no error code then throw a standard 401 unauthorized error
                throw new WebApplicationException(status(UNAUTHORIZED)
                        .header(WWW_AUTHENTICATE, oauthResponse.getHeader(WWW_AUTHENTICATE)).build());
            }
            final OAuthResponse oauthResponse = OAuthRSResponse.errorResponse(SC_UNAUTHORIZED)
                    .setRealm(resourceName).setError(e.getError()).setErrorDescription(e.getDescription())
                    .setErrorUri(e.getUri()).buildHeaderMessage();
            LOGGER.warn("Access from " + getClientAddress(request) + " is denied due to: " + e.getError() + "("
                    + e.getDescription() + ")");
            throw new WebApplicationException(status(BAD_REQUEST)
                    .header(WWW_AUTHENTICATE, oauthResponse.getHeader(WWW_AUTHENTICATE)).build());
        }
    }

    private static final void invalidCredentialError(final String resourceName, final String clientAddress)
            throws OAuthSystemException {
        // setup the OAuth error message
        final OAuthResponse oauthResponse = OAuthRSResponse.errorResponse(SC_UNAUTHORIZED).setRealm(resourceName)
                .setError(INVALID_TOKEN).buildHeaderMessage();
        LOGGER.warn("Access to resource " + resourceName + " from " + clientAddress
                + " is denied due to: invalid credentials");
        throw new NotAuthorizedException(
                status(UNAUTHORIZED).header(WWW_AUTHENTICATE, oauthResponse.getHeader(WWW_AUTHENTICATE)).build());
        /* throw new WebApplicationException(status(UNAUTHORIZED)
        .header(WWW_AUTHENTICATE, oauthResponse.getHeader(WWW_AUTHENTICATE))
        .build()) ; */
    }

    public static final String bearerHeader(final String token) {
        checkArgument(isNotBlank(token), "Uninitialized or invalid token");
        return AUTHORIZATION_HEADER_OAUTH2 + token;
    }

    public static MultivaluedMap<String, String> authzHeader(final String token) {
        checkArgument(isNotBlank(token), "Uninitialized or invalid token");
        final MultivaluedMap<String, String> map = new MultivaluedHashMap<String, String>();
        map.put(HEADER_AUTHORIZATION, newArrayList(bearerHeader(token)));
        return map;
    }

    /* Fluent API */

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {

        public OAuth2SecurityManager build(final String resourceName, final String clientAddress) {
            synchronized (OAuth2SecurityManager.class) {
                if (!initiated) {
                    init();
                }
            }
            return new OAuth2SecurityManager(resourceName, clientAddress);
        }

    }

}