Refactor couch_httpd_auth's AuthModule construct We were missing the change necessary to upgrade user documents with the move to pbkdf2 in a cluster. This also adds the ability for an AuthModule to return a context that will be used when updating user credentials.
COUCHDB-2491 Project: http://git-wip-us.apache.org/repos/asf/couchdb-couch/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-couch/commit/3e8286d4 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-couch/tree/3e8286d4 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-couch/diff/3e8286d4 Branch: refs/heads/2491-refactor-couch-httpd-auth Commit: 3e8286d40664fa0d7f32a3d375f64e64d181022c Parents: 2a45cb0 Author: Paul J. Davis <[email protected]> Authored: Thu Dec 4 13:07:56 2014 -0600 Committer: Paul J. Davis <[email protected]> Committed: Thu Dec 4 14:04:57 2014 -0600 ---------------------------------------------------------------------- src/couch_auth_cache.erl | 26 ++++++++++++++++++++------ src/couch_httpd_auth.erl | 37 +++++++++++++++++++------------------ src/couch_httpd_oauth.erl | 2 +- 3 files changed, 40 insertions(+), 25 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_auth_cache.erl ---------------------------------------------------------------------- diff --git a/src/couch_auth_cache.erl b/src/couch_auth_cache.erl index 8cf631b..9eaa7c6 100644 --- a/src/couch_auth_cache.erl +++ b/src/couch_auth_cache.erl @@ -16,7 +16,8 @@ -behaviour(config_listener). % public API --export([get_user_creds/1, get_admin/1, add_roles/2]). +-export([get_user_creds/1, get_user_creds/2, update_user_creds/3]). +-export([get_admin/1, add_roles/2]). % gen_server API -export([start_link/0, init/1, handle_call/3, handle_info/2, handle_cast/2]). @@ -42,12 +43,18 @@ -spec get_user_creds(UserName::string() | binary()) -> - Credentials::list() | nil. - -get_user_creds(UserName) when is_list(UserName) -> - get_user_creds(?l2b(UserName)); + {ok, Credentials::list(), term()} | nil. get_user_creds(UserName) -> + get_user_creds(nil, UserName). + +-spec get_user_creds(Req::#httpd{}, UserName::string() | binary()) -> + {ok, Credentials::list(), term()} | nil. + +get_user_creds(Req, UserName) when is_list(UserName) -> + get_user_creds(Req, ?l2b(UserName)); + +get_user_creds(_Req, UserName) -> UserCreds = case get_admin(UserName) of nil -> get_from_cache(UserName); @@ -61,6 +68,13 @@ get_user_creds(UserName) -> end, validate_user_creds(UserCreds). +update_user_creds(_Req, UserDoc, _AuthCtx) -> + DbNameList = config:get("couch_httpd_auth", "authentication_db", "_users"), + couch_util:with_db(?l2b(DbNameList), fun(UserDb) -> + {ok, _NewRev} = couch_db:update_doc(UserDb, UserDoc, []), + ok + end). + add_roles(Props, ExtraRoles) -> CurrentRoles = couch_util:get_value(<<"roles">>, Props), lists:keyreplace(<<"roles">>, 1, Props, {<<"roles">>, CurrentRoles ++ ExtraRoles}). @@ -123,7 +137,7 @@ validate_user_creds(UserCreds) -> " is used for authentication purposes.">> }) end, - UserCreds. + {ok, UserCreds, nil}. start_link() -> http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_httpd_auth.erl ---------------------------------------------------------------------- diff --git a/src/couch_httpd_auth.erl b/src/couch_httpd_auth.erl index 752dd20..e1af621 100644 --- a/src/couch_httpd_auth.erl +++ b/src/couch_httpd_auth.erl @@ -73,17 +73,18 @@ default_authentication_handler(Req) -> default_authentication_handler(Req, AuthModule) -> case basic_name_pw(Req) of {User, Pass} -> - case AuthModule:get_user_creds(User) of + case AuthModule:get_user_creds(Req, User) of nil -> throw({unauthorized, <<"Name or password is incorrect.">>}); - UserProps -> + {ok, UserProps, AuthCtx} -> reject_if_totp(UserProps), UserName = ?l2b(User), Password = ?l2b(Pass), case authenticate(Password, UserProps) of true -> - UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps, - AuthModule), + UserProps2 = maybe_upgrade_password_hash( + Req, UserName, Password, UserProps, + AuthModule, AuthCtx), Req#httpd{user_ctx=#user_ctx{ name=UserName, roles=couch_util:get_value(<<"roles">>, UserProps2, []) @@ -195,9 +196,9 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req, AuthModule) -> Req; SecretStr -> Secret = ?l2b(SecretStr), - case AuthModule:get_user_creds(User) of + case AuthModule:get_user_creds(Req, User) of nil -> Req; - UserProps -> + {ok, UserProps, _AuthCtx} -> UserSalt = couch_util:get_value(<<"salt">>, UserProps, <<"">>), FullSecret = <<Secret/binary, UserSalt/binary>>, ExpectedHash = crypto:sha_mac(FullSecret, User ++ ":" ++ TimeStr), @@ -284,14 +285,15 @@ handle_session_req(#httpd{method='POST', mochi_req=MochiReq}=Req, AuthModule) -> UserName = ?l2b(couch_util:get_value("name", Form, "")), Password = ?l2b(couch_util:get_value("password", Form, "")), couch_log:debug("Attempt Login: ~s",[UserName]), - UserProps = case AuthModule:get_user_creds(UserName) of - nil -> []; + {ok, UserProps, AuthCtx} = case AuthModule:get_user_creds(Req, UserName) of + nil -> {ok, [], nil}; Result -> Result end, case authenticate(Password, UserProps) of true -> verify_totp(UserProps, Form), - UserProps2 = maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule), + UserProps2 = maybe_upgrade_password_hash( + Req, UserName, Password, UserProps, AuthModule, AuthCtx), % setup the session cookie Secret = ?l2b(ensure_cookie_auth_secret()), UserSalt = couch_util:get_value(<<"salt">>, UserProps2), @@ -363,18 +365,17 @@ maybe_value(_Key, undefined, _Fun) -> []; maybe_value(Key, Else, Fun) -> [{Key, Fun(Else)}]. -maybe_upgrade_password_hash(UserName, Password, UserProps, AuthModule) -> +maybe_upgrade_password_hash(Req, UserName, Password, UserProps, + AuthModule, AuthCtx) -> 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 = AuthModule:update_user_creds(Req, NewUserDoc, AuthCtx), + {ok, NewUserProps, _} = AuthModule:get_user_creds(Req, UserName), + NewUserProps; _ -> UserProps end. http://git-wip-us.apache.org/repos/asf/couchdb-couch/blob/3e8286d4/src/couch_httpd_oauth.erl ---------------------------------------------------------------------- diff --git a/src/couch_httpd_oauth.erl b/src/couch_httpd_oauth.erl index 0215240..b72e72d 100644 --- a/src/couch_httpd_oauth.erl +++ b/src/couch_httpd_oauth.erl @@ -78,7 +78,7 @@ set_user_ctx(Req, Name) -> couch_log:debug("OAuth handler: user `~p` credentials not found", [Name]), Req; - User -> + {ok, User, _AuthCtx} -> Roles = couch_util:get_value(<<"roles">>, User, []), Req#httpd{user_ctx=#user_ctx{name=Name, roles=Roles}} end.
