From cad071cc0b5018bfd355ef49de8941e2d545900a Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Mon, 10 Nov 2014 23:59:07 +0000 Subject: [PATCH 1/5] Add _users DB callbacks when opening _users shards The check to determine whether to add the callback functions for _users DB operations was only checking the raw database name. When using the authentication DB on the clustered database this meant that this check would fail and the callbacks would not be added. This commit checks the DB name (rather than the shard name) against the value of chttpd_auth/authentication_db so that shards for clustered authentication DBs have the appropriate callbacks added. COUCHDB-2452 1/3 --- src/couch_server.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/couch_server.erl b/src/couch_server.erl index 8f9696b8..71e96db3 100644 --- a/src/couch_server.erl +++ b/src/couch_server.erl @@ -124,7 +124,9 @@ maybe_add_sys_db_callbacks(DbName, Options) -> IsReplicatorDb = DbName == config:get("replicator", "db", "_replicator") orelse path_ends_with(DbName, <<"_replicator">>), IsUsersDb = DbName ==config:get("couch_httpd_auth", "authentication_db", "_users") orelse - path_ends_with(DbName, <<"_users">>), + path_ends_with(DbName, <<"_users">>) orelse + binary_to_list(mem3:dbname(DbName)) == + config:get("chttpd_auth", "authentication_db", "_users"), if DbName == DbsDbName -> [sys_db | Options]; From 2ed693836af73c284c1f394a3c908ff3fcc30f59 Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Tue, 11 Nov 2014 00:06:19 +0000 Subject: [PATCH 2/5] Update auth DB docs via the auth module Documents in the authentication DB were being updated directly from couch_httpd_auth via couch_db:update_doc/3. This meant that updates to documents with the authentication DB on the clustered interface (5984) would fail. This commit makes the auth module responsible for the document update via a ?MODULE:update_auth_doc/1 function and add couch_auth_cache:update_auth_doc/1 which proxies to couch_db:update_doc/3. COUCHDB-2452 2/3 --- src/couch_auth_cache.erl | 8 ++++++++ src/couch_httpd_auth.erl | 13 +++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/couch_auth_cache.erl b/src/couch_auth_cache.erl index 8cf631b1..61ff9dfc 100644 --- a/src/couch_auth_cache.erl +++ b/src/couch_auth_cache.erl @@ -25,6 +25,8 @@ -export([handle_config_change/5]). -export([handle_db_event/3]). +-export([update_auth_doc/1]). + -include_lib("couch/include/couch_db.hrl"). -include("couch_js_functions.hrl"). @@ -455,3 +457,9 @@ auth_design_doc(DocId) -> {<<"validate_doc_update">>, ?AUTH_DB_DOC_VALIDATE_FUNCTION} ], {ok, couch_doc:from_json_obj({DocProps})}. + +update_auth_doc(Doc) -> + DbName = ?l2b(config:get("couch_httpd_auth", "authentication_db", "_users")), + couch_util:with_db(DbName, fun(UserDb) -> + couch_db:update_doc(UserDb, Doc, []) + end). diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl index 752dd20e..a905b6c4 100644 --- a/src/couch_httpd_auth.erl +++ b/src/couch_httpd_auth.erl @@ -367,14 +367,11 @@ maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule) -> IsAdmin = lists:member(<<"_admin">>, couch_util:get_value(<<"roles">>, UserProps, [])), case {IsAdmin, couch_util:get_value(<<"password_scheme">>, UserProps, <<"simple">>)} of {false, <<"simple">>} -> - DbName = ?l2b(config:get("couch_httpd_auth", "authentication_db", "_users")), - couch_util:with_db(DbName, fun(UserDb) -> - UserProps2 = proplists:delete(<<"password_sha">>, UserProps), - UserProps3 = [{<<"password">>, Password} | UserProps2], - NewUserDoc = couch_doc:from_json_obj({UserProps3}), - {ok, _NewRev} = couch_db:update_doc(UserDb, NewUserDoc, []), - AuthModule:get_user_creds(UserName) - end); + UserProps2 = proplists:delete(<<"password_sha">>, UserProps), + UserProps3 = [{<<"password">>, Password} | UserProps2], + NewUserDoc = couch_doc:from_json_obj({UserProps3}), + {ok, _NewRev} = AuthModule:update_auth_doc(NewUserDoc), + AuthModule:get_user_creds(UserName); _ -> UserProps end. From 6266b95415f8c8d8cde49a8ce221e9d31ebf18b8 Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Thu, 13 Nov 2014 18:02:05 +0000 Subject: [PATCH 3/5] Move admin ddoc check for _users DB to http layer In order to restrict access to design documents in the authentication DB to admins only we were checking whether a user was admin in the couch_server callback. When running the auth DB on the clustered interface this meant that admins could not read the design doc because the user context was not being passed to any of the calls to open the design doc. One possible fix is to add the user context to all the clustering code involving design doc access however given the amount of plumbing here is fairly substantial the chances of getting it wrong are rather high. The alternative is to move this check into the http layer where we already have access to the user context. This commit moves the admin check when accessing design docs in the auth DB into couch_httpd_db (for the admin port). A separate commit in couchdb-chttpd adds a similar check for requests through the clustered port. COUCHDB-2452 3/3 --- src/couch_httpd_db.erl | 23 +++++++++++++++++++++++ src/couch_users_db.erl | 15 +++------------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/couch_httpd_db.erl b/src/couch_httpd_db.erl index 34f04f21..12e59d10 100644 --- a/src/couch_httpd_db.erl +++ b/src/couch_httpd_db.erl @@ -489,6 +489,29 @@ db_doc_req(#httpd{method='DELETE'}=Req, Db, DocId) -> end; db_doc_req(#httpd{method = 'GET', mochi_req = MochiReq} = Req, Db, DocId) -> + case DocId of + <<"_design/", _/binary>> -> + DbName = mem3:dbname(Db#db.name), + AuthDbName = ?l2b(config:get("couch_httpd_auth", "authentication_db")), + case AuthDbName of + DbName -> + % in the authentication database, design doc access are admin-only. + %{SecProps} = fabric:get_security(DbName), + %case (catch couch_db:check_is_admin(Db#db{security=SecProps})) of + case (catch couch_db:check_is_admin(Db)) of + ok -> + ok; + _ -> + throw({forbidden, + <<"Only administrators can view design docs in the users database.">>}) + end; + _Else -> + % on other databases, design doc access is free for all. + ok + end; + _Else -> + ok + end, #doc_query_args{ rev = Rev, open_revs = Revs, diff --git a/src/couch_users_db.erl b/src/couch_users_db.erl index 3b768623..a16b874a 100644 --- a/src/couch_users_db.erl +++ b/src/couch_users_db.erl @@ -73,11 +73,8 @@ save_doc(#doc{body={Body}} = Doc) -> Doc#doc{body={Body3}} end. -% If the doc is a design doc -% If the request's userCtx identifies an admin -% -> return doc -% Else -% -> 403 // Forbidden +% If the doc is a design doc return it and relay on the http layer to restrict +% access to admins % If the request's userCtx identifies an admin % -> return doc % If the request's userCtx.name doesn't match the doc's name @@ -85,13 +82,7 @@ save_doc(#doc{body={Body}} = Doc) -> % Else % -> return doc after_doc_read(#doc{id = <>} = Doc, Db) -> - case (catch couch_db:check_is_admin(Db)) of - ok -> - Doc; - _ -> - throw({forbidden, - <<"Only administrators can view design docs in the users database.">>}) - end; + Doc; after_doc_read(Doc, #db{user_ctx = UserCtx} = Db) -> #user_ctx{name=Name} = UserCtx, DocName = get_doc_name(Doc), From 4e24b4caef90ab5749f614e62965b5dbe58f5283 Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Wed, 12 Nov 2014 15:39:14 +0000 Subject: [PATCH 4/5] Whitespace --- src/couch_server.erl | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/couch_server.erl b/src/couch_server.erl index 71e96db3..9e6ea703 100644 --- a/src/couch_server.erl +++ b/src/couch_server.erl @@ -122,26 +122,26 @@ maybe_add_sys_db_callbacks(DbName, Options) -> DbsDbName = config:get("mem3", "shard_db", "dbs"), NodesDbName = config:get("mem3", "shard_db", "nodes"), IsReplicatorDb = DbName == config:get("replicator", "db", "_replicator") orelse - path_ends_with(DbName, <<"_replicator">>), + path_ends_with(DbName, <<"_replicator">>), IsUsersDb = DbName ==config:get("couch_httpd_auth", "authentication_db", "_users") orelse - path_ends_with(DbName, <<"_users">>) orelse - binary_to_list(mem3:dbname(DbName)) == - config:get("chttpd_auth", "authentication_db", "_users"), + path_ends_with(DbName, <<"_users">>) orelse + binary_to_list(mem3:dbname(DbName)) == + config:get("chttpd_auth", "authentication_db", "_users"), if - DbName == DbsDbName -> - [sys_db | Options]; - DbName == NodesDbName -> - [sys_db | Options]; - IsReplicatorDb -> - [{before_doc_update, fun couch_replicator_manager:before_doc_update/2}, - {after_doc_read, fun couch_replicator_manager:after_doc_read/2}, - sys_db | Options]; - IsUsersDb -> - [{before_doc_update, fun couch_users_db:before_doc_update/2}, - {after_doc_read, fun couch_users_db:after_doc_read/2}, - sys_db | Options]; - true -> - Options + DbName == DbsDbName -> + [sys_db | Options]; + DbName == NodesDbName -> + [sys_db | Options]; + IsReplicatorDb -> + [{before_doc_update, fun couch_replicator_manager:before_doc_update/2}, + {after_doc_read, fun couch_replicator_manager:after_doc_read/2}, + sys_db | Options]; + IsUsersDb -> + [{before_doc_update, fun couch_users_db:before_doc_update/2}, + {after_doc_read, fun couch_users_db:after_doc_read/2}, + sys_db | Options]; + true -> + Options end. path_ends_with(Path, Suffix) -> From 633b7f1ee9e76cf9a8520b5878b87e0170d9f1a6 Mon Sep 17 00:00:00 2001 From: Mike Wallace Date: Fri, 16 Jan 2015 15:10:40 +0000 Subject: [PATCH 5/5] [squash] Move users DB check into its own fun The chain of orelse statements combined with the indentation was pretty hard to read. This commit moves the checks into their own function and uses lists:any (which also shortcircuits). --- src/couch_server.erl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/couch_server.erl b/src/couch_server.erl index 9e6ea703..024ab4de 100644 --- a/src/couch_server.erl +++ b/src/couch_server.erl @@ -116,6 +116,14 @@ create(DbName, Options0) -> delete(DbName, Options) -> gen_server:call(couch_server, {delete, DbName, Options}, infinity). +is_users_db(DbName) -> + IsUsersDbPreds = [ + fun() -> DbName == config:get("couch_httpd_auth", "authentication_db", "_users") end, + fun() -> path_ends_with(DbName, <<"_users">>) end, + fun() -> binary_to_list(mem3:dbname(DbName)) == config:get("chttpd_auth", "authentication_db", "_users") end + ], + lists:any(fun(F) -> F() end, IsUsersDbPreds). + maybe_add_sys_db_callbacks(DbName, Options) when is_binary(DbName) -> maybe_add_sys_db_callbacks(?b2l(DbName), Options); maybe_add_sys_db_callbacks(DbName, Options) -> @@ -123,10 +131,7 @@ maybe_add_sys_db_callbacks(DbName, Options) -> NodesDbName = config:get("mem3", "shard_db", "nodes"), IsReplicatorDb = DbName == config:get("replicator", "db", "_replicator") orelse path_ends_with(DbName, <<"_replicator">>), - IsUsersDb = DbName ==config:get("couch_httpd_auth", "authentication_db", "_users") orelse - path_ends_with(DbName, <<"_users">>) orelse - binary_to_list(mem3:dbname(DbName)) == - config:get("chttpd_auth", "authentication_db", "_users"), + IsUsersDb = is_users_db(DbName), if DbName == DbsDbName -> [sys_db | Options];