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

Quelle  test_oauth_flow.js   Sprache: JAVA

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


/* global crypto */

"use strict";

const {
  FxAccountsOAuth,
  ERROR_INVALID_SCOPES,
  ERROR_INVALID_SCOPED_KEYS,
  ERROR_INVALID_STATE,
  ERROR_SYNC_SCOPE_NOT_GRANTED,
  ERROR_NO_KEYS_JWE,
  ERROR_OAUTH_FLOW_ABANDONED,
} = ChromeUtils.importESModule(
  "resource://gre/modules/FxAccountsOAuth.sys.mjs"
);

const { SCOPE_PROFILE, OAUTH_CLIENT_ID } = ChromeUtils.importESModule(
  "resource://gre/modules/FxAccountsCommon.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs",
  FxAccountsKeys: "resource://gre/modules/FxAccountsKeys.sys.mjs",
});

initTestLogging("Trace");

add_task(function test_begin_oauth_flow() {
  const oauth = new FxAccountsOAuth();
  add_task(async function test_begin_oauth_flow_invalid_scopes() {
    try {
      await oauth.beginOAuthFlow("foo,fi,fum""foo");
      Assert.fail("Should have thrown error, scopes must be an array");
    } catch (e) {
      Assert.equal(e.message, ERROR_INVALID_SCOPES);
    }
    try {
      await oauth.beginOAuthFlow(["not-a-real-scope", SCOPE_PROFILE]);
      Assert.fail("Should have thrown an error, must use a valid scope");
    } catch (e) {
      Assert.equal(e.message, ERROR_INVALID_SCOPES);
    }
  });
  add_task(async function test_begin_oauth_flow_ok() {
    const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
    const queryParams = await oauth.beginOAuthFlow(scopes);

    // First verify default query parameters
    Assert.equal(queryParams.client_id, OAUTH_CLIENT_ID);
    Assert.equal(queryParams.action, "email");
    Assert.equal(queryParams.response_type, "code");
    Assert.equal(queryParams.access_type, "offline");
    Assert.equal(queryParams.scope, [SCOPE_PROFILE, SCOPE_APP_SYNC].join(" "));

    // Then, we verify that the state is a valid Base64 value
    const state = queryParams.state;
    ChromeUtils.base64URLDecode(state, { padding: "reject" });

    // Then, we verify that the codeVerifier, can be used to verify the code_challenge
    const code_challenge = queryParams.code_challenge;
    Assert.equal(queryParams.code_challenge_method, "S256");
    const oauthFlow = oauth.getFlow(state);
    const codeVerifierB64 = oauthFlow.verifier;
    const expectedChallenge = await crypto.subtle.digest(
      "SHA-256",
      new TextEncoder().encode(codeVerifierB64)
    );
    const expectedChallengeB64 = ChromeUtils.base64URLEncode(
      expectedChallenge,
      { pad: false }
    );
    Assert.equal(expectedChallengeB64, code_challenge);

    // Then, we verify that something encrypted with the `keys_jwk`, can be decrypted using the private key
    const keysJwk = queryParams.keys_jwk;
    const decodedKeysJwk = JSON.parse(
      new TextDecoder().decode(
        ChromeUtils.base64URLDecode(keysJwk, { padding: "reject" })
      )
    );
    const plaintext = "text to be encrypted and decrypted!";
    delete decodedKeysJwk.key_ops;
    const jwe = await jwcrypto.generateJWE(
      decodedKeysJwk,
      new TextEncoder().encode(plaintext)
    );
    const privateKey = oauthFlow.key;
    const decrypted = await jwcrypto.decryptJWE(jwe, privateKey);
    Assert.equal(new TextDecoder().decode(decrypted), plaintext);

    // Finally, we verify that we stored the requested scopes
    Assert.deepEqual(oauthFlow.requestedScopes, scopes.join(" "));
  });
});

add_task(function test_complete_oauth_flow() {
  add_task(async function test_invalid_state() {
    const oauth = new FxAccountsOAuth();
    const code = "foo";
    const state = "bar";
    const sessionToken = "01abcef12";
    try {
      await oauth.completeOAuthFlow(sessionToken, code, state);
      Assert.fail("Should have thrown an error");
    } catch (err) {
      Assert.equal(err.message, ERROR_INVALID_STATE);
    }
  });
  add_task(async function test_sync_scope_not_authorized() {
    const fxaClient = {
      oauthToken: () =>
        Promise.resolve({
          access_token: "access_token",
          refresh_token: "refresh_token",
          // Note that the scope does not include the sync scope
          scope: SCOPE_PROFILE,
        }),
    };
    const oauth = new FxAccountsOAuth(fxaClient);
    const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
    const sessionToken = "01abcef12";
    const queryParams = await oauth.beginOAuthFlow(scopes);
    try {
      await oauth.completeOAuthFlow(sessionToken, "foo", queryParams.state);
      Assert.fail(
        "Should have thrown an error because the sync scope was not authorized"
      );
    } catch (err) {
      Assert.equal(err.message, ERROR_SYNC_SCOPE_NOT_GRANTED);
    }
  });
  add_task(async function test_jwe_not_returned() {
    const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
    const fxaClient = {
      oauthToken: () =>
        Promise.resolve({
          access_token: "access_token",
          refresh_token: "refresh_token",
          scope: scopes.join(" "),
        }),
    };
    const oauth = new FxAccountsOAuth(fxaClient);
    const queryParams = await oauth.beginOAuthFlow(scopes);
    const sessionToken = "01abcef12";
    try {
      await oauth.completeOAuthFlow(sessionToken, "foo", queryParams.state);
      Assert.fail(
        "Should have thrown an error because we didn't get back a keys_nwe"
      );
    } catch (err) {
      Assert.equal(err.message, ERROR_NO_KEYS_JWE);
    }
  });
  add_task(async function test_complete_oauth_ok() {
    // First, we initialize some fake values we would typically get
    // from outside our system
    const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
    const oauthCode = "fake oauth code";
    const sessionToken = "01abcef12";
    const plainTextScopedKeys = {
      [SCOPE_APP_SYNC]: {
        kty: "oct",
        kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
        k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
        scope: SCOPE_APP_SYNC,
      },
    };
    const fakeAccessToken = "fake access token";
    const fakeRefreshToken = "fake refresh token";
    // Then, we initialize a fake http client, we'll add our fake oauthToken call
    // once we have started the oauth flow (so we have the public keys!)
    const fxaClient = {};
    const fxaKeys = new FxAccountsKeys(null);
    // Then, we initialize our oauth object with the given client and begin a new flow
    const oauth = new FxAccountsOAuth(fxaClient, fxaKeys);
    const queryParams = await oauth.beginOAuthFlow(scopes);
    // Now that we have the public keys in `keys_jwk`, we use it to generate a JWE
    // representing our scoped keys
    const keysJwk = queryParams.keys_jwk;
    const decodedKeysJwk = JSON.parse(
      new TextDecoder().decode(
        ChromeUtils.base64URLDecode(keysJwk, { padding: "reject" })
      )
    );
    delete decodedKeysJwk.key_ops;
    const jwe = await jwcrypto.generateJWE(
      decodedKeysJwk,
      new TextEncoder().encode(JSON.stringify(plainTextScopedKeys))
    );
    // We also grab the stored PKCE verifier that the oauth object stored internally
    // to verify that we correctly send it as a part of our HTTP request
    const storedVerifier = oauth.getFlow(queryParams.state).verifier;

    // To test what happens when more than one flow is completed simulatniously
    // We mimic a slow network call on the first oauthToken call and let the second
    // one win
    let callCount = 0;
    let slowResolve;
    const resolveFn = (payload, resolve) => {
      if (callCount === 1) {
        // This is the second call
        // lets resolve it so the second call wins
        resolve(payload);
      } else {
        callCount += 1;
        // This is the first call, let store our resolve function for later
        // it will be resolved once the fast flow is fully completed
        slowResolve = () => resolve(payload);
      }
    };

    // Now we initialize our mock of the HTTP request, it verifies we passed in all the correct
    // parameters and returns what we'd expect a healthy HTTP Response would look like
    fxaClient.oauthToken = (sessionTokenHex, code, verifier, clientId) => {
      Assert.equal(sessionTokenHex, sessionToken);
      Assert.equal(code, oauthCode);
      Assert.equal(verifier, storedVerifier);
      Assert.equal(clientId, queryParams.client_id);
      const response = {
        access_token: fakeAccessToken,
        refresh_token: fakeRefreshToken,
        scope: scopes.join(" "),
        keys_jwe: jwe,
      };
      return new Promise(resolve => {
        resolveFn(response, resolve);
      });
    };

    // Then, we call the completeOAuthFlow function, and get back our access token,
    // refresh token and scopedKeys

    // To test what happens when multiple flows race, we create two flows,
    // A slow one that will start first, but finish last
    // And a fast one that will beat the slow one
    const firstCompleteOAuthFlow = oauth
      .completeOAuthFlow(sessionToken, oauthCode, queryParams.state)
      .then(res => {
        // To mimic the slow network connection on the slowCompleteOAuthFlow
        // We resume the slow completeOAuthFlow once this one is complete
        slowResolve();
        return res;
      });
    const secondCompleteOAuthFlow = oauth
      .completeOAuthFlow(sessionToken, oauthCode, queryParams.state)
      .then(res => {
        // since we can't fully gaurentee which oauth flow finishes first, we also resolve here
        slowResolve();
        return res;
      });

    const { accessToken, refreshToken, scopedKeys } = await Promise.allSettled([
      firstCompleteOAuthFlow,
      secondCompleteOAuthFlow,
    ]).then(results => {
      let fast;
      let slow;
      for (const result of results) {
        if (result.status === "fulfilled") {
          fast = result.value;
        } else {
          slow = result.reason;
        }
      }
      // We make sure that we indeed have one slow flow that lost
      Assert.equal(slow.message, ERROR_OAUTH_FLOW_ABANDONED);
      return fast;
    });

    Assert.equal(accessToken, fakeAccessToken);
    Assert.equal(refreshToken, fakeRefreshToken);
    Assert.deepEqual(scopedKeys, plainTextScopedKeys);

    // Finally, we verify that all stored flows were cleared
    Assert.equal(oauth.numOfFlows(), 0);
  });
  add_task(async function test_complete_oauth_invalid_scoped_keys() {
    // First, we initialize some fake values we would typically get
    // from outside our system
    const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
    const oauthCode = "fake oauth code";
    const sessionToken = "01abcef12";
    const invalidScopedKeys = {
      [SCOPE_APP_SYNC]: {
        // ====== This is an invalid key type! Should be "oct", so we will raise an error once we realize
        kty: "EC",
        kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
        k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
        scope: SCOPE_APP_SYNC,
      },
    };
    const fakeAccessToken = "fake access token";
    const fakeRefreshToken = "fake refresh token";
    // Then, we initialize a fake http client, we'll add our fake oauthToken call
    // once we have started the oauth flow (so we have the public keys!)
    const fxaClient = {};
    const fxaKeys = new FxAccountsKeys(null);
    // Then, we initialize our oauth object with the given client and begin a new flow
    const oauth = new FxAccountsOAuth(fxaClient, fxaKeys);
    const queryParams = await oauth.beginOAuthFlow(scopes);
    // Now that we have the public keys in `keys_jwk`, we use it to generate a JWE
    // representing our scoped keys
    const keysJwk = queryParams.keys_jwk;
    const decodedKeysJwk = JSON.parse(
      new TextDecoder().decode(
        ChromeUtils.base64URLDecode(keysJwk, { padding: "reject" })
      )
    );
    delete decodedKeysJwk.key_ops;
    const jwe = await jwcrypto.generateJWE(
      decodedKeysJwk,
      new TextEncoder().encode(JSON.stringify(invalidScopedKeys))
    );
    // We also grab the stored PKCE verifier that the oauth object stored internally
    // to verify that we correctly send it as a part of our HTTP request
    const storedVerifier = oauth.getFlow(queryParams.state).verifier;

    // Now we initialize our mock of the HTTP request, it verifies we passed in all the correct
    // parameters and returns what we'd expect a healthy HTTP Response would look like
    fxaClient.oauthToken = (sessionTokenHex, code, verifier, clientId) => {
      Assert.equal(sessionTokenHex, sessionToken);
      Assert.equal(code, oauthCode);
      Assert.equal(verifier, storedVerifier);
      Assert.equal(clientId, queryParams.client_id);
      const response = {
        access_token: fakeAccessToken,
        refresh_token: fakeRefreshToken,
        scope: scopes.join(" "),
        keys_jwe: jwe,
      };
      return Promise.resolve(response);
    };

    // Then, we call the completeOAuthFlow function, and get back our access token,
    // refresh token and scopedKeys
    try {
      await oauth.completeOAuthFlow(sessionToken, oauthCode, queryParams.state);
      Assert.fail(
        "Should have thrown an error because the scoped keys are not valid"
      );
    } catch (err) {
      Assert.equal(err.message, ERROR_INVALID_SCOPED_KEYS);
    }
  });
});

Messung V0.5
C=87 H=98 G=92

¤ Dauer der Verarbeitung: 0.1 Sekunden  (vorverarbeitet)  ¤

*© 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 und die Messung sind noch experimentell.