Permalink
Browse files

add validate_doc_read property to a design document.

Like validate_doc_update, this function validate if the document can be
read by the curreny user.

ex:

    function(doc, userCtx) {
        if ((typeof doc.name !== 'undefined') && (doc.name != userCtx.name)) {
            throw({unauthorized: doc.name + ' cannnot read ' + doc._id});
        }
    }

will allow the current user to only read the documents where the
properties name is the name of the user.

Note: admins can always read the documents.
  • Loading branch information...
1 parent 480984b commit c539a83479137fe26bb47cae42567cbf05d6428a @benoitc benoitc committed Jul 11, 2012
@@ -153,6 +153,7 @@
name,
filepath,
validate_doc_funs = [],
+ validate_doc_read_funs = [],
security = [],
security_ptr = nil,
user_ctx = #user_ctx{},
@@ -60,9 +60,10 @@ var DDoc = (function() {
"lists" : Render.list,
"shows" : Render.show,
"filters" : Filter.filter,
- "views" : Filter.filter_view,
+ "views" : Filter.filter_view,
"updates" : Render.update,
- "validate_doc_update" : Validate.validate
+ "validate_doc_update" : Validate.validate,
+ "validate_doc_read" : Validate.validate
};
var ddocs = {};
return {
@@ -1348,16 +1348,44 @@ make_doc(#db{fd = Fd} = Db, Id, Deleted, Bp, RevisionPath) ->
"docs in a dropbox database.">>})
end;
_ ->
- after_doc_read(Db, Doc)
+ Doc1 = after_doc_read(Db, Doc),
+ ok = validate_doc_read(Db, Doc1),
+ Doc1
end.
-
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).
+validate_doc_read(#db{validate_doc_read_funs=[]}, _Doc) ->
+ ok;
+validate_doc_read(_Db, #doc{id= <<"_local/",_/binary>>}) ->
+ ok;
+validate_doc_read(Db, Doc) ->
+ case catch(check_is_admin(Db)) of
+ ok ->
+ ok;
+ _ ->
+ JsonCtx = couch_util:json_user_ctx(Db),
+ SecObj = get_security(Db),
+ try [case Fun(Doc, JsonCtx, SecObj) of
+ ok -> ok;
+ Error -> throw(Error)
+ end || Fun <- Db#db.validate_doc_read_funs],
+ ok
+ catch
+ throw:{forbidden, _}=Error ->
+ throw(Error);
+ throw:{unauthorized, _}=Error ->
+ throw(Error);
+ throw:Error ->
+ lager:error("Error while validating read: ~p~n", [Error]),
+ ok
+ end
+ end.
+
increment_stat(#db{options = Options}, Stat) ->
case lists:member(sys_db, Options) of
true ->
@@ -500,16 +500,24 @@ close_db(#db{fd_monitor = Ref}) ->
refresh_validate_doc_funs(Db0) ->
Db = Db0#db{user_ctx = #user_ctx{roles=[<<"_admin">>]}},
DesignDocs = couch_db:get_design_docs(Db),
- ProcessDocFuns = lists:flatmap(
- fun(DesignDocInfo) ->
- {ok, DesignDoc} = couch_db:open_doc_int(
- Db, DesignDocInfo, [ejson_body]),
- case couch_doc:get_validate_doc_fun(DesignDoc) of
- nil -> [];
- Fun -> [Fun]
- end
- end, DesignDocs),
- Db0#db{validate_doc_funs=ProcessDocFuns}.
+
+ {UpdateFuns, ReadFuns} = lists:foldl(fun(DesignDocInfo, {UAcc, RAcc}) ->
+ {ok, DesignDoc} = couch_db:open_doc_int(Db, DesignDocInfo,
+ [ejson_body]),
+
+ UAcc1 = case couch_doc:get_validate_doc_fun(DesignDoc) of
+ nil -> UAcc;
+ Fun -> [Fun|UAcc]
+ end,
+ RAcc1 = case couch_doc:get_validate_read_doc_fun(DesignDoc) of
+ nil -> RAcc;
+ Fun1 -> [Fun1|RAcc]
+ end,
+ {UAcc1, RAcc1}
+ end, {[], []}, DesignDocs),
+
+
+ Db0#db{validate_doc_funs=UpdateFuns, validate_doc_read_funs=ReadFuns}.
% rev tree functions
@@ -13,7 +13,8 @@
-module(couch_doc).
-export([to_doc_info/1,to_doc_info_path/1,parse_rev/1,parse_revs/1,rev_to_str/1,revs_to_strs/1]).
--export([att_foldl/3,range_att_foldl/5,att_foldl_decode/3,get_validate_doc_fun/1]).
+-export([att_foldl/3,range_att_foldl/5,att_foldl_decode/3,get_validate_doc_fun/1,
+ get_validate_read_doc_fun/1]).
-export([from_json_obj/1,to_json_obj/2,has_stubs/1, merge_stubs/2]).
-export([validate_docid/1]).
-export([doc_from_multi_part_stream/2]).
@@ -62,8 +63,8 @@ revid_to_str(RevId) ->
rev_to_str({Pos, RevId}) ->
?l2b([integer_to_list(Pos),"-",revid_to_str(RevId)]).
-
-
+
+
revs_to_strs([]) ->
[];
revs_to_strs([{Pos, RevId}| Rest]) ->
@@ -406,6 +407,15 @@ get_validate_doc_fun(#doc{body={Props}}=DDoc) ->
end
end.
+get_validate_read_doc_fun(#doc{body={Props}}=DDoc) ->
+ case couch_util:get_value(<<"validate_doc_read">>, Props) of
+ undefined ->
+ nil;
+ _Else ->
+ fun(Doc, Ctx, SecObj) ->
+ couch_query_servers:validate_doc_read(DDoc, Doc, Ctx, SecObj)
+ end
+ end.
has_stubs(#doc{atts=Atts}) ->
has_stubs(Atts);
@@ -423,7 +433,7 @@ merge_stubs(#doc{id=Id,atts=MemBins}=StubsDoc, #doc{atts=DiskBins}) ->
MergedBins = lists:map(
fun(#att{name=Name, data=stub, revpos=StubRevPos}) ->
case dict:find(Name, BinDict) of
- {ok, #att{revpos=DiskRevPos}=DiskAtt}
+ {ok, #att{revpos=DiskRevPos}=DiskAtt}
when DiskRevPos == StubRevPos orelse StubRevPos == nil ->
DiskAtt;
_ ->
@@ -517,7 +527,7 @@ doc_from_multi_part_stream(ContentType, DataFun) ->
end),
Ref = make_ref(),
Parser ! {get_doc_bytes, Ref, self()},
- receive
+ receive
{doc_bytes, Ref, DocBytes} ->
Doc = from_json_obj(?JSON_DECODE(DocBytes)),
% go through the attachments looking for 'follows' in the data,
@@ -132,6 +132,7 @@
}
">>).
+
-define(OAUTH_MAP_FUN, <<"
function(doc) {
if (doc.type === 'user' && doc.oauth && doc.oauth.consumer_keys) {
@@ -177,7 +177,7 @@ run(#evstate{ddocs=DDocs}=State, [<<"ddoc">>, DDocId | Rest]) ->
run(_, Unknown) ->
?LOG_ERROR("Native Process: Unknown command: ~p~n", [Unknown]),
throw({error, unknown_command}).
-
+
ddoc(State, {DDoc}, [FunPath, Args]) ->
% load fun from the FunPath
BFun = lists:foldl(fun
@@ -194,6 +194,8 @@ ddoc(State, {DDoc}, [FunPath, Args]) ->
ddoc(State, {_, Fun}, [<<"validate_doc_update">>], Args) ->
{State, (catch apply(Fun, Args))};
+ddoc(State, {_, Fun}, [<<"validate_doc_read">>], Args) ->
+ {State, (catch apply(Fun, Args))};
ddoc(State, {_, Fun}, [<<"filters">>|_], [Docs, Req]) ->
FilterFunWrapper = fun(Doc) ->
case catch Fun(Doc, Req) of
@@ -309,7 +311,7 @@ bindings(State, Sig, DDoc) ->
throw({timeout, list_pid_getrow})
end
end,
-
+
FoldRows = fun(Fun, Acc) -> foldrows(GetRow, Fun, Acc) end,
Bindings = [
@@ -19,7 +19,7 @@
code_change/3]).
-export([start_doc_map/3, map_docs/2, map_docs_raw/2, stop_doc_map/1,
raw_to_ejson/1]).
--export([reduce/3, rereduce/3,validate_doc_update/5]).
+-export([reduce/3, rereduce/3,validate_doc_update/5, validate_doc_read/4]).
-export([filter_view/3]).
-export([with_ddoc_proc/2, proc_prompt/2, ddoc_prompt/3, ddoc_proc_prompt/3,
json_doc/1]).
@@ -229,7 +229,8 @@ builtin_stats(rereduce, [[_,First]|Rest]) ->
validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx, SecObj) ->
JsonEditDoc = couch_doc:to_json_obj(EditDoc, [revs]),
JsonDiskDoc = json_doc(DiskDoc),
- case ddoc_prompt(DDoc, [<<"validate_doc_update">>], [JsonEditDoc, JsonDiskDoc, Ctx, SecObj]) of
+ case ddoc_prompt(DDoc, [<<"validate_doc_update">>],
+ [JsonEditDoc, JsonDiskDoc, Ctx, SecObj]) of
1 ->
ok;
{[{<<"forbidden">>, Message}]} ->
@@ -238,6 +239,21 @@ validate_doc_update(DDoc, EditDoc, DiskDoc, Ctx, SecObj) ->
throw({unauthorized, Message})
end.
+
+validate_doc_read(DDoc, Doc, Ctx, SecObj) ->
+ JsonDoc = couch_doc:to_json_obj(Doc, [revs]),
+ case ddoc_prompt(DDoc, [<<"validate_doc_read">>],
+ [JsonDoc, Ctx, SecObj]) of
+ 1 ->
+ ok;
+ {[{<<"forbidden">>, Message}]} ->
+ throw({forbidden, Message});
+ {[{<<"unauthorized">>, Message}]} ->
+ throw({unauthorized, Message})
+ end.
+
+
+
json_doc(nil) -> null;
json_doc(Doc) ->
couch_doc:to_json_obj(Doc, [revs]).

0 comments on commit c539a83

Please sign in to comment.