com.meadowhawk.homepi.service.UserRESTService.java Source code

Java tutorial

Introduction

Here is the source code for com.meadowhawk.homepi.service.UserRESTService.java

Source

/**
 *
 *
 * Copyright (c) 2012-2013 Lee Clarke
 *
 * LICENSE:
 *
 * This file is part of HomePi (http://homepi.org).
 *
 * HomePi 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 2 of the License, or (at your option) any
 * later version.
 *
 * HomePi 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 HomePi.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 *
 * @author Lee Clarke <lees2bytes[at]gmail[dot]com>
 * @license http://www.gnu.org/licenses/gpl.html
 * @copyright 2012-2013 Lee Clarke
 */
package com.meadowhawk.homepi.service;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;
import javax.ws.rs.core.UriInfo;

import org.apache.log4j.Logger;
import org.codehaus.jackson.map.ObjectMapper;
import org.restlet.data.Form;
import org.restlet.representation.Representation;
import org.restlet.resource.ClientResource;
import org.restlet.resource.ResourceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import com.meadowhawk.homepi.exception.HomePiAppException;
import com.meadowhawk.homepi.model.HomePiUser;
import com.meadowhawk.homepi.service.business.DeviceManagementService;
import com.meadowhawk.homepi.service.business.HomePiUserService;
import com.meadowhawk.homepi.util.StringUtil;
import com.meadowhawk.homepi.util.model.GoogleInfo;
import com.meadowhawk.homepi.util.model.PublicRESTDoc;
import com.meadowhawk.homepi.util.model.PublicRESTDocMethod;
import com.meadowhawk.homepi.util.service.AppConfigService;
import com.meadowhawk.homepi.util.service.ServiceUtils;
import com.sun.jersey.api.view.Viewable;

@Path("/user")
@Component
@PublicRESTDoc(serviceName = "UserRESTService", description = "User focused management services.")
public class UserRESTService {
    private static Logger log = Logger.getLogger(UserRESTService.class);

    @Context
    UriInfo uriInfo;

    @Autowired
    AppConfigService appConfigService;

    @Autowired
    HomePiUserService userService;

    @Autowired
    DeviceManagementService managementService;

    private static final String ACCESS_TOKEN = "access_token"; //First pass authorization token used to verify on second auth check. see google oAuth docs for more info
    private static final String GOOGLE_USER_INFO_LINK = "https://www.googleapis.com/oauth2/v3/userinfo?access_token=";
    private static final String GRANT_TYPE_AUTH = "authorization_code";
    private static final String AUTH_CALLBACK_URI = "https://accounts.google.com/o/oauth2/token";

    private String firstAuthUri;

    @Context
    UriInfo ui;

    //The following is set from the System Properties you know  -Dgoogle_auth_client_id
    @Value("#{systemProperties['google_auth_client_id']}")
    String google_auth_client_id;

    @Value("#{systemProperties['google_auth_client_secret']}")
    String google_auth_client_secret;

    @GET
    @Path("/profile/{user_name}")
    @Produces(MediaType.APPLICATION_JSON)
    @PublicRESTDocMethod(endPointName = "User Profile", description = "Retrieve user profile. Include access_token in head to gain owner view. Default key is user_name but if specified in queryparam then id search is supported.", sampleLinks = {
            "/user/profile/test_user", "/user/profile/1?key=id" })
    public Response getUser(@PathParam("user_name") String userKey, @HeaderParam(ACCESS_TOKEN) String authToken,
            @QueryParam("key") String queryKey) {

        if (!StringUtil.isNullOrEmpty(userKey)) {
            //get authfrom request or set to null

            HomePiUser hUser = null;
            if (StringUtil.isNullOrEmpty(queryKey)) {
                hUser = userService.getUserData(userKey, authToken);
            } else { //assume its id since no other option.
                Long userId = StringUtil.safeParseToLong(userKey);
                hUser = userService.getUserData(userId, authToken);
            }
            return Response.ok(hUser).build();
        } else {
            throw new HomePiAppException(Status.NOT_FOUND, "Invalid user ID");
        }

    }

    @POST
    @Path("/profile/{user_id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    @PublicRESTDocMethod(endPointName = "Update User Profile", description = "Update profile. Include access_token in head or will return error.", sampleLinks = {
            "/user/profile/1" })
    public Response updateUserInfo(HomePiUser updateUser, @PathParam("user_id") Long userId,
            @HeaderParam(ACCESS_TOKEN) String authToken) {
        if (StringUtil.isNullOrEmpty(authToken)) {
            authToken = updateUser.getGoogleAuthToken(); //See if token was passed in body
        }
        HomePiUser hUser = userService.updateUserData(userId, authToken, updateUser);
        return Response.ok(hUser).build();
    }

    @GET
    @Path("/googleauth")
    @PublicRESTDocMethod(endPointName = "Google Auth Request", description = "Initiates the OAuth requests for authentication with google doing redirect to google account accept page.", sampleLinks = {
            "/user/googleauth" })
    public Response makeGoogleAuthRequest(@Context HttpServletRequest request) {

        URI location;
        try {
            request.getHeaderNames();
            location = new URI(getFirstAuthReqURIui());

            return Response.temporaryRedirect(location).build();
        } catch (URISyntaxException e) {
            throw new HomePiAppException(Status.BAD_REQUEST, "Unable to authenticate.", e);
        }
    }

    @GET
    @Path("/gauthcallback")
    @Produces(MediaType.APPLICATION_JSON)
    @PublicRESTDocMethod(endPointName = "Google Auth Callback", description = "Google auth request, provides a callback for google oAuth and shouldnt be called directly elsewhere.", sampleLinks = {
            "/user/goocallback" })
    public Response googleAuthCallback(@QueryParam("state") String state, @QueryParam("code") String code,
            @QueryParam("error") String error) {
        GoogleInfo user = null;
        if (error != null) {
            throw new HomePiAppException(Status.UNAUTHORIZED);
        } else {
            Map<String, String> auth = requestSecondStageRequest(state, code);
            user = getUserInfo(auth);
        }
        HomePiUser hUser = null;
        if (user != null && !StringUtil.isNullOrEmpty(user.getEmail())) {
            hUser = userService.getUserFromGoogleAuth(user);
        } else {
            throw new HomePiAppException(Status.UNAUTHORIZED, "Auth was unauthorized or incomplete.");
        }

        try {
            Map<String, String> params = new HashMap<String, String>();
            params.put("auth_token", hUser.getGoogleAuthToken());
            params.put("user_id", "" + hUser.getUserId());
            URI dashboard = ServiceUtils.getUIResource(uriInfo, "home.html", params);

            return Response.seeOther(dashboard).build();
        } catch (UriBuilderException e) {
            log.error("Failed to create URI to html page! ", e);
            throw new HomePiAppException(Status.BAD_REQUEST, "Unable to authenticate.", e);
            //TODO Add redirect to 404 page

        } catch (URISyntaxException e) {
            log.error("Can't build URI to web resource.", e);
            throw new HomePiAppException(Status.INTERNAL_SERVER_ERROR, "Unable to create redirect.", e);
        }

    }

    /**
     * Retrieves G+ userInfo so we can figure out how they are. 
     * @param auth - initial auth response.
     * @return
     */
    protected GoogleInfo getUserInfo(Map<String, String> auth) {
        if (auth.containsKey(ACCESS_TOKEN)) {
            GoogleInfo userInfo = null;
            String accessToken = auth.get(ACCESS_TOKEN);
            ClientResource cr = new ClientResource(GOOGLE_USER_INFO_LINK + accessToken);
            Representation response = null;
            try {
                response = cr.get();
                if (cr.getStatus().isSuccess()) {
                    if (response != null) {
                        userInfo = GoogleInfo.buildGoogleInfo(response.getText());
                        userInfo.setAuth_token(accessToken);
                    }
                } else {
                    //TODO: Something has gone wrong or need to reauth? Not sure we ever get here.
                    log.debug("Secondary auth request failed: " + cr.getStatus());
                }
            } catch (ResourceException re) {
                log.warn("GAuth Error - Request error:", re);
                throw new HomePiAppException(Status.BAD_REQUEST, "Unable to retrieve user info from auth service.");
            } catch (IOException e) {
                log.warn("GAuth Error", e);
                throw new HomePiAppException(Status.BAD_REQUEST, "Unable to retrieve user info from auth service.");
            }
            return userInfo;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private Map<String, String> requestSecondStageRequest(String state, String code) {
        if (StringUtil.isNullOrEmpty(state) || StringUtil.isNullOrEmpty(code)) {
            throw new HomePiAppException(Status.PRECONDITION_FAILED, "Invalid response from google auth.");
        }

        Form form = new Form();
        form.set("code", code);
        form.set("client_id", google_auth_client_id);
        form.set("client_secret", google_auth_client_secret);
        form.set("redirect_uri", getGoogleRedirectURL(ui));
        form.set("grant_type", GRANT_TYPE_AUTH);

        ClientResource cr = new ClientResource(AUTH_CALLBACK_URI);

        Representation response = null;
        try {
            response = cr.post(form.getWebRepresentation());
        } catch (ResourceException re) {
            log.warn("GAuth Error - Request error:", re);
            throw new HomePiAppException(Status.BAD_REQUEST, "Unable to authorize with Google account.", re);
        }
        if (cr.getStatus().isSuccess()) {
            try {
                ObjectMapper om = new ObjectMapper();
                Map<String, String> resp = om.readValue(response.getText(), HashMap.class);
                return resp;
            } catch (IOException e) {
                log.warn("GAuth Error:", e);
                throw new HomePiAppException(Status.PRECONDITION_FAILED, "Unknown response from auth service.", e);
            }
        }
        return new HashMap<String, String>();
    }

    private String getGoogleRedirectURL(UriInfo ui) {
        UriBuilder ub = ui.getBaseUriBuilder().path(UserRESTService.class);
        URI gauthCallbackURI = ub.path(UserRESTService.class, "googleAuthCallback").build();

        return gauthCallbackURI.toString();
    }

    @SuppressWarnings("deprecation")
    private String getGoogleRedirectURLEncoded(UriInfo ui) {
        String callback = getGoogleRedirectURL(ui);
        return URLEncoder.encode(callback);
    }

    private String getFirstAuthReqURIui() {
        if (this.firstAuthUri == null) {
            //TODO:  Break this out so scope can be set depending on if user enables drive usage, calender scheduling etc..
            this.firstAuthUri = "https://accounts.google.com/o/oauth2/auth?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&state=%2Fprofile&redirect_uri="
                    + getGoogleRedirectURLEncoded(ui) + "&response_type=code&client_id=" + google_auth_client_id;
        }
        return this.firstAuthUri;
    }
}