You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2013/06/21 23:53:10 UTC

git commit: updated refs/heads/master to 8d7ab8b

Updated Branches:
  refs/heads/master 136b28991 -> 8d7ab8b18


Add a configurable whitelist of public user props

By default no user properties are public and attempts to view a users
document other than your own will return a 404. If the public_fields
setting of the users_db config section is set to a list of field
names, however, you will see that subset of fields for any user.

Also, if `public_fields` is set and non-empty,
`_users/_all_docs?include_docs=true` will return documents with stripped
field.

Contributed with code parts from @indutny


Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/8d7ab8b1
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/8d7ab8b1
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/8d7ab8b1

Branch: refs/heads/master
Commit: 8d7ab8b18dd20f8785e69f4420c6f93a2edbfa60
Parents: 136b289
Author: Robert Newson <rn...@apache.org>
Authored: Fri Jun 21 11:01:13 2013 +0100
Committer: Robert Newson <rn...@apache.org>
Committed: Fri Jun 21 22:49:46 2013 +0100

----------------------------------------------------------------------
 etc/couchdb/default.ini.tpl.in             |  2 ++
 share/www/script/test/users_db_security.js | 44 +++++++++++++++++++++++++
 src/couch_mrview/src/couch_mrview_http.erl | 43 ++++++++++++++++++++++--
 src/couchdb/couch_users_db.erl             | 15 +++++++--
 4 files changed, 99 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb/blob/8d7ab8b1/etc/couchdb/default.ini.tpl.in
----------------------------------------------------------------------
diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in
index 736d9cd..5eb7ebc 100644
--- a/etc/couchdb/default.ini.tpl.in
+++ b/etc/couchdb/default.ini.tpl.in
@@ -67,6 +67,8 @@ timeout = 600 ; number of seconds before automatic logout
 auth_cache_size = 50 ; size is number of cache entries
 allow_persistent_cookies = false ; set to true to allow persistent cookies
 iterations = 10 ; iterations for password hashing
+; comma-separated list of public fields, 404 if empty
+; public_fields =
 
 [cors]
 credentials = false

http://git-wip-us.apache.org/repos/asf/couchdb/blob/8d7ab8b1/share/www/script/test/users_db_security.js
----------------------------------------------------------------------
diff --git a/share/www/script/test/users_db_security.js b/share/www/script/test/users_db_security.js
index d439fcb..cdc3f17 100644
--- a/share/www/script/test/users_db_security.js
+++ b/share/www/script/test/users_db_security.js
@@ -256,6 +256,50 @@ couchTests.users_db_security = function(debug) {
       // log in one last time so run_on_modified_server can clean up the admin account
       TEquals(true, CouchDB.login("jan", "apple").ok);
     });
+
+    run_on_modified_server([
+        {
+          section: "couch_httpd_auth",
+          key: "iterations",
+          value: "1"
+        },
+        {
+          section: "couch_httpd_auth",
+          key: "public_fields",
+          value: "name,type"
+        },
+        {
+          section: "admins",
+          key: "jan",
+          value: "apple"
+        }
+      ], function() {
+        var res = usersDb.open("org.couchdb.user:jchris");
+        TEquals("jchris", res.name);
+        TEquals("user", res.type);
+        TEquals(undefined, res.roles);
+        TEquals(undefined, res.salt);
+        TEquals(undefined, res.password_scheme);
+        TEquals(undefined, res.derived_key);
+
+        // log in one last time so run_on_modified_server can clean up the admin account
+        TEquals(true, CouchDB.login("jan", "apple").ok);
+
+        var all = usersDb.allDocs({ include_docs: true });
+        T(all.rows);
+        if (all.rows) {
+          T(all.rows.every(function(row) {
+            T(row.doc);
+            if (row.doc) {
+              return Object.keys(row.doc).every(function(key) {
+                return key === 'name' || key === 'type';
+              });
+            } else {
+              return false;
+            }
+          }));
+        }
+    });
   };
 
   usersDb.deleteDb();

http://git-wip-us.apache.org/repos/asf/couchdb/blob/8d7ab8b1/src/couch_mrview/src/couch_mrview_http.erl
----------------------------------------------------------------------
diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl
index 91587f1..61db4c0 100644
--- a/src/couch_mrview/src/couch_mrview_http.erl
+++ b/src/couch_mrview/src/couch_mrview_http.erl
@@ -106,8 +106,22 @@ all_docs_req(Req, Db, Keys) ->
         ok ->
             do_all_docs_req(Req, Db, Keys);
         _ ->
-            throw({forbidden, <<"Only admins can access _all_docs",
-                " of system databases.">>})
+            DbName = ?b2l(Db#db.name),
+            case couch_config:get("couch_httpd_auth",
+                                  "authentication_db",
+                                  "_users") of
+            DbName ->
+                case couch_config:get("couch_httpd_auth", "public_fields") of
+                undefined ->
+                    throw({forbidden, <<"Only admins can access _all_docs",
+                                        " of system databases.">>});
+                _ ->
+                    do_all_docs_req(Req, Db, Keys)
+                end;
+            _ ->
+                throw({forbidden, <<"Only admins can access _all_docs",
+                                    " of system databases.">>})
+            end
         end;
     false ->
         do_all_docs_req(Req, Db, Keys)
@@ -126,7 +140,16 @@ do_all_docs_req(Req, Db, Keys) ->
     Args = Args0#mrargs{preflight_fun=ETagFun},
     {ok, Resp} = couch_httpd:etag_maybe(Req, fun() ->
         VAcc0 = #vacc{db=Db, req=Req},
-        couch_mrview:query_all_docs(Db, Args, fun view_cb/2, VAcc0)
+        DbName = ?b2l(Db#db.name),
+        Callback = case couch_config:get("couch_httpd_auth",
+                                         "authentication_db",
+                                         "_users") of
+        DbName ->
+            fun filtered_view_cb/2;
+        _ ->
+            fun view_cb/2
+        end,
+        couch_mrview:query_all_docs(Db, Args, Callback, VAcc0)
     end),
     case is_record(Resp, vacc) of
         true -> {ok, Resp#vacc.resp};
@@ -154,6 +177,20 @@ design_doc_view(Req, Db, DDoc, ViewName, Keys) ->
     end.
 
 
+filtered_view_cb({row, Row0}, Acc) ->
+  Row1 = lists:map(fun({doc, null}) ->
+        {doc, null};
+    ({doc, Body}) ->
+        Doc = couch_users_db:strip_non_public_fields(#doc{body=Body}),
+        {doc, Doc#doc.body};
+    (KV) ->
+        KV
+    end, Row0),
+    view_cb({row, Row1}, Acc);
+filtered_view_cb(Obj, Acc) ->
+    view_cb(Obj, Acc).
+
+
 view_cb({meta, Meta}, #vacc{resp=undefined}=Acc) ->
     Headers = [{"ETag", Acc#vacc.etag}],
     {ok, Resp} = couch_httpd:start_json_response(Acc#vacc.req, 200, Headers),

http://git-wip-us.apache.org/repos/asf/couchdb/blob/8d7ab8b1/src/couchdb/couch_users_db.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_users_db.erl b/src/couchdb/couch_users_db.erl
index de76142..9b875ba 100644
--- a/src/couchdb/couch_users_db.erl
+++ b/src/couchdb/couch_users_db.erl
@@ -12,7 +12,7 @@
 
 -module(couch_users_db).
 
--export([before_doc_update/2, after_doc_read/2]).
+-export([before_doc_update/2, after_doc_read/2, strip_non_public_fields/1]).
 
 -include("couch_db.hrl").
 
@@ -101,10 +101,21 @@ after_doc_read(Doc, #db{user_ctx = UserCtx} = Db) ->
     _ when Name =:= DocName ->
         Doc;
     _ ->
-        throw(not_found)
+        Doc1 = strip_non_public_fields(Doc),
+        case Doc1 of
+          #doc{body={[]}} ->
+              throw(not_found);
+          _ ->
+              Doc1
+        end
     end.
 
 get_doc_name(#doc{id= <<"org.couchdb.user:", Name/binary>>}) ->
     Name;
 get_doc_name(_) ->
     undefined.
+
+strip_non_public_fields(#doc{body={Props}}=Doc) ->
+    Public = re:split(couch_config:get("couch_httpd_auth", "public_fields", ""),
+                      "\\s*,\\s*", [{return, binary}]),
+    Doc#doc{body={[{K, V} || {K, V} <- Props, lists:member(K, Public)]}}.