/*
* 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.gatein.security.oauth.social;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.security.oauth.common.OAuthConstants;
import org.gatein.security.oauth.exception.OAuthException;
import org.gatein.security.oauth.exception.OAuthExceptionCode;
import org.gatein.security.oauth.utils.HttpResponseContext;
import org.gatein.security.oauth.utils.OAuthUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Processor to perform Facebook interaction
*
* @author Anil Saldhana
* @since Sep 22, 2011
*/
public class FacebookProcessor {
private static Logger log = LoggerFactory.getLogger(FacebookProcessor.class);
protected boolean trace = log.isTraceEnabled();
protected String clientID;
protected String clientSecret;
protected String scope;
protected String returnURL;
public FacebookProcessor(String clientID, String clientSecret, String scope, String returnURL) {
super();
this.clientID = clientID;
this.clientSecret = clientSecret;
this.scope = scope;
this.returnURL = returnURL;
}
public boolean initialInteraction(HttpServletRequest request, HttpServletResponse response, String verificationState) throws IOException {
Map<String, String> params = new HashMap<String, String>();
params.put(OAuthConstants.REDIRECT_URI_PARAMETER, returnURL);
params.put(OAuthConstants.CLIENT_ID_PARAMETER, clientID);
params.put(OAuthConstants.STATE_PARAMETER, verificationState);
if (scope != null) {
params.put(OAuthConstants.SCOPE_PARAMETER, scope);
}
String location = new StringBuilder(FacebookConstants.SERVICE_URL).append("?").append(OAuthUtils.createQueryString(params))
.toString();
if (trace)
log.trace("Redirect:" + location);
response.sendRedirect(location);
return false;
}
public String getAccessToken(HttpServletRequest request, HttpServletResponse response) throws OAuthException {
String authorizationCode = request.getParameter(OAuthConstants.CODE_PARAMETER);
if (authorizationCode == null) {
log.error("Authorization code parameter not found");
handleCodeRequestError(request, response);
return null;
}
String stateFromSession = (String)request.getSession().getAttribute(OAuthConstants.ATTRIBUTE_VERIFICATION_STATE);
String stateFromRequest = request.getParameter(OAuthConstants.STATE_PARAMETER);
if (stateFromSession == null || stateFromRequest == null || !stateFromSession.equals(stateFromRequest)) {
throw new OAuthException(OAuthExceptionCode.INVALID_STATE, "Validation of state parameter failed. stateFromSession="
+ stateFromSession + ", stateFromRequest=" + stateFromRequest);
}
String accessToken = new FacebookRequest<String>() {
@Override
protected URL createURL(String authorizationCode) throws IOException {
return sendAccessTokenRequest(authorizationCode);
}
@Override
protected String parseResponse(String httpResponse) throws JSONException {
Map<String, String> params = OAuthUtils.formUrlDecode(httpResponse);
String accessToken = params.get(OAuthConstants.ACCESS_TOKEN_PARAMETER);
String expires = params.get(FacebookConstants.EXPIRES);
if (trace)
log.trace("Access Token=" + accessToken + " :: Expires=" + expires);
return accessToken;
}
}.executeRequest(authorizationCode);
return accessToken;
}
protected URL sendAccessTokenRequest(String authorizationCode) throws IOException {
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(OAuthUtils.createQueryString(params)).toString();
if (trace)
log.trace("AccessToken Request=" + location);
return new URL(location);
}
public Set<String> getScopes(String accessToken) {
Set<String> scopes = new FacebookRequest<Set<String>>() {
@Override
protected URL createURL(String accessToken) throws IOException {
String urlString = new StringBuilder(FacebookConstants.PROFILE_ENDPOINT_URL).append("/permissions").append("?access_token=")
.append(URLEncoder.encode(accessToken, "UTF-8")).toString();
if (trace)
log.trace("Read info about available scopes:" + urlString);
return new URL(urlString);
}
@Override
protected Set<String> parseResponse(String httpResponse) throws JSONException {
JSONObject jsonObject = new JSONObject(httpResponse);
JSONArray json = jsonObject.getJSONArray("data");
if (json != null) {
jsonObject = json.optJSONObject(0);
if (jsonObject != null) {
String[] names = JSONObject.getNames(jsonObject);
if (names != null) {
Set<String> scopes = new HashSet<String>();
for (String name : names) {
scopes.add(name);
}
return scopes;
}
}
}
return new HashSet<String>();
}
}.executeRequest(accessToken);
return scopes;
}
public FacebookPrincipal getPrincipal(String accessToken) {
FacebookPrincipal facebookPrincipal = new FacebookRequest<FacebookPrincipal>() {
private String accessToken;
@Override
protected URL createURL(String accessToken) throws IOException {
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);
// Little hack but sufficient for now
this.accessToken = accessToken;
return new URL(urlString);
}
@Override
protected FacebookPrincipal parseResponse(String httpResponse) throws JSONException {
JSONObject jsonObject = new JSONObject(httpResponse);
FacebookPrincipal facebookPrincipal = new FacebookPrincipal();
facebookPrincipal.setAccessToken(accessToken);
facebookPrincipal.setId(jsonObject.getString("id"));
facebookPrincipal.setName(jsonObject.optString("name"));
facebookPrincipal.setUsername(jsonObject.optString("username"));
facebookPrincipal.setFirstName(jsonObject.optString("first_name"));
facebookPrincipal.setLastName(jsonObject.optString("last_name"));
facebookPrincipal.setGender(jsonObject.optString("gender"));
facebookPrincipal.setTimezone(jsonObject.optString("timezone"));
facebookPrincipal.setLocale(jsonObject.optString("locale"));
facebookPrincipal.setEmail(jsonObject.optString("email"));
facebookPrincipal.setJsonObject(jsonObject);
// This could happen with Facebook testing users
if (facebookPrincipal.getUsername() == null || facebookPrincipal.getUsername().length() == 0) {
facebookPrincipal.setUsername(facebookPrincipal.getId());
}
return facebookPrincipal;
}
}.executeRequest(accessToken);
return facebookPrincipal;
}
public void revokeToken(String accessToken) {
try {
String urlString = new StringBuilder(FacebookConstants.PROFILE_ENDPOINT_URL).append("/permissions?access_token=")
.append(URLEncoder.encode(accessToken, "UTF-8")).append("&method=delete").toString();
URL revokeUrl = new URL(urlString);
HttpResponseContext revokeContent = OAuthUtils.readUrlContent(revokeUrl.openConnection());
if (revokeContent.getResponseCode() != 200) {
throw new OAuthException(OAuthExceptionCode.TOKEN_REVOCATION_FAILED,
"Error when revoking token. Http response code: " + revokeContent.getResponseCode() + ", Error details: " + revokeContent.getResponse());
}
if (log.isTraceEnabled()) {
log.trace("Successfully revoked facebook accessToken " + accessToken + ", revokeContent=" + revokeContent);
}
} catch (IOException ioe) {
throw new OAuthException(OAuthExceptionCode.TOKEN_REVOCATION_FAILED, "Error when revoking token", ioe);
}
}
private void handleCodeRequestError(HttpServletRequest request, HttpServletResponse response) {
// Log all possible error parameters
StringBuilder errorBuilder = new StringBuilder();
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
if (paramName.startsWith("error")) {
errorBuilder.append(paramName + ": " + request.getParameter(paramName) + System.getProperty("line.separator"));
}
}
String errorMessage = errorBuilder.toString();
String error = request.getParameter(OAuthConstants.ERROR_PARAMETER);
if (error != null) {
if (OAuthConstants.ERROR_ACCESS_DENIED.equals(error)) {
throw new OAuthException(OAuthExceptionCode.USER_DENIED_SCOPE, errorMessage);
}
}
throw new OAuthException(OAuthExceptionCode.FACEBOOK_ERROR, errorMessage);
}
}