// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License.  You may obtain a copy
// of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
// License for the specific language governing permissions and limitations under
// the License.

couchTests.cookie_auth = function(debug) {
  // This tests cookie-based authentication.

  var db = new CouchDB("test_suite_db", {"X-Couch-Full-Commit":"false"});
  db.deleteDb();
  db.createDb();
  if (debug) debugger;

  var password = "3.141592653589";

  var loginUser = function(username) {
    var pws = {
      jan: "apple",
      "Jason Davies": password,
      jchris: "funnybone"
    };
    var username1 = username.replace(/[0-9]$/, "");
    var password = pws[username];
    //console.log("Logging in '" + username1 + "' with password '" + password + "'");
    T(CouchDB.login(username1, pws[username]).ok);
  };

  var open_as = function(db, docId, username) {
    loginUser(username);
    try {
      return db.open(docId, {"anti-cache": Math.round(Math.random() * 100000)});
    } finally {
      CouchDB.logout();
    }
  };

  var save_as = function(db, doc, username)
  {
    loginUser(username);
    try {
      return db.save(doc);
    } catch (ex) {
      return ex;
    } finally {
      CouchDB.logout();
    }
  };

  // Simple secret key generator
  function generateSecret(length) {
    var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var secret = '';
    for (var i=0; i<length; i++) {
      secret += tab.charAt(Math.floor(Math.random() * 64));
    }
    return secret;
  }

  // this function will be called on the modified server
  var testFun = function () {
    try {

      // test that the users db is born with the auth ddoc
      var ddoc = open_as(usersDb, "_design/_auth", "jan");
      T(ddoc.validate_doc_update);

      // TODO test that changing the config so an existing db becomes the users db installs the ddoc also

      // Create a user
      var jasonUserDoc = CouchDB.prepareUserDoc({
        name: "Jason Davies"
      }, password);
      T(usersDb.save(jasonUserDoc).ok);

      var checkDoc = open_as(usersDb, jasonUserDoc._id, "jan");
      TEquals("Jason Davies", checkDoc.name);

      var jchrisUserDoc = CouchDB.prepareUserDoc({
        name: "jchris@apache.org"
      }, "funnybone");
      T(usersDb.save(jchrisUserDoc).ok);

      // make sure we cant create duplicate users
      var duplicateJchrisDoc = CouchDB.prepareUserDoc({
        name: "jchris@apache.org"
      }, "eh, Boo-Boo?");

      try {
        usersDb.save(duplicateJchrisDoc);
        T(false && "Can't create duplicate user names. Should have thrown an error.");
      } catch (e) {
        TEquals("conflict", e.error);
        TEquals(409, usersDb.last_req.status);
      }

      // we can't create _names
      var underscoreUserDoc = CouchDB.prepareUserDoc({
        name: "_why"
      }, "copperfield");

      try {
        usersDb.save(underscoreUserDoc);
        T(false && "Can't create underscore user names. Should have thrown an error.");
      } catch (e) {
        TEquals("forbidden", e.error);
        TEquals(403, usersDb.last_req.status);
      }

      // we can't create docs with malformed ids
      var badIdDoc = CouchDB.prepareUserDoc({
        name: "w00x"
      }, "bar");

      badIdDoc._id = "org.apache.couchdb:w00x";

      try {
        usersDb.save(badIdDoc);
        T(false && "Can't create malformed docids. Should have thrown an error.");
      } catch (e) {
        TEquals("forbidden", e.error);
        TEquals(403, usersDb.last_req.status);
      }

      // login works
      T(CouchDB.login('Jason Davies', password).ok);
      TEquals('Jason Davies', CouchDB.session().userCtx.name);

      // JSON login works
      var xhr = CouchDB.request("POST", "/_session", {
        headers: {"Content-Type": "application/json"},
        body: JSON.stringify({
          name: 'Jason Davies',
          password: password
        })
      });

      T(JSON.parse(xhr.responseText).ok);
      TEquals('Jason Davies', CouchDB.session().userCtx.name);

      // update one's own credentials document
      jasonUserDoc.foo=2;
      T(usersDb.save(jasonUserDoc).ok);
      T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
      // can't delete another users doc unless you are admin
      try {
        usersDb.deleteDoc(jchrisUserDoc);
        T(false && "Can't delete other users docs. Should have thrown an error.");
      } catch (e) {
        TEquals("not_found", e.error);
        TEquals(404, usersDb.last_req.status);
      }

      // TODO should login() throw an exception here?
       T(!CouchDB.login('Jason Davies', "2.71828").ok);
       T(!CouchDB.login('Robert Allen Zimmerman', 'd00d').ok);

       // a failed login attempt should log you out
       T(CouchDB.session().userCtx.name != 'Jason Davies');

       // test redirect on success
       xhr = CouchDB.request("POST", "/_session?next=/", {
         headers: {"Content-Type": "application/x-www-form-urlencoded"},
         body: "name=Jason%20Davies&password="+encodeURIComponent(password)
       });
       // the browser should transparently follow the redirect and GET the server root (/)
       // see http://dev.w3.org/2006/webapi/XMLHttpRequest/#infrastructure-for-the-send-method
       if (xhr.status == 200) {
         T(/Welcome/.test(xhr.responseText))
       }

       // test redirect on fail
       xhr = CouchDB.request("POST", "/_session?fail=/", {
         headers: {"Content-Type": "application/x-www-form-urlencoded"},
         body: "name=Jason%20Davies&password=foobar"
       });
       if (xhr.status == 200) {
         T(/Welcome/.test(xhr.responseText));
       }

      // test users db validations
      //
      // test that you can't update docs unless you are logged in as the user (or are admin)
      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
      T(CouchDB.session().userCtx.name == "jchris@apache.org");
      T(CouchDB.session().userCtx.roles.length == 0);

      jasonUserDoc.foo=3;

      try {
        usersDb.save(jasonUserDoc);
        T(false && "Can't update someone else's user doc. Should have thrown an error.");
      } catch (e) {
        T(e.error == "not_found");
        T(usersDb.last_req.status == 404);
      }

      // test that you can't edit roles unless you are admin
      jchrisUserDoc.roles = ["foo"];

      try {
        usersDb.save(jchrisUserDoc);
        T(false && "Can't set roles unless you are admin. Should have thrown an error.");
      } catch (e) {
        T(e.error == "forbidden");
        T(usersDb.last_req.status == 403);
      }

      T(CouchDB.logout().ok);

      jchrisUserDoc.foo = ["foo"];
      T(save_as(usersDb, jchrisUserDoc, "jan"));

      // test that you can't save system (underscore) roles even if you are admin
      jchrisUserDoc.roles = ["_bar"];

      var res = save_as(usersDb, jchrisUserDoc, "jan");
      T(res.error == "forbidden");
      T(usersDb.last_req.status == 403);

      // make sure the foo role has been applied
      T(CouchDB.login("jchris@apache.org", "funnybone").ok);
      T(CouchDB.session().userCtx.name == "jchris@apache.org");
      T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1);
      T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);

      // now let's make jchris a server admin
      T(CouchDB.logout().ok);

      // set the -hashed- password so the salt matches
      // todo ask on the ML about this

      TEquals(true, CouchDB.login("jan", "apple").ok);
      run_on_modified_server([{section: "admins",
        key: "jchris@apache.org", value: "funnybone"}], function() {
          T(CouchDB.login("jchris@apache.org", "funnybone").ok);
          T(CouchDB.session().userCtx.name == "jchris@apache.org");
          T(CouchDB.session().userCtx.roles.indexOf("_admin") != -1);
          // test that jchris still has the foo role
          T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);

          // should work even when user doc has no password
          jchrisUserDoc = usersDb.open(jchrisUserDoc._id);
          delete jchrisUserDoc.salt;
          delete jchrisUserDoc.password_sha;
          T(usersDb.save(jchrisUserDoc).ok);
          T(CouchDB.logout().ok);
          T(CouchDB.login("jchris@apache.org", "funnybone").ok);
          var s = CouchDB.session();
          T(s.userCtx.name == "jchris@apache.org");
          T(s.userCtx.roles.indexOf("_admin") != -1);
          // test session info
          T(s.info.authenticated == "cookie");
          T(s.info.authentication_db == "test_suite_users");
          // test that jchris still has the foo role
          T(CouchDB.session().userCtx.roles.indexOf("foo") != -1);
        });

    } finally {
      // Make sure we erase any auth cookies so we don't affect other tests
      T(CouchDB.logout().ok);
    }
    // log in one last time so run_on_modified_server can clean up the admin account
    TEquals(true, CouchDB.login("jan", "apple").ok);
  };

  var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"});
  usersDb.deleteDb();

  run_on_modified_server(
    [
     {section: "couch_httpd_auth",
      key: "authentication_db", value: "test_suite_users"},
     {section: "couch_httpd_auth",
      key: "iterations", value: "1"},
     {section: "admins",
       key: "jan", value: "apple"}
    ],
    testFun
  );

};
