зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1117829 - Add Firefox Account-backed oauth client. r=rnewman
The oauth client exchanges Firefox Account assertions for oauth token grants. The client_id is assumed to have the "canGrant" capability on the oauth endpoint. ========d1a25c8233
Author: Nick Alexander <nalexander@mozilla.com> Bug 1117829 - Part 3: Add FxA oauth and profile clients. ========6c52ce9b53
Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Aug 18 13:53:56 2014 -0700 Bug 1117829 - Part 2: Support remote verifier v1 and v2. ========679e972d2c
Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Aug 18 11:52:45 2014 -0700 Bug 1117829 - Part 1: Generalize bearer token auth header providers. ========b55a14fe88
Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Aug 18 13:54:46 2014 -0700 Bug 1117829 - Pre: Add static methods for cross-class testing. ========5576662dd3
Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Aug 18 11:42:45 2014 -0700 Bug 1117829 - Pre: Fix debug printing of JWT structures.
This commit is contained in:
Родитель
a8a7dd9d99
Коммит
ac56d2a2ca
|
@ -798,7 +798,12 @@ sync_java_files = [
|
|||
'background/fxa/FxAccountClientException.java',
|
||||
'background/fxa/FxAccountRemoteError.java',
|
||||
'background/fxa/FxAccountUtils.java',
|
||||
'background/fxa/oauth/FxAccountAbstractClient.java',
|
||||
'background/fxa/oauth/FxAccountAbstractClientException.java',
|
||||
'background/fxa/oauth/FxAccountOAuthClient10.java',
|
||||
'background/fxa/oauth/FxAccountOAuthRemoteError.java',
|
||||
'background/fxa/PasswordStretcher.java',
|
||||
'background/fxa/profile/FxAccountProfileClient10.java',
|
||||
'background/fxa/QuickPasswordStretcher.java',
|
||||
'background/fxa/SkewHandler.java',
|
||||
'background/healthreport/AndroidConfigurationProvider.java',
|
||||
|
@ -835,7 +840,9 @@ sync_java_files = [
|
|||
'browserid/MockMyIDTokenFactory.java',
|
||||
'browserid/RSACryptoImplementation.java',
|
||||
'browserid/SigningPrivateKey.java',
|
||||
'browserid/verifier/BrowserIDRemoteVerifierClient.java',
|
||||
'browserid/verifier/AbstractBrowserIDRemoteVerifierClient.java',
|
||||
'browserid/verifier/BrowserIDRemoteVerifierClient10.java',
|
||||
'browserid/verifier/BrowserIDRemoteVerifierClient20.java',
|
||||
'browserid/verifier/BrowserIDVerifierClient.java',
|
||||
'browserid/verifier/BrowserIDVerifierDelegate.java',
|
||||
'browserid/verifier/BrowserIDVerifierException.java',
|
||||
|
@ -962,10 +969,12 @@ sync_java_files = [
|
|||
'sync/middleware/MiddlewareRepository.java',
|
||||
'sync/middleware/MiddlewareRepositorySession.java',
|
||||
'sync/MigrationSentinelSyncStage.java',
|
||||
'sync/net/AbstractBearerTokenAuthHeaderProvider.java',
|
||||
'sync/net/AuthHeaderProvider.java',
|
||||
'sync/net/BaseResource.java',
|
||||
'sync/net/BaseResourceDelegate.java',
|
||||
'sync/net/BasicAuthHeaderProvider.java',
|
||||
'sync/net/BearerAuthHeaderProvider.java',
|
||||
'sync/net/BrowserIDAuthHeaderProvider.java',
|
||||
'sync/net/ConnectionMonitorThread.java',
|
||||
'sync/net/HandleProgressException.java',
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa.oauth;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.FxAccountClientException;
|
||||
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientMalformedResponseException;
|
||||
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
|
||||
import org.mozilla.gecko.sync.net.Resource;
|
||||
import org.mozilla.gecko.sync.net.SyncResponse;
|
||||
import org.mozilla.gecko.sync.net.SyncStorageResponse;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpEntity;
|
||||
import ch.boye.httpclientandroidlib.HttpHeaders;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
|
||||
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
||||
|
||||
public abstract class FxAccountAbstractClient {
|
||||
protected static final String LOG_TAG = FxAccountAbstractClient.class.getSimpleName();
|
||||
|
||||
protected static final String ACCEPT_HEADER = "application/json;charset=utf-8";
|
||||
protected static final String AUTHORIZATION_RESPONSE_TYPE = "token";
|
||||
|
||||
public static final String JSON_KEY_ERROR = "error";
|
||||
public static final String JSON_KEY_MESSAGE = "message";
|
||||
public static final String JSON_KEY_CODE = "code";
|
||||
public static final String JSON_KEY_ERRNO = "errno";
|
||||
|
||||
protected static final String[] requiredErrorStringFields = { JSON_KEY_ERROR, JSON_KEY_MESSAGE };
|
||||
protected static final String[] requiredErrorLongFields = { JSON_KEY_CODE, JSON_KEY_ERRNO };
|
||||
|
||||
/**
|
||||
* The server's URI.
|
||||
* <p>
|
||||
* We assume throughout that this ends with a trailing slash (and guarantee as
|
||||
* much in the constructor).
|
||||
*/
|
||||
protected final String serverURI;
|
||||
|
||||
protected final Executor executor;
|
||||
|
||||
public FxAccountAbstractClient(String serverURI, Executor executor) {
|
||||
if (serverURI == null) {
|
||||
throw new IllegalArgumentException("Must provide a server URI.");
|
||||
}
|
||||
if (executor == null) {
|
||||
throw new IllegalArgumentException("Must provide a non-null executor.");
|
||||
}
|
||||
this.serverURI = serverURI.endsWith("/") ? serverURI : serverURI + "/";
|
||||
if (!this.serverURI.endsWith("/")) {
|
||||
throw new IllegalArgumentException("Constructed serverURI must end with a trailing slash: " + this.serverURI);
|
||||
}
|
||||
this.executor = executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a typed value extracted from a successful response (in an
|
||||
* endpoint-dependent way).
|
||||
*/
|
||||
public interface RequestDelegate<T> {
|
||||
public void handleError(Exception e);
|
||||
public void handleFailure(FxAccountAbstractClientRemoteException e);
|
||||
public void handleSuccess(T result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intepret a response from the auth server.
|
||||
* <p>
|
||||
* Throw an appropriate exception on errors; otherwise, return the response's
|
||||
* status code.
|
||||
*
|
||||
* @return response's HTTP status code.
|
||||
* @throws FxAccountClientException
|
||||
*/
|
||||
public static int validateResponse(HttpResponse response) throws FxAccountAbstractClientRemoteException {
|
||||
final int status = response.getStatusLine().getStatusCode();
|
||||
if (status == 200) {
|
||||
return status;
|
||||
}
|
||||
int code;
|
||||
int errno;
|
||||
String error;
|
||||
String message;
|
||||
ExtendedJSONObject body;
|
||||
try {
|
||||
body = new SyncStorageResponse(response).jsonObjectBody();
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredErrorStringFields, String.class);
|
||||
body.throwIfFieldsMissingOrMisTyped(requiredErrorLongFields, Long.class);
|
||||
code = body.getLong(JSON_KEY_CODE).intValue();
|
||||
errno = body.getLong(JSON_KEY_ERRNO).intValue();
|
||||
error = body.getString(JSON_KEY_ERROR);
|
||||
message = body.getString(JSON_KEY_MESSAGE);
|
||||
} catch (Exception e) {
|
||||
throw new FxAccountAbstractClientMalformedResponseException(response);
|
||||
}
|
||||
throw new FxAccountAbstractClientRemoteException(response, code, errno, error, message, body);
|
||||
}
|
||||
|
||||
protected <T> void invokeHandleError(final RequestDelegate<T> delegate, final Exception e) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected <T> void post(BaseResource resource, final ExtendedJSONObject requestBody, final RequestDelegate<T> delegate) {
|
||||
try {
|
||||
if (requestBody == null) {
|
||||
resource.post((HttpEntity) null);
|
||||
} else {
|
||||
resource.post(requestBody);
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate resource callbacks into request callbacks invoked on the provided
|
||||
* executor.
|
||||
* <p>
|
||||
* Override <code>handleSuccess</code> to parse the body of the resource
|
||||
* request and call the request callback. <code>handleSuccess</code> is
|
||||
* invoked via the executor, so you don't need to delegate further.
|
||||
*/
|
||||
protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
|
||||
protected abstract void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body);
|
||||
|
||||
protected final RequestDelegate<T> delegate;
|
||||
|
||||
/**
|
||||
* Create a delegate for an un-authenticated resource.
|
||||
*/
|
||||
public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate) {
|
||||
super(resource);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthHeaderProvider getAuthHeaderProvider() {
|
||||
return super.getAuthHeaderProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserAgent() {
|
||||
return FxAccountConstants.USER_AGENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpResponse(HttpResponse response) {
|
||||
try {
|
||||
final int status = validateResponse(response);
|
||||
invokeHandleSuccess(status, response);
|
||||
} catch (FxAccountAbstractClientRemoteException e) {
|
||||
invokeHandleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void invokeHandleFailure(final FxAccountAbstractClientRemoteException e) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
delegate.handleFailure(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void invokeHandleSuccess(final int status, final HttpResponse response) {
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ExtendedJSONObject body = new SyncResponse(response).jsonObjectBody();
|
||||
ResourceDelegate.this.handleSuccess(status, response, body);
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpProtocolException(final ClientProtocolException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpIOException(IOException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportException(GeneralSecurityException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
|
||||
super.addHeaders(request, client);
|
||||
|
||||
// The basics.
|
||||
final Locale locale = Locale.getDefault();
|
||||
request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, Utils.getLanguageTag(locale));
|
||||
request.addHeader(HttpHeaders.ACCEPT, ACCEPT_HEADER);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa.oauth;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.HTTPFailureException;
|
||||
import org.mozilla.gecko.sync.net.SyncStorageResponse;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
|
||||
/**
|
||||
* From <a href="https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md">https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md</a>.
|
||||
*/
|
||||
public class FxAccountAbstractClientException extends Exception {
|
||||
private static final long serialVersionUID = 1953459541558266597L;
|
||||
|
||||
public FxAccountAbstractClientException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
public FxAccountAbstractClientException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public static class FxAccountAbstractClientRemoteException extends FxAccountAbstractClientException {
|
||||
private static final long serialVersionUID = 1209313149952001097L;
|
||||
|
||||
public final HttpResponse response;
|
||||
public final long httpStatusCode;
|
||||
public final long apiErrorNumber;
|
||||
public final String error;
|
||||
public final String message;
|
||||
public final ExtendedJSONObject body;
|
||||
|
||||
public FxAccountAbstractClientRemoteException(HttpResponse response, long httpStatusCode, long apiErrorNumber, String error, String message, ExtendedJSONObject body) {
|
||||
super(new HTTPFailureException(new SyncStorageResponse(response)));
|
||||
if (body == null) {
|
||||
throw new IllegalArgumentException("body must not be null");
|
||||
}
|
||||
this.response = response;
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
this.apiErrorNumber = apiErrorNumber;
|
||||
this.error = error;
|
||||
this.message = message;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "<FxAccountAbstractClientRemoteException " + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message + ">";
|
||||
}
|
||||
}
|
||||
|
||||
public static class FxAccountAbstractClientMalformedResponseException extends FxAccountAbstractClientRemoteException {
|
||||
private static final long serialVersionUID = 1209313149952001098L;
|
||||
|
||||
public FxAccountAbstractClientMalformedResponseException(HttpResponse response) {
|
||||
super(response, 0, FxAccountOAuthRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", new ExtendedJSONObject());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa.oauth;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
|
||||
/**
|
||||
* Talk to an fxa-oauth-server to get "implicitly granted" OAuth tokens.
|
||||
* <p>
|
||||
* To use this client, you will need a pre-allocated fxa-oauth-server
|
||||
* "client_id" with special "implicit grant" permissions.
|
||||
* <p>
|
||||
* This client was written against the API documented at <a href="https://github.com/mozilla/fxa-oauth-server/blob/41538990df9e91158558ae5a8115194383ac3b05/docs/api.md">https://github.com/mozilla/fxa-oauth-server/blob/41538990df9e91158558ae5a8115194383ac3b05/docs/api.md</a>.
|
||||
*/
|
||||
public class FxAccountOAuthClient10 extends FxAccountAbstractClient {
|
||||
protected static final String LOG_TAG = FxAccountOAuthClient10.class.getSimpleName();
|
||||
|
||||
protected static final String AUTHORIZATION_RESPONSE_TYPE = "token";
|
||||
|
||||
protected static final String JSON_KEY_ACCESS_TOKEN = "access_token";
|
||||
protected static final String JSON_KEY_ASSERTION = "assertion";
|
||||
protected static final String JSON_KEY_CLIENT_ID = "client_id";
|
||||
protected static final String JSON_KEY_RESPONSE_TYPE = "response_type";
|
||||
protected static final String JSON_KEY_SCOPE = "scope";
|
||||
protected static final String JSON_KEY_STATE = "state";
|
||||
protected static final String JSON_KEY_TOKEN_TYPE = "token_type";
|
||||
|
||||
// access_token: A string that can be used for authorized requests to service providers.
|
||||
// scope: A string of space-separated permissions that this token has. May differ from requested scopes, since user can deny permissions.
|
||||
// token_type: A string representing the token type. Currently will always be "bearer".
|
||||
protected static final String[] AUTHORIZATION_RESPONSE_REQUIRED_STRING_FIELDS = new String[] { JSON_KEY_ACCESS_TOKEN, JSON_KEY_SCOPE, JSON_KEY_TOKEN_TYPE };
|
||||
|
||||
public FxAccountOAuthClient10(String serverURI, Executor executor) {
|
||||
super(serverURI, executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Thin container for an authorization response.
|
||||
*/
|
||||
public static class AuthorizationResponse {
|
||||
public final String access_token;
|
||||
public final String token_type;
|
||||
public final String scope;
|
||||
|
||||
public AuthorizationResponse(String access_token, String token_type, String scope) {
|
||||
this.access_token = access_token;
|
||||
this.token_type = token_type;
|
||||
this.scope = scope;
|
||||
}
|
||||
}
|
||||
|
||||
public void authorization(String client_id, String assertion, String state, String scope,
|
||||
RequestDelegate<AuthorizationResponse> delegate) {
|
||||
final BaseResource resource;
|
||||
try {
|
||||
resource = new BaseResource(new URI(serverURI + "authorization"));
|
||||
} catch (URISyntaxException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<AuthorizationResponse>(resource, delegate) {
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
body.throwIfFieldsMissingOrMisTyped(AUTHORIZATION_RESPONSE_REQUIRED_STRING_FIELDS, String.class);
|
||||
String access_token = body.getString(JSON_KEY_ACCESS_TOKEN);
|
||||
String token_type = body.getString(JSON_KEY_TOKEN_TYPE);
|
||||
String scope = body.getString(JSON_KEY_SCOPE);
|
||||
delegate.handleSuccess(new AuthorizationResponse(access_token, token_type, scope));
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final ExtendedJSONObject requestBody = new ExtendedJSONObject();
|
||||
requestBody.put(JSON_KEY_RESPONSE_TYPE, AUTHORIZATION_RESPONSE_TYPE);
|
||||
requestBody.put(JSON_KEY_CLIENT_ID, client_id);
|
||||
requestBody.put(JSON_KEY_ASSERTION, assertion);
|
||||
if (scope != null) {
|
||||
requestBody.put(JSON_KEY_SCOPE, scope);
|
||||
}
|
||||
if (state != null) {
|
||||
requestBody.put(JSON_KEY_STATE, state);
|
||||
}
|
||||
|
||||
post(resource, requestBody, delegate);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa.oauth;
|
||||
|
||||
public interface FxAccountOAuthRemoteError {
|
||||
public static final int ATTEMPT_TO_CREATE_AN_ACCOUNT_THAT_ALREADY_EXISTS = 101;
|
||||
public static final int UNKNOWN_CLIENT_ID = 101;
|
||||
public static final int INCORRECT_CLIENT_SECRET = 102;
|
||||
public static final int REDIRECT_URI_DOES_NOT_MATCH_REGISTERED_VALUE = 103;
|
||||
public static final int INVALID_FXA_ASSERTION = 104;
|
||||
public static final int UNKNOWN_CODE = 105;
|
||||
public static final int INCORRECT_CODE = 106;
|
||||
public static final int EXPIRED_CODE = 107;
|
||||
public static final int INVALID_TOKEN = 108;
|
||||
public static final int INVALID_REQUEST_PARAMETER = 109;
|
||||
public static final int UNKNOWN_ERROR = 999;
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.background.fxa.profile;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.net.AuthHeaderProvider;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.BearerAuthHeaderProvider;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Talk to an fxa-profile-server to get profile information like name, age, gender, and avatar image.
|
||||
* <p>
|
||||
* This client was written against the API documented at <a href="https://github.com/mozilla/fxa-profile-server/blob/0c065619f5a2e867f813a343b4c67da3fe2c82a4/docs/API.md">https://github.com/mozilla/fxa-profile-server/blob/0c065619f5a2e867f813a343b4c67da3fe2c82a4/docs/API.md</a>.
|
||||
*/
|
||||
public class FxAccountProfileClient10 extends FxAccountAbstractClient {
|
||||
public FxAccountProfileClient10(String serverURI, Executor executor) {
|
||||
super(serverURI, executor);
|
||||
}
|
||||
|
||||
public void profile(final String token, RequestDelegate<ExtendedJSONObject> delegate) {
|
||||
BaseResource resource;
|
||||
try {
|
||||
resource = new BaseResource(new URI(serverURI + "profile"));
|
||||
} catch (URISyntaxException e) {
|
||||
invokeHandleError(delegate, e);
|
||||
return;
|
||||
}
|
||||
|
||||
resource.delegate = new ResourceDelegate<ExtendedJSONObject>(resource, delegate) {
|
||||
@Override
|
||||
public AuthHeaderProvider getAuthHeaderProvider() {
|
||||
return new BearerAuthHeaderProvider(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
|
||||
try {
|
||||
delegate.handleSuccess(body);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
delegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
resource.get();
|
||||
}
|
||||
}
|
|
@ -186,8 +186,8 @@ public class JSONWebTokenUtils {
|
|||
System.out.println("Malformed certificate -- got exception trying to dump contents.");
|
||||
return false;
|
||||
}
|
||||
System.out.println("certificate header: " + c.getString("header"));
|
||||
System.out.println("certificate payload: " + c.getString("payload"));
|
||||
System.out.println("certificate header: " + c.getObject("header").toJSONString());
|
||||
System.out.println("certificate payload: " + c.getObject("payload").toJSONString());
|
||||
System.out.println("certificate signature: " + c.getString("signature"));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
@ -244,8 +244,8 @@ public class JSONWebTokenUtils {
|
|||
return false;
|
||||
}
|
||||
dumpCertificate(a.getString("certificate"));
|
||||
System.out.println("assertion header: " + a.getString("header"));
|
||||
System.out.println("assertion payload: " + a.getString("payload"));
|
||||
System.out.println("assertion header: " + a.getObject("header").toJSONString());
|
||||
System.out.println("assertion payload: " + a.getObject("payload").toJSONString());
|
||||
System.out.println("assertion signature: " + a.getString("signature"));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -5,29 +5,23 @@
|
|||
package org.mozilla.gecko.browserid.verifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierErrorResponseException;
|
||||
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierMalformedResponseException;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.BaseResourceDelegate;
|
||||
import org.mozilla.gecko.sync.net.Resource;
|
||||
import org.mozilla.gecko.sync.net.SyncResponse;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.NameValuePair;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity;
|
||||
import ch.boye.httpclientandroidlib.message.BasicNameValuePair;
|
||||
|
||||
public class BrowserIDRemoteVerifierClient implements BrowserIDVerifierClient {
|
||||
public abstract class AbstractBrowserIDRemoteVerifierClient implements BrowserIDVerifierClient {
|
||||
public static final String LOG_TAG = AbstractBrowserIDRemoteVerifierClient.class.getSimpleName();
|
||||
|
||||
protected static class RemoteVerifierResourceDelegate extends BaseResourceDelegate {
|
||||
private final BrowserIDVerifierDelegate delegate;
|
||||
|
||||
|
@ -93,44 +87,9 @@ public class BrowserIDRemoteVerifierClient implements BrowserIDVerifierClient {
|
|||
}
|
||||
}
|
||||
|
||||
public static final String LOG_TAG = "BrowserIDRemoteVerifierClient";
|
||||
|
||||
public static final String DEFAULT_VERIFIER_URL = "https://verifier.login.persona.org/verify";
|
||||
|
||||
protected final URI verifierUri;
|
||||
|
||||
public BrowserIDRemoteVerifierClient(URI verifierUri) {
|
||||
public AbstractBrowserIDRemoteVerifierClient(URI verifierUri) {
|
||||
this.verifierUri = verifierUri;
|
||||
}
|
||||
|
||||
public BrowserIDRemoteVerifierClient() throws URISyntaxException {
|
||||
this.verifierUri = new URI(DEFAULT_VERIFIER_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String audience, String assertion, final BrowserIDVerifierDelegate delegate) {
|
||||
if (audience == null) {
|
||||
throw new IllegalArgumentException("audience cannot be null.");
|
||||
}
|
||||
if (assertion == null) {
|
||||
throw new IllegalArgumentException("assertion cannot be null.");
|
||||
}
|
||||
if (delegate == null) {
|
||||
throw new IllegalArgumentException("delegate cannot be null.");
|
||||
}
|
||||
|
||||
BaseResource r = new BaseResource(verifierUri);
|
||||
|
||||
r.delegate = new RemoteVerifierResourceDelegate(r, delegate);
|
||||
|
||||
List<NameValuePair> nvps = Arrays.asList(new NameValuePair[] {
|
||||
new BasicNameValuePair("audience", audience),
|
||||
new BasicNameValuePair("assertion", assertion) });
|
||||
|
||||
try {
|
||||
r.post(new UrlEncodedFormEntity(nvps, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.browserid.verifier;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
|
||||
import ch.boye.httpclientandroidlib.NameValuePair;
|
||||
import ch.boye.httpclientandroidlib.client.entity.UrlEncodedFormEntity;
|
||||
import ch.boye.httpclientandroidlib.message.BasicNameValuePair;
|
||||
|
||||
/**
|
||||
* The verifier protocol changed: version 1 posts form-encoded data; version 2
|
||||
* posts JSON data.
|
||||
*/
|
||||
public class BrowserIDRemoteVerifierClient10 extends AbstractBrowserIDRemoteVerifierClient {
|
||||
public static final String LOG_TAG = BrowserIDRemoteVerifierClient10.class.getSimpleName();
|
||||
|
||||
public static final String DEFAULT_VERIFIER_URL = "https://verifier.login.persona.org/verify";
|
||||
|
||||
public BrowserIDRemoteVerifierClient10() throws URISyntaxException {
|
||||
super(new URI(DEFAULT_VERIFIER_URL));
|
||||
}
|
||||
|
||||
public BrowserIDRemoteVerifierClient10(URI verifierUri) {
|
||||
super(verifierUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String audience, String assertion, final BrowserIDVerifierDelegate delegate) {
|
||||
if (audience == null) {
|
||||
throw new IllegalArgumentException("audience cannot be null.");
|
||||
}
|
||||
if (assertion == null) {
|
||||
throw new IllegalArgumentException("assertion cannot be null.");
|
||||
}
|
||||
if (delegate == null) {
|
||||
throw new IllegalArgumentException("delegate cannot be null.");
|
||||
}
|
||||
|
||||
BaseResource r = new BaseResource(verifierUri);
|
||||
|
||||
r.delegate = new RemoteVerifierResourceDelegate(r, delegate);
|
||||
|
||||
List<NameValuePair> nvps = Arrays.asList(new NameValuePair[] {
|
||||
new BasicNameValuePair("audience", audience),
|
||||
new BasicNameValuePair("assertion", assertion) });
|
||||
|
||||
try {
|
||||
r.post(new UrlEncodedFormEntity(nvps, "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.browserid.verifier;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
|
||||
/**
|
||||
* The verifier protocol changed: version 1 posts form-encoded data; version 2
|
||||
* posts JSON data.
|
||||
*/
|
||||
public class BrowserIDRemoteVerifierClient20 extends AbstractBrowserIDRemoteVerifierClient {
|
||||
public static final String LOG_TAG = BrowserIDRemoteVerifierClient20.class.getSimpleName();
|
||||
|
||||
public static final String DEFAULT_VERIFIER_URL = "https://verifier.accounts.firefox.com/v2";
|
||||
|
||||
protected static final String JSON_KEY_ASSERTION = "assertion";
|
||||
protected static final String JSON_KEY_AUDIENCE = "audience";
|
||||
|
||||
public BrowserIDRemoteVerifierClient20() throws URISyntaxException {
|
||||
super(new URI(DEFAULT_VERIFIER_URL));
|
||||
}
|
||||
|
||||
public BrowserIDRemoteVerifierClient20(URI verifierUri) {
|
||||
super(verifierUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(String audience, String assertion, final BrowserIDVerifierDelegate delegate) {
|
||||
if (audience == null) {
|
||||
throw new IllegalArgumentException("audience cannot be null.");
|
||||
}
|
||||
if (assertion == null) {
|
||||
throw new IllegalArgumentException("assertion cannot be null.");
|
||||
}
|
||||
if (delegate == null) {
|
||||
throw new IllegalArgumentException("delegate cannot be null.");
|
||||
}
|
||||
|
||||
BaseResource r = new BaseResource(verifierUri);
|
||||
r.delegate = new RemoteVerifierResourceDelegate(r, delegate);
|
||||
|
||||
final ExtendedJSONObject requestBody = new ExtendedJSONObject();
|
||||
requestBody.put(JSON_KEY_AUDIENCE, audience);
|
||||
requestBody.put(JSON_KEY_ASSERTION, assertion);
|
||||
|
||||
try {
|
||||
r.post(requestBody);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
delegate.handleError(e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
package org.mozilla.gecko.fxa.activities;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mozilla.gecko.AppConstants;
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.background.common.log.Logger;
|
||||
|
@ -39,14 +43,6 @@ import android.preference.PreferenceScreen;
|
|||
import android.text.TextUtils;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* A fragment that displays the status of an AndroidFxAccount.
|
||||
* <p>
|
||||
|
@ -58,15 +54,6 @@ public class FxAccountStatusFragment
|
|||
implements OnPreferenceClickListener, OnPreferenceChangeListener {
|
||||
private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName();
|
||||
|
||||
/**
|
||||
* If a device claims to have synced before this date, we will assume it has never synced.
|
||||
*/
|
||||
private static final Date EARLIEST_VALID_SYNCED_DATE;
|
||||
static {
|
||||
final Calendar c = GregorianCalendar.getInstance();
|
||||
c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
|
||||
EARLIEST_VALID_SYNCED_DATE = c.getTime();
|
||||
}
|
||||
// When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
|
||||
// before trying to sync. Should we kill off the fragment before the sync
|
||||
// request happens, that's okay: the runnable will run if the UI thread is
|
||||
|
@ -542,9 +529,6 @@ public class FxAccountStatusFragment
|
|||
|
||||
// This is a helper function similar to TabsAccessor.getLastSyncedString() to calculate relative "Last synced" time span.
|
||||
private String getLastSyncedString(final long startTime) {
|
||||
if (new Date(startTime).before(EARLIEST_VALID_SYNCED_DATE)) {
|
||||
return getActivity().getString(R.string.remote_tabs_never_synced);
|
||||
}
|
||||
final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(startTime);
|
||||
return getActivity().getResources().getString(R.string.fxaccount_status_last_synced, relativeTimeSpanString);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.net;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
|
||||
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHeader;
|
||||
import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
|
||||
|
||||
/**
|
||||
* An <code>AuthHeaderProvider</code> that returns an Authorization header for
|
||||
* bearer tokens, adding a simple prefix.
|
||||
*/
|
||||
public abstract class AbstractBearerTokenAuthHeaderProvider implements AuthHeaderProvider {
|
||||
protected final String header;
|
||||
|
||||
public AbstractBearerTokenAuthHeaderProvider(String token) {
|
||||
if (token == null) {
|
||||
throw new IllegalArgumentException("token must not be null.");
|
||||
}
|
||||
|
||||
this.header = getPrefix() + " " + token;
|
||||
}
|
||||
|
||||
protected abstract String getPrefix();
|
||||
|
||||
@Override
|
||||
public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) {
|
||||
return new BasicHeader("Authorization", header);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.net;
|
||||
|
||||
/**
|
||||
* An <code>AuthHeaderProvider</code> that returns an Authorization header for
|
||||
* Bearer tokens in the format expected by a Mozilla Firefox Accounts Profile Server.
|
||||
* <p>
|
||||
* See <a href="https://github.com/mozilla/fxa-profile-server/blob/master/docs/API.md">https://github.com/mozilla/fxa-profile-server/blob/master/docs/API.md</a>.
|
||||
*/
|
||||
public class BearerAuthHeaderProvider extends AbstractBearerTokenAuthHeaderProvider {
|
||||
public BearerAuthHeaderProvider(String token) {
|
||||
super(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getPrefix() {
|
||||
return "Bearer";
|
||||
}
|
||||
}
|
|
@ -4,12 +4,6 @@
|
|||
|
||||
package org.mozilla.gecko.sync.net;
|
||||
|
||||
import ch.boye.httpclientandroidlib.Header;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
|
||||
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHeader;
|
||||
import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
|
||||
|
||||
/**
|
||||
* An <code>AuthHeaderProvider</code> that returns an Authorization header for
|
||||
* BrowserID assertions in the format expected by a Mozilla Services Token
|
||||
|
@ -17,21 +11,13 @@ import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
|
|||
* <p>
|
||||
* See <a href="http://docs.services.mozilla.com/token/apis.html">http://docs.services.mozilla.com/token/apis.html</a>.
|
||||
*/
|
||||
public class BrowserIDAuthHeaderProvider implements AuthHeaderProvider {
|
||||
protected final String assertion;
|
||||
|
||||
public class BrowserIDAuthHeaderProvider extends AbstractBearerTokenAuthHeaderProvider {
|
||||
public BrowserIDAuthHeaderProvider(String assertion) {
|
||||
if (assertion == null) {
|
||||
throw new IllegalArgumentException("assertion must not be null.");
|
||||
}
|
||||
|
||||
this.assertion = assertion;
|
||||
super(assertion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) {
|
||||
Header header = new BasicHeader("Authorization", "BrowserID " + assertion);
|
||||
|
||||
return header;
|
||||
protected String getPrefix() {
|
||||
return "BrowserID";
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче