summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Newson <rnewson@apache.org>2020-09-21 14:16:54 +0100
committerGitHub <noreply@github.com>2020-09-21 14:16:54 +0100
commit44204af782ffac64e6e3e48e7cc68dd1a4659348 (patch)
treee93f72cd35f7566f706e47f755d68dbed1511cbe
parent5b595116c03b3ccd9a373305565d39ae6d614adc (diff)
downloadcouchdb-44204af782ffac64e6e3e48e7cc68dd1a4659348.tar.gz
allow configurability of JWT claims that require a value (#3165)
e.g; [jwt] required_claims = {iss, "https://example.com/issuer"}
-rw-r--r--rel/overlay/etc/default.ini4
-rw-r--r--src/couch/src/couch_httpd.erl2
-rw-r--r--src/couch/src/couch_httpd_auth.erl19
-rw-r--r--test/elixir/test/jwtauth_test.exs77
4 files changed, 96 insertions, 6 deletions
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini
index 801062681..1a7a02108 100644
--- a/rel/overlay/etc/default.ini
+++ b/rel/overlay/etc/default.ini
@@ -145,7 +145,9 @@ max_db_number_for_dbs_info_req = 100
;[jwt_auth]
; List of claims to validate
-; required_claims =
+; can be the name of a claim like "exp" or a tuple if the claim requires
+; a parameter
+; required_claims = exp, {iss, "IssuerNameHere"}
;
; [jwt_keys]
; Configure at least one key here if using the JWT auth handler.
diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl
index ef90d6b2a..8f7fedd5e 100644
--- a/src/couch/src/couch_httpd.erl
+++ b/src/couch/src/couch_httpd.erl
@@ -931,6 +931,8 @@ error_info({error, {illegal_database_name, Name}}) ->
{400, <<"illegal_database_name">>, Message};
error_info({missing_stub, Reason}) ->
{412, <<"missing_stub">>, Reason};
+error_info({misconfigured_server, Reason}) ->
+ {500, <<"misconfigured_server">>, couch_util:to_binary(Reason)};
error_info({Error, Reason}) ->
{500, couch_util:to_binary(Error), couch_util:to_binary(Reason)};
error_info(Error) ->
diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl
index de3943fdb..96d60a202 100644
--- a/src/couch/src/couch_httpd_auth.erl
+++ b/src/couch/src/couch_httpd_auth.erl
@@ -209,13 +209,22 @@ jwt_authentication_handler(Req) ->
get_configured_claims() ->
Claims = config:get("jwt_auth", "required_claims", ""),
- case re:split(Claims, "\s*,\s*", [{return, list}]) of
- [[]] ->
- []; %% if required_claims is the empty string.
- List ->
- [list_to_existing_atom(C) || C <- List]
+ Re = "((?<key1>[a-z]+)|{(?<key2>[a-z]+)\s*,\s*\"(?<val>[^\"]+)\"})",
+ case re:run(Claims, Re, [global, {capture, [key1, key2, val], binary}]) of
+ nomatch when Claims /= "" ->
+ couch_log:error("[jwt_auth] required_claims is set to an invalid value.", []),
+ throw({misconfigured_server, <<"JWT is not configured correctly">>});
+ nomatch ->
+ [];
+ {match, Matches} ->
+ lists:map(fun to_claim/1, Matches)
end.
+to_claim([Key, <<>>, <<>>]) ->
+ binary_to_atom(Key, latin1);
+to_claim([<<>>, Key, Value]) ->
+ {binary_to_atom(Key, latin1), Value}.
+
cookie_authentication_handler(Req) ->
cookie_authentication_handler(Req, couch_auth_cache).
diff --git a/test/elixir/test/jwtauth_test.exs b/test/elixir/test/jwtauth_test.exs
index 55e075cb2..3f5102921 100644
--- a/test/elixir/test/jwtauth_test.exs
+++ b/test/elixir/test/jwtauth_test.exs
@@ -136,4 +136,81 @@ defmodule JwtAuthTest do
assert resp.body["userCtx"]["name"] == "adm"
assert resp.body["info"]["authenticated"] == "default"
end
+
+ test "jwt auth with required iss claim", _context do
+
+ secret = "zxczxc12zxczxc12"
+
+ server_config = [
+ %{
+ :section => "jwt_auth",
+ :key => "required_claims",
+ :value => "{iss, \"hello\"}"
+ },
+ %{
+ :section => "jwt_keys",
+ :key => "hmac:_default",
+ :value => :base64.encode(secret)
+ },
+ %{
+ :section => "jwt_auth",
+ :key => "allowed_algorithms",
+ :value => "HS256, HS384, HS512"
+ }
+ ]
+
+ run_on_modified_server(server_config, fn -> good_iss("HS256", secret) end)
+ run_on_modified_server(server_config, fn -> bad_iss("HS256", secret) end)
+ end
+
+ def good_iss(alg, key) do
+ {:ok, token} = :jwtf.encode(
+ {
+ [
+ {"alg", alg},
+ {"typ", "JWT"}
+ ]
+ },
+ {
+ [
+ {"iss", "hello"},
+ {"sub", "couch@apache.org"},
+ {"_couchdb.roles", ["testing"]
+ }
+ ]
+ }, key)
+
+ resp = Couch.get("/_session",
+ headers: [authorization: "Bearer #{token}"]
+ )
+
+ assert resp.body["userCtx"]["name"] == "couch@apache.org"
+ assert resp.body["userCtx"]["roles"] == ["testing"]
+ assert resp.body["info"]["authenticated"] == "jwt"
+ end
+
+ def bad_iss(alg, key) do
+ {:ok, token} = :jwtf.encode(
+ {
+ [
+ {"alg", alg},
+ {"typ", "JWT"}
+ ]
+ },
+ {
+ [
+ {"iss", "goodbye"},
+ {"sub", "couch@apache.org"},
+ {"_couchdb.roles", ["testing"]
+ }
+ ]
+ }, key)
+
+ resp = Couch.get("/_session",
+ headers: [authorization: "Bearer #{token}"]
+ )
+
+ assert resp.status_code == 400
+ end
+
end