From cd0e9c9b6384e4c9200d10088a13164ce4229ea6 Mon Sep 17 00:00:00 2001 From: John Christopher Anderson Date: Thu, 7 Jan 2010 20:02:46 +0000 Subject: merge account branch to trunk git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@896989 13f79535-47bb-0310-9956-ffa450edef68 --- share/www/script/test/cookie_auth.js | 253 +++++++++++++++++++++++------------ share/www/script/test/oauth.js | 11 +- share/www/script/test/users_db.js | 67 ++++++++++ 3 files changed, 240 insertions(+), 91 deletions(-) create mode 100644 share/www/script/test/users_db.js (limited to 'share/www/script/test') diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js index 0a42b4a9..9eadfee0 100644 --- a/share/www/script/test/cookie_auth.js +++ b/share/www/script/test/cookie_auth.js @@ -36,117 +36,192 @@ couchTests.cookie_auth = function(debug) { usersDb.deleteDb(); usersDb.createDb(); + // test that the users db is born with the auth ddoc + var ddoc = usersDb.open("_design/_auth"); + T(ddoc.validate_doc_update); + + // TODO test that changing the config so an existing db becomes the users db installs the ddoc also + var password = "3.141592653589"; // Create a user - T(usersDb.save({ - _id: "a1", - salt: "123", - password_sha: hex_sha1(password + "123"), + var jasonUserDoc = CouchDB.prepareUserDoc({ username: "Jason Davies", - author: "Jason Davies", - type: "user", - roles: ["_admin"] - }).ok); - - var validationDoc = { - _id : "_design/validate", - validate_doc_update: "(" + (function (newDoc, oldDoc, userCtx) { - // docs should have an author field. - if (!newDoc._deleted && !newDoc.author) { - throw {forbidden: - "Documents must have an author field"}; - } - if (oldDoc && oldDoc.author != userCtx.name) { - throw {unauthorized: - "You are not the author of this document. You jerk."+userCtx.name}; - } - }).toString() + ")" - }; - - T(db.save(validationDoc).ok); + roles: ["dev"] + }, password); + T(usersDb.save(jasonUserDoc).ok); + + var checkDoc = usersDb.open(jasonUserDoc._id); + T(checkDoc.username == "Jason Davies"); + + var jchrisUserDoc = CouchDB.prepareUserDoc({ + username: "jchris@apache.org" + }, "funnybone"); + T(usersDb.save(jchrisUserDoc).ok); + + // make sure we cant create duplicate users + var duplicateJchrisDoc = CouchDB.prepareUserDoc({ + username: "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) { + T(e.error == "conflict"); + T(usersDb.last_req.status == 409); + } + // we can't create _usernames + var underscoreUserDoc = CouchDB.prepareUserDoc({ + username: "_why" + }, "copperfield"); - T(CouchDB.login('Jason Davies', password).ok); - // update the credentials document - var doc = usersDb.open("a1"); - doc.foo=2; - T(usersDb.save(doc).ok); + try { + usersDb.save(underscoreUserDoc) + T(false && "Can't create underscore user names. Should have thrown an error."); + } catch (e) { + T(e.error == "forbidden"); + T(usersDb.last_req.status == 403); + } + + // we can't create docs with malformed ids + var badIdDoc = CouchDB.prepareUserDoc({ + username: "foo" + }, "bar"); + + badIdDoc._id = "org.apache.couchdb:w00x"; - // Save a document that's missing an author field. try { - // db has a validation function - db.save({foo:1}); - T(false && "Can't get here. Should have thrown an error 2"); + usersDb.save(badIdDoc) + T(false && "Can't create malformed docids. Should have thrown an error."); } catch (e) { T(e.error == "forbidden"); - T(db.last_req.status == 403); + T(usersDb.last_req.status == 403); } - // TODO should login() throw an exception here? - T(!CouchDB.login('Jason Davies', "2.71828").ok); - T(!CouchDB.login('Robert Allen Zimmerman', 'd00d').ok); - - // test redirect - xhr = CouchDB.request("POST", "/_session?next=/", { - headers: {"Content-Type": "application/x-www-form-urlencoded"}, - body: "username=Jason%20Davies&password="+encodeURIComponent(password) - }); - // should this be a redirect code instead of 200? - // The cURL adapter is returning the expected 302 here. - // I imagine this has to do with whether the client is willing - // to follow the redirect, ie, the browser follows and does a - // GET on the returned Location - T(xhr.status == 200 || xhr.status == 302); - - usersDb.deleteDb(); - // test user creation - T(CouchDB.createUser("test", "testpassword", "test@somemail.com", ['read', 'write']).ok); + try { + usersDb.save(underscoreUserDoc) + T(false && "Can't create underscore user names. Should have thrown an error."); + } catch (e) { + T(e.error == "forbidden"); + T(usersDb.last_req.status == 403); + } - // make sure we create a unique user - T(!CouchDB.createUser("test", "testpassword2", "test2@somemail.com", ['read', 'write']).ok); + // login works + T(CouchDB.login('Jason Davies', password).ok); + T(CouchDB.session().name == 'Jason Davies'); - // test login - T(CouchDB.login("test", "testpassword").ok); - T(!CouchDB.login('test', "testpassword2").ok); + // update one's own credentials document + jasonUserDoc.foo=2; + T(usersDb.save(jasonUserDoc).ok); + + // 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().name != 'Jason Davies'); + + // test redirect + xhr = CouchDB.request("POST", "/_session?next=/", { + headers: {"Content-Type": "application/x-www-form-urlencoded"}, + body: "username=Jason%20Davies&password="+encodeURIComponent(password) + }); + // should this be a redirect code instead of 200? + // The cURL adapter is returning the expected 302 here. + // I imagine this has to do with whether the client is willing + // to follow the redirect, ie, the browser follows and does a + // GET on the returned Location + if (xhr.status == 200) { + T(/Welcome/.test(xhr.responseText)) + } else { + T(xhr.status == 302) + T(xhr.getResponseHeader("Location")) + } + + // 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().name == "jchris@apache.org"); + T(CouchDB.session().roles.length == 0); - // test update user without changing password - T(CouchDB.updateUser("test", "test2@somemail.com").ok); - result = usersDb.view("_auth/users", {key: "test"}); - T(result.rows[0].value['email'] == "test2@somemail.com"); - - - // test changing password - result = usersDb.view("_auth/users", {key: "test"}); - T(CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword2", "testpassword").ok); - result1 = usersDb.view("_auth/users", {key: "test"}); - T(result.rows[0].value['password_sha'] != result1.rows[0].value['password_sha']); + 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 == "forbidden"); + T(usersDb.last_req.status == 403); + } + + // 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); + } - // test changing password with passing old password - T(!CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword2").ok); + T(CouchDB.logout().ok); + T(CouchDB.session().roles[0] == "_admin"); + + jchrisUserDoc.foo = ["foo"]; + T(usersDb.save(jchrisUserDoc).ok); + + // test that you can't save system (underscore) roles even if you are admin + jchrisUserDoc.roles = ["_bar"]; - // test changing password whith bad old password - T(!CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword2", "badpasswword").ok); + try { + usersDb.save(jchrisUserDoc) + T(false && "Can't add system roles to user's db. Should have thrown an error."); + } catch (e) { + T(e.error == "forbidden"); + T(usersDb.last_req.status == 403); + } - // Only admins can change roles - T(!CouchDB.updateUser("test", "test2@somemail.com", ['read', 'write']).ok); + // make sure the foo role has been applied + T(CouchDB.login("jchris@apache.org", "funnybone").ok); + T(CouchDB.session().name == "jchris@apache.org"); + T(CouchDB.session().roles.indexOf("_admin") == -1); + T(CouchDB.session().roles.indexOf("foo") != -1); + // now let's make jchris a server admin T(CouchDB.logout().ok); + T(CouchDB.session().roles[0] == "_admin"); + T(CouchDB.session().name == null); - T(CouchDB.updateUser("test", "test2@somemail.com").ok); - result = usersDb.view("_auth/users", {key: "test"}); - T(result.rows[0].value['email'] == "test2@somemail.com"); - - // test changing password, we don't need to set old password when we are admin - result = usersDb.view("_auth/users", {key: "test"}); - T(CouchDB.updateUser("test", "test2@somemail.com", [], "testpassword3").ok); - result1 = usersDb.view("_auth/users", {key: "test"}); - T(result.rows[0].value['password_sha'] != result1.rows[0].value['password_sha']); - - // Only admins can change roles - T(CouchDB.updateUser("test", "test2@somemail.com", ['read']).ok); + // set the -hashed- password so the salt matches + // todo ask on the ML about this + run_on_modified_server([{section: "admins", + key: "jchris@apache.org", value: "funnybone"}], function() { + T(CouchDB.login("jchris@apache.org", "funnybone").ok); + T(CouchDB.session().name == "jchris@apache.org"); + T(CouchDB.session().roles.indexOf("_admin") != -1); + // test that jchris still has the foo role + T(CouchDB.session().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.name == "jchris@apache.org"); + T(s.roles.indexOf("_admin") != -1); + // test session info + T(s.info.authenticated == "{couch_httpd_auth, cookie_authentication_handler}"); + T(s.info.user_db == "test_suite_users"); + // test that jchris still has the foo role + T(CouchDB.session().roles.indexOf("foo") != -1); + }); } finally { // Make sure we erase any auth cookies so we don't affect other tests @@ -157,7 +232,7 @@ couchTests.cookie_auth = function(debug) { run_on_modified_server( [{section: "httpd", key: "authentication_handlers", - value: "{couch_httpd_auth, cookie_authentication_handler}"}, + value: "{couch_httpd_auth, cookie_authentication_handler}, {couch_httpd_auth, default_authentication_handler}"}, {section: "couch_httpd_auth", key: "secret", value: generateSecret(64)}, {section: "couch_httpd_auth", diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js index 5c6c0083..d55d13e8 100644 --- a/share/www/script/test/oauth.js +++ b/share/www/script/test/oauth.js @@ -97,6 +97,8 @@ couchTests.oauth = function(debug) { CouchDB.request("GET", "/_sleep?time=50"); + CouchDB.newUuids(2); // so we have one to make the salt + CouchDB.request("PUT", "http://" + host + "/_config/couch_httpd_auth/require_valid_user", { headers: { "X-Couch-Persist": "false", @@ -113,7 +115,12 @@ couchTests.oauth = function(debug) { usersDb.createDb(); // Create a user - T(CouchDB.createUser("jason", "testpassword", "test@somemail.com", ['test'], adminBasicAuthHeaderValue()).ok); + var jasonUserDoc = CouchDB.prepareUserDoc({ + username: "jason", + roles: ["test"] + }, "testpassword"); + T(usersDb.save(jasonUserDoc).ok); + var accessor = { consumerSecret: consumerSecret, @@ -227,7 +234,7 @@ couchTests.oauth = function(debug) { run_on_modified_server( [ {section: "httpd", - key: "WWW-Authenticate", value: 'Basic realm="administrator",OAuth'}, + key: "WWW-Authenticate", value: 'OAuth'}, {section: "couch_httpd_auth", key: "secret", value: generateSecret(64)}, {section: "couch_httpd_auth", diff --git a/share/www/script/test/users_db.js b/share/www/script/test/users_db.js new file mode 100644 index 00000000..2cf63fcf --- /dev/null +++ b/share/www/script/test/users_db.js @@ -0,0 +1,67 @@ +// 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 = function(debug) { + // This tests the users db, especially validations + // this should also test that you can log into the couch + + var usersDb = new CouchDB("test_suite_users", {"X-Couch-Full-Commit":"false"}); + + // test that you can treat "_user" as a db-name + // this can complicate people who try to secure the users db with + // an http proxy and fail to get both the actual db and the _user path + // maybe it's not the right approach... + // hard to know what else to do, as we don't let non-admins inspect the config + // to determine the actual users db name. + + function testFun() { + usersDb.deleteDb(); + + // test that the validation function is installed + var ddoc = usersDb.open("_design/_auth"); + T(ddoc.validate_doc_update); + + // test that you can login as a user using basic auth + var jchrisUserDoc = CouchDB.prepareUserDoc({ + username: "jchris@apache.org" + }, "funnybone"); + T(usersDb.save(jchrisUserDoc).ok); + + T(CouchDB.session().name == null); + var s = CouchDB.session({ + headers : { + "Authorization" : "Basic amNocmlzQGFwYWNoZS5vcmc6ZnVubnlib25l" + } + }); + T(s.name == "jchris@apache.org"); + T(s.user_doc._id == "org.couchdb.user:jchris@apache.org") + T(s.info.authenticated == "{couch_httpd_auth, default_authentication_handler}"); + T(s.info.user_db == "test_suite_users"); + TEquals(["{couch_httpd_oauth, oauth_authentication_handler}", + "{couch_httpd_auth, cookie_authentication_handler}", + "{couch_httpd_auth, default_authentication_handler}"], s.info.handlers); + var s = CouchDB.session({ + headers : { + "Authorization" : "Basic Xzpf" // username and pass of _:_ + } + }); + T(s.name == null); + T(s.info.authenticated == "{couch_httpd_auth, default_authentication_handler}"); + }; + + run_on_modified_server( + [{section: "couch_httpd_auth", + key: "authentication_db", value: "test_suite_users"}], + testFun + ); + +} \ No newline at end of file -- cgit v1.2.3