rnewson commented on code in PR #4814:
URL: https://github.com/apache/couchdb/pull/4814#discussion_r1379093251


##########
src/couch/src/couch_password_hasher.erl:
##########
@@ -21,20 +21,41 @@
     init/1,
     handle_call/3,
     handle_cast/2,
+    handle_info/2,
     code_change/3
 ]).
 
--export([hash/1]).
+-export([maybe_upgrade_password_hash/4, hash_admin_passwords/1]).
 
--record(state, {}).
+-export([worker_loop/1]).
+
+-define(IN_PROGRESS_ETS, couch_password_hasher_in_progress).
+
+-record(state, {
+    worker_pid,
+    worker_mon
+}).
 
 %%%===================================================================
 %%% Public functions
 %%%===================================================================
 
--spec hash(Persist :: boolean()) -> Reply :: term().
-hash(Persist) ->
-    gen_server:cast(?MODULE, {hash_passwords, Persist}).
+maybe_upgrade_password_hash(AuthModule, UserName, Password, UserProps) ->
+    IsAdmin = is_admin(UserProps),
+    NeedsUpgrade = needs_upgrade(UserProps),
+    InProgress = in_progress(AuthModule, UserName),
+    if
+        not IsAdmin andalso NeedsUpgrade andalso not InProgress ->

Review Comment:
   I might try the admin upgrade to see what it looks like, now that I added 
the piece to preserve salt for upgrades I think I can mitigate any problems 
with the upgrade, it's just whether the fake admin doc makes the update harder, 
etc. but I would happily proceed with merging without it.



##########
src/couch/src/couch_password_hasher.erl:
##########
@@ -45,29 +66,95 @@ start_link() ->
 
 init(_Args) ->
     hash_admin_passwords(true),
-    {ok, #state{}}.
+    ?IN_PROGRESS_ETS = ets:new(?IN_PROGRESS_ETS, [named_table, 
{read_concurrency, true}]),
+    {ok, start_worker_loop(#state{})}.
 
 handle_call(Msg, _From, #state{} = State) ->
     {stop, {invalid_call, Msg}, {invalid_call, Msg}, State}.
 
-handle_cast({hash_passwords, Persist}, State) ->
-    hash_admin_passwords(Persist),
+handle_cast({upgrade_password_hash, AuthModule, UserName, Password, 
UserProps}, State) ->
+    case ets:insert_new(?IN_PROGRESS_ETS, {{AuthModule, UserName}}) of
+        true ->
+            State#state.worker_pid !
+                {upgrade_password_hash, AuthModule, UserName, Password, 
UserProps};
+        false ->
+            ok
+    end,
+    {noreply, State};
+handle_cast({hash_admin_passwords, Persist}, State) ->
+    hash_admin_passwords_int(Persist),
     {noreply, State};
 handle_cast(Msg, State) ->
     {stop, {invalid_cast, Msg}, State}.
 
+handle_info({done, AuthModule, UserName}, State) ->
+    ets:delete(?IN_PROGRESS_ETS, {AuthModule, UserName}),
+    {noreply, State};
+handle_info({'DOWN', MonRef, _, _, normal}, #state{worker_mon = MonRef} = 
State) ->
+    ets:delete_all_objects(?IN_PROGRESS_ETS),
+    {noreply, start_worker_loop(State)};
+handle_info(Msg, State) ->
+    {stop, {invalid_info, Msg}, State}.
+
 code_change(_OldVsn, #state{} = State, _Extra) ->
     {ok, State}.
 
 %%%===================================================================
 %%% Internal functions
 %%%===================================================================
 
-hash_admin_passwords(Persist) ->
+hash_admin_passwords_int(Persist) ->
     lists:foreach(
         fun({User, ClearPassword}) ->
             HashedPassword = 
couch_passwords:hash_admin_password(ClearPassword),
             config:set("admins", User, ?b2l(HashedPassword), Persist)
         end,
         couch_passwords:get_unhashed_admins()
     ).
+
+is_admin(UserProps) ->
+    Roles = couch_util:get_value(<<"roles">>, UserProps, []),
+    lists:member(<<"_admin">>, Roles).
+
+needs_upgrade(UserProps) ->
+    CurrentScheme = couch_util:get_value(<<"password_scheme">>, UserProps, 
<<"simple">>),
+    TargetScheme = ?l2b(chttpd_util:get_chttpd_auth_config("password_scheme", 
"pbkdf2")),
+    CurrentPRF = couch_util:get_value(<<"pbkdf2_prf">>, UserProps, <<"sha">>),
+    TargetPRF = ?l2b(chttpd_util:get_chttpd_auth_config("pbkdf2_prf", 
"sha256")),
+    CurrentIterations = couch_util:to_integer(
+        couch_util:get_value(<<"iterations">>, UserProps, "10")
+    ),
+    TargetIterations = chttpd_util:get_chttpd_auth_config_integer(
+        "iterations", 10
+    ),
+    if
+        CurrentScheme == TargetScheme andalso TargetScheme == <<"simple">> 
andalso
+            CurrentIterations == TargetIterations ->
+            false;
+        CurrentScheme == TargetScheme andalso TargetScheme == <<"pbkdf2">> 
andalso
+            CurrentPRF == TargetPRF andalso CurrentIterations == 
TargetIterations ->
+            false;
+        true ->
+            true
+    end.
+
+in_progress(AuthModule, UserName) ->
+    ets:member(?IN_PROGRESS_ETS, {AuthModule, UserName}).
+
+start_worker_loop(State) ->
+    {WorkerPid, WorkerMon} = spawn_monitor(?MODULE, worker_loop, [self()]),

Review Comment:
   updated



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscr...@couchdb.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to