diff options
author | Robert Newson <rnewson@apache.org> | 2013-06-21 11:01:13 +0100 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2013-06-21 22:49:46 +0100 |
commit | 8d7ab8b18dd20f8785e69f4420c6f93a2edbfa60 (patch) | |
tree | de66b86c2beb72c69b3795b292ae09f70ac62337 | |
parent | 136b28991fa40b92cde6e544f49c8fd18b9340ab (diff) | |
download | couchdb-8d7ab8b18dd20f8785e69f4420c6f93a2edbfa60.tar.gz |
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
-rw-r--r-- | etc/couchdb/default.ini.tpl.in | 2 | ||||
-rw-r--r-- | share/www/script/test/users_db_security.js | 44 | ||||
-rw-r--r-- | src/couch_mrview/src/couch_mrview_http.erl | 43 | ||||
-rw-r--r-- | src/couchdb/couch_users_db.erl | 15 |
4 files changed, 99 insertions, 5 deletions
diff --git a/etc/couchdb/default.ini.tpl.in b/etc/couchdb/default.ini.tpl.in index 736d9cd07..5eb7ebca7 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 diff --git a/share/www/script/test/users_db_security.js b/share/www/script/test/users_db_security.js index d439fcbfa..cdc3f17a1 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(); diff --git a/src/couch_mrview/src/couch_mrview_http.erl b/src/couch_mrview/src/couch_mrview_http.erl index 91587f1ff..61db4c008 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), diff --git a/src/couchdb/couch_users_db.erl b/src/couchdb/couch_users_db.erl index de76142b1..9b875ba56 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)]}}. |