diff options
Diffstat (limited to 'src/couchdb')
-rw-r--r-- | src/couchdb/couch_db.hrl | 3 | ||||
-rw-r--r-- | src/couchdb/couch_httpd.erl | 2 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_auth.erl | 91 | ||||
-rw-r--r-- | src/couchdb/couch_httpd_db.erl | 6 |
4 files changed, 60 insertions, 42 deletions
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl index 99ef8997..17917312 100644 --- a/src/couchdb/couch_db.hrl +++ b/src/couchdb/couch_db.hrl @@ -110,8 +110,7 @@ { name=null, roles=[], - handler, - user_doc + handler }). % This should be updated anytime a header change happens that requires more diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 252ecdb7..68478f4d 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -121,7 +121,7 @@ make_arity_3_fun(SpecStr) -> % SpecStr is "{my_module, my_fun}, {my_module2, my_fun2}" make_fun_spec_strs(SpecStr) -> - [FunSpecStr || FunSpecStr <- re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}])]. + re:split(SpecStr, "(?<=})\\s*,\\s*(?={)", [{return, list}]). stop() -> mochiweb_http:stop(?MODULE). diff --git a/src/couchdb/couch_httpd_auth.erl b/src/couchdb/couch_httpd_auth.erl index d7b8181f..d1081611 100644 --- a/src/couchdb/couch_httpd_auth.erl +++ b/src/couchdb/couch_httpd_auth.erl @@ -43,7 +43,7 @@ special_test_authentication_handler(Req) -> Req#httpd{user_ctx=#user_ctx{roles=[<<"_admin">>]}} end. -basic_username_pw(Req) -> +basic_name_pw(Req) -> AuthorizationHeader = header_value(Req, "Authorization"), case AuthorizationHeader of "Basic " ++ Base64Value -> @@ -63,7 +63,7 @@ basic_username_pw(Req) -> end. default_authentication_handler(Req) -> - case basic_username_pw(Req) of + case basic_name_pw(Req) of {User, Pass} -> case get_user(?l2b(User)) of nil -> @@ -76,8 +76,7 @@ default_authentication_handler(Req) -> true -> Req#httpd{user_ctx=#user_ctx{ name=?l2b(User), - roles=proplists:get_value(<<"roles">>, UserProps, []), - user_doc={UserProps} + roles=proplists:get_value(<<"roles">>, UserProps, []) }}; _Else -> throw({unauthorized, <<"Name or password is incorrect.">>}) @@ -105,8 +104,8 @@ null_authentication_handler(Req) -> get_user(UserName) -> case couch_config:get("admins", ?b2l(UserName)) of "-hashed-" ++ HashedPwdAndSalt -> - % the username is an admin, now check to see if there is a user doc - % which has a matching username, salt, and password_sha + % the name is an admin, now check to see if there is a user doc + % which has a matching name, salt, and password_sha [HashedPwd, Salt] = string:tokens(HashedPwdAndSalt, ","), case get_user_props_from_db(UserName) of nil -> @@ -117,8 +116,7 @@ get_user(UserName) -> DocRoles = proplists:get_value(<<"roles">>, UserProps), [{<<"roles">>, [<<"_admin">> | DocRoles]}, {<<"salt">>, ?l2b(Salt)}, - {<<"password_sha">>, ?l2b(HashedPwd)}, - {<<"user_doc">>, {UserProps}}] + {<<"password_sha">>, ?l2b(HashedPwd)}] end; Else -> get_user_props_from_db(UserName) @@ -128,15 +126,21 @@ get_user_props_from_db(UserName) -> DbName = couch_config:get("couch_httpd_auth", "authentication_db"), {ok, Db} = ensure_users_db_exists(?l2b(DbName)), DocId = <<"org.couchdb.user:", UserName/binary>>, - try couch_httpd_db:couch_doc_open(Db, DocId, nil, []) of - #doc{}=Doc -> - {DocProps} = couch_query_servers:json_doc(Doc), - case proplists:get_value(<<"type">>, DocProps) of - <<"user">> -> - DocProps; - _Else -> - ?LOG_ERROR("Invalid user doc. Id: ~p",[DocId]), - nil + try couch_httpd_db:couch_doc_open(Db, DocId, nil, [conflicts]) of + #doc{meta=Meta}=Doc -> + % check here for conflict state and throw error if conflicted + case proplists:get_value(conflicts,Meta,[]) of + [] -> + {DocProps} = couch_query_servers:json_doc(Doc), + case proplists:get_value(<<"type">>, DocProps) of + <<"user">> -> + DocProps; + _Else -> + ?LOG_ERROR("Invalid user doc. Id: ~p",[DocId]), + nil + end; + Else -> + throw({unauthorized, <<"User document conflict must be resolved before login.">>}) end catch throw:Throw -> @@ -180,23 +184,23 @@ auth_design_doc(DocId) -> if (newDoc._deleted === true) { // allow deletes by admins and matching users // without checking the other fields - if ((userCtx.roles.indexOf('_admin') != -1) || (userCtx.name == oldDoc.username)) { + if ((userCtx.roles.indexOf('_admin') != -1) || (userCtx.name == oldDoc.name)) { return; } else { throw({forbidden : 'Only admins may delete other user docs.'}); } } - if (!newDoc.username) { - throw({forbidden : 'doc.username is required'}); + if (!newDoc.name) { + throw({forbidden : 'doc.name is required'}); } if (!(newDoc.roles && (typeof newDoc.roles.length != 'undefined') )) { throw({forbidden : 'doc.roles must be an array'}); } - if (newDoc._id != 'org.couchdb.user:'+newDoc.username) { - throw({forbidden : 'Docid must be of the form org.couchdb.user:username'}); + if (newDoc._id != 'org.couchdb.user:'+newDoc.name) { + throw({forbidden : 'Docid must be of the form org.couchdb.user:name'}); } if (oldDoc) { // validate all updates - if (oldDoc.username != newDoc.username) { + if (oldDoc.name != newDoc.name) { throw({forbidden : 'Usernames may not be changed.'}); } } @@ -205,7 +209,7 @@ auth_design_doc(DocId) -> } if (userCtx.roles.indexOf('_admin') == -1) { // not an admin if (oldDoc) { // validate non-admin updates - if (userCtx.name != newDoc.username) { + if (userCtx.name != newDoc.name) { throw({forbidden : 'You may only update your own user document.'}); } // validate role updates @@ -229,8 +233,8 @@ auth_design_doc(DocId) -> throw({forbidden : 'No system roles (starting with underscore) in users db.'}); } }; - // no system names as usernames - if (newDoc.username[0] == '_') { + // no system names as names + if (newDoc.name[0] == '_') { throw({forbidden : 'Username may not start with underscore.'}); } }">> @@ -276,8 +280,7 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req) -> ?LOG_DEBUG("Successful cookie auth as: ~p", [User]), Req#httpd{user_ctx=#user_ctx{ name=?l2b(User), - roles=proplists:get_value(<<"roles">>, UserProps, []), - user_doc=proplists:get_value(<<"user_doc">>, UserProps, null) + roles=proplists:get_value(<<"roles">>, UserProps, []) }, auth={FullSecret, TimeLeft < Timeout*0.9}}; _Else -> Req @@ -340,7 +343,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> _ -> [] end, - UserName = ?l2b(proplists:get_value("username", Form, "")), + UserName = ?l2b(proplists:get_value("name", Form, "")), Password = ?l2b(proplists:get_value("password", Form, "")), ?LOG_DEBUG("Attempt Login: ~s",[UserName]), User = case get_user(UserName) of @@ -367,9 +370,8 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> send_json(Req#httpd{req_body=ReqBody}, Code, Headers, {[ {ok, true}, - {name, proplists:get_value(<<"username">>, User, null)}, - {roles, proplists:get_value(<<"roles">>, User, [])}, - {user_doc, proplists:get_value(<<"user_doc">>, User, null)} + {name, proplists:get_value(<<"name">>, User, null)}, + {roles, proplists:get_value(<<"roles">>, User, [])} ]}); _Else -> % clear the session @@ -377,6 +379,7 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req) -> send_json(Req, 401, [Cookie], {[{error, <<"unauthorized">>},{reason, <<"Name or password is incorrect.">>}]}) end; % get user info +% GET /_session handle_session_req(#httpd{method='GET', user_ctx=UserCtx}=Req) -> Name = UserCtx#user_ctx.name, ForceLogin = couch_httpd:qs_value(Req, "basic", "false"), @@ -385,15 +388,20 @@ handle_session_req(#httpd{method='GET', user_ctx=UserCtx}=Req) -> throw({unauthorized, <<"Please login.">>}); {Name, _} -> send_json(Req, {[ + % remove this ok {ok, true}, - {name, Name}, - {roles, UserCtx#user_ctx.roles}, + {<<"userCtx">>, {[ + {name, Name}, + {roles, UserCtx#user_ctx.roles} + ]}}, {info, {[ - {user_db, ?l2b(couch_config:get("couch_httpd_auth", "authentication_db"))}, - {handlers, [?l2b(H) || H <- couch_httpd:make_fun_spec_strs( + {authentication_db, ?l2b(couch_config:get("couch_httpd_auth", "authentication_db"))}, + {authentication_handlers, [auth_name(H) || H <- couch_httpd:make_fun_spec_strs( couch_config:get("httpd", "authentication_handlers"))]} - ] ++ maybe_value(authenticated, UserCtx#user_ctx.handler)}} - ] ++ maybe_value(user_doc, UserCtx#user_ctx.user_doc)}) + ] ++ maybe_value(authenticated, UserCtx#user_ctx.handler, fun(Handler) -> + auth_name(?b2l(Handler)) + end)}} + ]}) end; % logout by deleting the session handle_session_req(#httpd{method='DELETE'}=Req) -> @@ -408,9 +416,16 @@ handle_session_req(#httpd{method='DELETE'}=Req) -> handle_session_req(Req) -> send_method_not_allowed(Req, "GET,HEAD,POST,DELETE"). +maybe_value(Key, undefined, Fun) -> []; +maybe_value(Key, Else, Fun) -> + [{Key, Fun(Else)}]. maybe_value(Key, undefined) -> []; maybe_value(Key, Else) -> [{Key, Else}]. +auth_name(String) when is_list(String) -> + [_,_,_,_,_,Name|_] = re:split(String, "[\\W_]", [{return, list}]), + ?l2b(Name). + to_int(Value) when is_binary(Value) -> to_int(?b2l(Value)); to_int(Value) when is_list(Value) -> diff --git a/src/couchdb/couch_httpd_db.erl b/src/couchdb/couch_httpd_db.erl index 9233e953..28efc90e 100644 --- a/src/couchdb/couch_httpd_db.erl +++ b/src/couchdb/couch_httpd_db.erl @@ -467,7 +467,11 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) -> send_json(Req, 417, ErrorsJson) end; false -> - Docs = [couch_doc:from_json_obj(JsonObj) || JsonObj <- DocsArray], + Docs = lists:map(fun(JsonObj) -> + Doc = couch_doc:from_json_obj(JsonObj), + validate_attachment_names(Doc), + Doc + end, DocsArray), {ok, Errors} = couch_db:update_docs(Db, Docs, Options, replicated_changes), ErrorsJson = lists:map(fun update_doc_result_to_json/1, Errors), |