diff options
Diffstat (limited to 'src/fabric/src/fabric2_users_db.erl')
-rw-r--r-- | src/fabric/src/fabric2_users_db.erl | 88 |
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. |