diff options
author | Jiahui Li <54631519+jiahuili430@users.noreply.github.com> | 2021-05-25 17:18:24 -0500 |
---|---|---|
committer | Nick Vatamaniuc <nickva@users.noreply.github.com> | 2021-05-27 16:57:50 -0400 |
commit | a19189c66dc3b5b674d498785f8ccb82bf61feb8 (patch) | |
tree | f702ff02e2e1084fac85a420d78a505af6c85bee | |
parent | 1f21fe8041e0396156af03d777924b0006c6b5d6 (diff) | |
download | couchdb-a19189c66dc3b5b674d498785f8ccb82bf61feb8.tar.gz |
move couch_httpd_auth options to chttpd_auth main
Solved conflicts from "cherry-pick" 3.x commit with kdiff3 merge tool.
-rw-r--r-- | rel/overlay/etc/default.ini | 54 | ||||
-rw-r--r-- | src/chttpd/src/chttpd.erl | 10 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_util.erl | 25 | ||||
-rw-r--r-- | src/chttpd/test/eunit/chttpd_util_test.erl | 55 | ||||
-rw-r--r-- | src/couch/src/couch_httpd.erl | 10 | ||||
-rw-r--r-- | src/couch/src/couch_httpd_auth.erl | 62 | ||||
-rw-r--r-- | src/couch/src/couch_passwords.erl | 6 | ||||
-rw-r--r-- | src/fabric/src/fabric2_users_db.erl | 9 | ||||
-rw-r--r-- | test/elixir/test/cookie_auth_test.exs | 2 | ||||
-rw-r--r-- | test/elixir/test/users_db_security_test.exs | 2 | ||||
-rw-r--r-- | test/elixir/test/users_db_test.exs | 2 |
11 files changed, 163 insertions, 74 deletions
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 9aaaae87d..b98ff005c 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -148,8 +148,31 @@ socket_options = [{sndbuf, 262144}] [ssl] port = 6984 -; [chttpd_auth] -; authentication_db = _users +[chttpd_auth] +;authentication_db = _users + +; These options are moved from [couch_httpd_auth] +;authentication_redirect = /_utils/session.html +;require_valid_user = false +;timeout = 600 ; number of seconds before automatic logout +;auth_cache_size = 50 ; size is number of cache entries +;allow_persistent_cookies = true ; set to false to disallow persistent cookies +;iterations = 10 ; iterations for password hashing +;min_iterations = 1 +;max_iterations = 1000000000 +;password_scheme = pbkdf2 +; List of Erlang RegExp or tuples of RegExp and an optional error message. +; Where a new password must match all RegExp. +; Example: [{".{10,}", "Password min length is 10 characters."}, "\\d+"] +;password_regexp = [] +;proxy_use_secret = false +; comma-separated list of public fields, 404 if empty +;public_fields = +;secret = +;users_db_public = false +;cookie_domain = example.com +; Set the SameSite cookie property for the auth cookie. If empty, the SameSite property is not set. +;same_site = ; [chttpd_auth_cache] ; max_lifetime = 600000 @@ -185,27 +208,12 @@ port = 6984 ; WARNING! This only affects the node-local port (5986 by default). ; You probably want the settings under [chttpd]. authentication_db = _users -authentication_redirect = /_utils/session.html -require_valid_user = false -timeout = 600 ; number of seconds before automatic logout -auth_cache_size = 50 ; size is number of cache entries -allow_persistent_cookies = true ; set to false to disallow persistent cookies -iterations = 10 ; iterations for password hashing -; min_iterations = 1 -; max_iterations = 1000000000 -; password_scheme = pbkdf2 -; List of Erlang RegExp or tuples of RegExp and an optional error message. -; Where a new password must match all RegExp. -; Example: [{".{10,}", "Password min length is 10 characters."}, "\\d+"] -; password_regexp = [] -; proxy_use_secret = false -; comma-separated list of public fields, 404 if empty -; public_fields = -; secret = -; users_db_public = false -; cookie_domain = example.com -; Set the SameSite cookie property for the auth cookie. If empty, the SameSite property is not set. -; same_site = + +; These settings were moved to [chttpd_auth] +; authentication_redirect, require_valid_user, timeout, +; auth_cache_size, allow_persistent_cookies, iterations, min_iterations, +; max_iterations, password_scheme, password_regexp, proxy_use_secret, +; public_fields, secret, users_db_public, cookie_domain, same_site ; Settings for view indexing [couch_views] diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index 3c637f6b1..5f3d76d34 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -1111,14 +1111,16 @@ error_headers(#httpd{mochi_req=MochiReq}=Req, 401=Code, ErrorStr, ReasonStr) -> % redirect to the session page. case ErrorStr of <<"unauthorized">> -> - case config:get("couch_httpd_auth", "authentication_redirect", undefined) of + case chttpd_util:get_chttpd_auth_config( + "authentication_redirect", "/_utils/session.html") of undefined -> {Code, []}; AuthRedirect -> - case config:get("couch_httpd_auth", "require_valid_user", "false") of - "true" -> + case chttpd_util:get_chttpd_auth_config_boolean( + "require_valid_user", false) of + true -> % send the browser popup header no matter what if we are require_valid_user {Code, [{"WWW-Authenticate", "Basic realm=\"server\""}]}; - _False -> + false -> case MochiReq:accepts_content_type("application/json") of true -> {Code, []}; diff --git a/src/chttpd/src/chttpd_util.erl b/src/chttpd/src/chttpd_util.erl index 23dba7dde..5608c46d3 100644 --- a/src/chttpd/src/chttpd_util.erl +++ b/src/chttpd/src/chttpd_util.erl @@ -18,7 +18,11 @@ get_chttpd_config/1, get_chttpd_config/2, get_chttpd_config_integer/2, - get_chttpd_config_boolean/2 + get_chttpd_config_boolean/2, + get_chttpd_auth_config/1, + get_chttpd_auth_config/2, + get_chttpd_auth_config_integer/2, + get_chttpd_auth_config_boolean/2 ]). @@ -61,3 +65,22 @@ get_chttpd_config_integer(Key, Default) -> get_chttpd_config_boolean(Key, Default) -> config:get_boolean("chttpd", Key, config:get_boolean("httpd", Key, Default)). + + +get_chttpd_auth_config(Key) -> + config:get("chttpd_auth", Key, config:get("couch_httpd_auth", Key)). + + +get_chttpd_auth_config(Key, Default) -> + config:get("chttpd_auth", Key, + config:get("couch_httpd_auth", Key, Default)). + + +get_chttpd_auth_config_integer(Key, Default) -> + config:get_integer("chttpd_auth", Key, + config:get_integer("couch_httpd_auth", Key, Default)). + + +get_chttpd_auth_config_boolean(Key, Default) -> + config:get_boolean("chttpd_auth", Key, + config:get_boolean("couch_httpd_auth", Key, Default)). diff --git a/src/chttpd/test/eunit/chttpd_util_test.erl b/src/chttpd/test/eunit/chttpd_util_test.erl index fec05c830..24403b5cf 100644 --- a/src/chttpd/test/eunit/chttpd_util_test.erl +++ b/src/chttpd/test/eunit/chttpd_util_test.erl @@ -21,14 +21,22 @@ setup() -> ok = config:set("httpd", "both_exist", "get_in_httpd", _Persist = false), ok = config:set("chttpd", "both_exist", "get_in_chttpd", _Persist = false), ok = config:set("httpd", "httpd_only", "true", _Persist = false), - ok = config:set("chttpd", "chttpd_only", "1", _Persist = false). + ok = config:set("chttpd", "chttpd_only", "1", _Persist = false), + ok = config:set("couch_httpd_auth", "both_exist", "cha", _Persist = false), + ok = config:set("chttpd_auth", "both_exist", "ca", _Persist = false), + ok = config:set("couch_httpd_auth", "cha_only", "true", _Persist = false), + ok = config:set("chttpd_auth", "ca_only", "1", _Persist = false). teardown(_) -> ok = config:delete("httpd", "both_exist", _Persist = false), ok = config:delete("chttpd", "both_exist", _Persist = false), ok = config:delete("httpd", "httpd_only", _Persist = false), - ok = config:delete("chttpd", "chttpd_only", _Persist = false). + ok = config:delete("chttpd", "chttpd_only", _Persist = false), + ok = config:delete("couch_httpd_auth", "both_exist", _Persist = false), + ok = config:delete("chttpd_auth", "both_exist", _Persist = false), + ok = config:delete("couch_httpd_auth", "cha_only", _Persist = false), + ok = config:delete("chttpd_auth", "ca_only", _Persist = false). chttpd_util_config_test_() -> @@ -49,7 +57,12 @@ chttpd_util_config_test_() -> ?TDEF_FE(test_with_chttpd_option), ?TDEF_FE(test_with_chttpd_option_which_moved_from_httpd), ?TDEF_FE(test_get_chttpd_config_integer), - ?TDEF_FE(test_get_chttpd_config_boolean) + ?TDEF_FE(test_get_chttpd_config_boolean), + ?TDEF_FE(test_auth_behavior), + ?TDEF_FE(test_auth_with_undefined_option), + ?TDEF_FE(test_auth_with_moved_options), + ?TDEF_FE(test_get_chttpd_auth_config_integer), + ?TDEF_FE(test_get_chttpd_auth_config_boolean) ] } } @@ -105,3 +118,39 @@ test_get_chttpd_config_integer(_) -> test_get_chttpd_config_boolean(_) -> ?assert(chttpd_util:get_chttpd_config_boolean("allow_jsonp", true)). + + +test_auth_behavior(_) -> + ?assertEqual("ca", chttpd_util:get_chttpd_auth_config("both_exist")), + ?assertEqual(1, chttpd_util:get_chttpd_auth_config_integer("ca_only", 0)), + ?assert(chttpd_util:get_chttpd_auth_config_boolean("cha_only", false)). + + +test_auth_with_undefined_option(_) -> + ?assertEqual(undefined, chttpd_util:get_chttpd_auth_config("undefine")), + ?assertEqual(abc, chttpd_util:get_chttpd_auth_config("undefine", abc)), + ?assertEqual(123, chttpd_util:get_chttpd_auth_config("undefine", 123)), + ?assertEqual(0.2, chttpd_util:get_chttpd_auth_config("undefine", 0.2)), + ?assertEqual("a", chttpd_util:get_chttpd_auth_config("undefine", "a")), + ?assertEqual("", chttpd_util:get_chttpd_auth_config("undefine", "")), + ?assert(chttpd_util:get_chttpd_auth_config("undefine", true)), + ?assertNot(chttpd_util:get_chttpd_auth_config("undefine", false)). + + +test_auth_with_moved_options(_) -> + ?assertEqual("/_utils/session.html", chttpd_util:get_chttpd_auth_config( + "authentication_redirect", "/_utils/session.html")), + ?assert(chttpd_util:get_chttpd_auth_config("require_valid_user", true)), + ?assertEqual(10, chttpd_util:get_chttpd_auth_config("iterations", 10)). + + +test_get_chttpd_auth_config_integer(_) -> + ?assertEqual(123, chttpd_util:get_chttpd_auth_config_integer( + "timeout", 123)). + + +test_get_chttpd_auth_config_boolean(_) -> + ?assertNot(chttpd_util:get_chttpd_auth_config_boolean( + "require_valid_user", false)), + ?assert(chttpd_util:get_chttpd_auth_config_boolean( + "allow_persistent_cookies", true)). diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl index c6e7e5296..8a58e7738 100644 --- a/src/couch/src/couch_httpd.erl +++ b/src/couch/src/couch_httpd.erl @@ -622,14 +622,16 @@ error_headers(#httpd{mochi_req=MochiReq}=Req, Code, ErrorStr, ReasonStr) -> % redirect to the session page. case ErrorStr of <<"unauthorized">> -> - case config:get("couch_httpd_auth", "authentication_redirect", undefined) of + case chttpd_util:get_chttpd_auth_config( + "authentication_redirect", "/_utils/session.html") of undefined -> {Code, []}; AuthRedirect -> - case config:get("couch_httpd_auth", "require_valid_user", "false") of - "true" -> + case chttpd_util:get_chttpd_auth_config_boolean( + "require_valid_user", false) of + true -> % send the browser popup header no matter what if we are require_valid_user {Code, [{"WWW-Authenticate", "Basic realm=\"server\""}]}; - _False -> + false -> case MochiReq:accepts_content_type("application/json") of true -> {Code, []}; diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl index 93b00bd1a..9e733aec3 100644 --- a/src/couch/src/couch_httpd_auth.erl +++ b/src/couch/src/couch_httpd_auth.erl @@ -39,10 +39,11 @@ -compile({no_auto_import,[integer_to_binary/1, integer_to_binary/2]}). party_mode_handler(Req) -> - case config:get("couch_httpd_auth", "require_valid_user", "false") of - "true" -> + case chttpd_util:get_chttpd_auth_config_boolean( + "require_valid_user", false) of + true -> throw({unauthorized, <<"Authentication required.">>}); - "false" -> + false -> Req#httpd{user_ctx=#user_ctx{}} end. @@ -117,11 +118,12 @@ default_authentication_handler(Req, AuthModule) -> true -> Req; false -> - case config:get("couch_httpd_auth", "require_valid_user", "false") of - "true" -> Req; + case chttpd_util:get_chttpd_auth_config_boolean( + "require_valid_user", false) of + true -> Req; % If no admins, and no user required, then everyone is admin! % Yay, admin party! - _ -> Req#httpd{user_ctx=?ADMIN_USER} + false -> Req#httpd{user_ctx=?ADMIN_USER} end end end. @@ -156,12 +158,12 @@ proxy_authentification_handler(Req) -> proxy_authentication_handler(Req). proxy_auth_user(Req) -> - XHeaderUserName = config:get("couch_httpd_auth", "x_auth_username", - "X-Auth-CouchDB-UserName"), - XHeaderRoles = config:get("couch_httpd_auth", "x_auth_roles", - "X-Auth-CouchDB-Roles"), - XHeaderToken = config:get("couch_httpd_auth", "x_auth_token", - "X-Auth-CouchDB-Token"), + XHeaderUserName = chttpd_util:get_chttpd_auth_config( + "x_auth_username", "X-Auth-CouchDB-UserName"), + XHeaderRoles = chttpd_util:get_chttpd_auth_config( + "x_auth_roles", "X-Auth-CouchDB-Roles"), + XHeaderToken = chttpd_util:get_chttpd_auth_config( + "x_auth_token", "X-Auth-CouchDB-Token"), case header_value(Req, XHeaderUserName) of undefined -> nil; UserName -> @@ -170,9 +172,10 @@ proxy_auth_user(Req) -> Else -> [?l2b(R) || R <- string:tokens(Else, ",")] end, - case config:get("couch_httpd_auth", "proxy_use_secret", "false") of - "true" -> - case config:get("couch_httpd_auth", "secret", undefined) of + case chttpd_util:get_chttpd_auth_config_boolean( + "proxy_use_secret", false) of + true -> + case chttpd_util:get_chttpd_auth_config("secret") of undefined -> Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName), roles=Roles}}; Secret -> @@ -184,7 +187,7 @@ proxy_auth_user(Req) -> _ -> nil end end; - _ -> + false -> Req#httpd{user_ctx=#user_ctx{name=?l2b(UserName), roles=Roles}} end end. @@ -251,7 +254,7 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req, AuthModule) -> end, % Verify expiry and hash CurrentTime = make_cookie_time(), - case config:get("couch_httpd_auth", "secret", undefined) of + case chttpd_util:get_chttpd_auth_config("secret") of undefined -> ?LOG_DEBUG(#{what => cookie_auth_secret_undefined}), couch_log:debug("cookie auth secret is not set",[]), @@ -265,8 +268,8 @@ cookie_authentication_handler(#httpd{mochi_req=MochiReq}=Req, AuthModule) -> FullSecret = <<Secret/binary, UserSalt/binary>>, ExpectedHash = couch_util:hmac(sha, FullSecret, User ++ ":" ++ TimeStr), Hash = ?l2b(HashStr), - Timeout = list_to_integer( - config:get("couch_httpd_auth", "timeout", "600")), + Timeout = chttpd_util:get_chttpd_auth_config_integer( + "timeout", 600), couch_log:debug("timeout ~p", [Timeout]), case (catch erlang:list_to_integer(TimeStr, 16)) of TimeStamp when CurrentTime < TimeStamp + Timeout -> @@ -321,7 +324,7 @@ cookie_auth_cookie(Req, User, Secret, TimeStamp) -> [{path, "/"}] ++ cookie_scheme(Req) ++ max_age() ++ cookie_domain() ++ same_site()). ensure_cookie_auth_secret() -> - case config:get("couch_httpd_auth", "secret", undefined) of + case chttpd_util:get_chttpd_auth_config("secret") of undefined -> NewSecret = ?b2l(couch_uuids:random()), config:set("couch_httpd_auth", "secret", NewSecret), @@ -462,8 +465,8 @@ authenticate(Pass, UserProps) -> couch_passwords:verify(PasswordHash, ExpectedHash). verify_iterations(Iterations) when is_integer(Iterations) -> - Min = list_to_integer(config:get("couch_httpd_auth", "min_iterations", "1")), - Max = list_to_integer(config:get("couch_httpd_auth", "max_iterations", "1000000000")), + Min = chttpd_util:get_chttpd_auth_config_integer("min_iterations", 1), + Max = chttpd_util:get_chttpd_auth_config_integer("max_iterations", 1000000000), case Iterations < Min of true -> throw({forbidden, <<"Iteration count is too low for this server">>}); @@ -489,17 +492,18 @@ cookie_scheme(#httpd{mochi_req=MochiReq}) -> end. max_age() -> - case config:get("couch_httpd_auth", "allow_persistent_cookies", "true") of - "false" -> + case chttpd_util:get_chttpd_auth_config_boolean( + "allow_persistent_cookies", true) of + false -> []; - "true" -> - Timeout = list_to_integer( - config:get("couch_httpd_auth", "timeout", "600")), + true -> + Timeout = chttpd_util:get_chttpd_auth_config_integer( + "timeout", 600), [{max_age, Timeout}] end. cookie_domain() -> - Domain = config:get("couch_httpd_auth", "cookie_domain", ""), + Domain = chttpd_util:get_chttpd_auth_config("cookie_domain", ""), case Domain of "" -> []; _ -> [{domain, Domain}] @@ -507,7 +511,7 @@ cookie_domain() -> same_site() -> - SameSite = config:get("couch_httpd_auth", "same_site", ""), + SameSite = chttpd_util:get_chttpd_auth_config("same_site", ""), case string:to_lower(SameSite) of "" -> []; "none" -> [{same_site, none}]; diff --git a/src/couch/src/couch_passwords.erl b/src/couch/src/couch_passwords.erl index 87ed15144..55ffb359f 100644 --- a/src/couch/src/couch_passwords.erl +++ b/src/couch/src/couch_passwords.erl @@ -37,7 +37,7 @@ hash_admin_password(ClearPassword) when is_list(ClearPassword) -> hash_admin_password(?l2b(ClearPassword)); hash_admin_password(ClearPassword) when is_binary(ClearPassword) -> %% Support both schemes to smooth migration from legacy scheme - Scheme = config:get("couch_httpd_auth", "password_scheme", "pbkdf2"), + Scheme = chttpd_util:get_chttpd_auth_config("password_scheme", "pbkdf2"), hash_admin_password(Scheme, ClearPassword). hash_admin_password("simple", ClearPassword) -> % deprecated @@ -45,10 +45,10 @@ hash_admin_password("simple", ClearPassword) -> % deprecated Hash = crypto:hash(sha, <<ClearPassword/binary, Salt/binary>>), ?l2b("-hashed-" ++ couch_util:to_hex(Hash) ++ "," ++ ?b2l(Salt)); hash_admin_password("pbkdf2", ClearPassword) -> - Iterations = config:get("couch_httpd_auth", "iterations", "10000"), + Iterations = chttpd_util:get_chttpd_auth_config("iterations", "10"), Salt = couch_uuids:random(), DerivedKey = couch_passwords:pbkdf2(couch_util:to_binary(ClearPassword), - Salt ,list_to_integer(Iterations)), + Salt, list_to_integer(Iterations)), ?l2b("-pbkdf2-" ++ ?b2l(DerivedKey) ++ "," ++ ?b2l(Salt) ++ "," ++ Iterations). diff --git a/src/fabric/src/fabric2_users_db.erl b/src/fabric/src/fabric2_users_db.erl index 3714d341e..e1c8c3fcb 100644 --- a/src/fabric/src/fabric2_users_db.erl +++ b/src/fabric/src/fabric2_users_db.erl @@ -72,7 +72,7 @@ before_doc_update(Doc, Db, _UpdateType) -> % newDoc.password = null save_doc(#doc{body={Body}} = Doc) -> %% Support both schemes to smooth migration from legacy scheme - Scheme = config:get("couch_httpd_auth", "password_scheme", "pbkdf2"), + Scheme = chttpd_util:get_chttpd_auth_config("password_scheme", "pbkdf2"), case {fabric2_util:get_value(?PASSWORD, Body), Scheme} of {null, _} -> % server admins don't have a user-db password entry Doc; @@ -89,7 +89,8 @@ save_doc(#doc{body={Body}} = Doc) -> Doc#doc{body={Body3}}; {ClearPassword, "pbkdf2"} -> ok = validate_password(ClearPassword), - Iterations = list_to_integer(config:get("couch_httpd_auth", "iterations", "1000")), + Iterations = chttpd_util:get_chttpd_auth_config_integer( + "iterations", 10), Salt = couch_uuids:random(), DerivedKey = couch_passwords:pbkdf2(ClearPassword, Salt, Iterations), Body0 = ?replace(Body, ?PASSWORD_SCHEME, ?PBKDF2), @@ -114,7 +115,7 @@ save_doc(#doc{body={Body}} = Doc) -> % 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 + case chttpd_util:get_chttpd_auth_config("password_regexp", "") of "" -> ok; "[]" -> @@ -225,6 +226,6 @@ get_doc_name(_) -> strip_non_public_fields(#doc{body={Props}}=Doc) -> - PublicFields = config:get("couch_httpd_auth", "public_fields", ""), + PublicFields = chttpd_util:get_chttpd_auth_config("public_fields", ""), Public = re:split(PublicFields, "\\s*,\\s*", [{return, binary}]), Doc#doc{body={[{K, V} || {K, V} <- Props, lists:member(K, Public)]}}. diff --git a/test/elixir/test/cookie_auth_test.exs b/test/elixir/test/cookie_auth_test.exs index a6e480352..9608875cd 100644 --- a/test/elixir/test/cookie_auth_test.exs +++ b/test/elixir/test/cookie_auth_test.exs @@ -18,7 +18,7 @@ defmodule CookieAuthTest do @users_db }, { - "couch_httpd_auth", + "chttpd_auth", "iterations", "1" }, diff --git a/test/elixir/test/users_db_security_test.exs b/test/elixir/test/users_db_security_test.exs index 7b2c97df9..656749040 100644 --- a/test/elixir/test/users_db_security_test.exs +++ b/test/elixir/test/users_db_security_test.exs @@ -259,7 +259,7 @@ defmodule UsersDbSecurityTest do "true" }, { - "couch_httpd_auth", + "chttpd_auth", "iterations", "1" }, diff --git a/test/elixir/test/users_db_test.exs b/test/elixir/test/users_db_test.exs index 7c678c4f0..24418da3d 100644 --- a/test/elixir/test/users_db_test.exs +++ b/test/elixir/test/users_db_test.exs @@ -18,7 +18,7 @@ defmodule UsersDbTest do @users_db_name }, { - "couch_httpd_auth", + "chttpd_auth", "iterations", "1" }, |