diff options
-rw-r--r-- | share/www/_sidebar.html | 4 | ||||
-rw-r--r-- | share/www/dialog/_create_admin.html | 2 | ||||
-rw-r--r-- | share/www/dialog/_login.html | 4 | ||||
-rw-r--r-- | share/www/dialog/_signup.html | 2 | ||||
-rw-r--r-- | share/www/script/couch.js | 8 | ||||
-rw-r--r-- | share/www/script/couch_test_runner.js | 3 | ||||
-rw-r--r-- | share/www/script/futon.js | 37 | ||||
-rw-r--r-- | share/www/script/jquery.couch.js | 6 | ||||
-rw-r--r-- | share/www/script/test/cookie_auth.js | 54 | ||||
-rw-r--r-- | share/www/script/test/oauth.js | 2 | ||||
-rw-r--r-- | share/www/script/test/security_validation.js | 2 | ||||
-rw-r--r-- | share/www/script/test/users_db.js | 50 | ||||
-rw-r--r-- | src/couchdb/couch_db.hrl | 3 | ||||
-rw-r--r-- | src/couchdb/couch_httpd.erl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_auth.erl | 91 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 6 |
16 files changed, 162 insertions, 114 deletions
diff --git a/share/www/_sidebar.html b/share/www/_sidebar.html index 6c7abc99..13727cbd 100644 --- a/share/www/_sidebar.html +++ b/share/www/_sidebar.html @@ -35,12 +35,12 @@ specific language governing permissions and limitations under the License. <a href="#" class="signup">Signup</a> or <a href="#" class="login">Login</a> </span> <span class="loggedin"> - Welcome <a class="username">?</a>! + Welcome <a class="name">?</a>! <br/> <a href="#" class="logout">Logout</a> </span> <span class="loggedinadmin"> - Welcome <a class="username">?</a>! + Welcome <a class="name">?</a>! <br/> <a href="#" class="createadmin">Setup more admins</a> or <a href="#" class="logout">Logout</a> diff --git a/share/www/dialog/_create_admin.html b/share/www/dialog/_create_admin.html index e4141e1d..d4aec95a 100644 --- a/share/www/dialog/_create_admin.html +++ b/share/www/dialog/_create_admin.html @@ -27,7 +27,7 @@ specific language governing permissions and limitations under the License. </p> <table summary=""><tbody><tr> <th><label>Username:</label></th> - <td><input type="text" name="username" size="24"></td> + <td><input type="text" name="name" size="24"></td> </tr><tr> <th><label>Password:</label></th> <td><input type="password" name="password" size="24"></td> diff --git a/share/www/dialog/_login.html b/share/www/dialog/_login.html index 959f7233..f05a5fdc 100644 --- a/share/www/dialog/_login.html +++ b/share/www/dialog/_login.html @@ -16,11 +16,11 @@ specific language governing permissions and limitations under the License. <h2>Login</h2> <fieldset> <p class="help"> - Login to CouchDB with your username and password. + Login to CouchDB with your name and password. </p> <table summary=""><tbody><tr> <th><label>Username:</label></th> - <td><input type="text" name="username" size="24"></td> + <td><input type="text" name="name" size="24"></td> </tr><tr> <th><label>Password:</label></th> <td><input type="password" name="password" size="24"></td> diff --git a/share/www/dialog/_signup.html b/share/www/dialog/_signup.html index 884b4be2..7ba3448a 100644 --- a/share/www/dialog/_signup.html +++ b/share/www/dialog/_signup.html @@ -21,7 +21,7 @@ specific language governing permissions and limitations under the License. </p> <table summary=""><tbody><tr> <th><label>Username:</label></th> - <td><input type="text" name="username" size="24"></td> + <td><input type="text" name="name" size="24"></td> </tr><tr> <th><label>Password:</label></th> <td><input type="password" name="password" size="24"></td> diff --git a/share/www/script/couch.js b/share/www/script/couch.js index 21ea39b3..cb6fab89 100644 --- a/share/www/script/couch.js +++ b/share/www/script/couch.js @@ -121,7 +121,7 @@ function CouchDB(name, httpHeaders) { CouchDB.maybeThrowError(this.last_req); var results = JSON.parse(this.last_req.responseText); for (var i = 0; i < docs.length; i++) { - if(results[i].rev) { + if(results[i] && results[i].rev) { docs[i]._rev = results[i].rev; } } @@ -322,11 +322,11 @@ function CouchDB(name, httpHeaders) { // Use this from callers to check HTTP status or header values of requests. CouchDB.last_req = null; -CouchDB.login = function(username, password) { +CouchDB.login = function(name, password) { CouchDB.last_req = CouchDB.request("POST", "/_session", { headers: {"Content-Type": "application/x-www-form-urlencoded", "X-CouchDB-WWW-Authenticate": "Cookie"}, - body: "username=" + encodeURIComponent(username) + "&password=" + body: "name=" + encodeURIComponent(name) + "&password=" + encodeURIComponent(password) }); return JSON.parse(CouchDB.last_req.responseText); @@ -350,7 +350,7 @@ CouchDB.session = function(options) { CouchDB.user_prefix = "org.couchdb.user:"; CouchDB.prepareUserDoc = function(user_doc, new_password) { - user_doc._id = user_doc._id || CouchDB.user_prefix + user_doc.username; + user_doc._id = user_doc._id || CouchDB.user_prefix + user_doc.name; if (new_password) { // handle the password crypto user_doc.salt = CouchDB.newUuids(1)[0]; diff --git a/share/www/script/couch_test_runner.js b/share/www/script/couch_test_runner.js index 237f9312..ec5069e5 100644 --- a/share/www/script/couch_test_runner.js +++ b/share/www/script/couch_test_runner.js @@ -138,7 +138,8 @@ function setupAdminParty(fun) { } }; $.couch.session({ - success : function(userCtx) { + success : function(resp) { + var userCtx = resp.userCtx; if (userCtx.name && userCtx.roles.indexOf("_admin") != -1) { // admin but not admin party. dialog offering to make admin party $.showDialog("dialog/_admin_party.html", { diff --git a/share/www/script/futon.js b/share/www/script/futon.js index 9b5ba2d0..e2bdb468 100644 --- a/share/www/script/futon.js +++ b/share/www/script/futon.js @@ -14,9 +14,9 @@ function Session() { - function doLogin(username, password, callback) { + function doLogin(name, password, callback) { $.couch.login({ - username : username, + name : name, password : password, success : function() { $.futon.session.sidebar(); @@ -24,36 +24,36 @@ }, error : function(code, error, reason) { $.futon.session.sidebar(); - callback({username : "Error logging in: "+reason}); + callback({name : "Error logging in: "+reason}); } }); }; - function doSignup(username, password, callback, runLogin) { + function doSignup(name, password, callback, runLogin) { $.couch.signup({ - username : username + name : name }, password, { success : function() { if (runLogin) { - doLogin(username, password, callback); + doLogin(name, password, callback); } else { callback(); } }, error : function(status, error, reason) { $.futon.session.sidebar(); - if (error = "conflict") { - callback({username : "Name '"+username+"' is taken"}); + if (error == "conflict") { + callback({name : "Name '"+name+"' is taken"}); } else { - callback({username : "Signup error: "+reason}); + callback({name : "Signup error: "+reason}); } } }); }; function validateUsernameAndPassword(data, callback) { - if (!data.username || data.username.length == 0) { - callback({username: "Please enter a username."}); + if (!data.name || data.name.length == 0) { + callback({name: "Please enter a name."}); return false; }; if (!data.password || data.password.length == 0) { @@ -70,10 +70,10 @@ $.couch.config({ success : function() { callback(); - doLogin(data.username, data.password, callback); - doSignup(data.username, null, callback, false); + doLogin(data.name, data.password, callback); + doSignup(data.name, null, callback, false); } - }, "admins", data.username, data.password); + }, "admins", data.name, data.password); } }); return false; @@ -83,7 +83,7 @@ $.showDialog("dialog/_login.html", { submit: function(data, callback) { if (!validateUsernameAndPassword(data, callback)) return; - doLogin(data.username, data.password, callback); + doLogin(data.name, data.password, callback); } }); return false; @@ -101,7 +101,7 @@ $.showDialog("dialog/_signup.html", { submit: function(data, callback) { if (!validateUsernameAndPassword(data, callback)) return; - doSignup(data.username, data.password, callback, true); + doSignup(data.name, data.password, callback, true); } }); return false; @@ -118,9 +118,10 @@ // get users db info? $("#userCtx span").hide(); $.couch.session({ - success : function(userCtx) { + success : function(r) { + var userCtx = r.userCtx; if (userCtx.name) { - $("#userCtx .username").text(userCtx.name).attr({href : "/_utils/document.html?"+encodeURIComponent(userCtx.info.user_db)+"/org.couchdb.user%3A"+userCtx.name}); + $("#userCtx .name").text(userCtx.name).attr({href : "/_utils/document.html?"+encodeURIComponent(r.info.authentication_db)+"/org.couchdb.user%3A"+userCtx.name}); if (userCtx.roles.indexOf("_admin") != -1) { $("#userCtx .loggedinadmin").show(); } else { diff --git a/share/www/script/jquery.couch.js b/share/www/script/jquery.couch.js index 7e8a0236..0203fd45 100644 --- a/share/www/script/jquery.couch.js +++ b/share/www/script/jquery.couch.js @@ -28,7 +28,7 @@ return; } var user_prefix = "org.couchdb.user:"; - user_doc._id = user_doc._id || user_prefix + user_doc.username; + user_doc._id = user_doc._id || user_prefix + user_doc.name; if (new_password) { // handle the password crypto user_doc.salt = $.couch.newUUID(); @@ -102,7 +102,7 @@ userDb : function(callback) { $.couch.session({ success : function(resp) { - var userDb = $.couch.db(resp.info.user_db); + var userDb = $.couch.db(resp.info.authentication_db); callback(userDb); } }); @@ -121,7 +121,7 @@ options = options || {}; $.ajax({ type: "POST", url: "/_session", dataType: "json", - data: {username: options.username, password: options.password}, + data: {name: options.name, password: options.password}, complete: function(req) { var resp = $.httpData(req, "json"); if (req.status == 200) { diff --git a/share/www/script/test/cookie_auth.js b/share/www/script/test/cookie_auth.js index 125a6dcb..68ec882d 100644 --- a/share/www/script/test/cookie_auth.js +++ b/share/www/script/test/cookie_auth.js @@ -46,22 +46,22 @@ couchTests.cookie_auth = function(debug) { // Create a user var jasonUserDoc = CouchDB.prepareUserDoc({ - username: "Jason Davies", + name: "Jason Davies", roles: ["dev"] }, password); T(usersDb.save(jasonUserDoc).ok); var checkDoc = usersDb.open(jasonUserDoc._id); - T(checkDoc.username == "Jason Davies"); + T(checkDoc.name == "Jason Davies"); var jchrisUserDoc = CouchDB.prepareUserDoc({ - username: "jchris@apache.org" + name: "jchris@apache.org" }, "funnybone"); T(usersDb.save(jchrisUserDoc).ok); // make sure we cant create duplicate users var duplicateJchrisDoc = CouchDB.prepareUserDoc({ - username: "jchris@apache.org" + name: "jchris@apache.org" }, "eh, Boo-Boo?"); try { @@ -72,9 +72,9 @@ couchTests.cookie_auth = function(debug) { T(usersDb.last_req.status == 409); } - // we can't create _usernames + // we can't create _names var underscoreUserDoc = CouchDB.prepareUserDoc({ - username: "_why" + name: "_why" }, "copperfield"); try { @@ -87,7 +87,7 @@ couchTests.cookie_auth = function(debug) { // we can't create docs with malformed ids var badIdDoc = CouchDB.prepareUserDoc({ - username: "foo" + name: "foo" }, "bar"); badIdDoc._id = "org.apache.couchdb:w00x"; @@ -102,12 +102,12 @@ couchTests.cookie_auth = function(debug) { // login works T(CouchDB.login('Jason Davies', password).ok); - T(CouchDB.session().name == 'Jason Davies'); + T(CouchDB.session().userCtx.name == 'Jason Davies'); // update one's own credentials document jasonUserDoc.foo=2; T(usersDb.save(jasonUserDoc).ok); - T(CouchDB.session().roles.indexOf("_admin") == -1); + T(CouchDB.session().userCtx.roles.indexOf("_admin") == -1); // can't delete another users doc unless you are admin try { usersDb.deleteDoc(jchrisUserDoc); @@ -122,12 +122,12 @@ couchTests.cookie_auth = function(debug) { T(!CouchDB.login('Robert Allen Zimmerman', 'd00d').ok); // a failed login attempt should log you out - T(CouchDB.session().name != 'Jason Davies'); + T(CouchDB.session().userCtx.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) + body: "name=Jason%20Davies&password="+encodeURIComponent(password) }); // should this be a redirect code instead of 200? // The cURL adapter is returning the expected 302 here. @@ -145,8 +145,8 @@ couchTests.cookie_auth = function(debug) { // // 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); + T(CouchDB.session().userCtx.name == "jchris@apache.org"); + T(CouchDB.session().userCtx.roles.length == 0); jasonUserDoc.foo=3; @@ -170,7 +170,7 @@ couchTests.cookie_auth = function(debug) { } T(CouchDB.logout().ok); - T(CouchDB.session().roles[0] == "_admin"); + T(CouchDB.session().userCtx.roles[0] == "_admin"); jchrisUserDoc.foo = ["foo"]; T(usersDb.save(jchrisUserDoc).ok); @@ -188,24 +188,24 @@ couchTests.cookie_auth = function(debug) { // 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); + 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); - T(CouchDB.session().roles[0] == "_admin"); - T(CouchDB.session().name == null); + T(CouchDB.session().userCtx.roles[0] == "_admin"); + T(CouchDB.session().userCtx.name == null); // 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); + 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().roles.indexOf("foo") != -1); + T(CouchDB.session().userCtx.roles.indexOf("foo") != -1); // should work even when user doc has no password jchrisUserDoc = usersDb.open(jchrisUserDoc._id); @@ -215,13 +215,13 @@ couchTests.cookie_auth = function(debug) { 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); + T(s.userCtx.name == "jchris@apache.org"); + T(s.userCtx.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"); + T(s.info.authenticated == "cookie"); + T(s.info.authentication_db == "test_suite_users"); // test that jchris still has the foo role - T(CouchDB.session().roles.indexOf("foo") != -1); + T(CouchDB.session().userCtx.roles.indexOf("foo") != -1); }); } finally { diff --git a/share/www/script/test/oauth.js b/share/www/script/test/oauth.js index d55d13e8..55f2f430 100644 --- a/share/www/script/test/oauth.js +++ b/share/www/script/test/oauth.js @@ -116,7 +116,7 @@ couchTests.oauth = function(debug) { // Create a user var jasonUserDoc = CouchDB.prepareUserDoc({ - username: "jason", + name: "jason", roles: ["test"] }, "testpassword"); T(usersDb.save(jasonUserDoc).ok); diff --git a/share/www/script/test/security_validation.js b/share/www/script/test/security_validation.js index d07195e1..43968426 100644 --- a/share/www/script/test/security_validation.js +++ b/share/www/script/test/security_validation.js @@ -105,7 +105,7 @@ couchTests.security_validation = function(debug) { // test the _whoami endpoint var resp = userDb.request("GET", "/_session"); - var user = JSON.parse(resp.responseText) + var user = JSON.parse(resp.responseText).userCtx; T(user.name == "Damien Katz"); // test that the roles are listed properly TEquals(user.roles, []); diff --git a/share/www/script/test/users_db.js b/share/www/script/test/users_db.js index c287ce68..d2cd0a4c 100644 --- a/share/www/script/test/users_db.js +++ b/share/www/script/test/users_db.js @@ -32,11 +32,11 @@ couchTests.users_db = function(debug) { // test that you can login as a user using basic auth var jchrisUserDoc = CouchDB.prepareUserDoc({ - username: "jchris@apache.org" + name: "jchris@apache.org" }, "funnybone"); T(usersDb.save(jchrisUserDoc).ok); - T(CouchDB.session().name == null); + T(CouchDB.session().userCtx.name == null); // test that you can use basic auth aginst the users db var s = CouchDB.session({ @@ -44,20 +44,48 @@ couchTests.users_db = function(debug) { "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); + T(s.userCtx.name == "jchris@apache.org"); + T(s.info.authenticated == "default"); + T(s.info.authentication_db == "test_suite_users"); + TEquals(["oauth", "cookie", "default"], s.info.authentication_handlers); var s = CouchDB.session({ headers : { - "Authorization" : "Basic Xzpf" // username and pass of _:_ + "Authorization" : "Basic Xzpf" // name and pass of _:_ } }); T(s.name == null); - T(s.info.authenticated == "{couch_httpd_auth, default_authentication_handler}"); + T(s.info.authenticated == "default"); + + + // ok, now create a conflicting edit on the jchris doc, and make sure there's no login. + var jchrisUser2 = JSON.parse(JSON.stringify(jchrisUserDoc)); + jchrisUser2.foo = "bar"; + T(usersDb.save(jchrisUser2).ok); + try { + usersDb.save(jchrisUserDoc); + T(false && "should be an update conflict") + } catch(e) { + T(true); + } + // save as bulk with new_edits=false to force conflict save + var resp = usersDb.bulkSave([jchrisUserDoc],{all_or_nothing : true}); + + var jchrisWithConflict = usersDb.open(jchrisUserDoc._id, {conflicts : true}); + T(jchrisWithConflict._conflicts.length == 1) + + // no login with conflicted user doc + try { + var s = CouchDB.session({ + headers : { + "Authorization" : "Basic amNocmlzQGFwYWNoZS5vcmc6ZnVubnlib25l" + } + }); + T(false && "this will throw") + } catch(e) { + T(e.error == "unauthorized") + T(/conflict/.test(e.reason)) + } + }; run_on_modified_server( diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index 99ef8997..17917312 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -110,8 +110,7 @@ { name=null, roles=[], - handler, - user_doc + handler }). % This should be updated anytime a header change happens that requires more diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 252ecdb7..68478f4d 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -121,7 +121,7 @@ make_arity_3_fun(SpecStr) -> % SpecStr is "{my_module, my_fun}, {my_module2, my_fun2}" make_fun_spec_strs(SpecStr) -> - [FunSpecStr || FunSpecStr <- re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}])]. + re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}]). stop() -> mochiweb_http:stop(?MODULE). diff --git a/src/couchdb/couch_httpd_auth.erl b/src/couchdb/couch_httpd_auth.erl index d7b8181f..d1081611 100644 --- a/src/couchdb/couch_httpd_auth.erl +++ b/src/couchdb/couch_httpd_auth.erl @@ -43,7 +43,7 @@ special_test_authentication_handler(Req) -> Req#httpd{user_ctx=#user_ctx{roles=[<<"_admin">>]}} end. -basic_username_pw(Req) -> +basic_name_pw(Req) -> AuthorizationHeader = header_value(Req, "Authorization"), case AuthorizationHeader of "Basic " ++ Base64Value -> @@ -63,7 +63,7 @@ basic_username_pw(Req) -> end. default_authentication_handler(Req) -> - case basic_username_pw(Req) of + case basic_name_pw(Req) of {User, Pass} -> case get_user(?l2b(User)) of nil -> @@ -76,8 +76,7 @@ default_authentication_handler(Req) -> true -> Req#httpd{user_ctx=#user_ctx{ name=?l2b(User), - roles=proplists:get_value(<<"roles">>, UserProps, []), - user_doc={UserProps} + roles=proplists:get_value(<<"roles">>, UserProps, []) }}; _Else -> throw({unauthorized, <<"Name or password is incorrect.">>}) @@ -105,8 +104,8 @@ null_authentication_handler(Req) -> get_user(UserName) -> case couch_config:get("admins", ?b2l(UserName)) of "-hashed-" ++ HashedPwdAndSalt -> - % the username is an admin, now check to see if there is a user doc - % which has a matching username, salt, and password_sha + % the name is an admin, now check to see if there is a user doc + % which has a matching name, salt, and password_sha [HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","), case get_user_props_from_db(UserName) of nil -> @@ -117,8 +116,7 @@ get_user(UserName) -> DocRoles = proplists:get_value(<<"roles">>, UserProps), [{<<"roles">>, [<<"_admin">> | DocRoles]}, {<<"salt">>, ?l2b(Salt)}, - {<<"password_sha">>, ?l2b(HashedPwd)}, - {<<"user_doc">>, {UserProps}}] + {<<"password_sha">>, ?l2b(HashedPwd)}] end; Else -> get_user_props_from_db(UserName) @@ -128,15 +126,21 @@ get_user_props_from_db(UserName) -> DbName = couch_config:get("couch_httpd_auth", "authentication_db"), {ok, Db} = ensure_users_db_exists(?l2b(DbName)), DocId = <<"org.couchdb.user:", UserName/binary>>, - try couch_httpd_db:couch_doc_open(Db, DocId, nil, []) of - #doc{}=Doc -> - {DocProps} = couch_query_servers:json_doc(Doc), - case proplists:get_value(<<"type">>, DocProps) of - <<"user">> -> - DocProps; - _Else -> - ?LOG_ERROR("Invalid user doc. Id: ~p",[DocId]), - nil + try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of + #doc{meta=Meta}=Doc -> + % check here for conflict state and throw error if conflicted + case proplists:get_value(conflicts,Meta,[]) of + [] -> + {DocProps} = couch_query_servers:json_doc(Doc), + case proplists:get_value(<<"type">>, DocProps) of + <<"user">> -> + DocProps; + _Else -> + ?LOG_ERROR("Invalid user doc. Id: ~p",[DocId]), + nil + end; + Else -> + throw({unauthorized, <<"User document conflict must be resolved before login.">>}) end catch throw:Throw -> @@ -180,23 +184,23 @@ auth_design_doc(DocId) -> if (newDoc._deleted === true) { // allow deletes by admins and matching users // without checking the other fields - if ((userCtx.roles.indexOf('_admin') != -1) || (userCtx.name == oldDoc.username)) { + if ((userCtx.roles.indexOf('_admin') != -1) || (userCtx.name == oldDoc.name)) { return; } else { throw({forbidden : 'Only admins may delete other user docs.'}); } } - if (!newDoc.username) { - throw({forbidden : 'doc.username is required'}); + if (!newDoc.name) { + throw({forbidden : 'doc.name is required'}); } if (!(newDoc.roles && (typeof newDoc.roles.length != 'undefined') )) { throw({forbidden : 'doc.roles must be an array'}); } - if (newDoc._id != 'org.couchdb.user:'+newDoc.username) { - throw({forbidden : 'Docid must be of the form org.couchdb.user:username'}); + if (newDoc._id != 'org.couchdb.user:'+newDoc.name) { + throw({forbidden : 'Docid must be of the form org.couchdb.user:name'}); } if (oldDoc) { // validate all updates - if (oldDoc.username != newDoc.username) { + if (oldDoc.name != newDoc.name) { throw({forbidden : 'Usernames may not be changed.'}); } } @@ -205,7 +209,7 @@ auth_design_doc(DocId) -> } if (userCtx.roles.indexOf('_admin') == -1) { // not an admin if (oldDoc) { // validate non-admin updates - if (userCtx.name != newDoc.username) { + if (userCtx.name != newDoc.name) { throw({forbidden : 'You may only update your own user document.'}); } // validate role updates @@ -229,8 +233,8 @@ auth_design_doc(DocId) -> throw({forbidden : 'No system roles (starting with underscore) in users db.'}); } }; - // no system names as usernames - if (newDoc.username[0] == '_') { + // no system names as names + if (newDoc.name[0] == '_') { throw({forbidden : 'Username may not start with underscore.'}); } }">> @@ -276,8 +280,7 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req) -> ?LOG_DEBUG("Successful cookie auth as: ~p", [User]), Req#httpd{user_ctx=#user_ctx{ name=?l2b(User), - roles=proplists:get_value(<<"roles">>, UserProps, []), - user_doc=proplists:get_value(<<"user_doc">>, UserProps, null) + roles=proplists:get_value(<<"roles">>, UserProps, []) }, auth={FullSecret, TimeLeft < Timeout*0.9}}; _Else -> Req @@ -340,7 +343,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> _ -> [] end, - UserName = ?l2b(proplists:get_value("username", Form, "")), + UserName = ?l2b(proplists:get_value("name", Form, "")), Password = ?l2b(proplists:get_value("password", Form, "")), ?LOG_DEBUG("Attempt Login: ~s",[UserName]), User = case get_user(UserName) of @@ -367,9 +370,8 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> send_json(Req#httpd{req_body=ReqBody}, Code, Headers, {[ {ok, true}, - {name, proplists:get_value(<<"username">>, User, null)}, - {roles, proplists:get_value(<<"roles">>, User, [])}, - {user_doc, proplists:get_value(<<"user_doc">>, User, null)} + {name, proplists:get_value(<<"name">>, User, null)}, + {roles, proplists:get_value(<<"roles">>, User, [])} ]}); _Else -> % clear the session @@ -377,6 +379,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> send_json(Req, 401, [Cookie], {[{error, <<"unauthorized">>},{reason, <<"Name or password is incorrect.">>}]}) end; % get user info +% GET /_session handle_session_req(#httpd{method='GET', user_ctx=UserCtx}=Req) -> Name = UserCtx#user_ctx.name, ForceLogin = couch_httpd:qs_value(Req, "basic", "false"), @@ -385,15 +388,20 @@ handle_session_req(#httpd{method='GET', user_ctx=UserCtx}=Req) -> throw({unauthorized, <<"Please login.">>}); {Name, _} -> send_json(Req, {[ + % remove this ok {ok, true}, - {name, Name}, - {roles, UserCtx#user_ctx.roles}, + {<<"userCtx">>, {[ + {name, Name}, + {roles, UserCtx#user_ctx.roles} + ]}}, {info, {[ - {user_db, ?l2b(couch_config:get("couch_httpd_auth", "authentication_db"))}, - {handlers, [?l2b(H) || H <- couch_httpd:make_fun_spec_strs( + {authentication_db, ?l2b(couch_config:get("couch_httpd_auth", "authentication_db"))}, + {authentication_handlers, [auth_name(H) || H <- couch_httpd:make_fun_spec_strs( couch_config:get("httpd", "authentication_handlers"))]} - ] ++ maybe_value(authenticated, UserCtx#user_ctx.handler)}} - ] ++ maybe_value(user_doc, UserCtx#user_ctx.user_doc)}) + ] ++ maybe_value(authenticated, UserCtx#user_ctx.handler, fun(Handler) -> + auth_name(?b2l(Handler)) + end)}} + ]}) end; % logout by deleting the session handle_session_req(#httpd{method='DELETE'}=Req) -> @@ -408,9 +416,16 @@ handle_session_req(#httpd{method='DELETE'}=Req) -> handle_session_req(Req) -> send_method_not_allowed(Req, "GET,HEAD,POST,DELETE"). +maybe_value(Key, undefined, Fun) -> []; +maybe_value(Key, Else, Fun) -> + [{Key, Fun(Else)}]. maybe_value(Key, undefined) -> []; maybe_value(Key, Else) -> [{Key, Else}]. +auth_name(String) when is_list(String) -> + [_,_,_,_,_,Name|_] = re:split(String, "[\\W_]", [{return, list}]), + ?l2b(Name). + to_int(Value) when is_binary(Value) -> to_int(?b2l(Value)); to_int(Value) when is_list(Value) -> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 9233e953..28efc90e 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -467,7 +467,11 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) -> send_json(Req, 417, ErrorsJson) end; false -> - Docs = [couch_doc:from_json_obj(JsonObj) || JsonObj <- DocsArray], + Docs = lists:map(fun(JsonObj) -> + Doc = couch_doc:from_json_obj(JsonObj), + validate_attachment_names(Doc), + Doc + end, DocsArray), {ok, Errors} = couch_db:update_docs(Db, Docs, Options, replicated_changes), ErrorsJson = lists:map(fun update_doc_result_to_json/1, Errors), |