RestSecurityInterceptor.java

package net.bryansaunders.jee6divelog.security.interceptor;

/*
 * #%L
 * BSNet-DiveLog
 * $Id:$
 * $HeadURL:$
 * %%
 * Copyright (C) 2012 Bryan Saunders
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public 
 * License along with this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #L%
 */

import java.util.Date;
import java.util.List;

import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;

import net.bryansaunders.jee6divelog.model.UserAccount;
import net.bryansaunders.jee6divelog.security.Credentials;
import net.bryansaunders.jee6divelog.security.Identity;
import net.bryansaunders.jee6divelog.service.UserAccountService;
import net.bryansaunders.jee6divelog.service.rest.RestApi;
import net.bryansaunders.jee6divelog.util.SecurityUtils;

import org.jboss.resteasy.annotations.interception.Precedence;
import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.ResourceMethod;
import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.spi.Failure;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.interception.PreProcessInterceptor;
import org.jboss.resteasy.util.HttpResponseCodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Security Intercepter for REST API Calls.
 * 
 * @author Bryan Saunders <btsaunde@gmail.com>
 * 
 */
@ServerInterceptor
@Precedence("SECURITY")
public class RestSecurityInterceptor implements PreProcessInterceptor {

    /**
     * Logger.
     */
    private final Logger logger = LoggerFactory.getLogger(RestSecurityInterceptor.class);

    /**
     * Identity for current Session.
     */
    @Inject
    private Identity identity;

    /**
     * User Account Service.
     */
    @Inject
    private UserAccountService userAccountService;

    /**
     * Called before any REST service calls are processed.
     * 
     * @param httpRequest
     *            Initial HTTP Request
     * @param resourceMethod
     *            Resource Method being Called
     * @return Response for the Server, Null if the API call should proceed
     * @throws Failure
     *             Thrown on Failure
     * @throws WebApplicationException
     *             Thrown on Error
     */
    @Override
    public ServerResponse preProcess(final HttpRequest httpRequest, final ResourceMethod resourceMethod)
            throws Failure, WebApplicationException {
        final String requestUrl = httpRequest.getUri().getAbsolutePath().toString();
        ServerResponse response = null;
        String publicApiKey = null;
        String requestSignature = null;

        this.logger.info("REST API Called: " + requestUrl);

        final boolean isSecured = SecurityUtils.isMethodSecure(resourceMethod);

        if (isSecured) {
            this.logger.debug("Request requires Authentication");

            publicApiKey = this.getUserDefinedHeader(httpRequest, RestApi.PUBLIC_KEY_HEADER);
            requestSignature = this.getUserDefinedHeader(httpRequest, RestApi.SIGNATURE_HEADER);

            this.logger.debug("Using Headers: apiKey:" + publicApiKey + " | signature:" + requestSignature);

            // Get UserAccount by Username
            if (publicApiKey != null && requestSignature != null) {
                this.logger.debug("REST Request Headers Valid");
                final UserAccount example = new UserAccount();
                example.setPublicApiKey(publicApiKey);
                
                final List<UserAccount> userAccounts = this.userAccountService.findByExample(example);

                if (userAccounts != null && userAccounts.size() == 1) {
                    this.logger.debug("User Found For Key: " + publicApiKey);
                    UserAccount userAccount = userAccounts.get(0);
                    
                    // Check Expiration Date
                    final Date expirationDate = userAccount.getApiKeyExpiration();

                    if (expirationDate != null && System.currentTimeMillis() < expirationDate.getTime()) {
                        this.logger.debug("API Key Not Expired");
                        
                        // Generate Token
                        final String verb = httpRequest.getHttpMethod();
                        final String contentType = this.getUserDefinedHeader(httpRequest, HttpHeaders.CONTENT_TYPE);
                        final String contentMd5 = this.getUserDefinedHeader(httpRequest, RestApi.CONTENT_MD5_HEADER);
                        final String date = this.getUserDefinedHeader(httpRequest, HttpHeaders.DATE);
                        final String privateApiKey = userAccount.getPrivateApiKey();
                        final String expectedSignature = SecurityUtils.generateRestSignature(verb, contentType,
                                contentMd5, date, requestUrl, privateApiKey);

                        // Check Tokens
                        if (expectedSignature.equals(requestSignature)) {
                            this.logger.debug("Request Signature Valid");

                            // Set Identity
                            this.identity.setPublicApiKey(publicApiKey);
                            this.identity.setPrivateApiKey(privateApiKey);
                            this.identity.setApiKeyExpiration(expirationDate);
                            this.identity.setPermissions(userAccount.getPermissions());
                            this.identity.setRoles(userAccount.getRoles());
                            this.identity.setStatus(Identity.LOGGED_IN);

                            final Credentials credentials = new Credentials();
                            credentials.setUsername(userAccount.getEmail());
                            credentials.setPassword(userAccount.getPassword());
                            this.identity.setCredentials(credentials);

                            // Return null
                            response = null;

                        } else {
                            response = this.buildResponse(HttpResponseCodes.SC_UNAUTHORIZED, "Invalid Signature");
                        }
                    } else {
                        response = this.buildResponse(HttpResponseCodes.SC_UNAUTHORIZED, "Key Expired");
                    }
                } else {
                    response = this.buildResponse(HttpResponseCodes.SC_UNAUTHORIZED, "Invalid User");
                }
            } else {
                response = this.buildResponse(HttpResponseCodes.SC_BAD_REQUEST, "Missing Headers");
            }
        }

        return response;
    }

    /**
     * Build a ServerResponse Message.
     * 
     * @param statusCode
     *            Response Status
     * @param message
     *            Response Message
     * @return ServerResponse
     */
    private ServerResponse buildResponse(final int statusCode, final String message) {
        this.logger.info("Creating REST Response: " + statusCode + " - " + message);
        final ServerResponse response = new ServerResponse();
        response.setStatus(statusCode);
        response.setEntity(message);
        return response;
    }

    /**
     * Gets a User Defined Header.
     * 
     * @param httpRequest
     *            HTTP Request with Headers
     * @param headerName
     *            Name of User Defined Header
     * @return Header Value if Found, null if not found
     */
    private String getUserDefinedHeader(final HttpRequest httpRequest, final String headerName) {
        String headerValue = null;
        final HttpHeaders headers = httpRequest.getHttpHeaders();

        final List<String> headerList = headers.getRequestHeader(headerName);
        if (headerList != null && !headerList.isEmpty()) {
            headerValue = headerList.get(0);
        }

        return headerValue;
    }

}