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:
Nick Alexander 2014-08-18 14:07:57 -07:00
Родитель a8a7dd9d99
Коммит ac56d2a2ca
14 изменённых файлов: 670 добавлений и 88 удалений

Просмотреть файл

@ -798,7 +798,12 @@ sync_java_files = [
'background/fxa/FxAccountClientException.java', 'background/fxa/FxAccountClientException.java',
'background/fxa/FxAccountRemoteError.java', 'background/fxa/FxAccountRemoteError.java',
'background/fxa/FxAccountUtils.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/PasswordStretcher.java',
'background/fxa/profile/FxAccountProfileClient10.java',
'background/fxa/QuickPasswordStretcher.java', 'background/fxa/QuickPasswordStretcher.java',
'background/fxa/SkewHandler.java', 'background/fxa/SkewHandler.java',
'background/healthreport/AndroidConfigurationProvider.java', 'background/healthreport/AndroidConfigurationProvider.java',
@ -835,7 +840,9 @@ sync_java_files = [
'browserid/MockMyIDTokenFactory.java', 'browserid/MockMyIDTokenFactory.java',
'browserid/RSACryptoImplementation.java', 'browserid/RSACryptoImplementation.java',
'browserid/SigningPrivateKey.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/BrowserIDVerifierClient.java',
'browserid/verifier/BrowserIDVerifierDelegate.java', 'browserid/verifier/BrowserIDVerifierDelegate.java',
'browserid/verifier/BrowserIDVerifierException.java', 'browserid/verifier/BrowserIDVerifierException.java',
@ -962,10 +969,12 @@ sync_java_files = [
'sync/middleware/MiddlewareRepository.java', 'sync/middleware/MiddlewareRepository.java',
'sync/middleware/MiddlewareRepositorySession.java', 'sync/middleware/MiddlewareRepositorySession.java',
'sync/MigrationSentinelSyncStage.java', 'sync/MigrationSentinelSyncStage.java',
'sync/net/AbstractBearerTokenAuthHeaderProvider.java',
'sync/net/AuthHeaderProvider.java', 'sync/net/AuthHeaderProvider.java',
'sync/net/BaseResource.java', 'sync/net/BaseResource.java',
'sync/net/BaseResourceDelegate.java', 'sync/net/BaseResourceDelegate.java',
'sync/net/BasicAuthHeaderProvider.java', 'sync/net/BasicAuthHeaderProvider.java',
'sync/net/BearerAuthHeaderProvider.java',
'sync/net/BrowserIDAuthHeaderProvider.java', 'sync/net/BrowserIDAuthHeaderProvider.java',
'sync/net/ConnectionMonitorThread.java', 'sync/net/ConnectionMonitorThread.java',
'sync/net/HandleProgressException.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."); System.out.println("Malformed certificate -- got exception trying to dump contents.");
return false; return false;
} }
System.out.println("certificate header: " + c.getString("header")); System.out.println("certificate header: " + c.getObject("header").toJSONString());
System.out.println("certificate payload: " + c.getString("payload")); System.out.println("certificate payload: " + c.getObject("payload").toJSONString());
System.out.println("certificate signature: " + c.getString("signature")); System.out.println("certificate signature: " + c.getString("signature"));
return true; return true;
} catch (Exception e) { } catch (Exception e) {
@ -244,8 +244,8 @@ public class JSONWebTokenUtils {
return false; return false;
} }
dumpCertificate(a.getString("certificate")); dumpCertificate(a.getString("certificate"));
System.out.println("assertion header: " + a.getString("header")); System.out.println("assertion header: " + a.getObject("header").toJSONString());
System.out.println("assertion payload: " + a.getString("payload")); System.out.println("assertion payload: " + a.getObject("payload").toJSONString());
System.out.println("assertion signature: " + a.getString("signature")); System.out.println("assertion signature: " + a.getString("signature"));
return true; return true;
} catch (Exception e) { } catch (Exception e) {

Просмотреть файл

@ -5,29 +5,23 @@
package org.mozilla.gecko.browserid.verifier; package org.mozilla.gecko.browserid.verifier;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.List;
import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierErrorResponseException; import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierErrorResponseException;
import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierMalformedResponseException; import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierException.BrowserIDVerifierMalformedResponseException;
import org.mozilla.gecko.sync.ExtendedJSONObject; 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.BaseResourceDelegate;
import org.mozilla.gecko.sync.net.Resource; import org.mozilla.gecko.sync.net.Resource;
import org.mozilla.gecko.sync.net.SyncResponse; import org.mozilla.gecko.sync.net.SyncResponse;
import ch.boye.httpclientandroidlib.HttpResponse; import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.NameValuePair;
import ch.boye.httpclientandroidlib.client.ClientProtocolException; 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 { protected static class RemoteVerifierResourceDelegate extends BaseResourceDelegate {
private final BrowserIDVerifierDelegate delegate; 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; protected final URI verifierUri;
public BrowserIDRemoteVerifierClient(URI verifierUri) { public AbstractBrowserIDRemoteVerifierClient(URI verifierUri) {
this.verifierUri = 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; 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.AppConstants;
import org.mozilla.gecko.R; import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.common.log.Logger;
@ -39,14 +43,6 @@ import android.preference.PreferenceScreen;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.format.DateUtils; 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. * A fragment that displays the status of an AndroidFxAccount.
* <p> * <p>
@ -58,15 +54,6 @@ public class FxAccountStatusFragment
implements OnPreferenceClickListener, OnPreferenceChangeListener { implements OnPreferenceClickListener, OnPreferenceChangeListener {
private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName(); 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) // 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 // 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 // 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. // This is a helper function similar to TabsAccessor.getLastSyncedString() to calculate relative "Last synced" time span.
private String getLastSyncedString(final long startTime) { 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); final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(startTime);
return getActivity().getResources().getString(R.string.fxaccount_status_last_synced, relativeTimeSpanString); 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; 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 * An <code>AuthHeaderProvider</code> that returns an Authorization header for
* BrowserID assertions in the format expected by a Mozilla Services Token * BrowserID assertions in the format expected by a Mozilla Services Token
@ -17,21 +11,13 @@ import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
* <p> * <p>
* See <a href="http://docs.services.mozilla.com/token/apis.html">http://docs.services.mozilla.com/token/apis.html</a>. * 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 { public class BrowserIDAuthHeaderProvider extends AbstractBearerTokenAuthHeaderProvider {
protected final String assertion;
public BrowserIDAuthHeaderProvider(String assertion) { public BrowserIDAuthHeaderProvider(String assertion) {
if (assertion == null) { super(assertion);
throw new IllegalArgumentException("assertion must not be null.");
}
this.assertion = assertion;
} }
@Override @Override
public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) { protected String getPrefix() {
Header header = new BasicHeader("Authorization", "BrowserID " + assertion); return "BrowserID";
return header;
} }
} }