Skip to content

Commit

Permalink
[#816] OAuth2 error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
erwan committed May 9, 2011
1 parent 8e5ac58 commit c9a90ed
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 14 deletions.
9 changes: 6 additions & 3 deletions documentation/manual/libs.textile
Expand Up @@ -190,11 +190,14 @@ To connect to a service, you need the create a OAuth2 instance using the followi
bc. public static void auth() {
// FACEBOOK is a OAuth2 object
if (OAuth2.isCodeResponse()) {
String access_token = FACEBOOK.getAccessToken();
// Save access_token, you will need it to request the service
OAuth2.Response response = FACEBOOK.retrieveAccessToken(authUrl); // authUrl must be the same as the retrieveVerificationCode call
String accessToken = response.accessToken; // null if an error occurred
OAuth2.Error = response.error; // null if the call was a success
// Save accessToken, you will need it to request the service
index();
}
FACEBOOK.requestAccessToken(); // This will trigger a redirect
// authUrl is a String containing an absolute URL where the service should redirect the user back
FACEBOOK.requestVerificationCode(authUrl); // This will trigger a redirect
}

Once you have the access token associated to the current user, you can use it to query the service on behalf of the user:
Expand Down
107 changes: 100 additions & 7 deletions framework/src/play/libs/OAuth2.java
Expand Up @@ -7,6 +7,10 @@
import play.mvc.Scope.Params;
import play.mvc.results.Redirect;

import play.libs.WS.HttpResponse;

import com.google.gson.JsonObject;

/**
* Library to access ressources protected by OAuth 2.0. For OAuth 1.0a, see play.libs.OAuth.
* See the facebook-oauth2 example for usage.
Expand All @@ -33,23 +37,112 @@ public static boolean isCodeResponse() {
return Params.current().get("code") != null;
}

public void requestAccessToken() {
String callbackURL = Request.current().getBase() + Request.current().url;
throw new Redirect(accessTokenURL
/**
* First step of the OAuth2 process: redirects the user to the authorization page
*
* @param callbackURL
*/
public void retrieveVerificationCode(String callbackURL) {
throw new Redirect(authorizationURL
+ "?client_id=" + clientid
+ "&redirect_uri=" + callbackURL);
}

public String getAccessToken() {
String callbackURL = Request.current().getBase() + Request.current().url;
public void retrieveVerificationCode() {
retrieveVerificationCode(Request.current().getBase() + Request.current().url);
}

public Response retrieveAccessToken(String callbackURL) {
String accessCode = Params.current().get("code");
Map<String, Object> params = new HashMap<String, Object>();
params.put("client_id", clientid);
params.put("client_secret", secret);
params.put("redirect_uri", callbackURL);
params.put("code", accessCode);
Map<String, String> response = WS.url(authorizationURL).params(params).get().getQueryString();
return response.get("access_token");
HttpResponse response = WS.url(accessTokenURL).params(params).get();
return new Response(response);
}

public Response retrieveAccessToken() {
return retrieveAccessToken(Request.current().getBase() + Request.current().url);
}

/**
* @deprecated Use @{link play.libs.OAuth2.retrieveVerificationCode()} instead
*/
@Deprecated
public void requestAccessToken() {
retrieveVerificationCode();
}

/**
* @deprecated Use @{link play.libs.OAuth2.retrieveAccessToken()} instead
*/
@Deprecated
public String getAccessToken() {
return retrieveAccessToken().accessToken;
}

public static class Response {
public final String accessToken;
public final Error error;
public final WS.HttpResponse httpResponse;
private Response(String accessToken, Error error, WS.HttpResponse response) {
this.accessToken = accessToken;
this.error = error;
this.httpResponse = response;
}
public Response(WS.HttpResponse response) {
this.httpResponse = response;
Map<String, String> querystring = response.getQueryString();
if (querystring.containsKey("access_token")) {
this.accessToken = querystring.get("access_token");
this.error = null;
} else {
this.accessToken = null;
this.error = Error.oauth2(response);
}
}
public static Response error(Error error, WS.HttpResponse response) {
return new Response(null, error, response);
}
}

public static class Error {
public final Type type;
public final String error;
public final String description;
public enum Type {
COMMUNICATION,
OAUTH,
UNKNOWN
}
private Error(Type type, String error, String description) {
this.type = type;
this.error = error;
this.description = description;
}
static Error communication() {
return new Error(Type.COMMUNICATION, null, null);
}
static Error oauth2(WS.HttpResponse response) {
if (response.getQueryString().containsKey("error")) {
Map<String, String> qs = response.getQueryString();
return new Error(Type.OAUTH,
qs.get("error"),
qs.get("error_description"));
} else if (response.getContentType().startsWith("text/javascript")) { // Stupid Facebook returns JSON with the wrong encoding
JsonObject jsonResponse = response.getJson().getAsJsonObject().getAsJsonObject("error");
return new Error(Type.OAUTH,
jsonResponse.getAsJsonPrimitive("type").getAsString(),
jsonResponse.getAsJsonPrimitive("message").getAsString());
} else {
return new Error(Type.UNKNOWN, null, null);
}
}
@Override public String toString() {
return "OAuth2 Error: " + type + " - " + error + " (" + description + ")";
}
}

}
8 changes: 8 additions & 0 deletions framework/src/play/libs/WS.java
Expand Up @@ -23,6 +23,7 @@
import play.libs.OAuth.ServiceInfo;
import play.libs.ws.WSAsync;
import play.libs.ws.WSUrlFetch;
import play.mvc.Http;
import play.mvc.Http.Header;
import play.utils.NoOpEntityResolver;

Expand Down Expand Up @@ -434,6 +435,13 @@ public static abstract class HttpResponse {
*/
public abstract Integer getStatus();

/**
* @return true if the status code is 20x, false otherwise
*/
public boolean success() {
return Http.StatusCode.success(this.getStatus());
}

/**
* The http response content type
* @return the content type of the http response
Expand Down
18 changes: 14 additions & 4 deletions samples-and-tests/facebook-oauth2/app/controllers/Application.java
Expand Up @@ -11,9 +11,14 @@

public class Application extends Controller {

// The following keys correspond to a test application
// registered on Facebook, and associated with the loisant.org domain.
// You need to bind loisant.org to your machine with /etc/hosts to
// test the application locally.

public static OAuth2 FACEBOOK = new OAuth2(
"https://graph.facebook.com/oauth/access_token",
"https://graph.facebook.com/oauth/authorize",
"https://graph.facebook.com/oauth/access_token",
"95341411595",
"8eff1b488da7fe3426f9ecaf8de1ba54"
);
Expand All @@ -22,19 +27,20 @@ public static void index() {
User u = connected();
JsonObject me = null;
if (u != null && u.access_token != null) {
me = WS.url("https://graph.facebook.com/me?access_token=%s", u.access_token).get().getJson().getAsJsonObject();
me = WS.url("https://graph.facebook.com/me?access_token=%s", WS.encode(u.access_token)).get().getJson().getAsJsonObject();
}
render(me);
}

public static void auth() {
if (OAuth2.isCodeResponse()) {
User u = connected();
u.access_token = FACEBOOK.getAccessToken();
OAuth2.Response response = FACEBOOK.retrieveAccessToken(authURL());
u.access_token = response.accessToken;
u.save();
index();
}
FACEBOOK.requestAccessToken();
FACEBOOK.retrieveVerificationCode(authURL());
}

@Before
Expand All @@ -51,6 +57,10 @@ static void setuser() {
renderArgs.put("user", user);
}

static String authURL() {
return play.mvc.Router.getFullUrl("Application.auth");
}

static User connected() {
return (User)renderArgs.get("user");
}
Expand Down

0 comments on commit c9a90ed

Please sign in to comment.