merge m-c to elm

This commit is contained in:
Tim Taubert 2013-09-16 09:14:47 +02:00
commit b19217e348
38 changed files with 1227 additions and 1 deletions

View File

@ -1305,3 +1305,7 @@ pref("geo.wifi.uri", "https://www.googleapis.com/geolocation/v1/geolocate?key=%G
// Necko IPC security checks only needed for app isolation for cookies/cache/etc:
// currently irrelevant for desktop e10s
pref("network.disable.ipc.security", true);
// The URL where remote content that composes the UI for Firefox Accounts should
// be fetched.
pref("firefox.accounts.remoteUrl", "http://accounts.dev.lcip.org/flow");

View File

@ -0,0 +1,21 @@
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
}
#content {
width: 100%;
height: 100%;
border: 0;
display: flex;
}
#remote {
width: 100%;
height: 100%;
border: 0;
}

View File

@ -0,0 +1,89 @@
/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
function log(msg) {
//dump("FXA: " + msg + "\n");
};
let wrapper = {
iframe: null,
init: function () {
let iframe = document.getElementById("remote");
this.iframe = iframe;
iframe.addEventListener("load", this);
iframe.src = this._getAccountsURI();
},
handleEvent: function (evt) {
switch (evt.type) {
case "load":
this.iframe.contentWindow.addEventListener("FirefoxAccountsCommand", this);
this.iframe.removeEventListener("load", this);
break;
case "FirefoxAccountsCommand":
this.handleRemoteCommand(evt);
break;
}
},
onLogin: function (data) {
log("Received: 'login'. Data:" + JSON.stringify(data));
this.injectData("message", { status: "login" });
},
onCreate: function (data) {
log("Received: 'create'. Data:" + JSON.stringify(data));
this.injectData("message", { status: "create" });
},
onVerified: function (data) {
log("Received: 'verified'. Data:" + JSON.stringify(data));
this.injectData("message", { status: "verified" });
},
_getAccountsURI: function () {
return Services.urlFormatter.formatURLPref("firefox.accounts.remoteUrl");
},
handleRemoteCommand: function (evt) {
log('command: ' + evt.detail.command);
let data = evt.detail.data;
switch (evt.detail.command) {
case "create":
this.onCreate(data);
break;
case "login":
this.onLogin(data);
break;
case "verified":
this.onVerified(data);
break;
default:
log("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
break;
}
},
injectData: function (type, content) {
let authUrl = this._getAccountsURI();
let data = {
type: type,
content: content
};
this.iframe.contentWindow.postMessage(data, authUrl);
},
};
wrapper.init();

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
%htmlDTD;
<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
%brandDTD;
<!ENTITY % aboutAccountsDTD SYSTEM "chrome://browser/locale/aboutAccounts.dtd">
%aboutAccountsDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>&aboutAccounts.pageTitle;</title>
<link rel="icon" type="image/png" id="favicon"
href="chrome://branding/content/icon32.png"/>
<link rel="stylesheet"
href="chrome://browser/content/aboutaccounts/aboutaccounts.css"
type="text/css" />
</head>
<body>
<iframe mozframetype="content" id="remote" />
<script type="text/javascript;version=1.8"
src="chrome://browser/content/aboutaccounts/aboutaccounts.js" />
</body>
</html>

View File

@ -57,6 +57,7 @@ endif
MOCHITEST_BROWSER_FILES = \
head.js \
accounts_testRemoteCommands.html \
alltabslistener.html \
app_bug575561.html \
app_subframe_bug575561.html \
@ -65,6 +66,7 @@ MOCHITEST_BROWSER_FILES = \
blockPluginHard.xml \
blockPluginVulnerableNoUpdate.xml \
blockPluginVulnerableUpdatable.xml \
browser_aboutAccounts.js \
browser_aboutHealthReport.js \
browser_aboutHome.js \
browser_aboutSyncProgress.js \

View File

@ -0,0 +1,88 @@
<html>
<head>
<meta charset="utf-8">
<script>
function init() {
window.addEventListener("message", function process(e) {doTest(e)}, false);
// unless we relinquish the eventloop,
// tests will run before the chrome event handlers are ready
setTimeout(doTest, 0);
}
function checkStatusValue(payload, expectedValue) {
return payload.status == expectedValue;
}
var tests = [
{
info: "Check account creation",
event: "create",
payloadType: "message",
validateResponse: function(payload) {
return checkStatusValue(payload, "create");
},
},
{
info: "Check account verification",
event: "verified",
payloadType: "message",
validateResponse: function(payload) {
return checkStatusValue(payload, "verified");
},
},
{
info: "Check account log in",
event: "login",
payloadType: "message",
validateResponse: function(payload) {
return checkStatusValue(payload, "login");
},
},
];
var currentTest = -1;
function doTest(evt) {
if (evt) {
if (currentTest < 0 || !evt.data.content)
return; // not yet testing
var test = tests[currentTest];
if (evt.data.type != test.payloadType)
return; // skip unrequested events
var error = JSON.stringify(evt.data.content);
var pass = false;
try {
pass = test.validateResponse(evt.data.content)
} catch (e) {}
reportResult(test.info, pass, error);
}
// start the next test if there are any left
if (tests[++currentTest])
sendToBrowser(tests[currentTest].event);
else
reportFinished();
}
function reportResult(info, pass, error) {
var data = {type: "testResult", info: info, pass: pass, error: error};
window.parent.postMessage(data, "*");
}
function reportFinished(cmd) {
var data = {type: "testsComplete", count: tests.length};
window.parent.postMessage(data, "*");
}
function sendToBrowser(type) {
var event = new CustomEvent("FirefoxAccountsCommand", {detail: {command: type}, bubbles: true});
document.dispatchEvent(event);
}
</script>
</head>
<body onload="init()">
</body>
</html>

View File

@ -0,0 +1,89 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
registerCleanupFunction(function() {
// Ensure we don't pollute prefs for next tests.
Services.prefs.clearUserPref("firefox.accounts.remoteUrl");
});
let gTests = [
{
desc: "Test the remote commands",
setup: function ()
{
Services.prefs.setCharPref("firefox.accounts.remoteUrl",
"https://example.com/browser/browser/base/content/test/accounts_testRemoteCommands.html");
},
run: function ()
{
let deferred = Promise.defer();
let results = 0;
try {
let win = gBrowser.contentWindow;
win.addEventListener("message", function testLoad(e) {
if (e.data.type == "testResult") {
ok(e.data.pass, e.data.info);
results++;
}
else if (e.data.type == "testsComplete") {
is(results, e.data.count, "Checking number of results received matches the number of tests that should have run");
win.removeEventListener("message", testLoad, false, true);
deferred.resolve();
}
}, false, true);
} catch(e) {
ok(false, "Failed to get all commands");
deferred.reject();
}
return deferred.promise;
}
},
]; // gTests
function test()
{
waitForExplicitFinish();
Task.spawn(function () {
for (let test of gTests) {
info(test.desc);
test.setup();
yield promiseNewTabLoadEvent("about:accounts");
yield test.run();
gBrowser.removeCurrentTab();
}
finish();
});
}
function promiseNewTabLoadEvent(aUrl, aEventType="load")
{
let deferred = Promise.defer();
let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
tab.linkedBrowser.addEventListener(aEventType, function load(event) {
tab.linkedBrowser.removeEventListener(aEventType, load, true);
let iframe = tab.linkedBrowser.contentDocument.getElementById("remote");
iframe.addEventListener("load", function frameLoad(e) {
iframe.removeEventListener("load", frameLoad, false);
deferred.resolve();
}, false);
}, true);
return deferred.promise;
}

View File

@ -52,6 +52,9 @@ browser.jar:
content/browser/abouthealthreport/abouthealth.js (content/abouthealthreport/abouthealth.js)
content/browser/abouthealthreport/abouthealth.css (content/abouthealthreport/abouthealth.css)
#endif
content/browser/aboutaccounts/aboutaccounts.xhtml (content/aboutaccounts/aboutaccounts.xhtml)
content/browser/aboutaccounts/aboutaccounts.js (content/aboutaccounts/aboutaccounts.js)
content/browser/aboutaccounts/aboutaccounts.css (content/aboutaccounts/aboutaccounts.css)
content/browser/aboutRobots-icon.png (content/aboutRobots-icon.png)
content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png)
content/browser/aboutSocialError.xhtml (content/aboutSocialError.xhtml)

View File

@ -90,6 +90,8 @@ static RedirEntry kRedirMap[] = {
{ "healthreport", "chrome://browser/content/abouthealthreport/abouthealth.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
#endif
{ "accounts", "chrome://browser/content/aboutaccounts/aboutaccounts.xhtml",
nsIAboutModule::ALLOW_SCRIPT },
{ "app-manager", "chrome://browser/content/devtools/app-manager/index.xul",
nsIAboutModule::ALLOW_SCRIPT },
};

View File

@ -106,6 +106,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "permissions", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "accounts", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#ifdef MOZ_SERVICES_HEALTHREPORT
{ NS_ABOUT_MODULE_CONTRACTID_PREFIX "healthreport", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
#endif

View File

@ -0,0 +1,5 @@
<!-- 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/. -->
<!ENTITY aboutAccounts.pageTitle "&brandShortName; Accounts">

View File

@ -6,6 +6,7 @@
@AB_CD@.jar:
% locale browser @AB_CD@ %locale/browser/
locale/browser/aboutAccounts.dtd (%chrome/browser/aboutAccounts.dtd)
locale/browser/aboutCertError.dtd (%chrome/browser/aboutCertError.dtd)
locale/browser/aboutDialog.dtd (%chrome/browser/aboutDialog.dtd)
locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)

View File

@ -13,6 +13,7 @@
android:targetSdkVersion="16"/>
#include ../services/manifests/AnnouncementsAndroidManifest_permissions.xml.in
#include ../services/manifests/FxAccountAndroidManifest_permissions.xml.in
#include ../services/manifests/HealthReportAndroidManifest_permissions.xml.in
#include ../services/manifests/SyncAndroidManifest_permissions.xml.in
@ -207,6 +208,7 @@
</activity>
#include ../services/manifests/AnnouncementsAndroidManifest_activities.xml.in
#include ../services/manifests/FxAccountAndroidManifest_services.xml.in
#include ../services/manifests/SyncAndroidManifest_activities.xml.in
#include ../services/manifests/HealthReportAndroidManifest_activities.xml.in
@ -237,6 +239,7 @@
android:excludeFromRecents="true"/>
<provider android:name="org.mozilla.gecko.db.BrowserProvider"
android:label="@string/sync_configure_engines_title_bookmarks"
android:authorities="@ANDROID_PACKAGE_NAME@.db.browser"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER">
@ -252,16 +255,19 @@
Process name is a mangled version to avoid a Talos bug. (Bug 750548.)
-->
<provider android:name="org.mozilla.gecko.db.PasswordsProvider"
android:label="@string/sync_configure_engines_title_passwords"
android:authorities="@ANDROID_PACKAGE_NAME@.db.passwords"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.PASSWORD_PROVIDER"
android:process="@MANGLED_ANDROID_PACKAGE_NAME@.PasswordsProvider"/>
<provider android:name="org.mozilla.gecko.db.FormHistoryProvider"
android:label="@string/sync_configure_engines_title_history"
android:authorities="@ANDROID_PACKAGE_NAME@.db.formhistory"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.FORMHISTORY_PROVIDER"
android:protectionLevel="signature"/>
<provider android:name="org.mozilla.gecko.db.TabsProvider"
android:label="@string/sync_configure_engines_title_tabs"
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>

View File

@ -417,6 +417,7 @@ GARBAGE_DIRS += classes db jars res sync services
MOZ_ANDROID_SHARED_ID = "$(ANDROID_PACKAGE_NAME).sharedID"
MOZ_ANDROID_SHARED_ACCOUNT_TYPE = "$(ANDROID_PACKAGE_NAME)_sync"
MOZ_ANDROID_SHARED_FXACCOUNT_TYPE = "$(ANDROID_PACKAGE_NAME)_account"
# Bug 567884 - Need a way to find appropriate icons during packaging
ifeq ($(MOZ_APP_NAME),fennec)
@ -429,15 +430,19 @@ ICON_PATH_XXHDPI = $(topsrcdir)/$(MOZ_BRANDING_DIRECTORY)/content/fennec_144x144
ifeq (org.mozilla.firefox,$(ANDROID_PACKAGE_NAME))
MOZ_ANDROID_SHARED_ID = "org.mozilla.firefox.sharedID"
MOZ_ANDROID_SHARED_ACCOUNT_TYPE = "org.mozilla.firefox_sync"
MOZ_ANDROID_SHARED_FXACCOUNT_TYPE = "org.mozilla.firefox_account"
else ifeq (org.mozilla.firefox_beta,$(ANDROID_PACKAGE_NAME))
MOZ_ANDROID_SHARED_ID = "org.mozilla.firefox.sharedID"
MOZ_ANDROID_SHARED_ACCOUNT_TYPE = "org.mozilla.firefox_sync"
MOZ_ANDROID_SHARED_FXACCOUNT_TYPE = "org.mozilla.firefox_account"
else ifeq (org.mozilla.fennec_aurora,$(ANDROID_PACKAGE_NAME))
MOZ_ANDROID_SHARED_ID = "org.mozilla.fennec.sharedID"
MOZ_ANDROID_SHARED_ACCOUNT_TYPE = "org.mozilla.fennec_sync"
MOZ_ANDROID_SHARED_FXACCOUNT_TYPE = "org.mozilla.fennec_account"
else ifeq (org.mozilla.fennec,$(ANDROID_PACKAGE_NAME))
MOZ_ANDROID_SHARED_ID = "org.mozilla.fennec.sharedID"
MOZ_ANDROID_SHARED_ACCOUNT_TYPE = "org.mozilla.fennec_sync"
MOZ_ANDROID_SHARED_FXACCOUNT_TYPE = "org.mozilla.fennec_account"
endif
else
@ -451,6 +456,9 @@ endif
ifdef MOZ_ANDROID_SHARED_ACCOUNT_TYPE
DEFINES += -DMOZ_ANDROID_SHARED_ACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_ACCOUNT_TYPE)"
endif
ifdef MOZ_ANDROID_SHARED_FXACCOUNT_TYPE
DEFINES += -DMOZ_ANDROID_SHARED_FXACCOUNT_TYPE="$(MOZ_ANDROID_SHARED_FXACCOUNT_TYPE)"
endif
RES_LAYOUT = \
$(SYNC_RES_LAYOUT) \

View File

@ -5,6 +5,7 @@
# These files are managed in the android-sync repo. Do not modify directly, or your changes will be lost.
SYNC_PP_JAVA_FILES := \
background/common/GlobalConstants.java \
fxa/FxAccountConstants.java \
sync/SyncConstants.java \
background/announcements/AnnouncementsConstants.java \
background/healthreport/HealthReportConstants.java \
@ -55,6 +56,14 @@ SYNC_JAVA_FILES := \
background/healthreport/upload/ObsoleteDocumentTracker.java \
background/healthreport/upload/SubmissionClient.java \
background/healthreport/upload/SubmissionPolicy.java \
fxa/authenticator/FxAccountAuthenticator.java \
fxa/authenticator/FxAccountAuthenticatorService.java \
fxa/sync/FxAccountBookmarksSyncService.java \
fxa/sync/FxAccountHistorySyncService.java \
fxa/sync/FxAccountPasswordsSyncService.java \
fxa/sync/FxAccountSyncAdapter.java \
fxa/sync/FxAccountSyncService.java \
fxa/sync/FxAccountTabsSyncService.java \
sync/AlreadySyncingException.java \
sync/CollectionKeys.java \
sync/CommandProcessor.java \
@ -343,9 +352,14 @@ SYNC_RES_XML := \
$(NULL)
SYNC_PP_RES_XML := \
res/xml/sync_syncadapter.xml \
res/xml/sync_options.xml \
res/xml/sync_syncadapter.xml \
res/xml/sync_authenticator.xml \
res/xml/fxaccount_tabs_syncadapter.xml \
res/xml/fxaccount_passwords_syncadapter.xml \
res/xml/fxaccount_history_syncadapter.xml \
res/xml/fxaccount_bookmarks_syncadapter.xml \
res/xml/fxaccount_authenticator.xml \
$(NULL)
SYNC_THIRDPARTY_JAVA_FILES := \

View File

@ -0,0 +1,11 @@
#filter substitution
/* 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.fxa;
public class FxAccountConstants {
public static final String GLOBAL_LOG_TAG = "FxAccounts";
public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@";
}

View File

@ -0,0 +1,124 @@
/* 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.fxa.authenticator;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.fxa.FxAccountConstants;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Bundle;
public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
public static final String LOG_TAG = FxAccountAuthenticator.class.getSimpleName();
protected final Context context;
protected final AccountManager accountManager;
public FxAccountAuthenticator(Context context) {
super(context);
this.context = context;
this.accountManager = AccountManager.get(context);
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType, String[] requiredFeatures,
Bundle options)
throws NetworkErrorException {
Logger.debug(LOG_TAG, "addAccount");
final Bundle res = new Bundle();
if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
res.putInt(AccountManager.KEY_ERROR_CODE, -1);
res.putString(AccountManager.KEY_ERROR_MESSAGE, "Not adding unknown account type.");
return res;
}
final Account account = new Account("test@test.com", FxAccountConstants.ACCOUNT_TYPE);
final String password = "password";
final Bundle userData = Bundle.EMPTY;
if (!accountManager.addAccountExplicitly(account, password, userData)) {
res.putInt(AccountManager.KEY_ERROR_CODE, -1);
res.putString(AccountManager.KEY_ERROR_MESSAGE, "Failed to add account explicitly.");
return res;
}
Logger.info(LOG_TAG, "Added account named " + account.name + " of type " + account.type);
// Enable syncing by default.
for (String authority : new String[] {
AppConstants.ANDROID_PACKAGE_NAME + ".db.browser",
AppConstants.ANDROID_PACKAGE_NAME + ".db.formhistory",
AppConstants.ANDROID_PACKAGE_NAME + ".db.tabs",
AppConstants.ANDROID_PACKAGE_NAME + ".db.passwords",
}) {
ContentResolver.setSyncAutomatically(account, authority, true);
ContentResolver.setIsSyncable(account, authority, 1);
}
res.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
res.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
return res;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)
throws NetworkErrorException {
Logger.debug(LOG_TAG, "confirmCredentials");
return null;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
Logger.debug(LOG_TAG, "editProperties");
return null;
}
@Override
public Bundle getAuthToken(final AccountAuthenticatorResponse response,
final Account account, final String authTokenType, final Bundle options)
throws NetworkErrorException {
Logger.debug(LOG_TAG, "getAuthToken");
Logger.warn(LOG_TAG, "Returning null bundle for getAuthToken.");
return null;
}
@Override
public String getAuthTokenLabel(String authTokenType) {
Logger.debug(LOG_TAG, "getAuthTokenLabel");
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response,
Account account, String[] features) throws NetworkErrorException {
Logger.debug(LOG_TAG, "hasFeatures");
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
Logger.debug(LOG_TAG, "updateCredentials");
return null;
}
}

View File

@ -0,0 +1,44 @@
/* 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.fxa.authenticator;
import org.mozilla.gecko.background.common.log.Logger;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class FxAccountAuthenticatorService extends Service {
public static final String LOG_TAG = FxAccountAuthenticatorService.class.getSimpleName();
// Lazily initialized by <code>getAuthenticator</code>.
protected FxAccountAuthenticator accountAuthenticator = null;
protected FxAccountAuthenticator getAuthenticator() {
if (accountAuthenticator == null) {
accountAuthenticator = new FxAccountAuthenticator(this);
}
return accountAuthenticator;
}
@Override
public void onCreate() {
Logger.debug(LOG_TAG, "onCreate");
accountAuthenticator = getAuthenticator();
}
@Override
public IBinder onBind(Intent intent) {
Logger.debug(LOG_TAG, "onBind");
if (intent.getAction().equals(android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
return getAuthenticator().getIBinder();
}
return null;
}
}

View File

@ -0,0 +1,9 @@
/* 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.fxa.sync;
public class FxAccountBookmarksSyncService extends FxAccountSyncService {
}

View File

@ -0,0 +1,9 @@
/* 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.fxa.sync;
public class FxAccountHistorySyncService extends FxAccountSyncService {
}

View File

@ -0,0 +1,9 @@
/* 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.fxa.sync;
public class FxAccountPasswordsSyncService extends FxAccountHistorySyncService {
}

View File

@ -0,0 +1,30 @@
/* 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.fxa.sync;
import org.mozilla.gecko.background.common.log.Logger;
import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
Logger.info(LOG_TAG, "Syncing FxAccount" +
" account named " + account.name +
" for authority " + authority +
" with instance " + this + ".");
}
}

View File

@ -0,0 +1,28 @@
/* 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.fxa.sync;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public abstract class FxAccountSyncService extends Service {
private static final Object syncAdapterLock = new Object();
private static FxAccountSyncAdapter syncAdapter = null;
@Override
public void onCreate() {
synchronized (syncAdapterLock) {
if (syncAdapter == null) {
syncAdapter = new FxAccountSyncAdapter(getApplicationContext(), true);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
}

View File

@ -0,0 +1,9 @@
/* 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.fxa.sync;
public class FxAccountTabsSyncService extends FxAccountSyncService {
}

View File

@ -7,6 +7,8 @@
<!ENTITY syncBrand.fullName.label "Firefox Sync">
<!ENTITY syncBrand.shortName.label "Sync">
<!ENTITY fxaccountBrand.fullName.label "Firefox Account">
<!-- Main titles. -->
<!ENTITY sync.app.name.label '&syncBrand.fullName.label;'>
<!ENTITY sync.title.connect.label 'Connect to &syncBrand.shortName.label;'>
@ -104,3 +106,6 @@
<!ENTITY sync.text.redirect.to.set.up.sync.label 'Set up &syncBrand.fullName.label; on your device to send tabs to other devices.'>
<!ENTITY sync.text.tab.sent.label 'Your tab was sent!'>
<!ENTITY sync.text.tab.not.sent.label 'There was a problem sending your tab.'>
<!-- Firefox Account strings -->
<!ENTITY fxaccount.label '&fxaccountBrand.fullName.label;'>

View File

@ -0,0 +1,13 @@
#filter substitution
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@"
android:icon="@drawable/icon"
android:smallIcon="@drawable/icon"
android:label="@string/fxaccount_label"
/>

View File

@ -0,0 +1,13 @@
#filter substitution
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@"
android:contentAuthority="@ANDROID_PACKAGE_NAME@.db.browser"
android:isAlwaysSyncable="true"
android:supportsUploading="true"
android:userVisible="true"
/>

View File

@ -0,0 +1,13 @@
#filter substitution
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@"
android:contentAuthority="@ANDROID_PACKAGE_NAME@.db.formhistory"
android:isAlwaysSyncable="true"
android:supportsUploading="true"
android:userVisible="true"
/>

View File

@ -0,0 +1,13 @@
#filter substitution
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@"
android:contentAuthority="@ANDROID_PACKAGE_NAME@.db.passwords"
android:isAlwaysSyncable="true"
android:supportsUploading="true"
android:userVisible="true"
/>

View File

@ -0,0 +1,13 @@
#filter substitution
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@"
android:contentAuthority="@ANDROID_PACKAGE_NAME@.db.tabs"
android:isAlwaysSyncable="true"
android:supportsUploading="true"
android:userVisible="true"
/>

View File

@ -42,6 +42,14 @@ background/healthreport/upload/HealthReportUploadStartReceiver.java
background/healthreport/upload/ObsoleteDocumentTracker.java
background/healthreport/upload/SubmissionClient.java
background/healthreport/upload/SubmissionPolicy.java
fxa/authenticator/FxAccountAuthenticator.java
fxa/authenticator/FxAccountAuthenticatorService.java
fxa/sync/FxAccountBookmarksSyncService.java
fxa/sync/FxAccountHistorySyncService.java
fxa/sync/FxAccountPasswordsSyncService.java
fxa/sync/FxAccountSyncAdapter.java
fxa/sync/FxAccountSyncService.java
fxa/sync/FxAccountTabsSyncService.java
sync/AlreadySyncingException.java
sync/CollectionKeys.java
sync/CommandProcessor.java

View File

@ -0,0 +1,9 @@
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />

View File

@ -0,0 +1,55 @@
<service
android:exported="true"
android:name="org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticatorService" >
<intent-filter >
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/fxaccount_authenticator" />
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.fxa.sync.FxAccountBookmarksSyncService" >
<intent-filter >
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/fxaccount_bookmarks_syncadapter" />
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.fxa.sync.FxAccountHistorySyncService" >
<intent-filter >
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/fxaccount_history_syncadapter" />
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.fxa.sync.FxAccountPasswordsSyncService" >
<intent-filter >
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/fxaccount_passwords_syncadapter" />
</service>
<service
android:exported="false"
android:name="org.mozilla.gecko.fxa.sync.FxAccountTabsSyncService" >
<intent-filter >
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/fxaccount_tabs_syncadapter" />
</service>

View File

@ -1,4 +1,5 @@
background/common/GlobalConstants.java
fxa/FxAccountConstants.java
sync/SyncConstants.java
background/announcements/AnnouncementsConstants.java
background/healthreport/HealthReportConstants.java

View File

@ -97,3 +97,6 @@
<string name="sync_text_redirect_to_set_up_sync">&sync.text.redirect.to.set.up.sync.label;</string>
<string name="sync_text_tab_sent">&sync.text.tab.sent.label;</string>
<string name="sync_text_tab_not_sent">&sync.text.tab.not.sent.label;</string>
<!-- Firefox Account strings -->
<string name="fxaccount_label">&fxaccount.label;</string>

View File

@ -51,6 +51,17 @@ this.CryptoUtils = {
return result;
},
/**
* Encode the message into UTF-8 and feed the resulting bytes into the
* given hasher. Does not return a hash. This can be called multiple times
* with a single hasher, but eventually you must extract the result
* yourself.
*/
updateUTF8: function(message, hasher) {
let bytes = this._utf8Converter.convertToByteArray(message, {});
hasher.update(bytes, bytes.length);
},
/**
* UTF-8 encode a message and perform a SHA-1 over it.
*
@ -342,6 +353,159 @@ this.CryptoUtils = {
return header += ', ext="' + ext +'"';
},
/**
* Given an HTTP header value, strip out any attributes.
*/
stripHeaderAttributes: function(value) {
let value = value || "";
let i = value.indexOf(";");
return value.substring(0, (i >= 0) ? i : undefined).trim().toLowerCase();
},
/**
* Compute the HAWK client values (mostly the header) for an HTTP request.
*
* @param URI
* (nsIURI) HTTP request URI.
* @param method
* (string) HTTP request method.
* @param options
* (object) extra parameters (all but "credentials" are optional):
* credentials - (object, mandatory) HAWK credentials object.
* All three keys are required:
* id - (string) key identifier
* key - (string) raw key bytes
* algorithm - (string) which hash to use: "sha1" or "sha256"
* ext - (string) application-specific data, included in MAC
* localtimeOffsetMsec - (number) local clock offset (vs server)
* payload - (string) payload to include in hash, containing the
* HTTP request body. If not provided, the HAWK hash
* will not cover the request body, and the server
* should not check it either. This will be UTF-8
* encoded into bytes before hashing. This function
* cannot handle arbitrary binary data, sorry (the
* UTF-8 encoding process will corrupt any codepoints
* between U+0080 and U+00FF). Callers must be careful
* to use an HTTP client function which encodes the
* payload exactly the same way, otherwise the hash
* will not match.
* contentType - (string) payload Content-Type. This is included
* (without any attributes like "charset=") in the
* HAWK hash. It does *not* affect interpretation
* of the "payload" property.
* hash - (base64 string) pre-calculated payload hash. If
* provided, "payload" is ignored.
* ts - (number) pre-calculated timestamp, secs since epoch
* now - (number) current time, ms-since-epoch, for tests
* nonce - (string) pre-calculated nonce. Should only be defined
* for testing as this function will generate a
* cryptographically secure random one if not defined.
* @returns
* (object) Contains results of operation. The object has the
* following keys:
* field - (string) HAWK header, to use in Authorization: header
* artifacts - (object) other generated values:
* ts - (number) timestamp, in seconds since epoch
* nonce - (string)
* method - (string)
* resource - (string) path plus querystring
* host - (string)
* port - (number)
* hash - (string) payload hash (base64)
* ext - (string) app-specific data
* MAC - (string) request MAC (base64)
*/
computeHAWK: function(uri, method, options) {
let credentials = options.credentials;
let ts = options.ts || Math.floor(((options.now || Date.now()) +
(options.localtimeOffsetMsec || 0))
/ 1000);
let hash_algo, hmac_algo;
if (credentials.algorithm == "sha1") {
hash_algo = Ci.nsICryptoHash.SHA1;
hmac_algo = Ci.nsICryptoHMAC.SHA1;
} else if (credentials.algorithm == "sha256") {
hash_algo = Ci.nsICryptoHash.SHA256;
hmac_algo = Ci.nsICryptoHMAC.SHA256;
} else {
throw new Error("Unsupported algorithm: " + credentials.algorithm);
}
let port;
if (uri.port != -1) {
port = uri.port;
} else if (uri.scheme == "http") {
port = 80;
} else if (uri.scheme == "https") {
port = 443;
} else {
throw new Error("Unsupported URI scheme: " + uri.scheme);
}
let artifacts = {
ts: ts,
nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
method: method.toUpperCase(),
resource: uri.path, // This includes both path and search/queryarg.
host: uri.asciiHost.toLowerCase(), // This includes punycoding.
port: port.toString(10),
hash: options.hash,
ext: options.ext,
};
let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);
if (!artifacts.hash && options.hasOwnProperty("payload")) {
let hasher = Cc["@mozilla.org/security/hash;1"]
.createInstance(Ci.nsICryptoHash);
hasher.init(hash_algo);
CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
CryptoUtils.updateUTF8(contentType+"\n", hasher);
CryptoUtils.updateUTF8(options.payload, hasher);
CryptoUtils.updateUTF8("\n", hasher);
let hash = hasher.finish(false);
// HAWK specifies this .hash to include trailing "==" padding.
let hash_b64 = CommonUtils.encodeBase64URL(hash, true);
artifacts.hash = hash_b64;
}
let requestString = ("hawk.1.header" + "\n" +
artifacts.ts.toString(10) + "\n" +
artifacts.nonce + "\n" +
artifacts.method + "\n" +
artifacts.resource + "\n" +
artifacts.host + "\n" +
artifacts.port + "\n" +
(artifacts.hash || "") + "\n");
if (artifacts.ext) {
requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
}
requestString += "\n";
let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
CryptoUtils.makeHMACKey(credentials.key));
artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
// The output MAC uses "+" and "/", and padded== .
function escape(attribute) {
// This is used for "x=y" attributes inside HTTP headers.
return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
}
let header = ('Hawk id="' + credentials.id + '", ' +
'ts="' + artifacts.ts + '", ' +
'nonce="' + artifacts.nonce + '", ' +
(artifacts.hash ? ('hash="' + artifacts.hash + '", ') : "") +
(artifacts.ext ? ('ext="' + escape(artifacts.ext) + '", ') : "") +
'mac="' + artifacts.mac + '"');
return {
artifacts: artifacts,
field: header,
};
},
};
XPCOMUtils.defineLazyGetter(CryptoUtils, "_utf8Converter", function() {

View File

@ -0,0 +1,281 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-crypto/utils.js");
function run_test() {
initTestLogging();
run_next_test();
}
add_test(function test_hawk() {
let compute = CryptoUtils.computeHAWK;
// vectors copied from the HAWK (node.js) tests
let credentials_sha1 = {
id: "123456",
key: "2983d45yun89q",
algorithm: "sha1",
};
let method = "POST";
let ts = 1353809207;
let nonce = "Ygvqdz";
let result;
let uri_http = CommonUtils.makeURI("http://example.net/somewhere/over/the/rainbow");
let sha1_opts = { credentials: credentials_sha1,
ext: "Bazinga!",
ts: ts,
nonce: nonce,
payload: "something to write about",
};
result = compute(uri_http, method, sha1_opts);
// The HAWK spec uses non-urlsafe base64 (+/) for its output MAC string.
do_check_eq(result.field,
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
'hash="bsvY3IfUllw6V5rvk4tStEvpBhE=", ext="Bazinga!", ' +
'mac="qbf1ZPG/r/e06F4ht+T77LXi5vw="'
);
do_check_eq(result.artifacts.ts, ts);
do_check_eq(result.artifacts.nonce, nonce);
do_check_eq(result.artifacts.method, method);
do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
do_check_eq(result.artifacts.host, "example.net");
do_check_eq(result.artifacts.port, 80);
// artifacts.hash is the *payload* hash, not the overall request MAC.
do_check_eq(result.artifacts.hash, "bsvY3IfUllw6V5rvk4tStEvpBhE=");
do_check_eq(result.artifacts.ext, "Bazinga!");
let credentials_sha256 = {
id: "123456",
key: "2983d45yun89q",
algorithm: "sha256",
};
let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
let sha256_opts = { credentials: credentials_sha256,
ext: "Bazinga!",
ts: ts,
nonce: nonce,
payload: "something to write about",
contentType: "text/plain",
};
result = compute(uri_https, method, sha256_opts);
do_check_eq(result.field,
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
'ext="Bazinga!", ' +
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
);
do_check_eq(result.artifacts.ts, ts);
do_check_eq(result.artifacts.nonce, nonce);
do_check_eq(result.artifacts.method, method);
do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
do_check_eq(result.artifacts.host, "example.net");
do_check_eq(result.artifacts.port, 443);
do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
do_check_eq(result.artifacts.ext, "Bazinga!");
let sha256_opts_noext = { credentials: credentials_sha256,
ts: ts,
nonce: nonce,
payload: "something to write about",
contentType: "text/plain",
};
result = compute(uri_https, method, sha256_opts_noext);
do_check_eq(result.field,
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="'
);
do_check_eq(result.artifacts.ts, ts);
do_check_eq(result.artifacts.nonce, nonce);
do_check_eq(result.artifacts.method, method);
do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
do_check_eq(result.artifacts.host, "example.net");
do_check_eq(result.artifacts.port, 443);
do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
/* Leaving optional fields out should work, although of course then we can't
* assert much about the resulting hashes. The resulting header should look
* roughly like:
* Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
*/
result = compute(uri_https, method, { credentials: credentials_sha256 });
let fields = result.field.split(" ");
do_check_eq(fields[0], "Hawk");
do_check_eq(fields[1], 'id="123456",'); // from creds.id
do_check_true(fields[2].startsWith('ts="'));
/* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch.
* Warning: this test will fail in the year 33658, and for time travellers
* who journey earlier than 2001. Please plan accordingly. */
do_check_true(result.artifacts.ts > 1000*1000*1000);
do_check_true(result.artifacts.ts < 1000*1000*1000*1000);
do_check_true(fields[3].startsWith('nonce="'));
do_check_eq(fields[3].length, ('nonce="12345678901=",').length);
do_check_eq(result.artifacts.nonce.length, ("12345678901=").length);
let result2 = compute(uri_https, method, { credentials: credentials_sha256 });
do_check_neq(result.artifacts.nonce, result2.artifacts.nonce);
/* Using an upper-case URI hostname shouldn't affect the hash. */
let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
result = compute(uri_https_upper, method, sha256_opts);
do_check_eq(result.field,
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
'ext="Bazinga!", ' +
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
);
/* Using a lower-case method name shouldn't affect the hash. */
result = compute(uri_https_upper, method.toLowerCase(), sha256_opts);
do_check_eq(result.field,
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
'ext="Bazinga!", ' +
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
);
/* The localtimeOffsetMsec field should be honored. HAWK uses this to
* compensate for clock skew between client and server: if the request is
* rejected with a timestamp out-of-range error, the error includes the
* server's time, and the client computes its clock offset and tries again.
* Clients can remember this offset for a while.
*/
result = compute(uri_https, method, { credentials: credentials_sha256,
now: 1378848968650,
});
do_check_eq(result.artifacts.ts, 1378848968);
result = compute(uri_https, method, { credentials: credentials_sha256,
now: 1378848968650,
localtimeOffsetMsec: 1000*1000,
});
do_check_eq(result.artifacts.ts, 1378848968 + 1000);
/* Search/query-args in URIs should be included in the hash. */
let makeURI = CommonUtils.makeURI;
result = compute(makeURI("http://example.net/path"), method, sha256_opts);
do_check_eq(result.artifacts.resource, "/path");
do_check_eq(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
result = compute(makeURI("http://example.net/path/"), method, sha256_opts);
do_check_eq(result.artifacts.resource, "/path/");
do_check_eq(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
result = compute(makeURI("http://example.net/path?query=search"), method, sha256_opts);
do_check_eq(result.artifacts.resource, "/path?query=search");
do_check_eq(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
/* Test handling of the payload, which is supposed to be a bytestring
(String with codepoints from U+0000 to U+00FF, pre-encoded). */
result = compute(makeURI("http://example.net/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
});
do_check_eq(result.artifacts.hash, undefined);
do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
result = compute(makeURI("http://example.net/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
payload: "hello",
});
do_check_eq(result.artifacts.hash, "uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA=");
do_check_eq(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
// update, utf-8 payload
result = compute(makeURI("http://example.net/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
payload: "andré@example.org", // non-ASCII
});
do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
/* If "hash" is provided, "payload" is ignored. */
result = compute(makeURI("http://example.net/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
payload: "something else",
});
do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
/* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes
* "ëxample.net" into "xn--xample-ova.net" before hashing. I still think
* punycode was a bad joke that got out of the lab and into a spec.
*/
result = compute(makeURI("http://ëxample.net/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
});
do_check_eq(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
do_check_eq(result.artifacts.host, "xn--xample-ova.net");
result = compute(makeURI("http://example.net/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
ext: "backslash=\\ quote=\" EOF",
});
do_check_eq(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
result = compute(makeURI("http://example.net:1234/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
});
do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
/* HAWK (the node.js library) uses a URL parser which stores the "port"
* field as a string, but makeURI() gives us an integer. So we'll diverge
* on ports with a leading zero. This test vector would fail on the node.js
* library (HAWK-1.1.1), where they get a MAC of
* "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be
* updated to do what we do here, so port="01234" should get the same hash
* as port="1234".
*/
result = compute(makeURI("http://example.net:01234/path"), method,
{ credentials: credentials_sha256,
ts: 1353809207,
nonce: "Ygvqdz",
});
do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
run_next_test();
});
add_test(function test_strip_header_attributes() {
let strip = CryptoUtils.stripHeaderAttributes;
do_check_eq(strip(undefined), "");
do_check_eq(strip("text/plain"), "text/plain");
do_check_eq(strip("TEXT/PLAIN"), "text/plain");
do_check_eq(strip(" text/plain "), "text/plain");
do_check_eq(strip("text/plain ; charset=utf-8 "), "text/plain");
run_next_test();
});

View File

@ -11,6 +11,7 @@ firefox-appdir = browser
# Bug 676977: test hangs consistently on Android
skip-if = os == "android"
[test_utils_hawk.js]
[test_utils_hkdfExpand.js]
[test_utils_httpmac.js]
[test_utils_pbkdf2.js]