You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2012/01/03 20:33:21 UTC
[1/11] git commit: Implement "System Database Security"
Updated Branches:
refs/heads/1.2.x 5ef0f3cb9 -> 6d6400c37
refs/heads/master adb62ce3a -> 3f2537fc7
refs/heads/system-db-security-1.2.x-asf [created] 6d6400c37
refs/heads/system-db-security-master-asf [created] 3f2537fc7
Implement "System Database Security"
System databases at this point are the _users database and the
_replicator database. For each database we implement two call-
backs: before_doc_update and after_doc_read to modify documents
just before they are written to the database and right after
reading them from the database.
_users database:
The before_doc_update callback has the following workflow:
If the request's userCtx identifies an admin or db-admin
-> save_doc (see below)
If the request's userCtx.name is null:
-> save_doc
// this is an anonymous user registering a new document
// in case a user doc with the same id already exists, the anonymous
// user will get a regular doc update conflict.
If the request's userCtx.name doesn't match the doc's name
-> 404 // Not Found
Else
-> save_doc
When a "password" field is present, save_doc() will hash the password
and write the result to the "password_sha" field, add a "salt" field
and delete the "password" field:
If newDoc.password == null:
->
noop
Else -> // calculate password hash server side
newDoc.password_sha = hash_pw(newDoc.password + salt)
newDoc.salt = salt
newDoc.password = null
The after_doc_read callback has the following workflow:
If the request's userCtx identifies an admin or db-admin
-> return doc
If the doc is a design doc and the userCtx doesn't identify
an admin or db-admin:
-> 403 // Forbidden
If the request's userCtx.name doesn't match the doc's name
-> 404 // Not Found
Else
-> return doc
_replicator database:
after_doc_read callback:
If the request's userCtx identifies an admin
-> return doc
If the request's userCtx.name doesn't match the doc's owner
-> strip/hide sensitive data (passwords, oauth tokens)
Else
-> return doc
before_write callback:
If Couch is in admin party mode
-> save doc (see below)
If the request's userCtx identifies an admin or the _replicator role
-> save_doc (see below)
If the request's userCtx.name doesn't match the doc's owner or doc.owner == null
-> 401
Else
-> save_doc (see below)
save_doc:
If doc.owner == undefined
-> If CouchDB is in Admin Party
-> doc.owner = null
Else
-> doc.owner = req.userCtx.name
Save doc to db.
Feature by Filipe, Benoit and Jan.
Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/1386bfec
Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/1386bfec
Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/1386bfec
Branch: refs/heads/1.2.x
Commit: 1386bfec42240255ff96a232d6113f3d910708b2
Parents: 225b39b
Author: Jan Lehnardt <ja...@apache.org>
Authored: Thu Dec 22 15:52:22 2011 +0100
Committer: Jan Lehnardt <ja...@apache.org>
Committed: Tue Jan 3 19:23:59 2012 +0100
----------------------------------------------------------------------
src/couchdb/Makefile.am | 2 +
src/couchdb/couch_db.erl | 33 +++++++-
src/couchdb/couch_db.hrl | 4 +-
src/couchdb/couch_db_updater.erl | 7 +-
src/couchdb/couch_httpd_db.erl | 29 ++++++-
src/couchdb/couch_js_functions.hrl | 31 +++++++-
src/couchdb/couch_replication_manager.erl | 68 +++++++++++++++-
src/couchdb/couch_server.erl | 29 ++++++-
src/couchdb/couch_users_db.erl | 101 ++++++++++++++++++++++++
src/couchdb/couch_view_group.erl | 2 +-
src/couchdb/couch_view_updater.erl | 5 +-
11 files changed, 293 insertions(+), 18 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/Makefile.am
----------------------------------------------------------------------
diff --git a/src/couchdb/Makefile.am b/src/couchdb/Makefile.am
index 181cebf..e16b066 100644
--- a/src/couchdb/Makefile.am
+++ b/src/couchdb/Makefile.am
@@ -83,6 +83,7 @@ source_files = \
couch_stats_collector.erl \
couch_stream.erl \
couch_task_status.erl \
+ couch_users_db.erl \
couch_util.erl \
couch_uuids.erl \
couch_view.erl \
@@ -153,6 +154,7 @@ compiled_files = \
couch_stats_collector.beam \
couch_stream.beam \
couch_task_status.beam \
+ couch_users_db.beam \
couch_util.beam \
couch_uuids.beam \
couch_view.beam \
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_db.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl
index b249ab4..2b45dd8 100644
--- a/src/couchdb/couch_db.erl
+++ b/src/couchdb/couch_db.erl
@@ -28,7 +28,7 @@
-export([init/1,terminate/2,handle_call/3,handle_cast/2,code_change/3,handle_info/2]).
-export([changes_since/4,changes_since/5,read_doc/2,new_revid/1]).
-export([check_is_admin/1, check_is_member/1]).
--export([reopen/1]).
+-export([reopen/1, is_system_db/1]).
-include("couch_db.hrl").
@@ -100,6 +100,9 @@ reopen(#db{main_pid = Pid, fd_ref_counter = OldRefCntr, user_ctx = UserCtx}) ->
end,
{ok, NewDb#db{user_ctx = UserCtx}}.
+is_system_db(#db{options = Options}) ->
+ lists:member(sys_db, Options).
+
ensure_full_commit(#db{update_pid=UpdatePid,instance_start_time=StartTime}) ->
ok = gen_server:call(UpdatePid, full_commit, infinity),
{ok, StartTime}.
@@ -681,9 +684,11 @@ check_dup_atts2(_) ->
update_docs(Db, Docs, Options, replicated_changes) ->
increment_stat(Db, {couchdb, database_writes}),
+
% associate reference with each doc in order to track duplicates
Docs2 = lists:map(fun(Doc) -> {Doc, make_ref()} end, Docs),
- DocBuckets = group_alike_docs(Docs2),
+ DocBuckets = before_docs_update(Db, group_alike_docs(Docs2)),
+
case (Db#db.validate_doc_funs /= []) orelse
lists:any(
fun({#doc{id= <<?DESIGN_DOC_PREFIX, _/binary>>}, _Ref}) -> true;
@@ -724,7 +729,7 @@ update_docs(Db, Docs, Options, interactive_edit) ->
end
end, {[], []}, Docs2),
- DocBuckets = group_alike_docs(Docs3),
+ DocBuckets = before_docs_update(Db, group_alike_docs(Docs3)),
case (Db#db.validate_doc_funs /= []) orelse
lists:any(
@@ -873,6 +878,17 @@ prepare_doc_summaries(Db, BucketList) ->
Bucket) || Bucket <- BucketList].
+before_docs_update(#db{before_doc_update = nil}, BucketList) ->
+ BucketList;
+before_docs_update(#db{before_doc_update = Fun} = Db, BucketList) ->
+ [lists:map(
+ fun({Doc, Ref}) ->
+ NewDoc = Fun(couch_doc:with_ejson_body(Doc), Db),
+ {NewDoc, Ref}
+ end,
+ Bucket) || Bucket <- BucketList].
+
+
set_new_att_revpos(#doc{revs={RevPos,_Revs},atts=Atts}=Doc) ->
Doc#doc{atts= lists:map(fun(#att{data={_Fd,_Sp}}=Att) ->
% already commited to disk, do not set new rev
@@ -1286,13 +1302,20 @@ make_doc(#db{updater_fd = Fd} = Db, Id, Deleted, Bp, RevisionPath) ->
data={Fd,Sp}}
end, Atts0)}
end,
- #doc{
+ Doc = #doc{
id = Id,
revs = RevisionPath,
body = BodyData,
atts = Atts,
deleted = Deleted
- }.
+ },
+ after_doc_read(Db, Doc).
+
+
+after_doc_read(#db{after_doc_read = nil}, Doc) ->
+ Doc;
+after_doc_read(#db{after_doc_read = Fun} = Db, Doc) ->
+ Fun(couch_doc:with_ejson_body(Doc), Db).
increment_stat(#db{options = Options}, Stat) ->
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_db.hrl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index cc97351..59a5815 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -188,7 +188,9 @@
revs_limit = 1000,
fsync_options = [],
options = [],
- compression
+ compression,
+ before_doc_update = nil, % nil | fun(Doc, Db) -> NewDoc
+ after_doc_read = nil % nil | fun(Doc, Db) -> NewDoc
}).
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_db_updater.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_db_updater.erl b/src/couchdb/couch_db_updater.erl
index 933bad1..b87ffcd 100644
--- a/src/couchdb/couch_db_updater.erl
+++ b/src/couchdb/couch_db_updater.erl
@@ -481,7 +481,9 @@ init_db(DbName, Filepath, Fd, ReaderFd, Header0, Options) ->
revs_limit = Header#db_header.revs_limit,
fsync_options = FsyncOptions,
options = Options,
- compression = Compression
+ compression = Compression,
+ before_doc_update = couch_util:get_value(before_doc_update, Options, nil),
+ after_doc_read = couch_util:get_value(after_doc_read, Options, nil)
}.
open_reader_fd(Filepath, Options) ->
@@ -499,7 +501,8 @@ close_db(#db{fd_ref_counter = RefCntr}) ->
refresh_validate_doc_funs(Db) ->
- {ok, DesignDocs} = couch_db:get_design_docs(Db),
+ {ok, DesignDocs} = couch_db:get_design_docs(
+ Db#db{user_ctx = #user_ctx{roles=[<<"_admin">>]}}),
ProcessDocFuns = lists:flatmap(
fun(DesignDoc) ->
case couch_doc:get_validate_doc_fun(DesignDoc) of
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_httpd_db.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl
index f2fab56..d7ecb4a 100644
--- a/src/couchdb/couch_httpd_db.erl
+++ b/src/couchdb/couch_httpd_db.erl
@@ -155,12 +155,23 @@ handle_design_req(#httpd{
path_parts=[_DbName, _Design, DesignName, <<"_",_/binary>> = Action | _Rest],
design_url_handlers = DesignUrlHandlers
}=Req, Db) ->
+ case couch_db:is_system_db(Db) of
+ true ->
+ case (catch couch_db:check_is_admin(Db)) of
+ ok -> ok;
+ _ ->
+ throw({forbidden, <<"Only admins can access design document",
+ " actions for system databases.">>})
+ end;
+ false -> ok
+ end,
+
% load ddoc
DesignId = <<"_design/", DesignName/binary>>,
DDoc = couch_httpd_db:couch_doc_open(Db, DesignId, nil, [ejson_body]),
Handler = couch_util:dict_find(Action, DesignUrlHandlers, fun(_, _, _) ->
- throw({not_found, <<"missing handler: ", Action/binary>>})
- end),
+ throw({not_found, <<"missing handler: ", Action/binary>>})
+ end),
Handler(Req, Db, DDoc);
handle_design_req(Req, Db) ->
@@ -487,6 +498,20 @@ db_req(#httpd{path_parts=[_, DocId | FileNameParts]}=Req, Db) ->
db_attachment_req(Req, Db, DocId, FileNameParts).
all_docs_view(Req, Db, Keys) ->
+ case couch_db:is_system_db(Db) of
+ true ->
+ case (catch couch_db:check_is_admin(Db)) of
+ ok ->
+ do_all_docs_view(Req, Db, Keys);
+ _ ->
+ throw({forbidden, <<"Only admins can access _all_docs",
+ " of system databases.">>})
+ end;
+ false ->
+ do_all_docs_view(Req, Db, Keys)
+ end.
+
+do_all_docs_view(Req, Db, Keys) ->
RawCollator = fun(A, B) -> A < B end,
#view_query_args{
start_key = StartKey,
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_js_functions.hrl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_js_functions.hrl b/src/couchdb/couch_js_functions.hrl
index 1949904..7ce8ea1 100644
--- a/src/couchdb/couch_js_functions.hrl
+++ b/src/couchdb/couch_js_functions.hrl
@@ -11,7 +11,7 @@
% the License.
-define(AUTH_DB_DOC_VALIDATE_FUNCTION, <<"
- function(newDoc, oldDoc, userCtx) {
+ function(newDoc, oldDoc, userCtx, secObj) {
if (newDoc._deleted === true) {
// allow deletes by admins and matching users
// without checking the other fields
@@ -54,7 +54,34 @@
});
}
- if (userCtx.roles.indexOf('_admin') === -1) {
+ var is_server_or_database_admin = function(userCtx, secObj) {
+ // see if the user is a server admin
+ if(userCtx.roles.indexOf('_admin') !== -1) {
+ return true; // a server admin
+ }
+
+ // see if the user a database admin specified by name
+ if(secObj && secObj.admins && secObj.admins.names) {
+ if(secObj.admins.names.indexOf(userCtx.name) !== -1) {
+ return true; // database admin
+ }
+ }
+
+ // see if the user a database admin specified by role
+ if(secObj && secObj.admins && secObj.admins.roles) {
+ var db_roles = secObj.admins.roles;
+ for(var idx = 0; idx < userCtx.roles.length; idx++) {
+ var user_role = userCtx.roles[idx];
+ if(db_roles.indexOf(user_role) !== -1) {
+ return true; // role matches!
+ }
+ }
+ }
+
+ return false; // default to no admin
+ }
+
+ if (!is_server_or_database_admin(userCtx, secObj)) {
if (oldDoc) { // validate non-admin updates
if (userCtx.name !== newDoc.name) {
throw({
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_replication_manager.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_replication_manager.erl b/src/couchdb/couch_replication_manager.erl
index 9b58cc4..7b998a8 100644
--- a/src/couchdb/couch_replication_manager.erl
+++ b/src/couchdb/couch_replication_manager.erl
@@ -16,6 +16,8 @@
% public API
-export([replication_started/1, replication_completed/2, replication_error/2]).
+-export([before_doc_update/2, after_doc_read/2]).
+
% gen_server callbacks
-export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]).
-export([code_change/3, terminate/2]).
@@ -28,6 +30,9 @@
-define(REP_TO_STATE, couch_rep_id_to_rep_state).
-define(INITIAL_WAIT, 2.5). % seconds
-define(MAX_WAIT, 600). % seconds
+-define(OWNER, <<"owner">>).
+
+-define(replace(L, K, V), lists:keystore(K, 1, L, {K, V})).
-record(rep_state, {
rep,
@@ -237,8 +242,7 @@ changes_feed_loop() ->
#changes_args{
include_docs = true,
feed = "continuous",
- timeout = infinity,
- db_open_options = [sys_db]
+ timeout = infinity
},
{json_req, null},
Db
@@ -628,3 +632,63 @@ state_after_error(#rep_state{retries_left = Left, wait = Wait} = State) ->
_ ->
State#rep_state{retries_left = Left - 1, wait = Wait2}
end.
+
+
+before_doc_update(#doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = Doc, _Db) ->
+ Doc;
+before_doc_update(#doc{body = {Body}} = Doc, #db{user_ctx=UserCtx} = Db) ->
+ #user_ctx{roles = Roles, name = Name} = UserCtx,
+ case lists:member(<<"_replicator">>, Roles) of
+ true ->
+ Doc;
+ false ->
+ case couch_util:get_value(?OWNER, Body) of
+ undefined ->
+ Doc#doc{body = {?replace(Body, ?OWNER, Name)}};
+ Name ->
+ Doc;
+ Other ->
+ case (catch couch_db:check_is_admin(Db)) of
+ ok when Other =:= null ->
+ Doc#doc{body = {?replace(Body, ?OWNER, Name)}};
+ ok ->
+ Doc;
+ _ ->
+ throw({forbidden, <<"Can't update replication documents",
+ " from other users.">>})
+ end
+ end
+ end.
+
+
+after_doc_read(#doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = Doc, _Db) ->
+ Doc;
+after_doc_read(#doc{body = {Body}} = Doc, #db{user_ctx=UserCtx} = Db) ->
+ #user_ctx{name = Name} = UserCtx,
+ case (catch couch_db:check_is_admin(Db)) of
+ ok ->
+ Doc;
+ _ ->
+ case couch_util:get_value(?OWNER, Body) of
+ Name ->
+ Doc;
+ _Other ->
+ Source = strip_credentials(couch_util:get_value(<<"source">>, Body)),
+ Target = strip_credentials(couch_util:get_value(<<"target">>, Body)),
+ NewBody0 = ?replace(Body, <<"source">>, Source),
+ NewBody = ?replace(NewBody0, <<"target">>, Target),
+ #doc{revs = {Pos, [_ | Revs]}} = Doc,
+ NewDoc = Doc#doc{body = {NewBody}, revs = {Pos - 1, Revs}},
+ NewRevId = couch_db:new_revid(NewDoc),
+ NewDoc#doc{revs = {Pos, [NewRevId | Revs]}}
+ end
+ end.
+
+
+strip_credentials(Url) when is_binary(Url) ->
+ re:replace(Url,
+ "http(s)?://(?:[^:]+):[^@]+@(.*)$",
+ "http\\1://\\2",
+ [{return, binary}]);
+strip_credentials({Props}) ->
+ {lists:keydelete(<<"oauth">>, 1, Props)}.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_server.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_server.erl b/src/couchdb/couch_server.erl
index 7418e23..1185a20 100644
--- a/src/couchdb/couch_server.erl
+++ b/src/couchdb/couch_server.erl
@@ -51,7 +51,8 @@ get_stats() ->
sup_start_link() ->
gen_server:start_link({local, couch_server}, couch_server, [], []).
-open(DbName, Options) ->
+open(DbName, Options0) ->
+ Options = maybe_add_sys_db_callbacks(DbName, Options0),
case gen_server:call(couch_server, {open, DbName, Options}, infinity) of
{ok, Db} ->
Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
@@ -60,7 +61,8 @@ open(DbName, Options) ->
Error
end.
-create(DbName, Options) ->
+create(DbName, Options0) ->
+ Options = maybe_add_sys_db_callbacks(DbName, Options0),
case gen_server:call(couch_server, {create, DbName, Options}, infinity) of
{ok, Db} ->
Ctx = couch_util:get_value(user_ctx, Options, #user_ctx{}),
@@ -72,6 +74,29 @@ create(DbName, Options) ->
delete(DbName, Options) ->
gen_server:call(couch_server, {delete, DbName, Options}, infinity).
+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) ->
+ case couch_config:get("replicator", "db", "_replicator") of
+ DbName ->
+ [
+ {before_doc_update, fun couch_replication_manager:before_doc_update/2},
+ {after_doc_read, fun couch_replication_manager:after_doc_read/2},
+ sys_db | Options
+ ];
+ _ ->
+ case couch_config:get("couch_httpd_auth", "authentication_db", "_users") of
+ DbName ->
+ [
+ {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
+ ];
+ _ ->
+ Options
+ end
+ end.
+
check_dbname(#server{dbname_regexp=RegExp}, DbName) ->
case re:run(DbName, RegExp, [{capture, none}]) of
nomatch ->
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_users_db.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_users_db.erl b/src/couchdb/couch_users_db.erl
new file mode 100644
index 0000000..d6e522e
--- /dev/null
+++ b/src/couchdb/couch_users_db.erl
@@ -0,0 +1,101 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_users_db).
+
+-export([before_doc_update/2, after_doc_read/2]).
+
+-include("couch_db.hrl").
+
+-define(NAME, <<"name">>).
+-define(PASSWORD, <<"password">>).
+-define(PASSWORD_SHA, <<"password_sha">>).
+-define(SALT, <<"salt">>).
+-define(replace(L, K, V), lists:keystore(K, 1, L, {K, V})).
+
+% If the request's userCtx identifies an admin
+% -> save_doc (see below)
+%
+% If the request's userCtx.name is null:
+% -> save_doc
+% // this is an anonymous user registering a new document
+% // in case a user doc with the same id already exists, the anonymous
+% // user will get a regular doc update conflict.
+% If the request's userCtx.name doesn't match the doc's name
+% -> 404 // Not Found
+% Else
+% -> save_doc
+before_doc_update(Doc, #db{user_ctx = UserCtx} = Db) ->
+ #user_ctx{name=Name} = UserCtx,
+ DocName = get_doc_name(Doc),
+ case (catch couch_db:check_is_admin(Db)) of
+ ok ->
+ save_doc(Doc);
+ _ when Name =:= DocName orelse Name =:= null ->
+ save_doc(Doc);
+ _ ->
+ throw(not_found)
+ end.
+
+% If newDoc.password == null:
+% ->
+% noop
+% Else -> // calculate password hash server side
+% newDoc.password_sha = hash_pw(newDoc.password + salt)
+% newDoc.salt = salt
+% newDoc.password = null
+save_doc(#doc{body={Body}} = Doc) ->
+ case couch_util:get_value(?PASSWORD, Body) of
+ undefined ->
+ Doc;
+ ClearPassword ->
+ Salt = ?b2l(couch_uuids:random()),
+ PasswordSha = couch_util:to_hex(crypto:sha(?b2l(ClearPassword) ++ Salt)),
+ Body1 = ?replace(Body, ?PASSWORD_SHA, ?l2b(PasswordSha)),
+ Body2 = ?replace(Body1, ?SALT, ?l2b(Salt)),
+ Body3 = proplists:delete(?PASSWORD, Body2),
+ 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 request's userCtx identifies an admin
+% -> return doc
+% If the request's userCtx.name doesn't match the doc's name
+% -> 404 // Not Found
+% Else
+% -> return doc
+after_doc_read(#doc{id = <<?DESIGN_DOC_PREFIX, _/binary>>} = 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;
+after_doc_read(Doc, #db{user_ctx = UserCtx} = Db) ->
+ #user_ctx{name=Name} = UserCtx,
+ DocName = get_doc_name(Doc),
+ case (catch couch_db:check_is_admin(Db)) of
+ ok ->
+ Doc;
+ _ when Name =:= DocName ->
+ Doc;
+ _ ->
+ throw(not_found)
+ end.
+
+get_doc_name(#doc{body={Body}}) ->
+ couch_util:get_value(?NAME, Body).
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_view_group.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_view_group.erl b/src/couchdb/couch_view_group.erl
index 10a16fd..d65964e 100644
--- a/src/couchdb/couch_view_group.erl
+++ b/src/couchdb/couch_view_group.erl
@@ -527,7 +527,7 @@ sort_lib([{LName, LCode}|Rest], LAcc) ->
sort_lib(Rest, [{LName, LCode}|LAcc]).
open_db_group(DbName, GroupId) ->
- case couch_db:open_int(DbName, []) of
+ case couch_db:open_int(DbName, [{user_ctx, #user_ctx{roles=[<<"_admin">>]}}]) of
{ok, Db} ->
case couch_db:open_doc(Db, GroupId, [ejson_body]) of
{ok, Doc} ->
http://git-wip-us.apache.org/repos/asf/couchdb/blob/1386bfec/src/couchdb/couch_view_updater.erl
----------------------------------------------------------------------
diff --git a/src/couchdb/couch_view_updater.erl b/src/couchdb/couch_view_updater.erl
index 084ce77..51f06b4 100644
--- a/src/couchdb/couch_view_updater.erl
+++ b/src/couchdb/couch_view_updater.erl
@@ -19,7 +19,10 @@
-spec update(_, #group{}, Dbname::binary()) -> no_return().
update(Owner, Group, DbName) when is_binary(DbName) ->
- {ok, Db} = couch_db:open_int(DbName, []),
+ {ok, Db} = couch_db:open_int(DbName, [
+ % allow reading docs from system dbs
+ {user_ctx, #user_ctx{roles=[<<"_admin">>]}
+ }]),
try
update(Owner, Group, Db)
after