summaryrefslogtreecommitdiff
path: root/src/fabric/src/fabric2_users_db.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/fabric/src/fabric2_users_db.erl')
-rw-r--r--src/fabric/src/fabric2_users_db.erl88
1 files changed, 87 insertions, 1 deletions
diff --git a/src/fabric/src/fabric2_users_db.erl b/src/fabric/src/fabric2_users_db.erl
index 9a8a462c3..3714d341e 100644
--- a/src/fabric/src/fabric2_users_db.erl
+++ b/src/fabric/src/fabric2_users_db.erl
@@ -19,6 +19,7 @@
]).
-include_lib("couch/include/couch_db.hrl").
+-include_lib("kernel/include/logger.hrl").
-define(NAME, <<"name">>).
-define(PASSWORD, <<"password">>).
@@ -30,6 +31,8 @@
-define(ITERATIONS, <<"iterations">>).
-define(SALT, <<"salt">>).
-define(replace(L, K, V), lists:keystore(K, 1, L, {K, V})).
+-define(REQUIREMENT_ERROR, "Password does not conform to requirements.").
+-define(PASSWORD_SERVER_ERROR, "Server cannot hash passwords at this time.").
-define(
DDOCS_ADMIN_ONLY,
@@ -76,6 +79,7 @@ save_doc(#doc{body={Body}} = Doc) ->
{undefined, _} ->
Doc;
{ClearPassword, "simple"} -> % deprecated
+ ok = validate_password(ClearPassword),
Salt = couch_uuids:random(),
PasswordSha = couch_passwords:simple(ClearPassword, Salt),
Body0 = ?replace(Body, ?PASSWORD_SCHEME, ?SIMPLE),
@@ -84,6 +88,7 @@ save_doc(#doc{body={Body}} = Doc) ->
Body3 = proplists:delete(?PASSWORD, Body2),
Doc#doc{body={Body3}};
{ClearPassword, "pbkdf2"} ->
+ ok = validate_password(ClearPassword),
Iterations = list_to_integer(config:get("couch_httpd_auth", "iterations", "1000")),
Salt = couch_uuids:random(),
DerivedKey = couch_passwords:pbkdf2(ClearPassword, Salt, Iterations),
@@ -94,8 +99,89 @@ save_doc(#doc{body={Body}} = Doc) ->
Body4 = proplists:delete(?PASSWORD, Body3),
Doc#doc{body={Body4}};
{_ClearPassword, Scheme} ->
+ ?LOG_ERROR(#{
+ what => invalid_config_setting,
+ section => couch_httpd_auth,
+ key => password_scheme,
+ value => Scheme,
+ details => "password_scheme must one of (simple, pbkdf2)"
+ }),
couch_log:error("[couch_httpd_auth] password_scheme value of '~p' is invalid.", [Scheme]),
- throw({forbidden, "Server cannot hash passwords at this time."})
+ throw({forbidden, ?PASSWORD_SERVER_ERROR})
+ end.
+
+% Validate if a new password matches all RegExp in the password_regexp setting.
+% Throws if not.
+% In this function the [couch_httpd_auth] password_regexp config is parsed.
+validate_password(ClearPassword) ->
+ case config:get("couch_httpd_auth", "password_regexp", "") of
+ "" ->
+ ok;
+ "[]" ->
+ ok;
+ ValidateConfig ->
+ RequirementList = case couch_util:parse_term(ValidateConfig) of
+ {ok, RegExpList} when is_list(RegExpList) ->
+ RegExpList;
+ {ok, NonListValue} ->
+ couch_log:error(
+ "[couch_httpd_auth] password_regexp value of '~p'"
+ " is not a list.",
+ [NonListValue]
+ ),
+ throw({forbidden, ?PASSWORD_SERVER_ERROR});
+ {error, ErrorInfo} ->
+ couch_log:error(
+ "[couch_httpd_auth] password_regexp value of '~p'"
+ " could not get parsed. ~p",
+ [ValidateConfig, ErrorInfo]
+ ),
+ throw({forbidden, ?PASSWORD_SERVER_ERROR})
+ end,
+ % Check the password on every RegExp.
+ lists:foreach(fun(RegExpTuple) ->
+ case get_password_regexp_and_error_msg(RegExpTuple) of
+ {ok, RegExp, PasswordErrorMsg} ->
+ check_password(ClearPassword, RegExp, PasswordErrorMsg);
+ {error} ->
+ couch_log:error(
+ "[couch_httpd_auth] password_regexp part of '~p' "
+ "is not a RegExp string or "
+ "a RegExp and Reason tuple.",
+ [RegExpTuple]
+ ),
+ throw({forbidden, ?PASSWORD_SERVER_ERROR})
+ end
+ end, RequirementList),
+ ok
+ end.
+
+% Get the RegExp out of the tuple and combine the the error message.
+% First is with a Reason string.
+get_password_regexp_and_error_msg({RegExp, Reason})
+ when is_list(RegExp) andalso is_list(Reason)
+ andalso length(Reason) > 0 ->
+ {ok, RegExp, lists:concat([?REQUIREMENT_ERROR, " ", Reason])};
+% With a not correct Reason string.
+get_password_regexp_and_error_msg({RegExp, _Reason}) when is_list(RegExp) ->
+ {ok, RegExp, ?REQUIREMENT_ERROR};
+% Without a Reason string.
+get_password_regexp_and_error_msg({RegExp}) when is_list(RegExp) ->
+ {ok, RegExp, ?REQUIREMENT_ERROR};
+% If the RegExp is only a list/string.
+get_password_regexp_and_error_msg(RegExp) when is_list(RegExp) ->
+ {ok, RegExp, ?REQUIREMENT_ERROR};
+% Not correct RegExpValue.
+get_password_regexp_and_error_msg(_) ->
+ {error}.
+
+% Check the password if it matches a RegExp.
+check_password(Password, RegExp, ErrorMsg) ->
+ case re:run(Password, RegExp, [{capture, none}]) of
+ match ->
+ ok;
+ _ ->
+ throw({bad_request, ErrorMsg})
end.