| // 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.users_db_security = function(debug) { |
| var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); |
| if (debug) debugger; |
| |
| function wait(ms) { |
| var t0 = new Date(), t1; |
| do { |
| CouchDB.request("GET", "/"); |
| t1 = new Date(); |
| } while ((t1 - t0) <= ms); |
| } |
| |
| var loginUser = function(username) { |
| var pws = { |
| jan: "apple", |
| jchris: "mp3", |
| jchris1: "couch", |
| fdmanana: "foobar", |
| benoitc: "test" |
| }; |
| 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 testFun = function() |
| { |
| usersDb.deleteDb(); |
| |
| // _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"); |
| userDoc = usersDb.open("org.couchdb.user: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 |
| run_on_modified_server([ |
| { |
| section: "couch_httpd_auth", |
| key: "iterations", |
| value: "1" |
| }, |
| { |
| section: "admins", |
| key: "jan", |
| value: "apple" |
| } |
| ], function() { |
| |
| // 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(100); |
| var jchrisDoc = open_as(usersDb, "org.couchdb.user:jchris", "jchris1"); |
| |
| 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 amin |
| 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); }" |
| } |
| } |
| }; |
| |
| 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 two 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 two 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 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); |
| }); |
| }; |
| |
| usersDb.deleteDb(); |
| run_on_modified_server( |
| [{section: "couch_httpd_auth", |
| key: "iterations", value: "1"}, |
| {section: "couch_httpd_auth", |
| key: "authentication_db", value: usersDb.name}], |
| testFun |
| ); |
| usersDb.deleteDb(); // cleanup |
| |
| }; |