/** * Generate a fresh meta/global record. * * @return meta/global record. */ public MetaGlobal generateNewMetaGlobal() { final String newSyncID = Utils.generateGuid(); final String metaURL = this.config.metaURL(); ExtendedJSONObject engines = new ExtendedJSONObject(); for (String engineName : enabledEngineNames()) { EngineSettings engineSettings = null; try { GlobalSyncStage globalStage = this.getSyncStageByName(engineName); Integer version = globalStage.getStorageVersion(); if (version == null) { continue; // Don't want this stage to be included in meta/global. } engineSettings = new EngineSettings(Utils.generateGuid(), version.intValue()); } catch (NoSuchStageException e) { // No trouble; Android Sync might not recognize this engine yet. // By default, version 0. Other clients will see the 0 version and reset/wipe accordingly. engineSettings = new EngineSettings(Utils.generateGuid(), 0); } engines.put(engineName, engineSettings.toJSONObject()); } MetaGlobal metaGlobal = new MetaGlobal(metaURL, this.getAuthHeaderProvider()); metaGlobal.setSyncID(newSyncID); metaGlobal.setStorageVersion(STORAGE_VERSION); metaGlobal.setEngines(engines); return metaGlobal; }
public InfoCollections(final ExtendedJSONObject record) { Logger.debug(LOG_TAG, "info/collections is " + record.toJSONString()); HashMap<String, Long> map = new HashMap<String, Long>(); for (Entry<String, Object> entry : record.entrySet()) { final String key = entry.getKey(); final Object value = entry.getValue(); // These objects are most likely going to be Doubles. Regardless, we // want to get them in a more sane time format. if (value instanceof Double) { map.put(key, Utils.decimalSecondsToMilliseconds((Double) value)); continue; } if (value instanceof Long) { map.put(key, Utils.decimalSecondsToMilliseconds((Long) value)); continue; } if (value instanceof Integer) { map.put(key, Utils.decimalSecondsToMilliseconds((Integer) value)); continue; } Logger.warn(LOG_TAG, "Skipping info/collections entry for " + key); } this.timestamps = Collections.unmodifiableMap(map); }
@Test public void testEncodeDecodeRandomSizeArrays() { for (int i = 0; i < 10; i++) { int length1 = Utils.generateBigIntegerLessThan(BigInteger.valueOf(50)).intValue() + 10; int length2 = Utils.generateBigIntegerLessThan(BigInteger.valueOf(50)).intValue() + 10; doTestEncodeDecodeArrays(length1, length2); } }
public User(String email, byte[] quickStretchedPW) { this.email = email; this.quickStretchedPW = quickStretchedPW; this.uid = "uid/" + this.email; this.verified = false; this.kA = Utils.generateRandomBytes(FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES); this.wrapkB = Utils.generateRandomBytes(FxAccountUtils.CRYPTO_KEY_LENGTH_BYTES); this.devices = new HashMap<String, FxAccountDevice>(); }
@Test public void testUploadUpdatedMetaGlobal() throws Exception { // Set up session with meta/global. final MockGlobalSessionCallback callback = new MockGlobalSessionCallback(); final GlobalSession session = MockPrefsGlobalSession.getSession( TEST_USERNAME, TEST_PASSWORD, new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback, null, null); session.config.metaGlobal = session.generateNewMetaGlobal(); session.enginesToUpdate.clear(); // Set enabledEngines in meta/global, including a "new engine." String[] origEngines = new String[] {"bookmarks", "clients", "forms", "history", "tabs", "new-engine"}; ExtendedJSONObject origEnginesJSONObject = new ExtendedJSONObject(); for (String engineName : origEngines) { EngineSettings mockEngineSettings = new EngineSettings(Utils.generateGuid(), Integer.valueOf(0)); origEnginesJSONObject.put(engineName, mockEngineSettings); } session.config.metaGlobal.setEngines(origEnginesJSONObject); // Engines to remove. String[] toRemove = new String[] {"bookmarks", "tabs"}; for (String name : toRemove) { session.removeEngineFromMetaGlobal(name); } // Engines to add. String[] toAdd = new String[] {"passwords"}; for (String name : toAdd) { String syncId = Utils.generateGuid(); session.recordForMetaGlobalUpdate(name, new EngineSettings(syncId, Integer.valueOf(1))); } // Update engines. session.uploadUpdatedMetaGlobal(); // Check resulting enabledEngines. Set<String> expected = new HashSet<String>(); for (String name : origEngines) { expected.add(name); } for (String name : toRemove) { expected.remove(name); } for (String name : toAdd) { expected.add(name); } assertEquals(expected, session.config.metaGlobal.getEnabledEngineNames()); }
public void doTestEncodeDecodeArrays(int length1, int length2) { if (4 + length1 + length2 > 127) { throw new IllegalArgumentException("Total length must be < 128 - 4."); } byte[] first = Utils.generateRandomBytes(length1); byte[] second = Utils.generateRandomBytes(length2); byte[] encoded = ASNUtils.encodeTwoArraysToASN1(first, second); byte[][] arrays = ASNUtils.decodeTwoArraysFromASN1(encoded); Assert.assertArrayEquals(first, arrays[0]); Assert.assertArrayEquals(second, arrays[1]); }
protected LoginResponse addLogin(User user, byte[] sessionToken, byte[] keyFetchToken) { // byte[] sessionToken = Utils.generateRandomBytes(8); if (sessionToken != null) { sessionTokens.put(Utils.byte2Hex(sessionToken), user.email); } // byte[] keyFetchToken = Utils.generateRandomBytes(8); if (keyFetchToken != null) { keyFetchTokens.put(Utils.byte2Hex(keyFetchToken), user.email); } return new LoginResponse(user.email, user.uid, user.verified, sessionToken, keyFetchToken); }
public GlobalSession( SyncConfiguration config, BaseGlobalSessionCallback callback, Context context, Bundle extras, ClientsDataDelegate clientsDelegate, NodeAssignmentCallback nodeAssignmentCallback) throws SyncConfigurationException, IllegalArgumentException, IOException, ParseException, NonObjectJSONException { if (callback == null) { throw new IllegalArgumentException("Must provide a callback to GlobalSession constructor."); } Logger.debug(LOG_TAG, "GlobalSession initialized with bundle " + extras); this.callback = callback; this.context = context; this.clientsDelegate = clientsDelegate; this.nodeAssignmentCallback = nodeAssignmentCallback; this.config = config; registerCommands(); prepareStages(); Collection<String> knownStageNames = SyncConfiguration.validEngineNames(); config.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras); // TODO: data-driven plan for the sync, referring to prepareStages. }
/** Return a hex-encoded string value as a byte array. */ public byte[] getByteArrayHex(String key) { String s = (String) this.object.get(key); if (s == null) { return null; } return Utils.hex2Byte(s); }
/** This needs to return a string because of the tortured prefs access in GlobalSession. */ public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException { String profile = getProfile(); String username = account.name; if (profile == null) { throw new IllegalStateException("Missing profile. Cannot fetch prefs."); } if (username == null) { throw new IllegalStateException("Missing username. Cannot fetch prefs."); } final String tokenServerURI = getTokenServerURI(); if (tokenServerURI == null) { throw new IllegalStateException("No token server URI. Cannot fetch prefs."); } final String fxaServerURI = getAccountServerURI(); if (fxaServerURI == null) { throw new IllegalStateException("No account server URI. Cannot fetch prefs."); } final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".fxa"; final long version = CURRENT_PREFS_VERSION; // This is unique for each syncing 'view' of the account. final String serverURLThing = fxaServerURI + "!" + tokenServerURI; return Utils.getPrefsPath(product, username, serverURLThing, profile, version); }
@SuppressWarnings("static-method") @Test public void testUTF8() throws UnsupportedEncodingException { final String in = "pïgéons1"; final String out = "pïgéons1"; assertEquals(out, Utils.decodeUTF8(in)); }
/** * The timestamp returned from a Sync server is a decimal number of seconds, e.g., 1323393518.04. * * <p>We want milliseconds since epoch. * * @return milliseconds since the epoch, as a long, or -1 if the header was missing or invalid. */ public long normalizedWeaveTimestamp() { String h = "x-weave-timestamp"; if (!this.hasHeader(h)) { return -1; } return Utils.decimalSecondsToMilliseconds(this.response.getFirstHeader(h).getValue()); }
public static byte[] hex2Byte(String str, int byteLength) { byte[] second = hex2Byte(str); if (second.length >= byteLength) { return second; } // New Java arrays are zeroed: // http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5 byte[] first = new byte[byteLength - second.length]; return Utils.concatAll(first, second); }
/** * Extract a JSON dictionary of the string values associated to this account. * * <p><b>For debugging use only!</b> The contents of this JSON object completely determine the * user's Firefox Account status and yield access to whatever user data the device has access to. * * @return JSON-object of Strings. */ public ExtendedJSONObject toJSONObject() { ExtendedJSONObject o = unbundle(); o.put("email", account.name); try { o.put("emailUTF8", Utils.byte2Hex(account.name.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e) { // Ignore. } return o; }
/** * Asynchronously request an immediate sync, optionally syncing only the given named stages. * * <p>Returns immediately. * * @param account the Android <code>Account</code> instance to sync. * @param stageNames stage names to sync, or <code>null</code> to sync all known stages. */ public static void requestImmediateSync(final Account account, final String[] stageNames) { if (account == null) { Logger.warn(LOG_TAG, "Not requesting immediate sync because Android Account is null."); return; } final Bundle extras = new Bundle(); Utils.putStageNamesToSync(extras, stageNames, null); extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); ContentResolver.requestSync(account, BrowserContract.AUTHORITY, extras); }
protected void linkifyOldFirefoxLink() { TextView oldFirefox = (TextView) findViewById(R.id.old_firefox); String text = getResources().getString(R.string.fxaccount_getting_started_old_firefox); String VERSION = AppConstants.MOZ_APP_VERSION; String OS = AppConstants.OS_TARGET; // We'll need to adjust this when we have active locale switching. String LOCALE = Utils.getLanguageTag(Locale.getDefault()); String url = getResources().getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); FxAccountConstants.pii( LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular. ActivityUtils.linkTextView(oldFirefox, text, url); }
@Test public void testAuthHeaderFromPassword() throws NonObjectJSONException, IOException, ParseException { final ExtendedJSONObject parsed = new ExtendedJSONObject(DESKTOP_PASSWORD_JSON); final String password = parsed.getString("password"); final String decoded = Utils.decodeUTF8(password); final byte[] expectedBytes = Utils.decodeBase64(BTOA_PASSWORD); final String expected = new String(expectedBytes, "UTF-8"); assertEquals(DESKTOP_ASSERTED_SIZE, password.length()); assertEquals(expected, decoded); System.out.println("Retrieved password: "******"Expected password: "******"Rescued password: " + decoded); assertEquals(getCreds(expected), getCreds(decoded)); assertEquals(getCreds(decoded), DESKTOP_BASIC_AUTH); }
@Override public synchronized String getAccountGUID() { String accountGUID = accountSharedPreferences.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null); if (accountGUID == null) { Logger.debug(LOG_TAG, "Account GUID was null. Creating a new one."); accountGUID = Utils.generateGuid(); accountSharedPreferences .edit() .putString(SyncConfiguration.PREF_ACCOUNT_GUID, accountGUID) .commit(); } return accountGUID; }
/** * Get names of stages to sync: (ALL intersect SYNC) intersect (ALL minus SKIP). * * @param knownStageNames collection of known stage names (set ALL above). * @param extras a <code>Bundle</code> instance (possibly null) optionally containing keys <code> * EXTRAS_KEY_STAGES_TO_SYNC</code> (set SYNC above) and <code>EXTRAS_KEY_STAGES_TO_SKIP * </code> (set SKIP above). * @return stage names. */ public static Collection<String> getStagesToSyncFromBundle( final Collection<String> knownStageNames, final Bundle extras) { if (extras == null) { return knownStageNames; } String toSyncString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SYNC); String toSkipString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SKIP); if (toSyncString == null && toSkipString == null) { return knownStageNames; } ArrayList<String> toSync = null; ArrayList<String> toSkip = null; if (toSyncString != null) { try { toSync = new ArrayList<String>(ExtendedJSONObject.parseJSONObject(toSyncString).keySet()); } catch (Exception e) { Logger.warn(LOG_TAG, "Got exception parsing stages to sync: '" + toSyncString + "'.", e); } } if (toSkipString != null) { try { toSkip = new ArrayList<String>(ExtendedJSONObject.parseJSONObject(toSkipString).keySet()); } catch (Exception e) { Logger.warn(LOG_TAG, "Got exception parsing stages to skip: '" + toSkipString + "'.", e); } } Logger.info( LOG_TAG, "Asked to sync '" + Utils.toCommaSeparatedString(toSync) + "' and to skip '" + Utils.toCommaSeparatedString(toSkip) + "'."); return getStagesToSync(knownStageNames, toSync, toSkip); }
@Override public void recoveryEmailStatus( byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> requestDelegate) { String email = sessionTokens.get(Utils.byte2Hex(sessionToken)); User user = users.get(email); if (email == null || user == null) { handleFailure( requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken"); return; } requestDelegate.handleSuccess(new RecoveryEmailStatusResponse(email, user.verified)); }
public FxAccount10CreateDelegate( String email, byte[] stretchedPWBytes, String mainSalt, String srpSalt) throws NoSuchAlgorithmException, UnsupportedEncodingException { this.email = email; this.mainSalt = mainSalt; this.srpSalt = srpSalt; byte[] srpSaltBytes = Utils.hex2Byte(srpSalt, FxAccountUtils.SALT_LENGTH_BYTES); this.v = FxAccountUtils.srpVerifierLowercaseV( email.getBytes("UTF-8"), stretchedPWBytes, srpSaltBytes, SRPConstants._2048.g, SRPConstants._2048.N); }
@SuppressWarnings("unchecked") private void finishUp() { try { flushQueues(); Logger.debug( LOG_TAG, "Have " + parentToChildArray.size() + " folders whose children might need repositioning."); for (Entry<String, JSONArray> entry : parentToChildArray.entrySet()) { String guid = entry.getKey(); JSONArray onServer = entry.getValue(); try { final long folderID = getIDForGUID(guid); final JSONArray inDB = new JSONArray(); final boolean clean = getChildrenArray(folderID, false, inDB); final boolean sameArrays = Utils.sameArrays(onServer, inDB); // If the local children and the remote children are already // the same, then we don't need to bump the modified time of the // parent: we wouldn't upload a different record, so avoid the cycle. if (!sameArrays) { int added = 0; for (Object o : inDB) { if (!onServer.contains(o)) { onServer.add(o); added++; } } Logger.debug(LOG_TAG, "Added " + added + " items locally."); Logger.debug(LOG_TAG, "Untracking and bumping " + guid + "(" + folderID + ")"); dataAccessor.bumpModified(folderID, now()); untrackGUID(guid); } // If the arrays are different, or they're the same but not flushed to disk, // write them out now. if (!sameArrays || !clean) { dataAccessor.updatePositions(new ArrayList<String>(onServer)); } } catch (Exception e) { Logger.warn(LOG_TAG, "Error repositioning children for " + guid, e); } } } finally { super.storeDone(); } }
public boolean hasUpdatedMetaGlobal() { if (enginesToUpdate.isEmpty()) { Logger.info( LOG_TAG, "Not uploading updated meta/global record since there are no engines requesting upload."); return false; } if (Logger.shouldLogVerbose(LOG_TAG)) { Logger.trace( LOG_TAG, "Uploading updated meta/global record since there are engine changes to meta/global."); Logger.trace( LOG_TAG, "Engines requesting update [" + Utils.toCommaSeparatedString(enginesToUpdate.keySet()) + "]"); } return true; }
@Override public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate) { String email = keyFetchTokens.get(Utils.byte2Hex(keyFetchToken)); User user = users.get(email); if (email == null || user == null) { handleFailure( requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid keyFetchToken"); return; } if (!user.verified) { handleFailure( requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified"); return; } requestDelegate.handleSuccess(new TwoKeys(user.kA, user.wrapkB)); }
private String constructPrefsPath(String product, long version, String extra) throws GeneralSecurityException, UnsupportedEncodingException { String profile = getProfile(); String username = account.name; if (profile == null) { throw new IllegalStateException("Missing profile. Cannot fetch prefs."); } if (username == null) { throw new IllegalStateException("Missing username. Cannot fetch prefs."); } final String fxaServerURI = getAccountServerURI(); if (fxaServerURI == null) { throw new IllegalStateException("No account server URI. Cannot fetch prefs."); } // This is unique for each syncing 'view' of the account. final String serverURLThing = fxaServerURI + "!" + extra; return Utils.getPrefsPath(product, username, serverURLThing, profile, version); }
protected synchronized EventDispatcherImpl registerNewInstance(String classname, String filename) throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IOException { Log.d(LOGTAG, "Attempting to instantiate " + classname + "from filename " + filename); // It's important to maintain the extension, either .dex, .apk, .jar. final String extension = getExtension(filename); final File dexFile = GeckoJarReader.extractStream( mApplicationContext, filename, mApplicationContext.getCacheDir(), "." + extension); try { if (dexFile == null) { throw new IOException("Could not find file " + filename); } final File tmpDir = mApplicationContext.getDir("dex", 0); // We'd prefer getCodeCacheDir but it's API 21+. final DexClassLoader loader = new DexClassLoader( dexFile.getAbsolutePath(), tmpDir.getAbsolutePath(), null, mApplicationContext.getClassLoader()); final Class<?> c = loader.loadClass(classname); final Constructor<?> constructor = c.getDeclaredConstructor(Context.class, JavaAddonInterfaceV1.EventDispatcher.class); final String guid = Utils.generateGuid(); final EventDispatcherImpl dispatcher = new EventDispatcherImpl(guid, filename); final Object instance = constructor.newInstance(mApplicationContext, dispatcher); mGUIDToDispatcherMap.put(guid, dispatcher); return dispatcher; } finally { // DexClassLoader writes an optimized version, so we can get rid of our temporary extracted // version. if (dexFile != null) { dexFile.delete(); } } }
protected void showClientRemoteException(final FxAccountClientRemoteException e) { if (!e.isAccountLocked()) { remoteErrorTextView.setText(e.getErrorMessageStringResource()); return; } // This horrible bit of special-casing is because we want this error message // to contain a clickable, extra chunk of text, but we don't want to pollute // the exception class with Android specifics. final int messageId = e.getErrorMessageStringResource(); final int clickableId = R.string.fxaccount_resend_unlock_code_button_label; final Spannable span = Utils.interpolateClickableSpan( this, messageId, clickableId, new ClickableSpan() { @Override public void onClick(View widget) { // It would be best to capture the email address sent to the server // and use it here, but this will do for now. If the user modifies // the email address entered, the error text is hidden, so sending a // changed email address would be the result of an unusual race. final String email = emailEdit.getText().toString(); byte[] emailUTF8 = null; try { emailUTF8 = email.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { // It's okay, we'll fail in the code resender. } FxAccountUnlockCodeResender.resendUnlockCode( FxAccountAbstractSetupActivity.this, getAuthServerEndpoint(), emailUTF8); } }); remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance()); remoteErrorTextView.setText(span); }
// Add a bookmark in the Desktop folder so we can check the folder navigation in the bookmarks // page private void setUpDesktopBookmarks() { blockForGeckoReady(); // Get the folder id of the StringHelper.DESKTOP_FOLDER_LABEL folder Long desktopFolderId = mDatabaseHelper.getFolderIdFromGuid("toolbar"); // Generate a Guid for the bookmark final String generatedGuid = Utils.generateGuid(); mAsserter.ok( (generatedGuid != null), "Generating a random Guid for the bookmark", "We could not generate a Guid for the bookmark"); // Insert the bookmark ContentResolver resolver = getActivity().getContentResolver(); Uri bookmarksUri = mDatabaseHelper.buildUri(DatabaseHelper.BrowserDataType.BOOKMARKS); long now = System.currentTimeMillis(); ContentValues values = new ContentValues(); values.put("title", StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE); values.put("url", DESKTOP_BOOKMARK_URL); values.put("parent", desktopFolderId); values.put("modified", now); values.put("type", 1); values.put("guid", generatedGuid); values.put("position", 10); values.put("created", now); int updated = resolver.update(bookmarksUri, values, "url = ?", new String[] {DESKTOP_BOOKMARK_URL}); if (updated == 0) { Uri uri = resolver.insert(bookmarksUri, values); mAsserter.ok(true, "Inserted at: ", uri.toString()); } else { mAsserter.ok(false, "Failed to insert the Desktop bookmark", "Something went wrong"); } }
@Override public void sign( byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate) { String email = sessionTokens.get(Utils.byte2Hex(sessionToken)); User user = users.get(email); if (email == null || user == null) { handleFailure( requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken"); return; } if (!user.verified) { handleFailure( requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified"); return; } try { final long iat = System.currentTimeMillis(); final long dur = certificateDurationInMilliseconds; final long exp = iat + dur; String certificate = mockMyIdTokenFactory.createMockMyIDCertificate( RSACryptoImplementation.createPublicKey(publicKey), "test", iat, exp); requestDelegate.handleSuccess(certificate); } catch (Exception e) { requestDelegate.handleError(e); } }
@Override public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate) { String email = sessionTokens.get(Utils.byte2Hex(sessionToken)); User user = users.get(email); if (email == null || user == null) { handleFailure( requestDelegate, HttpStatus.SC_UNAUTHORIZED, FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN, "invalid sessionToken"); return; } if (!user.verified) { handleFailure( requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified"); return; } Collection<FxAccountDevice> devices = user.devices.values(); FxAccountDevice[] devicesArray = devices.toArray(new FxAccountDevice[devices.size()]); requestDelegate.handleSuccess(devicesArray); }