| // 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(); |
| }; |