couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dam...@apache.org
Subject svn commit: r718311 - in /incubator/couchdb/trunk: etc/couchdb/ share/www/script/ src/couchdb/
Date Mon, 17 Nov 2008 18:18:51 GMT
Author: damien
Date: Mon Nov 17 10:18:51 2008
New Revision: 718311

URL: http://svn.apache.org/viewvc?rev=718311&view=rev
Log:
More security and validation work. Still incomplete.

Modified:
    incubator/couchdb/trunk/etc/couchdb/local_dev.ini
    incubator/couchdb/trunk/share/www/script/couch.js
    incubator/couchdb/trunk/share/www/script/couch_tests.js
    incubator/couchdb/trunk/src/couchdb/couch_db.hrl
    incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl
    incubator/couchdb/trunk/src/couchdb/couch_httpd.erl
    incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl
    incubator/couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl
    incubator/couchdb/trunk/src/couchdb/couch_rep.erl
    incubator/couchdb/trunk/src/couchdb/couch_server.erl

Modified: incubator/couchdb/trunk/etc/couchdb/local_dev.ini
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/etc/couchdb/local_dev.ini?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/etc/couchdb/local_dev.ini (original)
+++ incubator/couchdb/trunk/etc/couchdb/local_dev.ini Mon Nov 17 10:18:51 2008
@@ -12,7 +12,7 @@
 ;bind_address = 127.0.0.1
 
 [log]
-;level = info
+level = debug
 
 [update_notification]
 ;unique notifier name=/full/path/to/exe -with "cmd line arg"
@@ -24,3 +24,27 @@
 
 [test]
 foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar
+
+[test]
+foo = bar

Modified: incubator/couchdb/trunk/share/www/script/couch.js
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/couch.js?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/share/www/script/couch.js [utf-8] (original)
+++ incubator/couchdb/trunk/share/www/script/couch.js [utf-8] Mon Nov 17 10:18:51 2008
@@ -13,58 +13,64 @@
 // A simple class to represent a database. Uses XMLHttpRequest to interface with
 // the CouchDB server.
 
-function CouchDB(name, options) {
+function CouchDB(name, httpHeaders) {
   this.name = name;
   this.uri = "/" + encodeURIComponent(name) + "/";
+  
+  // The XMLHttpRequest object from the most recent request. Callers can
+  // use this to check result http status and headers.
+  this.last_req = null;
+  
   request = function(method, uri, requestOptions) {
-      return CouchDB.request(method, uri, combine(requestOptions, options));
+      requestOptions = requestOptions || {}
+      requestOptions.headers = combine(requestOptions.headers, httpHeaders)
+      return CouchDB.request(method, uri, requestOptions);
     }
 
   // Creates the database on the server
   this.createDb = function() {
-    var req = request("PUT", this.uri);
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    this.last_req = request("PUT", this.uri);
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   // Deletes the database on the server
   this.deleteDb = function() {
-    var req = request("DELETE", this.uri);
-    if (req.status == 404)
+    this.last_req = request("DELETE", this.uri);
+    if (this.last_req.status == 404)
       return false;
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   // Save a document to the database
   this.save = function(doc, options) {
-    var req;
     if (doc._id == undefined)
       doc._id = CouchDB.newUuids(1)[0];
 
-    req = request("PUT", this.uri  + encodeURIComponent(doc._id) + encodeOptions(options),
{
-      body: JSON.stringify(doc)
-    });
-    maybeThrowError(req);
-    var result = JSON.parse(req.responseText);
+    this.last_req = request("PUT", this.uri  + 
+        encodeURIComponent(doc._id) + encodeOptions(options),
+        {body: JSON.stringify(doc)});
+    CouchDB.maybeThrowError(this.last_req);
+    var result = JSON.parse(this.last_req.responseText);
     doc._rev = result.rev;
     return result;
   }
 
   // Open a document from the database
   this.open = function(docId, options) {
-    var req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
-    if (req.status == 404)
+    this.last_req = request("GET", this.uri + encodeURIComponent(docId) + encodeOptions(options));
+    if (this.last_req.status == 404)
       return null;
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   // Deletes a document from the database
   this.deleteDoc = function(doc) {
-    var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" + doc._rev);
-    maybeThrowError(req);
-    var result = JSON.parse(req.responseText);
+    this.last_req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "?rev=" +
doc._rev);
+    CouchDB.maybeThrowError(this.last_req);
+    var result = JSON.parse(this.last_req.responseText);
     doc._rev = result.rev; //record rev in input document
     doc._deleted = true;
     return result;
@@ -72,9 +78,9 @@
 
   // Deletes an attachment from a document
   this.deleteDocAttachment = function(doc, attachment_name) {
-    var req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name
+ "?rev=" + doc._rev);
-    maybeThrowError(req);
-    var result = JSON.parse(req.responseText);
+    this.last_req = request("DELETE", this.uri + encodeURIComponent(doc._id) + "/" + attachment_name
+ "?rev=" + doc._rev);
+    CouchDB.maybeThrowError(this.last_req);
+    var result = JSON.parse(this.last_req.responseText);
     doc._rev = result.rev; //record rev in input document
     return result;
   }
@@ -92,11 +98,11 @@
       if (docs[i]._id == undefined)
         docs[i]._id = newUuids.pop();
     }
-    var req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), {
+    this.last_req = request("POST", this.uri + "_bulk_docs" + encodeOptions(options), {
       body: JSON.stringify({"docs": docs})
     });
-    maybeThrowError(req);
-    var result = JSON.parse(req.responseText);
+    CouchDB.maybeThrowError(this.last_req);
+    var result = JSON.parse(this.last_req.responseText);
     for (var i = 0; i < docs.length; i++) {
         docs[i]._rev = result.new_revs[i].rev;
     }
@@ -117,49 +123,49 @@
         reduceFun = reduceFun.toSource ? reduceFun.toSource() : "(" + reduceFun.toString()
+ ")";
       body.reduce = reduceFun;
     }
-    var req = request("POST", this.uri + "_temp_view" + encodeOptions(options), {
+    this.last_req = request("POST", this.uri + "_temp_view" + encodeOptions(options), {
       headers: {"Content-Type": "application/json"},
       body: JSON.stringify(body)
     });
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   this.view = function(viewname, options, keys) {
-    var req = null ;
     if(!keys) {
-      req = request("GET", this.uri + "_view/" + viewname + encodeOptions(options));    
 
+      this.last_req = request("GET", this.uri + "_view/" +
+          viewname + encodeOptions(options));      
     } else {
-      req = request("POST", this.uri + "_view/" + viewname + encodeOptions(options), {
+      this.last_req = request("POST", this.uri + "_view/" + 
+        viewname + encodeOptions(options), {
         headers: {"Content-Type": "application/json"},
         body: JSON.stringify({keys:keys})
       });      
     }
-    if (req.status == 404)
+    if (this.last_req.status == 404)
       return null;
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   // gets information about the database
   this.info = function() {
-    var req = request("GET", this.uri);
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    this.last_req = request("GET", this.uri);
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   this.allDocs = function(options,keys) {
-    var req = null;
     if(!keys) {
-      req = request("GET", this.uri + "_all_docs" + encodeOptions(options));      
+      this.last_req = request("GET", this.uri + "_all_docs" + encodeOptions(options));  
   
     } else {
-      req = request("POST", this.uri + "_all_docs" + encodeOptions(options), {
+      this.last_req = request("POST", this.uri + "_all_docs" + encodeOptions(options), {
         headers: {"Content-Type": "application/json"},
         body: JSON.stringify({keys:keys})
       });      
     }
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   this.allDocsBySeq = function(options,keys) {
@@ -172,14 +178,14 @@
         body: JSON.stringify({keys:keys})
       });      
     }
-    maybeThrowError(req);
+    CouchDB.maybeThrowError(req);
     return JSON.parse(req.responseText);
   }
 
   this.compact = function() {
-    var req = request("POST", this.uri + "_compact");
-    maybeThrowError(req);
-    return JSON.parse(req.responseText);
+    this.last_req = request("POST", this.uri + "_compact");
+    CouchDB.maybeThrowError(this.last_req);
+    return JSON.parse(this.last_req.responseText);
   }
 
   // Convert a options object to an url query string.
@@ -207,59 +213,44 @@
   }
   
   function combine(object1, object2) {
-    if (!object2) {
+    if (!object2)
       return object1;
-    }
-    if (!object1) {
+    if (!object1)
       return object2;
-    }
-    for (var name in object2) {
+      
+    for (var name in object2)
       object1[name] = object2[name];
-    }
+      
     return object1;
   }
   
-  function maybeThrowError(req) {
-    if (req.status >= 400) {
-      if (req.responseText) {
-        try {
-          var result = JSON.parse(req.responseText);
-        } catch (ParseError) {
-          var result = {error:"unknown", reason:req.responseText};
-        }
-      } else {
-        var result = {};
-      }
-      result.http_status = req.status;
-      throw result;
-    }
-  }
+  
 }
 
+// this is the XMLHttpRequest object from last request made by the following
+// CouchDB.* functions (except for calls to request itself).
+// Use this from callers to check HTTP status or header values of requests.
+CouchDB.last_req = null; 
+
+
 CouchDB.allDbs = function() {
-  var req = CouchDB.request("GET", "/_all_dbs");
-  var result = JSON.parse(req.responseText);
-  if (req.status != 200)
-    throw result;
-  return result;
+  CouchDB.last_req = CouchDB.request("GET", "/_all_dbs");
+    CouchDB.maybeThrowError(CouchDB.last_req);
+  return JSON.parse(CouchDB.last_req.responseText);
 }
 
 CouchDB.getVersion = function() {
-  var req = CouchDB.request("GET", "/");
-  var result = JSON.parse(req.responseText);
-  if (req.status != 200)
-    throw result;
-  return result.version;
+  CouchDB.last_req = CouchDB.request("GET", "/");
+  CouchDB.maybeThrowError(CouchDB.last_req);
+  return JSON.parse(CouchDB.last_req.responseText).version;
 }
 
 CouchDB.replicate = function(source, target) {
-  var req = CouchDB.request("POST", "/_replicate", {
+  CouchDB.last_req = CouchDB.request("POST", "/_replicate", {
     body: JSON.stringify({source: source, target: target})
   });
-  var result = JSON.parse(req.responseText);
-  if (req.status != 200)
-    throw result;
-  return result;
+  CouchDB.maybeThrowError(CouchDB.last_req);
+  return JSON.parse(CouchDB.last_req.responseText);
 }
 
 CouchDB.request = function(method, uri, options) {
@@ -272,7 +263,7 @@
   } else {
     throw new Error("No XMLHTTPRequest support detected");
   }
-  req.open(method, uri, false, options.username, options.password);
+  req.open(method, uri, false);
   if (options.headers) {
     var headers = options.headers;
     for (var headerName in headers) {
@@ -297,12 +288,22 @@
     }
     return uuids;
   } else {
-    var req = CouchDB.request("POST", "/_uuids?count=" + (100 + n));
-    var result = JSON.parse(req.responseText);
-    if (req.status != 200)
-      throw result;
+    CouchDB.last_req = CouchDB.request("POST", "/_uuids?count=" + (100 + n));
+    CouchDB.maybeThrowError(CouchDB.last_req);
+    var result = JSON.parse(CouchDB.last_req.responseText);
     CouchDB.uuids_cache =
         CouchDB.uuids_cache.concat(result.uuids.slice(0, 100));
     return result.uuids.slice(100);
   }
 }
+
+CouchDB.maybeThrowError = function(req) {
+  if (req.status >= 400) {
+    try {
+      var result = JSON.parse(req.responseText);
+    } catch (ParseError) {
+      var result = {error:"unknown", reason:req.responseText};
+    }
+    throw result;
+  }
+}

Modified: incubator/couchdb/trunk/share/www/script/couch_tests.js
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/share/www/script/couch_tests.js?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] (original)
+++ incubator/couchdb/trunk/share/www/script/couch_tests.js [utf-8] Mon Nov 17 10:18:51 2008
@@ -1991,11 +1991,25 @@
                 reason:"Documents must have an author field",
                 http_status:403};
         }
-        if (oldDoc && oldDoc.author != userCtx.name) {
+        
+        // Note, the next line could be:
+        //
+        // if (oldDoc && oldDoc.author != userCtx.name) {
+        //  
+        // when name is the result of basic authentication, and added:
+        // 
+        // headers:
+        //    {"WWW-Authenticate": "Basic realm=\"" + userCtx.db + "\""},
+        //
+        // to the thrown exception. But when trying to authenticate in this
+        // manner, most browsers have  weird behaviors that make testing it
+        // in the browser difficult. So instead we use special header values
+        // a proof of concept.
+        if (oldDoc && oldDoc.author != userCtx["X-Couch-Username"]) {
             throw {error:"unauthorized",
                 reason:"You are not the author of this document. You jerk.",
                 headers:
-                  {"WWW-Authenticate": "Basic realm=\"" + userCtx.db + "\""},
+                  {"X-Couch-Foo": "bar"},
                 http_status:401};
         }
       }).toString() + ")"
@@ -2003,14 +2017,14 @@
     
     db.save(designDoc);
     
-    var userDb = new CouchDB("test_suite_db", {username:"test user", password:"foo"});
+    var userDb = new CouchDB("test_suite_db", {"X-Couch-Username":"test user"});
     
     try {
       userDb.save({foo:1});
       T(false && "Can't get here. Should have thrown an error");
     } catch (e) {
       T(e.error == "forbidden");
-      T(e.http_status == 403);
+      T(userDb.last_req.status == 403);
     }
     
     userDb.save({_id:"testdoc", foo:1, author:"test user"});
@@ -2019,7 +2033,7 @@
     doc.foo=2;
     userDb.save(doc);
     
-    var user2Db = new CouchDB("test_suite_db", {username:"test user2"});
+    var user2Db = new CouchDB("test_suite_db", {"X-Couch-Username":"test user2"});
     
     var doc = user2Db.open("testdoc");
     doc.foo=3;
@@ -2028,7 +2042,8 @@
       T(false && "Can't get here. Should have thrown an error 2");
     } catch (e) {
       T(e.error == "unauthorized");
-      T(e.http_status == 401);
+      T(user2Db.last_req.status == 401);
+      T(user2Db.last_req.getResponseHeader("X-Couch-Foo") == "bar");
     }
     
     

Modified: incubator/couchdb/trunk/src/couchdb/couch_db.hrl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_db.hrl?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_db.hrl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_db.hrl Mon Nov 17 10:18:51 2008
@@ -59,7 +59,8 @@
     {mochi_req,
     method,
     path_parts,
-    db_url_handlers
+    db_url_handlers,
+    user_ctx
     }).
     
 

Modified: incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_db_updater.erl Mon Nov 17 10:18:51 2008
@@ -35,7 +35,7 @@
     
     Db = init_db(DbName, Filepath, Fd, Header),
     Db2 = refresh_validate_doc_funs(Db),
-    {ok, Db#db{main_pid=MainPid}}.
+    {ok, Db2#db{main_pid=MainPid}}.
 
 terminate(_Reason, Db) ->
     close_db(Db).

Modified: incubator/couchdb/trunk/src/couchdb/couch_httpd.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_httpd.erl?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_httpd.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_httpd.erl Mon Nov 17 10:18:51 2008
@@ -13,16 +13,17 @@
 -module(couch_httpd).
 -include("couch_db.hrl").
 
--export([start_link/0, stop/0, handle_request/3]).
+-export([start_link/0, stop/0, handle_request/4]).
 
 -export([header_value/2,header_value/3,qs_value/2,qs_value/3,qs/1,path/1]).
--export([check_is_admin/1,unquote/1,creds/1]).
+-export([check_is_admin/1,unquote/1]).
 -export([parse_form/1,json_body/1,body/1,doc_etag/1]).
 -export([primary_header_value/2,partition/1,serve_file/3]).
 -export([start_chunked_response/3,send_chunk/2]).
 -export([start_json_response/2, start_json_response/3, end_json_response/1]).
 -export([send_response/4,send_method_not_allowed/2,send_error/4]).
 -export([send_json/2,send_json/3,send_json/4]).
+-export([default_authenticate/1]).
 
 
 % Maximum size of document PUT request body (4GB)
@@ -36,32 +37,24 @@
 
     BindAddress = couch_config:get("httpd", "bind_address", any),
     Port = couch_config:get("httpd", "port", "5984"),
-    
+    AuthenticationFun = make_arity_1_fun(
+            couch_config:get("httpd", "authentication", 
+            "{couch_httpd, default_authenticate}")),
+        
     UrlHandlersList = lists:map(
         fun({UrlKey, SpecStr}) ->
-            case couch_util:parse_term(SpecStr) of
-            {ok, {M, F, A}} ->
-                {list_to_binary(UrlKey), fun(Req) -> apply(M, F, [Req, A]) end};
-            {ok, {M, F}} ->
-                {list_to_binary(UrlKey), fun(Req) -> apply(M, F, [Req]) end}
-            end
+            {list_to_binary(UrlKey), make_arity_1_fun(SpecStr)}
         end, couch_config:get("httpd_global_handlers")),
         
     DbUrlHandlersList = lists:map(
         fun({UrlKey, SpecStr}) ->
-            case couch_util:parse_term(SpecStr) of
-            {ok, {M, F, A}} ->
-                {list_to_binary(UrlKey),
-                    fun(Req, Db) -> apply(M, F, [Req, Db, A]) end};
-            {ok, {M, F}} ->
-                {list_to_binary(UrlKey),
-                    fun(Req, Db) -> apply(M, F, [Req, Db]) end}
-            end
+            {list_to_binary(UrlKey), make_arity_2_fun(SpecStr)}
         end, couch_config:get("httpd_db_handlers")),
     UrlHandlers = dict:from_list(UrlHandlersList),
     DbUrlHandlers = dict:from_list(DbUrlHandlersList),
     Loop = fun(Req)->
-            apply(?MODULE, handle_request, [Req, UrlHandlers, DbUrlHandlers])
+            apply(?MODULE, handle_request,
+                    [Req, UrlHandlers, DbUrlHandlers, AuthenticationFun])
         end,
 
     % and off we go
@@ -76,6 +69,8 @@
             ?MODULE:stop();
         ("httpd", "port") ->
             ?MODULE:stop();
+        ("httpd", "authentication") ->
+            ?MODULE:stop();
         ("httpd_global_handlers", _) ->
             ?MODULE:stop();
         ("httpd_db_handlers", _) ->
@@ -84,11 +79,29 @@
 
     {ok, Pid}.
 
+% SpecStr is a string like "{my_module, my_fun}"  or "{my_module, my_fun, foo}"
+make_arity_1_fun(SpecStr) ->
+    case couch_util:parse_term(SpecStr) of
+    {ok, {Mod, Fun, SpecArg}} ->
+        fun(Arg) -> apply(Mod, Fun, [Arg, SpecArg]) end;
+    {ok, {Mod, Fun}} ->
+        fun(Arg) -> apply(Mod, Fun, [Arg]) end
+    end.
+
+make_arity_2_fun(SpecStr) ->
+    case couch_util:parse_term(SpecStr) of
+    {ok, {Mod, Fun, SpecArg}} ->
+        fun(Arg1, Arg2) -> apply(Mod, Fun, [Arg1, Arg2, SpecArg]) end;
+    {ok, {Mod, Fun}} ->
+        fun(Arg1, Arg2) -> apply(Mod, Fun, [Arg1, Arg2]) end
+    end.
+    
+
 stop() ->
     mochiweb_http:stop(?MODULE).
     
 
-handle_request(MochiReq, UrlHandlers, DbUrlHandlers) ->
+handle_request(MochiReq, UrlHandlers, DbUrlHandlers, AuthenticationFun) ->
 
     % for the path, use the raw path with the query string and fragment
     % removed, but URL quoting left intact
@@ -128,13 +141,16 @@
                 || Part <- string:tokens(Path, "/")],
         db_url_handlers = DbUrlHandlers
         },
-    
     DefaultFun = fun couch_httpd_db:handle_request/1,
     HandlerFun = couch_util:dict_find(HandlerKey, UrlHandlers, DefaultFun),
+    CouchHeaders = [{?l2b(K), ?l2b(V)}
+            || {"X-Couch-" ++ _= K,V}
+            <- mochiweb_headers:to_list(MochiReq:get(headers))],
     
     {ok, Resp} =
     try
-        HandlerFun(HttpReq)
+        {UserCtxProps} = AuthenticationFun(HttpReq),
+        HandlerFun(HttpReq#httpd{user_ctx={UserCtxProps ++ CouchHeaders}})
     catch
         Error ->
             send_error(HttpReq, Error)
@@ -150,6 +166,17 @@
 
 
 
+default_authenticate(Req) ->
+    % by default, we just assume the users credentials for basic authentication
+    % are correct.
+    case basic_username_pw(Req) of
+    {Username, _Pw} ->
+        {[{<<"name">>, ?l2b(Username)}]};
+    nil ->
+        {[]}
+    end.
+
+
 % Utilities
 
 partition(Path) ->
@@ -197,17 +224,7 @@
 doc_etag(#doc{revs=[DiskRev|_]}) ->
      "\"" ++ binary_to_list(DiskRev) ++ "\"".
 
-
-% user credentials
-creds(Req) ->
-    case username_pw(Req) of
-    {User, _Pw} ->
-        {[{<<"name">>, ?l2b(User)}]};
-    nil ->
-        {[]}
-    end.
-
-username_pw(Req) ->
+basic_username_pw(Req) ->
     case header_value(Req, "Authorization") of
     "Basic " ++ Base64Value ->
         case string:tokens(?b2l(couch_util:decodeBase64(Base64Value)),":") of
@@ -224,7 +241,7 @@
 
 check_is_admin(Req) ->
     IsNamedAdmin =
-    case username_pw(Req) of
+    case basic_username_pw(Req) of
     {User, Pass} ->
         couch_server:is_admin(User, Pass);
     nil ->

Modified: incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_httpd_db.erl Mon Nov 17 10:18:51 2008
@@ -41,9 +41,9 @@
         do_db_req(Req, Handler)
     end.
 
-create_db_req(Req, DbName) ->
+create_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
     ok = couch_httpd:check_is_admin(Req),
-    case couch_server:create(DbName, [{creds, couch_httpd:creds(Req)}]) of
+    case couch_server:create(DbName, [{user_ctx, UserCtx}]) of
     {ok, Db} ->
         couch_db:close(Db),
         send_json(Req, 201, {[{ok, true}]});
@@ -51,17 +51,17 @@
         throw(Error)
     end.
 
-delete_db_req(Req, DbName) ->
+delete_db_req(#httpd{user_ctx=UserCtx}=Req, DbName) ->
     ok = couch_httpd:check_is_admin(Req),
-    case couch_server:delete(DbName, [{creds, couch_httpd:creds(Req)}]) of
+    case couch_server:delete(DbName, [{user_ctx, UserCtx}]) of
     ok ->
         send_json(Req, 200, {[{ok, true}]});
     Error ->
         throw(Error)
     end.
 
-do_db_req(#httpd{path_parts=[DbName|_]}=Req, Fun) ->
-    case couch_db:open(DbName, [{creds, couch_httpd:creds(Req)}]) of
+do_db_req(#httpd{user_ctx=UserCtx,path_parts=[DbName|_]}=Req, Fun) ->
+    case couch_db:open(DbName, [{user_ctx, UserCtx}]) of
     {ok, Db} ->
         try
             Fun(Req, Db)

Modified: incubator/couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_httpd_misc_handlers.erl Mon Nov 17 10:18:51
2008
@@ -62,12 +62,28 @@
     send_method_not_allowed(Req, "GET,HEAD").
 
 
-handle_replicate_req(#httpd{method='POST'}=Req) ->
+handle_replicate_req(#httpd{user_ctx=UserCtx,method='POST'}=Req) ->
     {Props} = couch_httpd:json_body(Req),
     Source = proplists:get_value(<<"source">>, Props),
     Target = proplists:get_value(<<"target">>, Props),
+    
+    {SrcOpts} = proplists:get_value(<<"source_options">>, Props, {[]}),
+    {SrcHeadersBinary} = proplists:get_value(<<"headers">>, SrcOpts, {[]}),
+    SrcHeaders = [{?b2l(K),(V)} || {K,V} <- SrcHeadersBinary],
+    
+    {TgtOpts} = proplists:get_value(<<"target_options">>, Props, {[]}),
+    {TgtHeadersBinary} = proplists:get_value(<<"headers">>, TgtOpts, {[]}),
+    TgtHeaders = [{?b2l(K),(V)} || {K,V} <- TgtHeadersBinary],
+    
     {Options} = proplists:get_value(<<"options">>, Props, {[]}),
-    {ok, {JsonResults}} = couch_rep:replicate(Source, Target, Options),
+    Options2 = [{source_options,
+                    [{headers, SrcHeaders},
+                    {user_ctx, UserCtx}]},
+                {target_options,
+                    [{headers, TgtHeaders},
+                    {user_ctx, UserCtx}]}
+                | Options],
+    {ok, {JsonResults}} = couch_rep:replicate(Source, Target, Options2),
     send_json(Req, {[{ok, true} | JsonResults]});
 handle_replicate_req(Req) ->
     send_method_not_allowed(Req, "POST").

Modified: incubator/couchdb/trunk/src/couchdb/couch_rep.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_rep.erl?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_rep.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_rep.erl Mon Nov 17 10:18:51 2008
@@ -14,6 +14,11 @@
 
 -include("couch_db.hrl").
 
+-record(http_db, {
+    uri,
+    headers
+}).
+
 -export([replicate/2, replicate/3]).
 
 url_encode(Bin) when is_binary(Bin) ->
@@ -44,9 +49,11 @@
     replicate(DbNameA, DbNameB, []).
 
 replicate(Source, Target, Options) ->
-    {ok, DbSrc} = open_db(Source),
+    {ok, DbSrc} = open_db(Source,
+            proplists:get_value(source_options, Options, [])),
     try
-        {ok, DbTgt} = open_db(Target),
+        {ok, DbTgt} = open_db(Target,
+                proplists:get_value(target_options, Options, [])),
         try
             replicate2(Source, DbSrc, Target, DbTgt, Options)
         after
@@ -216,20 +223,19 @@
     end.
 
 
-do_http_request(Url, Action) ->
-    do_http_request(Url, Action, []).
+do_http_request(Url, Action, Headers) ->
+    do_http_request(Url, Action, Headers, []).
 
-do_http_request(Url, Action, JsonBody) ->
+do_http_request(Url, Action, Headers, JsonBody) ->
     ?LOG_DEBUG("couch_rep HTTP client request:", []),
     ?LOG_DEBUG("\tAction: ~p", [Action]),
     ?LOG_DEBUG("\tUrl: ~p", [Url]),
-
     Request =
     case JsonBody of
     [] ->
-        {Url, []};
+        {Url, Headers};
     _ ->
-        {Url, [], "application/json; charset=utf-8", iolist_to_binary(?JSON_ENCODE(JsonBody))}
+        {Url, Headers, "application/json; charset=utf-8", iolist_to_binary(?JSON_ENCODE(JsonBody))}
     end,
     {ok, {{_, ResponseCode,_},_Headers, ResponseBody}} = http:request(Action, Request, [],
[]),
     if
@@ -249,29 +255,32 @@
     Url = binary_to_list(UrlBin),
     case lists:last(Url) of
     $/ ->
-        {ok, Url};
+        Url;
     _ ->
-        {ok, Url ++ "/"}
+        Url ++ "/"
     end.
 
-open_db(<<"http://", _/binary>>=UrlBin)->
-    fix_url(UrlBin);
-open_db(<<"https://", _/binary>>=UrlBin)->
-    fix_url(UrlBin);
-open_db(DbName)->
-    couch_db:open(DbName, []).
+open_http_db(UrlBin, Options) ->
+    Headers = proplists:get_value(headers, Options, {[]}),
+    {ok, #http_db{uri=fix_url(UrlBin), headers=Headers}}.
+            
+open_db(<<"http://", _/binary>>=Url, Options)->
+    open_http_db(Url, Options);
+open_db(<<"https://", _/binary>>=Url, Options)->
+    open_http_db(Url, Options);
+open_db(DbName, Options)->
+    couch_db:open(DbName, Options).
 
-close_db("http://" ++ _)->
-    ok;
-close_db("https://" ++ _)->
+close_db(#http_db{})->
     ok;
-close_db(DbName)->
-    couch_db:close(DbName).
+close_db(Db)->
+    couch_db:close(Db).
 
 
-enum_docs_since(DbUrl, StartSeq, InFun, InAcc) when is_list(DbUrl) ->
-    Url = DbUrl ++ "_all_docs_by_seq?count=100&startkey=" ++ integer_to_list(StartSeq),
-    {Results} = do_http_request(Url, get),
+enum_docs_since(#http_db{uri=DbUrl, headers=Headers}=Db, Start, InFun, InAcc)->
+    Url = DbUrl ++ "_all_docs_by_seq?count=100&startkey="
+            ++ integer_to_list(Start),
+    {Results} = do_http_request(Url, get, Headers),
     DocInfoList=
     lists:map(fun({RowInfoList}) ->
         {RowValueProps} = proplists:get_value(<<"value">>, RowInfoList),
@@ -291,23 +300,25 @@
     _ ->
         Acc2 = enum_docs0(InFun, DocInfoList, InAcc),
         #doc_info{update_seq=LastSeq} = lists:last(DocInfoList),
-        enum_docs_since(DbUrl, LastSeq, InFun, Acc2)
+        enum_docs_since(Db, LastSeq, InFun, Acc2)
     end;
 enum_docs_since(DbSource, StartSeq, Fun, Acc) ->
     couch_db:enum_docs_since(DbSource, StartSeq, Fun, Acc).
 
-get_missing_revs(DbUrl, DocIdRevsList) when is_list(DbUrl) ->
-    {ResponseMembers} = do_http_request(DbUrl ++ "_missing_revs", post, {DocIdRevsList}),
+get_missing_revs(#http_db{uri=DbUrl, headers=Headers}, DocIdRevsList) ->
+    {ResponseMembers} = do_http_request(DbUrl ++ "_missing_revs", post, Headers,
+            {DocIdRevsList}),
     {DocMissingRevsList} = proplists:get_value(<<"missing_revs">>, ResponseMembers),
     {ok, DocMissingRevsList};
 get_missing_revs(Db, DocId) ->
     couch_db:get_missing_revs(Db, DocId).
 
 
-update_doc(DbUrl, #doc{id=DocId}=Doc, _Options) when is_list(DbUrl) ->
+update_doc(#http_db{uri=DbUrl, headers=Headers}, #doc{id=DocId}=Doc, Options) ->
+    [] = Options,
     Url = DbUrl ++ url_encode(DocId),
-    {ResponseMembers} =
-        do_http_request(Url, put, couch_doc:to_json_obj(Doc, [revs,attachments])),
+    {ResponseMembers} = do_http_request(Url, put, Headers,
+            couch_doc:to_json_obj(Doc, [revs,attachments])),
     RevId = proplists:get_value(<<"_rev">>, ResponseMembers),
     {ok, RevId};
 update_doc(Db, Doc, Options) ->
@@ -315,28 +326,30 @@
 
 update_docs(_, [], _, _) ->
     ok;
-update_docs(DbUrl, Docs, [], NewEdits) when is_list(DbUrl) ->
+update_docs(#http_db{uri=DbUrl, headers=Headers}, Docs, [], NewEdits) ->
     JsonDocs = [couch_doc:to_json_obj(Doc, [revs,attachments]) || Doc <- Docs],
     {Returned} =
-        do_http_request(DbUrl ++ "_bulk_docs", post, {[{new_edits, NewEdits}, {docs, JsonDocs}]}),
+        do_http_request(DbUrl ++ "_bulk_docs", post, Headers,
+                {[{new_edits, NewEdits}, {docs, JsonDocs}]}),
     true = proplists:get_value(<<"ok">>, Returned),
     ok;
 update_docs(Db, Docs, Options, NewEdits) ->
     couch_db:update_docs(Db, Docs, Options, NewEdits).
 
 
-open_doc(DbUrl, DocId, []) when is_list(DbUrl) ->
-    case do_http_request(DbUrl ++ url_encode(DocId), get) of
+open_doc(#http_db{uri=DbUrl, headers=Headers}, DocId, Options) ->
+    [] = Options,
+    case do_http_request(DbUrl ++ url_encode(DocId), get, Headers) of
     {[{<<"error">>, ErrId}, {<<"reason">>, Reason}]} -> % binaries?
         {list_to_atom(binary_to_list(ErrId)), Reason};
     Doc  ->
         {ok, couch_doc:from_json_obj(Doc)}
     end;
-open_doc(Db, DocId, Options) when not is_list(Db) ->
+open_doc(Db, DocId, Options) ->
     couch_db:open_doc(Db, DocId, Options).
 
 
-open_doc_revs(DbUrl, DocId, Revs, Options) when is_list(DbUrl) ->
+open_doc_revs(#http_db{uri=DbUrl, headers=Headers}, DocId, Revs, Options) ->
     QueryOptionStrs =
     lists:map(fun(latest) ->
             % latest is only option right now
@@ -344,7 +357,7 @@
         end, Options),
     RevsQueryStrs = lists:flatten(?JSON_ENCODE(Revs)),
     Url = DbUrl ++ url_encode(DocId) ++ "?" ++ couch_util:implode(["revs=true", "attachments=true",
"open_revs=" ++ RevsQueryStrs ] ++ QueryOptionStrs, "&"),
-    JsonResults = do_http_request(Url, get, []),
+    JsonResults = do_http_request(Url, get, Headers),
     Results =
     lists:map(
         fun({[{<<"missing">>, Rev}]}) ->

Modified: incubator/couchdb/trunk/src/couchdb/couch_server.erl
URL: http://svn.apache.org/viewvc/incubator/couchdb/trunk/src/couchdb/couch_server.erl?rev=718311&r1=718310&r2=718311&view=diff
==============================================================================
--- incubator/couchdb/trunk/src/couchdb/couch_server.erl (original)
+++ incubator/couchdb/trunk/src/couchdb/couch_server.erl Mon Nov 17 10:18:51 2008
@@ -202,7 +202,7 @@
     {reply, {ok, Root}, Server};
 handle_call({open, DbName, Options}, {FromPid,_}, Server) ->
     DbNameList = binary_to_list(DbName),
-    UserCreds = proplists:get_value(creds, Options, nil),
+    UserCtx = proplists:get_value(user_ctx, Options, nil),
     case check_dbname(Server, DbNameList) of
     ok ->
         Filepath = get_full_filename(Server, DbNameList),
@@ -218,7 +218,7 @@
                     true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
                     DbsOpen = Server2#server.current_dbs_open + 1,
                     {reply,
-                        couch_db:open_ref_counted(MainPid, FromPid, UserCreds),
+                        couch_db:open_ref_counted(MainPid, FromPid, UserCtx),
                         Server2#server{current_dbs_open=DbsOpen}};
                 Error ->
                     {reply, Error, Server2}
@@ -231,7 +231,7 @@
             true = ets:delete(couch_dbs_by_lru, PrevLruTime),
             true = ets:insert(couch_dbs_by_lru, {LruTime, DbName}),
             {reply,
-                couch_db:open_ref_counted(MainPid, FromPid, UserCreds),
+                couch_db:open_ref_counted(MainPid, FromPid, UserCtx),
                 Server}
         end;
     Error ->
@@ -239,7 +239,7 @@
     end;
 handle_call({create, DbName, Options}, {FromPid,_}, Server) ->
     DbNameList = binary_to_list(DbName),
-    UserCreds = proplists:get_value(creds, Options, nil),
+    UserCtx = proplists:get_value(user_ctx, Options, nil),
     case check_dbname(Server, DbNameList) of
     ok ->
         Filepath = get_full_filename(Server, DbNameList),
@@ -255,7 +255,7 @@
                 DbsOpen = Server#server.current_dbs_open + 1,
                 couch_db_update_notifier:notify({created, DbName}),
                 {reply,
-                    couch_db:open_ref_counted(MainPid, FromPid, UserCreds), 
+                    couch_db:open_ref_counted(MainPid, FromPid, UserCtx), 
                     Server#server{current_dbs_open=DbsOpen}};
             Error ->
                 {reply, Error, Server}
@@ -268,7 +268,7 @@
     end;
 handle_call({delete, DbName, Options}, _From, Server) ->
     DbNameList = binary_to_list(DbName),
-    _UserCreds = proplists:get_value(creds, Options, nil),
+    _UserCtx = proplists:get_value(user_ctx, Options, nil),
     case check_dbname(Server, DbNameList) of
     ok ->
         FullFilepath = get_full_filename(Server, DbNameList),



Mime
View raw message