// 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.elixir = true; couchTests.users_db_security = function(debug) { var db_name = '_users'; var usersDb = new CouchDB(db_name, {"X-Couch-Full-Commit":"false"}); try { usersDb.createDb(); } catch (e) { /* ignore if exists*/ } if (debug) debugger; var loginUser = function(username) { var pws = { jan: "apple", jchris: "mp3", jchris1: "couch", fdmanana: "foobar", benoitc: "test" }; // we are changing jchris’s password further down // the next two lines keep the code cleaner in // the actual tests var username1 = username.replace(/[0-9]$/, ""); var password = pws[username]; 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 view_as = function(db, viewname, username) { loginUser(username); try { return db.view(viewname); } finally { CouchDB.logout(); } }; var save_as = function(db, doc, username) { loginUser(username); try { return db.save(doc); } catch (ex) { return ex; } finally { CouchDB.logout(); } }; var changes_as = function(db, username) { loginUser(username); try { return db.changes(); } catch(ex) { return ex; } finally { CouchDB.logout(); } }; var request_as = function(db, ddoc_path, username) { loginUser(username); try { var uri = db.uri + ddoc_path; var req = CouchDB.request("GET", uri); return req; } finally { CouchDB.logout(); } }; var testFun = function() { // _users db // a doc with a field 'password' should be hashed to 'derived_key' // with salt and salt stored in 'salt', 'password' is set to null. // Exising 'derived_key' and 'salt' fields are overwritten with new values // when a non-null 'password' field exists. // anonymous should be able to create a user document var userDoc = { _id: "org.couchdb.user:jchris", type: "user", name: "jchris", password: "mp3", roles: [] }; // jan's gonna be admin as he's the first user TEquals(true, usersDb.save(userDoc).ok, "should save document"); wait(5000) userDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris"); TEquals(undefined, userDoc.password, "password field should be null 1"); TEquals(40, userDoc.derived_key.length, "derived_key should exist"); TEquals(32, userDoc.salt.length, "salt should exist"); // create server admin // anonymous should not be able to read an existing user's user document var res = usersDb.open("org.couchdb.user:jchris"); TEquals(null, res, "anonymous user doc read should be not found"); // anonymous should not be able to read /_users/_changes try { var ch = usersDb.changes(); T(false, "anonymous can read _changes"); } catch(e) { TEquals("unauthorized", e.error, "anoymous can't read _changes"); } // user should be able to read their own document var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris"); TEquals("org.couchdb.user:jchris", jchrisDoc._id); // user should not be able to read /_users/_changes var changes = changes_as(usersDb, "jchris"); TEquals("unauthorized", changes.error, "user can't read _changes"); // new 'password' fields should trigger new hashing routine jchrisDoc.password = "couch"; TEquals(true, save_as(usersDb, jchrisDoc, "jchris").ok); // wait(10000); var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jan"); TEquals(undefined, jchrisDoc.password, "password field should be null 2"); TEquals(40, jchrisDoc.derived_key.length, "derived_key should exist"); TEquals(32, jchrisDoc.salt.length, "salt should exist"); TEquals(true, userDoc.salt != jchrisDoc.salt, "should have new salt"); TEquals(true, userDoc.derived_key != jchrisDoc.derived_key, "should have new derived_key"); // user should not be able to read another user's user document var fdmananaDoc = { _id: "org.couchdb.user:fdmanana", type: "user", name: "fdmanana", password: "foobar", roles: [] }; usersDb.save(fdmananaDoc); var fdmananaDocAsReadByjchris = open_as(usersDb, "org.couchdb.user:fdmanana", "jchris1"); TEquals(null, fdmananaDocAsReadByjchris, "should not_found opening another user's user doc"); // save a db admin var benoitcDoc = { _id: "org.couchdb.user:benoitc", type: "user", name: "benoitc", password: "test", roles: ["user_admin"] }; save_as(usersDb, benoitcDoc, "jan"); TEquals(true, CouchDB.login("jan", "apple").ok); T(usersDb.setSecObj({ "admins" : { roles : [], names : ["benoitc"] } }).ok); CouchDB.logout(); // user should not be able to read from any view var ddoc = { _id: "_design/user_db_auth", views: { test: { map: "function(doc) { emit(doc._id, null); }" } }, lists: { names: "function(head, req) { " + "var row; while (row = getRow()) { send(row.key + \"\\n\"); }" + "}" }, shows: { name: "function(doc, req) { return doc.name; }" } }; save_as(usersDb, ddoc, "jan"); try { usersDb.view("user_db_auth/test"); T(false, "user had access to view in admin db"); } catch(e) { TEquals("forbidden", e.error, "non-admins should not be able to read a view"); } // admin should be able to read from any view var result = view_as(usersDb, "user_db_auth/test", "jan"); TEquals(3, result.total_rows, "should allow access and list four users to admin"); // db admin should be able to read from any view var result = view_as(usersDb, "user_db_auth/test", "benoitc"); TEquals(3, result.total_rows, "should allow access and list four users to db admin"); // non-admins can't read design docs try { open_as(usersDb, "_design/user_db_auth", "jchris1"); T(false, "non-admin read design doc, should not happen"); } catch(e) { TEquals("forbidden", e.error, "non-admins can't read design docs"); } // admin shold be able to read _list var listPath = ddoc["_id"] + "/_list/names/test"; var result = request_as(usersDb, listPath, "jan"); var lines = result.responseText.split("\n"); T(result.status == 200, "should allow access to db admin"); TEquals(4, lines.length, "should list users to db admin"); // non-admins can't read _list var result = request_as(usersDb, listPath, "jchris1"); T(result.status == 403, "should deny access to non-admin"); // admin should be able to read _show var showPath = ddoc["_id"] + "/_show/name/org.couchdb.user:jchris"; var result = request_as(usersDb, showPath, "jan"); T(result.status == 200, "should allow access to db admin"); TEquals("jchris", result.responseText, "should show username to db admin"); // non-admin should be able to access own _show var result = request_as(usersDb, showPath, "jchris1"); T(result.status == 200, "should allow access to own user record"); TEquals("jchris", result.responseText, "should show own username"); // non-admin can't read other's _show var showPath = ddoc["_id"] + "/_show/name/org.couchdb.user:jan"; var result = request_as(usersDb, showPath, "jchris1"); T(result.status == 404, "non-admin can't read others's user docs"); // admin should be able to read and edit any user doc fdmananaDoc.password = "mobile"; var result = save_as(usersDb, fdmananaDoc, "jan"); TEquals(true, result.ok, "admin should be able to update any user doc"); // admin should be able to read and edit any user doc fdmananaDoc.password = "mobile1"; var result = save_as(usersDb, fdmananaDoc, "benoitc"); TEquals(true, result.ok, "db admin by role should be able to update any user doc"); TEquals(true, CouchDB.login("jan", "apple").ok); T(usersDb.setSecObj({ "admins" : { roles : ["user_admin"], names : [] } }).ok); CouchDB.logout(); // db admin should be able to read and edit any user doc fdmananaDoc.password = "mobile2"; var result = save_as(usersDb, fdmananaDoc, "benoitc"); TEquals(true, result.ok, "db admin should be able to update any user doc"); // ensure creation of old-style docs still works var robertDoc = CouchDB.prepareUserDoc({ name: "robert" }, "anchovy"); var result = usersDb.save(robertDoc); TEquals(true, result.ok, "old-style user docs should still be accepted"); // log in one last time so run_on_modified_server can clean up the admin account TEquals(true, CouchDB.login("jan", "apple").ok); // run_on_modified_server([ // { // section: "couch_httpd_auth", // key: "iterations", // value: "1" // }, // { // section: "couch_httpd_auth", // key: "public_fields", // value: "name,type" // }, // { // section: "couch_httpd_auth", // key: "users_db_public", // value: "true" // }, // { // section: "admins", // key: "jan", // value: "apple" // } // ], function() { // var res = usersDb.open("org.couchdb.user:jchris"); // TEquals("jchris", res.name); // TEquals("user", res.type); // TEquals(undefined, res.roles); // TEquals(undefined, res.salt); // TEquals(undefined, res.password_scheme); // TEquals(undefined, res.derived_key); // // TEquals(true, CouchDB.login("jan", "apple").ok); // // var all = usersDb.allDocs({ include_docs: true }); // T(all.rows); // if (all.rows) { // T(all.rows.every(function(row) { // if (row.doc) { // return Object.keys(row.doc).every(function(key) { // return key === 'name' || key === 'type'; // }); // } else { // if(row.id[0] == "_") { // // ignore design docs // return true // } else { // return false; // } // } // })); // } // // log in one last time so run_on_modified_server can clean up the admin account // TEquals(true, CouchDB.login("jan", "apple").ok); // }); run_on_modified_server([ { section: "couch_httpd_auth", key: "public_fields", value: "name" }, { section: "couch_httpd_auth", key: "users_db_public", value: "false" } ], function() { TEquals(true, CouchDB.login("jchris", "couch").ok); try { var all = usersDb.allDocs({ include_docs: true }); T(false); // should never hit } catch(e) { TEquals("unauthorized", e.error, "should throw"); } // COUCHDB-1888 make sure admins always get all fields TEquals(true, CouchDB.login("jan", "apple").ok); var all_admin = usersDb.allDocs({ include_docs: "true" }); TEquals("user", all_admin.rows[2].doc.type, "should return type"); // log in one last time so run_on_modified_server can clean up the admin account TEquals(true, CouchDB.login("jan", "apple").ok); }); }; run_on_modified_server( [ { section:"couchdb", key:"users_db_security_editable", value:"true" }, { section: "couch_httpd_auth", key: "iterations", value: "1" }, { section: "admins", key: "jan", value: "apple" }], function() { try { testFun(); } finally { CouchDB.login("jan", "apple"); usersDb.deleteDb(); // cleanup waitForSuccess(function() { var req = CouchDB.request("GET", db_name); if (req.status == 404) { return true } throw({}); }, 'usersDb.deleteDb') usersDb.createDb(); waitForSuccess(function() { var req = CouchDB.request("GET", db_name); if (req.status == 200) { return true } throw({}); }, 'usersDb.creteDb') } } ); CouchDB.logout(); };