Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/services/sync/tests/unit/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 32 kB image not shown  

Quelle  test_sync_auth_manager.js   Sprache: JAVA

 
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */


const { AuthenticationError, SyncAuthManager } = ChromeUtils.importESModule(
  "resource://services-sync/sync_auth.sys.mjs"
);
const { Resource } = ChromeUtils.importESModule(
  "resource://services-sync/resource.sys.mjs"
);
const { initializeIdentityWithTokenServerResponse } =
  ChromeUtils.importESModule(
    "resource://testing-common/services/sync/fxa_utils.sys.mjs"
  );
const { HawkClient } = ChromeUtils.importESModule(
  "resource://services-common/hawkclient.sys.mjs"
);
const { FxAccounts } = ChromeUtils.importESModule(
  "resource://gre/modules/FxAccounts.sys.mjs"
);
const { FxAccountsClient } = ChromeUtils.importESModule(
  "resource://gre/modules/FxAccountsClient.sys.mjs"
);
const {
  ERRNO_INVALID_AUTH_TOKEN,
  ONLOGIN_NOTIFICATION,
  ONVERIFIED_NOTIFICATION,
  SCOPE_APP_SYNC,
} = ChromeUtils.importESModule(
  "resource://gre/modules/FxAccountsCommon.sys.mjs"
);
const { Service } = ChromeUtils.importESModule(
  "resource://services-sync/service.sys.mjs"
);
const { Status } = ChromeUtils.importESModule(
  "resource://services-sync/status.sys.mjs"
);
const { TokenServerClient, TokenServerClientServerError } =
  ChromeUtils.importESModule(
    "resource://services-common/tokenserverclient.sys.mjs"
  );
const { AccountState, ERROR_INVALID_ACCOUNT_STATE } =
  ChromeUtils.importESModule("resource://gre/modules/FxAccounts.sys.mjs");

const SECOND_MS = 1000;
const MINUTE_MS = SECOND_MS * 60;
const HOUR_MS = MINUTE_MS * 60;

const MOCK_ACCESS_TOKEN =
  "e3c5caf17f27a0d9e351926a928938b3737df43e91d4992a5a5fca9a7bdef8ba";

var globalIdentityConfig = makeIdentityConfig();
var globalSyncAuthManager = new SyncAuthManager();
configureFxAccountIdentity(globalSyncAuthManager, globalIdentityConfig);

/**
 * Mock client clock and skew vs server in FxAccounts signed-in user module and
 * API client.  sync_auth.js queries these values to construct HAWK
 * headers.  We will use this to test clock skew compensation in these headers
 * below.
 */

var MockFxAccountsClient = function () {
  FxAccountsClient.apply(this);
};
MockFxAccountsClient.prototype = {
  accountStatus() {
    return Promise.resolve(true);
  },
  getScopedKeyData() {
    return Promise.resolve({
      [SCOPE_APP_SYNC]: {
        identifier: SCOPE_APP_SYNC,
        keyRotationSecret:
          "0000000000000000000000000000000000000000000000000000000000000000",
        keyRotationTimestamp: 1234567890123,
      },
    });
  },
};
Object.setPrototypeOf(
  MockFxAccountsClient.prototype,
  FxAccountsClient.prototype
);

add_test(function test_initial_state() {
  _("Verify initial state");
  Assert.ok(!globalSyncAuthManager._token);
  Assert.ok(!globalSyncAuthManager._hasValidToken());
  run_next_test();
});

add_task(async function test_initialialize() {
  _("Verify start after fetching token");
  await globalSyncAuthManager._ensureValidToken();
  Assert.ok(!!globalSyncAuthManager._token);
  Assert.ok(globalSyncAuthManager._hasValidToken());
});

add_task(async function test_refreshOAuthTokenOn401() {
  _("Refreshes the FXA OAuth token after a 401.");
  let getTokenCount = 0;
  let syncAuthManager = new SyncAuthManager();
  let identityConfig = makeIdentityConfig();
  let fxaInternal = makeFxAccountsInternalMock(identityConfig);
  configureFxAccountIdentity(syncAuthManager, identityConfig, fxaInternal);
  syncAuthManager._fxaService._internal.initialize();
  syncAuthManager._fxaService.getOAuthToken = () => {
    ++getTokenCount;
    return Promise.resolve(MOCK_ACCESS_TOKEN);
  };

  let didReturn401 = false;
  let didReturn200 = false;
  let mockTSC = mockTokenServer(() => {
    if (getTokenCount <= 1) {
      didReturn401 = true;
      return {
        status: 401,
        headers: { "content-type""application/json" },
        body: JSON.stringify({}),
      };
    }
    didReturn200 = true;
    return {
      status: 200,
      headers: { "content-type""application/json" },
      body: JSON.stringify({
        id: "id",
        key: "key",
        api_endpoint: "http://example.com/",
        uid: "uid",
        duration: 300,
      }),
    };
  });

  syncAuthManager._tokenServerClient = mockTSC;

  await syncAuthManager._ensureValidToken();

  Assert.equal(getTokenCount, 2);
  Assert.ok(didReturn401);
  Assert.ok(didReturn200);
  Assert.ok(syncAuthManager._token);
  Assert.ok(syncAuthManager._hasValidToken());
});

add_task(async function test_initialializeWithAuthErrorAndDeletedAccount() {
  _("Verify sync state with auth error + account deleted");

  var identityConfig = makeIdentityConfig();
  var syncAuthManager = new SyncAuthManager();

  // Use the real `getOAuthToken` method that calls
  // `mockFxAClient.accessTokenWithSessionToken`.
  let fxaInternal = makeFxAccountsInternalMock(identityConfig);
  delete fxaInternal.getOAuthToken;

  configureFxAccountIdentity(syncAuthManager, identityConfig, fxaInternal);
  syncAuthManager._fxaService._internal.initialize();

  let accessTokenWithSessionTokenCalled = false;
  let accountStatusCalled = false;
  let sessionStatusCalled = false;

  let AuthErrorMockFxAClient = function () {
    FxAccountsClient.apply(this);
  };
  AuthErrorMockFxAClient.prototype = {
    accessTokenWithSessionToken() {
      accessTokenWithSessionTokenCalled = true;
      return Promise.reject({
        code: 401,
        errno: ERRNO_INVALID_AUTH_TOKEN,
      });
    },
    accountStatus() {
      accountStatusCalled = true;
      return Promise.resolve(false);
    },
    sessionStatus() {
      sessionStatusCalled = true;
      return Promise.resolve(false);
    },
  };
  Object.setPrototypeOf(
    AuthErrorMockFxAClient.prototype,
    FxAccountsClient.prototype
  );

  let mockFxAClient = new AuthErrorMockFxAClient();
  syncAuthManager._fxaService._internal._fxAccountsClient = mockFxAClient;

  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    err => {
      Assert.equal(err.message, ERROR_INVALID_ACCOUNT_STATE);
      return true// expected error
    },
    "should reject because the account was deleted"
  );

  Assert.ok(accessTokenWithSessionTokenCalled);
  Assert.ok(sessionStatusCalled);
  Assert.ok(accountStatusCalled);
  Assert.ok(!syncAuthManager._token);
  Assert.ok(!syncAuthManager._hasValidToken());
});

add_task(async function test_getResourceAuthenticator() {
  _(
    "SyncAuthManager supplies a Resource Authenticator callback which returns a Hawk header."
  );
  configureFxAccountIdentity(globalSyncAuthManager);
  let authenticator = globalSyncAuthManager.getResourceAuthenticator();
  Assert.ok(!!authenticator);
  let req = {
    uri: CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow"),
    method: "GET",
  };
  let output = await authenticator(req, "GET");
  Assert.ok("headers" in output);
  Assert.ok("authorization" in output.headers);
  Assert.ok(output.headers.authorization.startsWith("Hawk"));
  _("Expected internal state after successful call.");
  Assert.equal(
    globalSyncAuthManager._token.uid,
    globalIdentityConfig.fxaccount.token.uid
  );
});

add_task(async function test_resourceAuthenticatorSkew() {
  _(
    "SyncAuthManager Resource Authenticator compensates for clock skew in Hawk header."
  );

  // Clock is skewed 12 hours into the future
  // We pick a date in the past so we don't risk concealing bugs in code that
  // uses new Date() instead of our given date.
  let now =
    new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS;
  let syncAuthManager = new SyncAuthManager();
  let hawkClient = new HawkClient("https://example.net/v1", "/foo");

  // mock fxa hawk client skew
  hawkClient.now = function () {
    dump("mocked client now: " + now + "\n");
    return now;
  };
  // Imagine there's already been one fxa request and the hawk client has
  // already detected skew vs the fxa auth server.
  let localtimeOffsetMsec = -1 * 12 * HOUR_MS;
  hawkClient._localtimeOffsetMsec = localtimeOffsetMsec;

  let fxaClient = new MockFxAccountsClient();
  fxaClient.hawk = hawkClient;

  // Sanity check
  Assert.equal(hawkClient.now(), now);
  Assert.equal(hawkClient.localtimeOffsetMsec, localtimeOffsetMsec);

  // Properly picked up by the client
  Assert.equal(fxaClient.now(), now);
  Assert.equal(fxaClient.localtimeOffsetMsec, localtimeOffsetMsec);

  let identityConfig = makeIdentityConfig();
  let fxaInternal = makeFxAccountsInternalMock(identityConfig);
  fxaInternal._now_is = now;
  fxaInternal.fxAccountsClient = fxaClient;

  // Mocks within mocks...
  configureFxAccountIdentity(
    syncAuthManager,
    globalIdentityConfig,
    fxaInternal
  );

  Assert.equal(syncAuthManager._fxaService._internal.now(), now);
  Assert.equal(
    syncAuthManager._fxaService._internal.localtimeOffsetMsec,
    localtimeOffsetMsec
  );

  Assert.equal(syncAuthManager._fxaService._internal.now(), now);
  Assert.equal(
    syncAuthManager._fxaService._internal.localtimeOffsetMsec,
    localtimeOffsetMsec
  );

  let request = new Resource("https://example.net/i/like/pie/");
  let authenticator = syncAuthManager.getResourceAuthenticator();
  let output = await authenticator(request, "GET");
  dump("output" + JSON.stringify(output));
  let authHeader = output.headers.authorization;
  Assert.ok(authHeader.startsWith("Hawk"));

  // Skew correction is applied in the header and we're within the two-minute
  // window.
  Assert.equal(getTimestamp(authHeader), now - 12 * HOUR_MS);
  Assert.ok(getTimestampDelta(authHeader, now) - 12 * HOUR_MS < 2 * MINUTE_MS);
});

add_task(async function test_RESTResourceAuthenticatorSkew() {
  _(
    "SyncAuthManager REST Resource Authenticator compensates for clock skew in Hawk header."
  );

  // Clock is skewed 12 hours into the future from our arbitary date
  let now =
    new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS;
  let syncAuthManager = new SyncAuthManager();
  let hawkClient = new HawkClient("https://example.net/v1", "/foo");

  // mock fxa hawk client skew
  hawkClient.now = function () {
    return now;
  };
  // Imagine there's already been one fxa request and the hawk client has
  // already detected skew vs the fxa auth server.
  hawkClient._localtimeOffsetMsec = -1 * 12 * HOUR_MS;

  let fxaClient = new MockFxAccountsClient();
  fxaClient.hawk = hawkClient;

  let identityConfig = makeIdentityConfig();
  let fxaInternal = makeFxAccountsInternalMock(identityConfig);
  fxaInternal._now_is = now;
  fxaInternal.fxAccountsClient = fxaClient;

  configureFxAccountIdentity(
    syncAuthManager,
    globalIdentityConfig,
    fxaInternal
  );

  Assert.equal(syncAuthManager._fxaService._internal.now(), now);

  let request = new Resource("https://example.net/i/like/pie/");
  let authenticator = syncAuthManager.getResourceAuthenticator();
  let output = await authenticator(request, "GET");
  dump("output" + JSON.stringify(output));
  let authHeader = output.headers.authorization;
  Assert.ok(authHeader.startsWith("Hawk"));

  // Skew correction is applied in the header and we're within the two-minute
  // window.
  Assert.equal(getTimestamp(authHeader), now - 12 * HOUR_MS);
  Assert.ok(getTimestampDelta(authHeader, now) - 12 * HOUR_MS < 2 * MINUTE_MS);
});

add_task(async function test_ensureLoggedIn() {
  configureFxAccountIdentity(globalSyncAuthManager);
  await globalSyncAuthManager._ensureValidToken();
  Assert.equal(Status.login, LOGIN_SUCCEEDED, "original initialize worked");
  Assert.ok(globalSyncAuthManager._token);

  // arrange for no logged in user.
  let fxa = globalSyncAuthManager._fxaService;
  let signedInUser =
    fxa._internal.currentAccountState.storageManager.accountData;
  fxa._internal.currentAccountState.storageManager.accountData = null;
  await Assert.rejects(
    globalSyncAuthManager._ensureValidToken(true),
    /no user is logged in/,
    "expecting rejection due to no user"
  );
  // Restore the logged in user to what it was.
  fxa._internal.currentAccountState.storageManager.accountData = signedInUser;
  Status.login = LOGIN_FAILED_LOGIN_REJECTED;
  await globalSyncAuthManager._ensureValidToken(true);
  Assert.equal(Status.login, LOGIN_SUCCEEDED, "final ensureLoggedIn worked");
});

add_task(async function test_syncState() {
  // Avoid polling for an unverified user.
  let identityConfig = makeIdentityConfig();
  let fxaInternal = makeFxAccountsInternalMock(identityConfig);
  fxaInternal.startVerifiedCheck = () => {};
  configureFxAccountIdentity(
    globalSyncAuthManager,
    globalIdentityConfig,
    fxaInternal
  );

  // arrange for no logged in user.
  let fxa = globalSyncAuthManager._fxaService;
  let signedInUser =
    fxa._internal.currentAccountState.storageManager.accountData;
  fxa._internal.currentAccountState.storageManager.accountData = null;
  await Assert.rejects(
    globalSyncAuthManager._ensureValidToken(true),
    /no user is logged in/,
    "expecting rejection due to no user"
  );
  // Restore to an unverified user.
  Services.prefs.setStringPref("services.sync.username", signedInUser.email);
  signedInUser.verified = false;
  fxa._internal.currentAccountState.storageManager.accountData = signedInUser;
  Status.login = LOGIN_FAILED_LOGIN_REJECTED;
  // The sync_auth observers are async, so call them directly.
  await globalSyncAuthManager.observe(null, ONLOGIN_NOTIFICATION, "");
  Assert.equal(
    Status.login,
    LOGIN_FAILED_LOGIN_REJECTED,
    "should not have changed the login state for an unverified user"
  );

  // now pretend the user because verified.
  signedInUser.verified = true;
  await globalSyncAuthManager.observe(null, ONVERIFIED_NOTIFICATION, "");
  Assert.equal(
    Status.login,
    LOGIN_SUCCEEDED,
    "should have changed the login state to success"
  );
});

add_task(async function test_tokenExpiration() {
  _("SyncAuthManager notices token expiration:");
  let bimExp = new SyncAuthManager();
  configureFxAccountIdentity(bimExp, globalIdentityConfig);

  let authenticator = bimExp.getResourceAuthenticator();
  Assert.ok(!!authenticator);
  let req = {
    uri: CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow"),
    method: "GET",
  };
  await authenticator(req, "GET");

  // Mock the clock.
  _("Forcing the token to expire ...");
  Object.defineProperty(bimExp, "_now", {
    value: function customNow() {
      return Date.now() + 3000001;
    },
    writable: true,
  });
  Assert.ok(bimExp._token.expiration < bimExp._now());
  _("... means SyncAuthManager knows to re-fetch it on the next call.");
  Assert.ok(!bimExp._hasValidToken());
});

add_task(async function test_getTokenErrors() {
  _("SyncAuthManager correctly handles various failures to get a token.");

  _("Arrange for a 401 - Sync should reflect an auth error.");
  initializeIdentityWithTokenServerResponse({
    status: 401,
    headers: { "content-type""application/json" },
    body: JSON.stringify({}),
  });
  let syncAuthManager = Service.identity;

  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    AuthenticationError,
    "should reject due to 401"
  );
  Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");

  // XXX - other interesting responses to return?

  // And for good measure, some totally "unexpected" errors - we generally
  // assume these problems are going to magically go away at some point.
  _(
    "Arrange for an empty body with a 200 response - should reflect a network error."
  );
  initializeIdentityWithTokenServerResponse({
    status: 200,
    headers: [],
    body: "",
  });
  syncAuthManager = Service.identity;
  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    TokenServerClientServerError,
    "should reject due to non-JSON response"
  );
  Assert.equal(
    Status.login,
    LOGIN_FAILED_NETWORK_ERROR,
    "login state is LOGIN_FAILED_NETWORK_ERROR"
  );
});

add_task(async function test_refreshAccessTokenOn401() {
  _("SyncAuthManager refreshes the FXA OAuth access token after a 401.");
  var identityConfig = makeIdentityConfig();
  var syncAuthManager = new SyncAuthManager();
  // Use the real `getOAuthToken` method that calls
  // `mockFxAClient.accessTokenWithSessionToken`.
  let fxaInternal = makeFxAccountsInternalMock(identityConfig);
  delete fxaInternal.getOAuthToken;
  configureFxAccountIdentity(syncAuthManager, identityConfig, fxaInternal);
  syncAuthManager._fxaService._internal.initialize();

  let getTokenCount = 0;

  let CheckSignMockFxAClient = function () {
    FxAccountsClient.apply(this);
  };
  CheckSignMockFxAClient.prototype = {
    accessTokenWithSessionToken() {
      ++getTokenCount;
      return Promise.resolve({ access_token: "token" });
    },
  };
  Object.setPrototypeOf(
    CheckSignMockFxAClient.prototype,
    FxAccountsClient.prototype
  );

  let mockFxAClient = new CheckSignMockFxAClient();
  syncAuthManager._fxaService._internal._fxAccountsClient = mockFxAClient;

  let didReturn401 = false;
  let didReturn200 = false;
  let mockTSC = mockTokenServer(() => {
    if (getTokenCount <= 1) {
      didReturn401 = true;
      return {
        status: 401,
        headers: { "content-type""application/json" },
        body: JSON.stringify({}),
      };
    }
    didReturn200 = true;
    return {
      status: 200,
      headers: { "content-type""application/json" },
      body: JSON.stringify({
        id: "id",
        key: "key",
        api_endpoint: "http://example.com/",
        uid: "uid",
        duration: 300,
      }),
    };
  });

  syncAuthManager._tokenServerClient = mockTSC;

  await syncAuthManager._ensureValidToken();

  Assert.equal(getTokenCount, 2);
  Assert.ok(didReturn401);
  Assert.ok(didReturn200);
  Assert.ok(syncAuthManager._token);
  Assert.ok(syncAuthManager._hasValidToken());
});

add_task(async function test_getTokenErrorWithRetry() {
  _("tokenserver sends an observer notification on various backoff headers.");

  // Set Sync's backoffInterval to zero - after we simulated the backoff header
  // it should reflect the value we sent.
  Status.backoffInterval = 0;
  _("Arrange for a 503 with a Retry-After header.");
  initializeIdentityWithTokenServerResponse({
    status: 503,
    headers: { "content-type""application/json""retry-after""100" },
    body: JSON.stringify({}),
  });
  let syncAuthManager = Service.identity;

  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    TokenServerClientServerError,
    "should reject due to 503"
  );

  // The observer should have fired - check it got the value in the response.
  Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
  // Sync will have the value in ms with some slop - so check it is at least that.
  Assert.ok(Status.backoffInterval >= 100000);

  _("Arrange for a 200 with an X-Backoff header.");
  Status.backoffInterval = 0;
  initializeIdentityWithTokenServerResponse({
    status: 503,
    headers: { "content-type""application/json""x-backoff""200" },
    body: JSON.stringify({}),
  });
  syncAuthManager = Service.identity;

  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    TokenServerClientServerError,
    "should reject due to no token in response"
  );

  // The observer should have fired - check it got the value in the response.
  Assert.ok(Status.backoffInterval >= 200000);
});

add_task(async function test_getKeysErrorWithBackoff() {
  _(
    "Auth server (via hawk) sends an observer notification on backoff headers."
  );

  // Set Sync's backoffInterval to zero - after we simulated the backoff header
  // it should reflect the value we sent.
  Status.backoffInterval = 0;
  _("Arrange for a 503 with a X-Backoff header.");

  let config = makeIdentityConfig();
  // We want no scopedKeys so we attempt to fetch them.
  delete config.fxaccount.user.scopedKeys;
  config.fxaccount.user.keyFetchToken = "keyfetchtoken";
  await initializeIdentityWithHAWKResponseFactory(
    config,
    function (method, data, uri) {
      Assert.equal(method, "get");
      Assert.equal(uri, "http://mockedserver:9999/account/keys");
      return {
        status: 503,
        headers: { "content-type""application/json""x-backoff""100" },
        body: "{}",
      };
    }
  );

  let syncAuthManager = Service.identity;
  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    TokenServerClientServerError,
    "should reject due to 503"
  );

  // The observer should have fired - check it got the value in the response.
  Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
  // Sync will have the value in ms with some slop - so check it is at least that.
  Assert.ok(Status.backoffInterval >= 100000);
});

add_task(async function test_getKeysErrorWithRetry() {
  _("Auth server (via hawk) sends an observer notification on retry headers.");

  // Set Sync's backoffInterval to zero - after we simulated the backoff header
  // it should reflect the value we sent.
  Status.backoffInterval = 0;
  _("Arrange for a 503 with a Retry-After header.");

  let config = makeIdentityConfig();
  // We want no scopedKeys so we attempt to fetch them.
  delete config.fxaccount.user.scopedKeys;
  config.fxaccount.user.keyFetchToken = "keyfetchtoken";
  await initializeIdentityWithHAWKResponseFactory(
    config,
    function (method, data, uri) {
      Assert.equal(method, "get");
      Assert.equal(uri, "http://mockedserver:9999/account/keys");
      return {
        status: 503,
        headers: { "content-type""application/json""retry-after""100" },
        body: "{}",
      };
    }
  );

  let syncAuthManager = Service.identity;
  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    TokenServerClientServerError,
    "should reject due to 503"
  );

  // The observer should have fired - check it got the value in the response.
  Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
  // Sync will have the value in ms with some slop - so check it is at least that.
  Assert.ok(Status.backoffInterval >= 100000);
});

add_task(async function test_getHAWKErrors() {
  _("SyncAuthManager correctly handles various HAWK failures.");

  _("Arrange for a 401 - Sync should reflect an auth error.");
  let config = makeIdentityConfig();
  await initializeIdentityWithHAWKResponseFactory(
    config,
    function (method, data, uri) {
      if (uri == "http://mockedserver:9999/oauth/token") {
        Assert.equal(method, "post");
        return {
          status: 401,
          headers: { "content-type""application/json" },
          body: JSON.stringify({
            code: 401,
            errno: 110,
            error: "invalid token",
          }),
        };
      }
      // For any follow-up requests that check account status.
      return {
        status: 200,
        headers: { "content-type""application/json" },
        body: JSON.stringify({}),
      };
    }
  );
  Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");

  // XXX - other interesting responses to return?

  // And for good measure, some totally "unexpected" errors - we generally
  // assume these problems are going to magically go away at some point.
  _(
    "Arrange for an empty body with a 200 response - should reflect a network error."
  );
  await initializeIdentityWithHAWKResponseFactory(
    config,
    function (method, data, uri) {
      Assert.equal(method, "post");
      Assert.equal(uri, "http://mockedserver:9999/oauth/token");
      return {
        status: 200,
        headers: [],
        body: "",
      };
    }
  );
  Assert.equal(
    Status.login,
    LOGIN_FAILED_NETWORK_ERROR,
    "login state is LOGIN_FAILED_NETWORK_ERROR"
  );
});

add_task(async function test_getGetKeysFailing401() {
  _("SyncAuthManager correctly handles 401 responses fetching keys.");
  if (Services.prefs.getBoolPref("identity.fxaccounts.oauth.enabled"false)) {
    return;
  }

  _("Arrange for a 401 - Sync should reflect an auth error.");
  let config = makeIdentityConfig();
  // We want no scopedKeys so we attempt to fetch them.
  delete config.fxaccount.user.scopedKeys;
  config.fxaccount.user.keyFetchToken = "keyfetchtoken";
  await initializeIdentityWithHAWKResponseFactory(
    config,
    function (method, data, uri) {
      Assert.equal(method, "get");
      Assert.equal(uri, "http://mockedserver:9999/account/keys");
      return {
        status: 401,
        headers: { "content-type""application/json" },
        body: "{}",
      };
    }
  );
  Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
});

add_task(async function test_getGetKeysFailing503() {
  _("SyncAuthManager correctly handles 5XX responses fetching keys.");
  if (Services.prefs.getBoolPref("identity.fxaccounts.oauth.enabled"false)) {
    return;
  }

  _("Arrange for a 503 - Sync should reflect a network error.");
  let config = makeIdentityConfig();
  // We want no scopedKeys so we attempt to fetch them.
  delete config.fxaccount.user.scopedKeys;
  config.fxaccount.user.keyFetchToken = "keyfetchtoken";
  await initializeIdentityWithHAWKResponseFactory(
    config,
    function (method, data, uri) {
      Assert.equal(method, "get");
      Assert.equal(uri, "http://mockedserver:9999/account/keys");
      return {
        status: 503,
        headers: { "content-type""application/json" },
        body: "{}",
      };
    }
  );
  Assert.equal(
    Status.login,
    LOGIN_FAILED_NETWORK_ERROR,
    "state reflects network error"
  );
});

add_task(async function test_getKeysMissing() {
  _(
    "SyncAuthManager correctly handles getKeyForScope succeeding but not returning the key."
  );
  if (Services.prefs.getBoolPref("identity.fxaccounts.oauth.enabled"false)) {
    return;
  }

  let syncAuthManager = new SyncAuthManager();
  let identityConfig = makeIdentityConfig();
  // our mock identity config already has scopedKeys remove them or we never
  // try and fetch them.
  delete identityConfig.fxaccount.user.scopedKeys;
  identityConfig.fxaccount.user.keyFetchToken = "keyFetchToken";

  configureFxAccountIdentity(syncAuthManager, identityConfig);

  // Mock a fxAccounts object
  let fxa = new FxAccounts({
    fxAccountsClient: new MockFxAccountsClient(),
    newAccountState(credentials) {
      // We only expect this to be called with null indicating the (mock)
      // storage should be read.
      if (credentials) {
        throw new Error("Not expecting to have credentials passed");
      }
      let storageManager = new MockFxaStorageManager();
      storageManager.initialize(identityConfig.fxaccount.user);
      return new AccountState(storageManager);
    },
  });
  fxa.getOAuthTokenAndKey = () => {
    // And the keys object with a mock that returns no keys.
    return Promise.resolve({ key: null, token: "fake token" });
  };
  syncAuthManager._fxaService = fxa;

  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    /browser does not have the sync key, cannot sync/
  );
});

add_task(async function test_getKeysUnexpecedError() {
  _(
    "SyncAuthManager correctly handles getKeyForScope throwing an unexpected error."
  );

  if (Services.prefs.getBoolPref("identity.fxaccounts.oauth.enabled"false)) {
    return;
  }

  let syncAuthManager = new SyncAuthManager();
  let identityConfig = makeIdentityConfig();
  // our mock identity config already has scopedKeys - remove them or we never
  // try and fetch them.
  delete identityConfig.fxaccount.user.scopedKeys;
  identityConfig.fxaccount.user.keyFetchToken = "keyFetchToken";

  configureFxAccountIdentity(syncAuthManager, identityConfig);

  // Mock a fxAccounts object
  let fxa = new FxAccounts({
    fxAccountsClient: new MockFxAccountsClient(),
    newAccountState(credentials) {
      // We only expect this to be called with null indicating the (mock)
      // storage should be read.
      if (credentials) {
        throw new Error("Not expecting to have credentials passed");
      }
      let storageManager = new MockFxaStorageManager();
      storageManager.initialize(identityConfig.fxaccount.user);
      return new AccountState(storageManager);
    },
  });

  fxa.getOAuthTokenAndKey = () => {
    return Promise.reject("well that was unexpected");
  };

  syncAuthManager._fxaService = fxa;

  await Assert.rejects(
    syncAuthManager._ensureValidToken(),
    /well that was unexpected/
  );
});

add_task(async function test_signedInUserMissing() {
  _(
    "SyncAuthManager detects getSignedInUser returning incomplete account data"
  );

  let syncAuthManager = new SyncAuthManager();
  // Delete stored keys and the key fetch token.
  delete globalIdentityConfig.fxaccount.user.scopedKeys;
  delete globalIdentityConfig.fxaccount.user.keyFetchToken;

  configureFxAccountIdentity(syncAuthManager, globalIdentityConfig);

  let fxa = new FxAccounts({
    fetchAndUnwrapKeys() {
      return Promise.resolve({});
    },
    fxAccountsClient: new MockFxAccountsClient(),
    newAccountState(credentials) {
      // We only expect this to be called with null indicating the (mock)
      // storage should be read.
      if (credentials) {
        throw new Error("Not expecting to have credentials passed");
      }
      let storageManager = new MockFxaStorageManager();
      storageManager.initialize(globalIdentityConfig.fxaccount.user);
      return new AccountState(storageManager);
    },
  });

  syncAuthManager._fxaService = fxa;

  let status = await syncAuthManager.unlockAndVerifyAuthState();
  Assert.equal(status, LOGIN_FAILED_LOGIN_REJECTED);
});

// End of tests
// Utility functions follow

// Create a new sync_auth object and initialize it with a
// hawk mock that simulates HTTP responses.
// The callback function will be called each time the mocked hawk server wants
// to make a request.  The result of the callback should be the mock response
// object that will be returned to hawk.
// A token server mock will be used that doesn't hit a server, so we move
// directly to a hawk request.
async function initializeIdentityWithHAWKResponseFactory(
  config,
  cbGetResponse
) {
  // A mock request object.
  function MockRESTRequest(uri, credentials, extra) {
    this._uri = uri;
    this._credentials = credentials;
    this._extra = extra;
  }
  MockRESTRequest.prototype = {
    setHeader() {},
    async post(data) {
      this.response = cbGetResponse(
        "post",
        data,
        this._uri,
        this._credentials,
        this._extra
      );
      return this.response;
    },
    async get() {
      // Skip /status requests (sync_auth checks if the account still
      // exists after an auth error)
      if (this._uri.startsWith("http://mockedserver:9999/account/status")) {
        this.response = {
          status: 200,
          headers: { "content-type""application/json" },
          body: JSON.stringify({ exists: true }),
        };
      } else {
        this.response = cbGetResponse(
          "get",
          null,
          this._uri,
          this._credentials,
          this._extra
        );
      }
      return this.response;
    },
  };

  // The hawk client.
  function MockedHawkClient() {}
  MockedHawkClient.prototype = new HawkClient("http://mockedserver:9999");
  MockedHawkClient.prototype.constructor = MockedHawkClient;
  MockedHawkClient.prototype.newHAWKAuthenticatedRESTRequest = function (
    uri,
    credentials,
    extra
  ) {
    return new MockRESTRequest(uri, credentials, extra);
  };
  // Arrange for the same observerPrefix as FxAccountsClient uses
  MockedHawkClient.prototype.observerPrefix = "FxA:hawk";

  // tie it all together - configureFxAccountIdentity isn't useful here :(
  let fxaClient = new MockFxAccountsClient();
  fxaClient.hawk = new MockedHawkClient();
  let internal = {
    fxAccountsClient: fxaClient,
    newAccountState(credentials) {
      // We only expect this to be called with null indicating the (mock)
      // storage should be read.
      if (credentials) {
        throw new Error("Not expecting to have credentials passed");
      }
      let storageManager = new MockFxaStorageManager();
      storageManager.initialize(config.fxaccount.user);
      return new AccountState(storageManager);
    },
  };
  let fxa = new FxAccounts(internal);

  globalSyncAuthManager._fxaService = fxa;
  await Assert.rejects(
    globalSyncAuthManager._ensureValidToken(true),
    // TODO: Ideally this should have a specific check for an error.
    () => true,
    "expecting rejection due to hawk error"
  );
}

function getTimestamp(hawkAuthHeader) {
  return parseInt(/ts="(\d+)"/.exec(hawkAuthHeader)[1], 10) * SECOND_MS;
}

function getTimestampDelta(hawkAuthHeader, now = Date.now()) {
  return Math.abs(getTimestamp(hawkAuthHeader) - now);
}

function mockTokenServer(func) {
  let requestLog = Log.repository.getLogger("testing.mock-rest");
  if (!requestLog.appenders.length) {
    // might as well see what it says :)
    requestLog.addAppender(new Log.DumpAppender());
    requestLog.level = Log.Level.Trace;
  }
  function MockRESTRequest() {}
  MockRESTRequest.prototype = {
    _log: requestLog,
    setHeader() {},
    async get() {
      this.response = func();
      return this.response;
    },
  };
  // The mocked TokenServer client which will get the response.
  function MockTSC() {}
  MockTSC.prototype = new TokenServerClient();
  MockTSC.prototype.constructor = MockTSC;
  MockTSC.prototype.newRESTRequest = function (url) {
    return new MockRESTRequest(url);
  };
  // Arrange for the same observerPrefix as sync_auth uses.
  MockTSC.prototype.observerPrefix = "weave:service";
  return new MockTSC();
}

93%


¤ Dauer der Verarbeitung: 0.13 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.