diff options
author | Robert Newson <rnewson@apache.org> | 2020-05-14 16:17:58 +0100 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2020-05-18 18:35:13 +0100 |
commit | 4f7d1d97fd7d960f7ef6e9f1764bfd6e55ba8e0c (patch) | |
tree | 52c9f19aab57aeadd4561e6d6ee0115592aba670 | |
parent | 03992009e788d631b1a09aff3cfb88f97f73ce23 (diff) | |
download | couchdb-4f7d1d97fd7d960f7ef6e9f1764bfd6e55ba8e0c.tar.gz |
allow configurability of JWT claims that require a value
e.g;
[jwt]
required_claims = {iss, "https://example.com/issuer"}
-rw-r--r-- | rel/overlay/etc/default.ini | 4 | ||||
-rw-r--r-- | src/couch/src/couch_httpd.erl | 2 | ||||
-rw-r--r-- | src/couch/src/couch_httpd_auth.erl | 19 | ||||
-rw-r--r-- | test/elixir/test/jwtauth_test.exs | 77 |
4 files changed, 96 insertions, 6 deletions
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 6fe2260b4..057ed4c1c 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -142,7 +142,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 2383be798..0d3add0c8 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 2fb89c3af..7281ed146 100644 --- a/test/elixir/test/jwtauth_test.exs +++ b/test/elixir/test/jwtauth_test.exs @@ -137,4 +137,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 |