/* 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/. */

// Creates calendars in various configurations (current and legacy) and performs
// requests in each of them to prove that OAuth2 authentication is working as expected.

var { CalDavCalendar } = ChromeUtils.import("resource:///modules/CalDavCalendar.jsm");
var { CalDavGenericRequest } = ChromeUtils.import("resource:///modules/caldav/CalDavRequest.jsm");
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");

var LoginInfo = Components.Constructor(
  "@mozilla.org/login-manager/loginInfo;1",
  Ci.nsILoginInfo,
  "init"
);

// Ideal login info. This is what would be saved if you created a new calendar.
const ORIGIN = "oauth://mochi.test";
const SCOPE = "test_scope";
const USERNAME = "bob@test.invalid";
const VALID_TOKEN = "bobs_refresh_token";

/**
 * Set a string pref for the given calendar.
 *
 * @param {string} calendarId
 * @param {string} key
 * @param {string} value
 */
function setPref(calendarId, key, value) {
  Services.prefs.setStringPref(`calendar.registry.${calendarId}.${key}`, value);
}

/**
 * Clear any existing saved logins and add the given ones.
 *
 * @param {string[][]} - Zero or more arrays consisting of origin, realm, username, and password.
 */
function setLogins(...logins) {
  Services.logins.removeAllLogins();
  for (let [origin, realm, username, password] of logins) {
    Services.logins.addLogin(new LoginInfo(origin, null, realm, username, password, "", ""));
  }
}

/**
 * Create a calendar with the given id, perform a request, and check that the correct
 * authorisation header was used. If the user is required to re-authenticate with the provider,
 * check that the new token is stored in the right place.
 *
 * @param {string} calendarId - ID of the new calendar
 * @param {string} [newTokenUsername] - If given, re-authentication must happen and the new token
 *   stored with this user name.
 */
async function subtest(calendarId, newTokenUsername) {
  let calendar = new CalDavCalendar();
  calendar.id = calendarId;

  let request = new CalDavGenericRequest(
    calendar.wrappedJSObject.session,
    calendar,
    "GET",
    Services.io.newURI(
      "http://mochi.test:8888/browser/comm/mailnews/addrbook/test/browser/data/auth_headers.sjs"
    )
  );
  let response = await request.commit();
  let headers = JSON.parse(response.text);

  if (newTokenUsername) {
    Assert.equal(headers.authorization, "Bearer new_access_token");

    let logins = Services.logins
      .findLogins(ORIGIN, null, SCOPE)
      .filter(l => l.username == newTokenUsername);
    Assert.equal(logins.length, 1);
    Assert.equal(logins[0].username, newTokenUsername);
    Assert.equal(logins[0].password, "new_refresh_token");
  } else {
    Assert.equal(headers.authorization, "Bearer bobs_access_token");
  }

  Services.logins.removeAllLogins();
}

// Test making a request when there is no matching token stored.

/** No token stored, no username or session ID set. */
add_task(function testCalendarOAuth_id_none() {
  let calendarId = "testCalendarOAuth_id_none";
  return subtest(calendarId, calendarId);
});

/** No token stored, session ID set. */
add_task(function testCalendarOAuth_sessionId_none() {
  let calendarId = "testCalendarOAuth_sessionId_none";
  setPref(calendarId, "sessionId", "test_session");
  return subtest(calendarId, "test_session");
});

/** No token stored, username set. */
add_task(function testCalendarOAuth_username_none() {
  let calendarId = "testCalendarOAuth_username_none";
  setPref(calendarId, "username", USERNAME);
  return subtest(calendarId, USERNAME);
});

// Test making a request when there IS a matching token, but the server rejects it.
// Currently a new token is not requested on failure.

/** Expired token stored with calendar ID. */
add_task(function testCalendarOAuth_id_expired() {
  let calendarId = "testCalendarOAuth_id_expired";
  setLogins([`oauth:${calendarId}`, "Google CalDAV v2", calendarId, "expired_token"]);
  return subtest(calendarId, calendarId);
}).skip(); // Broken.

/** Expired token stored with session ID. */
add_task(function testCalendarOAuth_sessionId_expired() {
  let calendarId = "testCalendarOAuth_sessionId_expired";
  setPref(calendarId, "sessionId", "test_session");
  setLogins(["oauth:test_session", "Google CalDAV v2", "test_session", "expired_token"]);
  return subtest(calendarId, "test_session");
}).skip(); // Broken.

/** Expired token stored with calendar ID, username set. */
add_task(function testCalendarOAuth_username_expired() {
  let calendarId = "testCalendarOAuth_username_expired";
  setPref(calendarId, "username", USERNAME);
  setLogins([`oauth:${calendarId}`, "Google CalDAV v2", calendarId, "expired_token"]);
  return subtest(calendarId, USERNAME);
}).skip(); // Broken.

// Test making a request with a valid token, using Lightning's client ID and secret.

/** Valid token stored with calendar ID. */
add_task(function testCalendarOAuth_id_valid() {
  let calendarId = "testCalendarOAuth_id_valid";
  setLogins([`oauth:${calendarId}`, "Google CalDAV v2", calendarId, VALID_TOKEN]);
  return subtest(calendarId);
});

/** Valid token stored with session ID. */
add_task(function testCalendarOAuth_sessionId_valid() {
  let calendarId = "testCalendarOAuth_sessionId_valid";
  setPref(calendarId, "sessionId", "test_session");
  setLogins(["oauth:test_session", "Google CalDAV v2", "test_session", VALID_TOKEN]);
  return subtest(calendarId);
});

/** Valid token stored with calendar ID, username set. */
add_task(function testCalendarOAuth_username_valid() {
  let calendarId = "testCalendarOAuth_username_valid";
  setPref(calendarId, "username", USERNAME);
  setLogins([`oauth:${calendarId}`, "Google CalDAV v2", calendarId, VALID_TOKEN]);
  return subtest(calendarId, USERNAME);
});

// Test making a request with a valid token, using Thunderbird's client ID and secret.

/** Valid token stored with calendar ID. */
add_task(function testCalendarOAuthTB_id_valid() {
  let calendarId = "testCalendarOAuthTB_id_valid";
  setLogins([ORIGIN, SCOPE, calendarId, VALID_TOKEN]);
  return subtest(calendarId);
});

/** Valid token stored with session ID. */
add_task(function testCalendarOAuthTB_sessionId_valid() {
  let calendarId = "testCalendarOAuthTB_sessionId_valid";
  setPref(calendarId, "sessionId", "test_session");
  setLogins([ORIGIN, SCOPE, "test_session", VALID_TOKEN]);
  return subtest(calendarId);
});

/** Valid token stored with calendar ID, username set. */
add_task(function testCalendarOAuthTB_username_valid() {
  let calendarId = "testCalendarOAuthTB_username_valid";
  setPref(calendarId, "username", USERNAME);
  setLogins([ORIGIN, SCOPE, calendarId, VALID_TOKEN]);
  return subtest(calendarId, USERNAME);
});

/** Valid token stored with username, exact scope. */
add_task(function testCalendarOAuthTB_username_validSingle() {
  let calendarId = "testCalendarOAuthTB_username_validSingle";
  setPref(calendarId, "username", USERNAME);
  setLogins(
    [ORIGIN, SCOPE, USERNAME, VALID_TOKEN],
    [ORIGIN, "other_scope", USERNAME, "other_refresh_token"]
  );
  return subtest(calendarId);
});

/** Valid token stored with username, many scopes. */
add_task(function testCalendarOAuthTB_username_validMultiple() {
  let calendarId = "testCalendarOAuthTB_username_validMultiple";
  setPref(calendarId, "username", USERNAME);
  setLogins([ORIGIN, "scope test_scope other_scope", USERNAME, VALID_TOKEN]);
  return subtest(calendarId);
});
