net.anthavio.vinbudin.OAuthController.java Source code

Java tutorial

Introduction

Here is the source code for net.anthavio.vinbudin.OAuthController.java

Source

/**
 * This file is part of vinbudin.
 *
 * vinbudin is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * vinbudin 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with vinbudin.  If not, see <http://www.gnu.org/licenses/>.
 */
package net.anthavio.vinbudin;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.servlet.http.HttpSession;

import net.anthavio.aspect.Logged;
import net.anthavio.httl.HttlResponse;
import net.anthavio.httl.HttlResponseExtractor;
import net.anthavio.httl.HttlResponseExtractor.ExtractedResponse;
import net.anthavio.httl.HttlSender;
import net.anthavio.httl.auth.OAuth2;
import net.anthavio.httl.auth.OAuth2Builder;
import net.anthavio.httl.auth.OAuthTokenResponse;
import net.anthavio.httl.util.HttlUtil;
import net.anthavio.vinbudin.vui.ChatUI;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 
 * @author martin.vanek
 *
 */
@Controller
@RequestMapping("/oauth")
public class OAuthController {

    public static enum OAuthProvider {
        GOOGLE, FACEBOOK, GITHUB, LINKEDIN, DISQUS;//, BITLY, WOT;

        private OAuth2 oauth;

        private String scopes;

        public static OAuthProvider getByName(String provider) {
            provider = provider.toLowerCase();
            OAuthProvider[] values = values();
            for (OAuthProvider value : values) {
                if (value.name().toLowerCase().equals(provider)) {
                    return value;
                }
            }
            throw new IllegalArgumentException("No provider: " + provider + " Available: " + Arrays.asList(values));
        }

        public OAuth2 getOAuth() {
            return oauth;
        }

        public String getScopes() {
            return scopes;
        }

        private void setOAuth(OAuth2 oauth, String scopes) {
            if (oauth == null) {
                throw new IllegalArgumentException("Null OAuth2");
            } else if (this.oauth != null) {
                throw new IllegalStateException("OAuth2 already set for " + name());
            }
            this.oauth = oauth;
            if (scopes == null) {
                throw new IllegalArgumentException("Null scopes");
            }
            this.scopes = scopes;
        }
    }

    private ChatService service;

    @Autowired
    public OAuthController(ChatService service) {
        this.service = service;
        Properties properties = load("oauth.properties");
        String redirectUri = properties.getProperty("oauth.redirect_uri");
        OAuthProvider.GOOGLE.setOAuth(buildGoogle(properties, redirectUri), "openid profile");
        OAuthProvider.FACEBOOK.setOAuth(buildFacebook(properties, redirectUri), "public_profile");
        OAuthProvider.LINKEDIN.setOAuth(buildLinkedIn(properties, redirectUri), "r_basicprofile");
        OAuthProvider.GITHUB.setOAuth(buildGithub(properties, redirectUri), "");
        OAuthProvider.DISQUS.setOAuth(buildDisqus(properties, redirectUri), "read");
        //OAuthProvider.BITLY.setOAuth(buildBitly(properties, redirectUri), "");
        //OAuthProvider.WOT.setOAuth(buildWot(properties, redirectUri), "");
    }

    @RequestMapping("authorize/{provider}")
    public String authorize(@PathVariable(value = "provider") String provider) {
        OAuthProvider op = OAuthProvider.getByName(provider);
        String url = op.getOAuth().getAuthorizationUrl(op.getScopes(), String.valueOf(System.currentTimeMillis()));
        return "redirect:" + url;
    }

    @ResponseBody
    @RequestMapping(value = "callback/wot", params = "status=error")
    public String wotErrorCallback(@RequestParam(value = "status") String status,
            @RequestParam(value = "message") String message, @RequestParam(value = "code") String code) {
        if ("AUTH_CANCEL".equals(message)) {
            return oauthErrorCallback("wot", "access_denied", null);
        } else {
            return oauthErrorCallback("wot", code, message);
        }
    }

    @ResponseBody
    @RequestMapping(value = "callback/wot", params = "status=ok")
    public String wotSuccessCallback(@RequestParam(value = "status") String status,
            @RequestParam(value = "access_token") String access_token,
            @RequestParam(value = "nickname") String nickname,
            @RequestParam(value = "account_id") String account_id,
            @RequestParam(value = "expires_at") String expires_at) {
        return "Hello " + nickname;
    }

    /**
     * When user denies or on any error Bitly does not bother with returning anything...
     */
    @ResponseBody
    @RequestMapping(value = "callback/bitly")
    public String bitlyErrorCallback() {
        return oauthErrorCallback("bitly", "access_denied", "bitly_says_no");
    }

    @ResponseBody
    @RequestMapping(value = "callback/bitly", params = "code")
    public String bitlyCodeCallback(@RequestParam(value = "code") String code) {
        OAuthProvider op = OAuthProvider.getByName("bitly");
        Map<String, String> response = op.getOAuth().access(code)
                .get(new XFormEncodedExtractor("application/x-www-form-urlencoded"));
        String access_token = response.get("access_token");
        response.get("login");
        return access_token;
    }

    @Logged
    @RequestMapping(value = "callback/{provider}", params = "error")
    public String oauthErrorCallback(@PathVariable(value = "provider") String provider,
            @RequestParam(value = "error") String error,
            @RequestParam(value = "error_description", required = false) String error_description) {
        /*
        if (error_description != null) {
           return error + ": " + error_description;
        } else {
           return error;
        }
        */
        //When user clicks Deny -> error=access_denied
        //return service.oauthErrorCallback(error);
        // Don't care much...
        return "redirect:/ui?login_error=" + error;
    }

    @Logged
    @RequestMapping(value = "callback/{provider}", params = "code")
    public String oauthCodeCallback(@PathVariable(value = "provider") String provider,
            @RequestParam(value = "code") String code, HttpSession session) {

        OAuthProvider p = OAuthProvider.getByName(provider);
        OAuthTokenResponse tokenResponse;

        String name;
        switch (p) {
        case FACEBOOK:
            name = facebook(code);
            break;
        case GOOGLE:
            tokenResponse = p.getOAuth().access(code).get();
            name = google(tokenResponse.getAccess_token());
            break;
        case GITHUB:
            tokenResponse = p.getOAuth().access(code).get();
            name = github(tokenResponse.getAccess_token());
            break;
        case LINKEDIN:
            tokenResponse = p.getOAuth().access(code).get();
            name = linkedin(tokenResponse.getAccess_token());
            break;
        case DISQUS:
            Map mapOfToken = p.getOAuth().access(code).get(Map.class);
            name = (String) mapOfToken.get("username");
            break;
        default:
            throw new IllegalStateException("Unknown " + p);
        }
        ChatMan me = new ChatMan(name, p);
        session.setAttribute(ChatUI.ME_KEY, me);
        //Say hello...
        ChatMessage message = new ChatMessage(me, "Logged in...");
        service.addMessage(message);
        //Go back to Vaddin UI
        return "redirect:/ui";
    }

    private String facebook(String code) {
        //https://developers.facebook.com/docs/graph-api/reference/v2.1/user

        Map<String, String> mapOfToken = OAuthProvider.FACEBOOK.getOAuth().access(code)
                .get(new XFormEncodedExtractor("text/plain"));
        String access_token = mapOfToken.get("access_token");

        HttlSender sender = HttlSender.url("https://graph.facebook.com").build();
        ExtractedResponse<Map> response = sender.GET("/me").header("Authorization", "Bearer " + access_token)
                .extract(Map.class);
        Map map = response.getBody();
        return (String) map.get("name");
    }

    private String linkedin(String access_token) {
        //https://developer.linkedin.com/documents/profile-api
        HttlSender sender = HttlSender.url("https://api.linkedin.com").build();
        ExtractedResponse<Map> response = sender.GET("/v1/people/~")
                .header("Authorization", "Bearer " + access_token).header("x-li-format", "json").extract(Map.class);
        Map map = response.getBody();
        //System.out.println(map);
        return (String) map.get("firstName") + " " + map.get("lastName");

    }

    private String google(String access_token) {
        HttlSender sender = HttlSender.url("https://www.googleapis.com").build();
        ExtractedResponse<Map> response = sender.GET("/plus/v1/people/me")
                .header("Authorization", "Bearer " + access_token).extract(Map.class);
        Map map = response.getBody();
        return (String) map.get("displayName");
    }

    private String github(String access_token) {
        HttlSender sender = HttlSender.url("https://api.github.com").build();
        ExtractedResponse<Map> response = sender.GET("/user").header("Authorization", "token " + access_token)
                .extract(Map.class);
        Map map = response.getBody();
        return (String) map.get("login");
    }

    static class XFormEncodedExtractor implements HttlResponseExtractor<Map<String, String>> {

        private final String mediaType;

        public XFormEncodedExtractor(String mediaType) {
            this.mediaType = mediaType;
        }

        @Override
        public Map<String, String> extract(HttlResponse response) throws IOException {
            int code = response.getHttpStatusCode();
            if (code < 200 || code > 299) {
                throw new IllegalArgumentException("Unexpected status code " + response);
            }
            if (!mediaType.equals(response.getMediaType())) {
                throw new IllegalArgumentException("Unexpected media type " + response);
            }
            Map<String, String> map = new HashMap<String, String>();
            String line = HttlUtil.readAsString(response);
            String[] pairs = line.split("\\&");
            for (int i = 0; i < pairs.length; i++) {
                String[] fields = pairs[i].split("=");
                String name = URLDecoder.decode(fields[0], response.getEncoding());
                String value = URLDecoder.decode(fields[1], response.getEncoding());
                map.put(name, value);
            }
            return map;
        }

    }

    private OAuth2 buildDisqus(Properties properties, String redirectUri) {
        HttlSender sender = HttlSender.url("https://disqus.com").httpClient4().sender().build();

        String clientId = properties.getProperty("disqus.client_id");
        String clientSecret = properties.getProperty("disqus.client_secret");
        redirectUri = redirectUri.replace("{provider}", "disqus");

        OAuth2 oauth = new OAuth2Builder().setClientId(clientId).setClientSecret(clientSecret)
                .setTokenEndpoint(sender, "/api/oauth/2.0/access_token/")
                .setAuthorizationUrl("/api/oauth/2.0/authorize/").setRedirectUri(redirectUri).build();
        return oauth;
    }

    private OAuth2 buildGithub(Properties properties, String redirectUri) {
        HttlSender sender = HttlSender.url("https://github.com").httpClient4().sender()
                .addHeader("Accept", "application/json").build();

        String clientId = properties.getProperty("github.client_id");
        String clientSecret = properties.getProperty("github.client_secret");
        redirectUri = redirectUri.replace("{provider}", "github");

        OAuth2 oauth = new OAuth2Builder().setClientId(clientId).setClientSecret(clientSecret)
                .setTokenEndpoint(sender, "/login/oauth/access_token").setAuthorizationUrl("/login/oauth/authorize")
                .setRedirectUri(redirectUri).build();
        return oauth;
    }

    private OAuth2 buildGoogle(Properties properties, String redirectUri) {
        HttlSender sender = HttlSender.url("https://accounts.google.com").httpClient4().sender().build();

        String clientId = properties.getProperty("google.client_id");
        String clientSecret = properties.getProperty("google.client_secret");
        redirectUri = redirectUri.replace("{provider}", "google");

        OAuth2 oauth = new OAuth2Builder().setClientId(clientId).setClientSecret(clientSecret)
                .setTokenEndpoint(sender, "/o/oauth2/token").setAuthorizationUrl("/o/oauth2/auth")
                .setRedirectUri(redirectUri).build();
        return oauth;
    }

    private OAuth2 buildLinkedIn(Properties properties, String redirectUri) {
        HttlSender sender = HttlSender.url("https://www.linkedin.com").httpClient4().sender().build();

        String clientId = properties.getProperty("linkedin.client_id");
        String clientSecret = properties.getProperty("linkedin.client_secret");
        redirectUri = redirectUri.replace("{provider}", "linkedin");

        OAuth2 oauth = new OAuth2Builder().setClientId(clientId).setClientSecret(clientSecret)
                .setTokenEndpoint(sender, "/uas/oauth2/accessToken")
                .setAuthorizationUrl("/uas/oauth2/authorization").setRedirectUri(redirectUri).build();
        return oauth;
    }

    /**
     * https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow/v2.1
     */
    private OAuth2 buildFacebook(Properties properties, String redirectUri) {
        HttlSender sender = HttlSender.url("https://graph.facebook.com").httpClient4().sender().build();

        String clientId = properties.getProperty("facebook.client_id");
        String clientSecret = properties.getProperty("facebook.client_secret");
        redirectUri = redirectUri.replace("{provider}", "facebook");

        OAuth2 oauth = new OAuth2Builder().setClientId(clientId).setClientSecret(clientSecret)
                .setAuthParam("display", "popup").setTokenEndpoint(sender, "/oauth/access_token")
                .setAuthorizationUrl("https://www.facebook.com/dialog/oauth").setRedirectUri(redirectUri).build();
        return oauth;
    }

    private OAuth2 buildBitly(Properties properties, String redirectUri) {
        HttlSender sender = HttlSender.url("https://api-ssl.bitly.com").httpClient4().sender().build();

        String clientId = properties.getProperty("bitly.client_id");
        String clientSecret = properties.getProperty("bitly.client_secret");
        redirectUri = redirectUri.replace("{provider}", "bitly");

        OAuth2 oauth = new OAuth2Builder().setClientId(clientId).setClientSecret(clientSecret)
                .setTokenEndpoint(sender, "/oauth/access_token")
                .setAuthorizationUrl("https://bitly.com/oauth/authorize").setRedirectUri(redirectUri).build();
        return oauth;
    }

    private OAuth2 buildWot(Properties properties, String redirectUri) {

        String application_id = properties.getProperty("wot.application_id");
        redirectUri = redirectUri.replace("{provider}", "wot");

        OAuth2 oauth = new OAuth2Builder().setStrict(false)
                .setAuthorizationUrl("https://api.worldoftanks.eu/wot/auth/login/")
                .setAuthParam("application_id", application_id).setRedirectUri(redirectUri).build();

        return oauth;
    }

    private Properties load(String name) {
        String property = System.getProperty(name, name);

        try {
            InputStream stream;
            File file = new File(property);
            if (file.exists()) {
                stream = new FileInputStream(file);
            } else {
                ClassLoader loader = Thread.currentThread().getContextClassLoader();
                stream = loader.getResourceAsStream(property);
                if (stream == null) {
                    throw new IllegalArgumentException("Properties resource not found: " + property);
                }
            }

            Properties properties = new Properties();
            properties.load(stream);
            return properties;
        } catch (IOException iox) {
            throw new IllegalStateException("Properties resource " + property + " failed to load", iox);
        }
    }
}