/* * JBoss, Home of Professional Open Source * * Copyright 2013 Red Hat, Inc. and/or its affiliates. * * 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.picketlink.social.standalone.fb; import org.apache.log4j.Logger; import org.json.JSONException; import org.json.JSONObject; import org.picketlink.social.standalone.oauth.OAuthConstants; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.Principal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; /** * Processor to perform Facebook interaction * * @author Anil Saldhana * @since Sep 22, 2011 */ public class FacebookProcessor { public static final String FB_AUTH_STATE_SESSION_ATTRIBUTE = "FB_AUTH_STATE_SESSION_ATTRIBUTE"; protected static Logger log = Logger.getLogger(FacebookProcessor.class); protected boolean trace = log.isTraceEnabled(); protected FacebookUtil util = new FacebookUtil(FacebookConstants.SERVICE_URL); public static ThreadLocal<Principal> cachedPrincipal = new ThreadLocal<Principal>(); public static ThreadLocal<List<String>> cachedRoles = new ThreadLocal<List<String>>(); public static final String EMPTY_PASSWORD = "EMPTY"; protected List<String> roles = new ArrayList<String>(); public enum STATES { AUTH, AUTHZ, FINISH } ; protected String clientID; protected String clientSecret; protected String scope; protected String returnURL; public FacebookProcessor(String clientID, String clientSecret, String scope, String returnURL, List<String> requiredRoles) { super(); this.clientID = clientID; this.clientSecret = clientSecret; this.scope = scope; this.returnURL = returnURL; this.roles.addAll(requiredRoles); } public void setRoleString(String roleStr) { if (roleStr == null) throw new RuntimeException("Role String is null in configuration"); StringTokenizer st = new StringTokenizer(roleStr, ","); while (st.hasMoreElements()) { roles.add(st.nextToken()); } } public boolean initialInteraction(HttpServletRequest request, HttpServletResponse response) throws IOException { HttpSession session = request.getSession(); Map<String, String> params = new HashMap<String, String>(); params.put(OAuthConstants.REDIRECT_URI_PARAMETER, returnURL); params.put(OAuthConstants.CLIENT_ID_PARAMETER, clientID); if (scope != null) { params.put(OAuthConstants.SCOPE_PARAMETER, scope); } String location = new StringBuilder(FacebookConstants.SERVICE_URL).append("?").append(util.createQueryString(params)) .toString(); try { session.setAttribute(FB_AUTH_STATE_SESSION_ATTRIBUTE, STATES.AUTH.name()); if (trace) log.trace("Redirect:" + location); response.sendRedirect(location); return false; } catch (IOException e) { throw new RuntimeException(e); } } public boolean handleAuthStage(HttpServletRequest request, HttpServletResponse response) { request.getSession().setAttribute(FB_AUTH_STATE_SESSION_ATTRIBUTE, STATES.AUTHZ.name()); sendAuthorizeRequest(this.returnURL, response); return false; } protected void sendAuthorizeRequest(String returnUrl, HttpServletResponse response) { String returnUri = returnUrl; Map<String, String> params = new HashMap<String, String>(); params.put(OAuthConstants.REDIRECT_URI_PARAMETER, returnUri); params.put(OAuthConstants.CLIENT_ID_PARAMETER, clientID); if (scope != null) { params.put(OAuthConstants.SCOPE_PARAMETER, scope); } String location = new StringBuilder(FacebookConstants.AUTHENTICATION_ENDPOINT_URL).append("?") .append(util.createQueryString(params)).toString(); try { response.sendRedirect(location); } catch (IOException e) { throw new RuntimeException(e); } } public Principal getPrincipal(HttpServletRequest request, HttpServletResponse response) { Principal facebookPrincipal = handleAuthenticationResponse(request, response); if (facebookPrincipal == null) return null; request.getSession().setAttribute("PRINCIPAL", facebookPrincipal); return facebookPrincipal; } public Principal handleAuthenticationResponse(HttpServletRequest request, HttpServletResponse response) { String error = request.getParameter(OAuthConstants.ERROR_PARAMETER); if (error != null) { throw new RuntimeException("error:" + error); } else { String returnUrl = returnURL; String authorizationCode = request.getParameter(OAuthConstants.CODE_PARAMETER); if (authorizationCode == null) { log.error("Authorization code parameter not found"); return null; } URLConnection connection = sendAccessTokenRequest(returnUrl, authorizationCode, response); Map<String, String> params = formUrlDecode(readUrlContent(connection)); String accessToken = params.get(OAuthConstants.ACCESS_TOKEN_PARAMETER); String expires = params.get(FacebookConstants.EXPIRES); if (trace) log.trace("Access Token=" + accessToken + " :: Expires=" + expires); if (accessToken == null) { throw new RuntimeException("No access token found"); } return readInIdentity(request, response, accessToken, returnUrl); } } protected URLConnection sendAccessTokenRequest(String returnUrl, String authorizationCode, HttpServletResponse response) { String returnUri = returnURL; Map<String, String> params = new HashMap<String, String>(); params.put(OAuthConstants.REDIRECT_URI_PARAMETER, returnUri); params.put(OAuthConstants.CLIENT_ID_PARAMETER, clientID); params.put(OAuthConstants.CLIENT_SECRET_PARAMETER, clientSecret); params.put(OAuthConstants.CODE_PARAMETER, authorizationCode); String location = new StringBuilder(FacebookConstants.ACCESS_TOKEN_ENDPOINT_URL).append("?") .append(util.createQueryString(params)).toString(); try { if (trace) log.trace("AccessToken Request=" + location); URL url = new URL(location); URLConnection connection = url.openConnection(); return connection; } catch (IOException e) { throw new RuntimeException(e); } } private Principal readInIdentity(HttpServletRequest request, HttpServletResponse response, String accessToken, String returnUrl) { FacebookPrincipal facebookPrincipal = null; try { String urlString = new StringBuilder(FacebookConstants.PROFILE_ENDPOINT_URL).append("?access_token=") .append(URLEncoder.encode(accessToken, "UTF-8")).toString(); if (trace) log.trace("Profile read:" + urlString); URL profileUrl = new URL(urlString); String profileContent = readUrlContent(profileUrl.openConnection()); JSONObject jsonObject = new JSONObject(profileContent); facebookPrincipal = new FacebookPrincipal(); facebookPrincipal.setAccessToken(accessToken); facebookPrincipal.setId(jsonObject.getString("id")); if (jsonObject.has("name")) { facebookPrincipal.setName(jsonObject.getString("name")); } if (jsonObject.has("username")) { facebookPrincipal.setUsername(jsonObject.getString("username")); } if (jsonObject.has("first_name")) { facebookPrincipal.setFirstName(jsonObject.getString("first_name")); } if (jsonObject.has("last_name")) { facebookPrincipal.setLastName(jsonObject.getString("last_name")); } if (jsonObject.has("gender")) { facebookPrincipal.setGender(jsonObject.getString("gender")); } if (jsonObject.has("timezone")) { facebookPrincipal.setTimezone(jsonObject.getString("timezone")); } if (jsonObject.has("locale")) { facebookPrincipal.setLocale(jsonObject.getString("locale")); } if (jsonObject.has("email")) { String emailString = jsonObject.getString("email"); facebookPrincipal.setName(emailString); facebookPrincipal.setEmail(emailString); } facebookPrincipal.setJsonObject(jsonObject); } catch (JSONException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } return facebookPrincipal; } private String readUrlContent(URLConnection connection) { StringBuilder result = new StringBuilder(); Reader reader = null; try { reader = new InputStreamReader(connection.getInputStream()); char[] buffer = new char[50]; int nrOfChars; while ((nrOfChars = reader.read(buffer)) != -1) { result.append(buffer, 0, nrOfChars); } } catch (IOException e) { throw new RuntimeException(e); } finally{ try{ if(reader != null){ reader.close(); } }catch(IOException ioe){ } } return result.toString(); } private Map<String, String> formUrlDecode(String encodedData) { Map<String, String> params = new HashMap<String, String>(); String[] elements = encodedData.split("&"); for (String element : elements) { String[] pair = element.split("="); if (pair.length == 2) { String paramName = pair[0]; String paramValue; try { paramValue = URLDecoder.decode(pair[1], "UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } params.put(paramName, paramValue); } else { throw new RuntimeException("Unexpected name-value pair in response: " + element); } } return params; } }