summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/www/_sidebar.html4
-rw-r--r--share/www/dialog/_create_admin.html2
-rw-r--r--share/www/dialog/_login.html4
-rw-r--r--share/www/dialog/_signup.html2
-rw-r--r--share/www/script/couch.js8
-rw-r--r--share/www/script/couch_test_runner.js3
-rw-r--r--share/www/script/futon.js37
-rw-r--r--share/www/script/jquery.couch.js6
-rw-r--r--share/www/script/test/cookie_auth.js54
-rw-r--r--share/www/script/test/oauth.js2
-rw-r--r--share/www/script/test/security_validation.js2
-rw-r--r--share/www/script/test/users_db.js50
-rw-r--r--src/couchdb/couch_db.hrl3
-rw-r--r--src/couchdb/couch_httpd.erl2
-rw-r--r--src/couchdb/couch_httpd_auth.erl91
-rw-r--r--src/couchdb/couch_httpd_db.erl6
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),