org.callimachusproject.auth.FacebookAuthReader.java Source code

Java tutorial

Introduction

Here is the source code for org.callimachusproject.auth.FacebookAuthReader.java

Source

/*
 * Copyright (c) 2012, James Leigh Some rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.callimachusproject.auth;

import static org.callimachusproject.util.PercentCodec.decode;
import static org.callimachusproject.util.PercentCodec.encode;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Scanner;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.util.EntityUtils;
import org.callimachusproject.client.HttpUriClient;
import org.callimachusproject.server.exceptions.BadGateway;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FacebookAuthReader implements ParameterAuthReader {
    private static final String OAUTH_URL = "https://www.facebook.com/dialog/oauth";
    private static final String ACCESS_URL = "https://graph.facebook.com/oauth/access_token";
    private static final String ME_URL = "https://graph.facebook.com/me";

    private final Logger logger = LoggerFactory.getLogger(FacebookAuthReader.class);
    private final Map<String, AccessToken> tokens = new HashMap<String, AccessToken>();
    private final HttpUriClient client;
    private final String appId;
    private final String secret;

    public FacebookAuthReader(String self, String appId, CharSequence secret, HttpUriClient client) {
        this.client = client;
        this.appId = appId;
        this.secret = secret.toString();
    }

    @Override
    public String getLoginPage(String returnTo, boolean loggedIn, String parameters, String[] via) {
        StringBuilder sb = new StringBuilder();
        sb.append(OAUTH_URL);
        sb.append("?client_id=").append(encode(getFacebookAppId()));
        sb.append("&redirect_uri=").append(encode(returnTo));
        sb.append("&state=").append(encode(state(via)));
        sb.append("&scope=email");
        return sb.toString();
    }

    @Override
    public String getParameters(String method, String uri, String query, HttpEntity body) {
        return "redirect_uri=" + encode(uri) + '&' + query;
    }

    @Override
    public boolean isLoggingIn(String parameters) {
        return parameters.contains("state=");
    }

    @Override
    public boolean isCanncelled(String parameters) {
        return parameters.contains("error=");
    }

    @Override
    public boolean isValidParameters(String parameters, String[] via) throws IOException {
        return getAccessToken(parameters, via) != null;
    }

    @Override
    public String getUserIdentifier(String parameters) {
        AccessToken token = getAccessToken(parameters);
        if (token == null)
            return null;
        return token.getLink();
    }

    @Override
    public String getUserFullName(String parameters) {
        AccessToken token = getAccessToken(parameters);
        if (token == null)
            return null;
        return token.getName();
    }

    @Override
    public String getUserLogin(String parameters) {
        AccessToken token = getAccessToken(parameters);
        if (token == null)
            return null;
        return token.getLogin();
    }

    private String getFacebookAppId() {
        return appId;
    }

    private String getFacebookSecret() {
        return secret;
    }

    private AccessToken getAccessToken(String parameters) {
        return getAccessToken(parameters, null);
    }

    private AccessToken getAccessToken(String parameters, String[] via) {
        AccessToken token = null;
        synchronized (tokens) {
            token = tokens.get(parameters);
        }
        if (token == null && via != null) {
            token = verify(parameters, via);
        }
        if (token == null)
            return null;
        return token;
    }

    private AccessToken verify(String parameters, String[] via) {
        String code = getValueAfter(parameters, "code=");
        String error_description = getValueAfter(parameters, "error_description=");
        String state = getValueAfter(parameters, "state=");
        boolean verified = verifyState(state, via);
        if (error_description != null && error_description.length() > 0) {
            logger.warn("Facebook says: {}", error_description);
            return null;
        } else if (!verified) {
            logger.error("Invalid facebook manager state");
            return null;
        } else if (code == null) {
            logger.error("Could not login facebook user");
            return null;
        }
        long now = System.currentTimeMillis();
        synchronized (tokens) {
            Iterator<AccessToken> iter = tokens.values().iterator();
            while (iter.hasNext()) {
                AccessToken token = iter.next();
                if (token.isExpired(now)) {
                    iter.remove();
                }
            }
            if (tokens.containsKey(parameters))
                return tokens.get(parameters);
        }
        String redirect_uri = getValueAfter(parameters, "redirect_uri=");
        String url = ACCESS_URL + "?client_id=" + encode(getFacebookAppId()) + "&redirect_uri="
                + encode(redirect_uri) + "&client_secret=" + encode(getFacebookSecret()) + "&code=" + encode(code);
        try {
            HttpEntity entity = client.getEntity(url, "application/x-www-form-urlencoded");
            try {
                Scanner scanner = new Scanner(entity.getContent(), "UTF-8");
                String content = scanner.useDelimiter("\\A").next();
                AccessToken token = new AccessToken(now, content);
                synchronized (tokens) {
                    tokens.put(parameters, token);
                    return token;
                }
            } finally {
                EntityUtils.consume(entity);
            }
        } catch (Exception e) {
            logger.error(url, e);
            return null;
        }
    }

    private String state(String[] via) {
        StringBuilder sb = new StringBuilder();
        for (String v : via) {
            sb.append(v);
        }
        sb.append(secret);
        return DigestUtils.md5Hex(sb.toString());
    }

    private boolean verifyState(String decode, String[] via) {
        return decode != null && decode.equals(state(via));
    }

    private String getValueAfter(String parameters, String token) {
        if (parameters == null)
            return null;
        int idx = parameters.indexOf(token);
        if (idx < 0)
            return null;
        int start = idx + token.length();
        int end = parameters.indexOf('&', start);
        if (end < 0) {
            end = parameters.length();
        }
        return decode(parameters.substring(start, end));
    }

    private class AccessToken {
        private String access;
        private long expires;
        private JSONObject info;

        private AccessToken(long now, String access_token) {
            for (String pair : access_token.split("&")) {
                if (pair.startsWith("access_token=")) {
                    String en = pair.substring("access_token=".length());
                    access = decode(en);
                } else if (pair.startsWith("expires=")) {
                    String en = pair.substring("expires=".length());
                    long age = Long.parseLong(decode(en));
                    expires = now + age;
                }
            }
        }

        public String getAccess() {
            return access;
        }

        public String getLink() {
            try {
                return getInfo().getString("link");
            } catch (JSONException e) {
                throw new BadGateway(e);
            }
        }

        public String getName() {
            try {
                JSONObject info = getInfo();
                return info.getString("name");
            } catch (JSONException e) {
                throw new BadGateway(e);
            }
        }

        public String getLogin() {
            try {
                JSONObject info = getInfo();
                String email = info.optString("email");
                if (email != null)
                    return email;
                return info.getString("username");
            } catch (JSONException e) {
                throw new BadGateway(e);
            }
        }

        /**
         * middle_name link, locale, updated_time, id, first_name, username,
         * timezone, verified, name, last_name, gender, email (optional)
         */
        private synchronized JSONObject getInfo() {
            if (info != null)
                return info;
            try {
                String access = this.getAccess();
                String url = ME_URL + "?access_token=" + encode(access);
                InputStream in = client.getEntity(url, "application/json").getContent();
                try {
                    Reader reader = new InputStreamReader(in, "UTF-8");
                    return info = new JSONObject(new JSONTokener(reader));
                } finally {
                    in.close();
                }
            } catch (JSONException e) {
                throw new BadGateway(e);
            } catch (IOException e) {
                throw new BadGateway(e);
            }
        }

        public boolean isExpired(long now) {
            return now >= expires;
        }
    }

}