diff options
author | Ronny Berndt <ronny@apache.org> | 2023-02-20 16:10:04 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-02-20 16:10:04 +0100 |
commit | b38885114414518c327bc411b46bdbf45c15a6d9 (patch) | |
tree | 6e90e42ac6b47e2f8768820ace9af80f5cf89780 | |
parent | 9f8cf48e9ebd8cedf9ec393969b98e0b443d6749 (diff) | |
download | couchdb-b38885114414518c327bc411b46bdbf45c15a6d9.tar.gz |
Allow definition of JWT roles claim as comma-seperated list (#4431)
Now it is possible to define a JWT roles claim as a comma-seperated
list or as a JSON array of strings (the only allowed old behavior).
-rw-r--r-- | src/couch/src/couch_httpd_auth.erl | 9 | ||||
-rw-r--r-- | src/docs/src/api/server/authn.rst | 25 | ||||
-rw-r--r-- | src/docs/src/config/auth.rst | 2 | ||||
-rw-r--r-- | test/elixir/test/jwt_roles_claim_test.exs | 40 |
4 files changed, 72 insertions, 4 deletions
diff --git a/src/couch/src/couch_httpd_auth.erl b/src/couch/src/couch_httpd_auth.erl index 4a7b217d1..89203bf4f 100644 --- a/src/couch/src/couch_httpd_auth.erl +++ b/src/couch/src/couch_httpd_auth.erl @@ -261,7 +261,7 @@ get_roles_claim(Claims) -> RolesClaimPath = config:get( "jwt_auth", "roles_claim_path" ), - Result = + Roles = case RolesClaimPath of undefined -> couch_util:get_value( @@ -284,6 +284,13 @@ get_roles_claim(Claims) -> TokenizedJsonPath = tokenize_json_path(RolesClaimPath, MatchPositions), couch_util:get_nested_json_value({Claims}, TokenizedJsonPath) end, + Result = + case is_list(Roles) of + true -> + Roles; + false -> + re:split(Roles, "\\s*,\\s*", [trim, {return, binary}]) + end, case lists:all(fun erlang:is_binary/1, Result) of true -> Result; diff --git a/src/docs/src/api/server/authn.rst b/src/docs/src/api/server/authn.rst index bffe0bf27..982c931fd 100644 --- a/src/docs/src/api/server/authn.rst +++ b/src/docs/src/api/server/authn.rst @@ -397,9 +397,32 @@ is valid. You can set the user roles claim name through the config setting :config:option:`roles_claim_name <jwt_auth/roles_claim_name>`. If you don't set an explicit value, then ``_couchdb.roles`` will be set as the default claim name. -If presented, as a JSON array of strings, it is used as the CouchDB user's roles +If presented, it is used as the CouchDB user's roles list as long as the JWT token is valid. +.. note:: + + Before CouchDB v3.3.2 it was only possible to define roles as a JSON + array of strings. Now you can also use a comma-seperated list to define + the user roles in your JWT token. The following declarations + are equal: + + JSON array of strings: + + .. code-block:: json + + { + "_couchdb.roles": ["accounting-role", "view-role"] + } + + JSON comma-seperated strings: + + .. code-block:: json + + { + "_couchdb.roles": "accounting-role, view-role" + } + .. warning:: ``roles_claim_name`` is deprecated in CouchDB 3.3, and will be removed later. diff --git a/src/docs/src/config/auth.rst b/src/docs/src/config/auth.rst index d43810054..87f181afe 100644 --- a/src/docs/src/config/auth.rst +++ b/src/docs/src/config/auth.rst @@ -398,7 +398,7 @@ Authentication Configuration ``roles_claim_name`` is deprecated in CouchDB 3.3, and will be removed later. Please migrate to ``roles_claim_path``. - If presented, as a JSON array of strings, it is used as the CouchDB user's roles + If presented, it is used as the CouchDB user's roles list as long as the JWT token is valid. The default value for ``roles_claim_name`` is ``_couchdb.roles``. diff --git a/test/elixir/test/jwt_roles_claim_test.exs b/test/elixir/test/jwt_roles_claim_test.exs index cd23a3c25..28b280e9c 100644 --- a/test/elixir/test/jwt_roles_claim_test.exs +++ b/test/elixir/test/jwt_roles_claim_test.exs @@ -17,7 +17,15 @@ defmodule JwtRolesClaimTest do :value => ~w( NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7 Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyH - kHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=) |> Enum.join() + kHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c= + ) |> Enum.join() + }, + %{ + :section => "jwt_keys", + :key => "hmac:myjwttestkey2", + :value => ~w( + VW5kb3VidGVkbHktRW5nYWdpbmctUm9hZHdheS0wMjk= + ) |> Enum.join() } ] @@ -26,6 +34,7 @@ defmodule JwtRolesClaimTest do run_on_modified_server(server_config, fn -> test_roles(["_couchdb.roles_1", "_couchdb.roles_2"]) + test_roles_as_string(["_couchdb_string.roles_1", "_couchdb_string.roles_2"]) end) end @@ -41,6 +50,7 @@ defmodule JwtRolesClaimTest do run_on_modified_server(server_config, fn -> test_roles(["my._couchdb.roles_1", "my._couchdb.roles_2"]) + test_roles_as_string(["my._couchdb_string.roles_1", "my._couchdb_string.roles_2"]) end) end @@ -56,6 +66,7 @@ defmodule JwtRolesClaimTest do run_on_modified_server(server_config, fn -> test_roles(["my_nested_role_1", "my_nested_role_2"]) + test_roles_as_string(["my_nested_string_role_1", "my_nested_string_role_2"]) end) end @@ -76,6 +87,7 @@ defmodule JwtRolesClaimTest do run_on_modified_server(server_config, fn -> test_roles(["my_nested_role_1", "my_nested_role_2"]) + test_roles_as_string(["my_nested_string_role_1", "my_nested_string_role_2"]) end) end @@ -143,6 +155,32 @@ defmodule JwtRolesClaimTest do assert resp.body["info"]["authenticated"] == "jwt" end + def test_roles_as_string(roles) do + # Different token + token = ~w( + eyJ0eXAiOiJKV1QiLCJraWQiOiJteWp3dHRlc3RrZXkyIiwiYWxnIjoiSFMyNTYifQ. + eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWU + sImlhdCI6MTY1NTI5NTgxMCwiZXhwIjoxNzU1Mjk5NDEwLCJteSI6eyJuZXN0ZWQiOn + siX2NvdWNoZGIucm9sZXMiOiJteV9uZXN0ZWRfY291Y2hkYl9zdHJpbmcucm9sZXNfM + SwgbXlfbmVzdGVkX2NvdWNoZGJfc3RyaW5nLnJvbGVzXzEifX0sIl9jb3VjaGRiLnJv + bGVzIjoiX2NvdWNoZGJfc3RyaW5nLnJvbGVzXzEsX2NvdWNoZGJfc3RyaW5nLnJvbGV + zXzIiLCJteS5fY291Y2hkYi5yb2xlcyI6Im15Ll9jb3VjaGRiX3N0cmluZy5yb2xlc1 + 8xLCBteS5fY291Y2hkYl9zdHJpbmcucm9sZXNfMiIsImZvbyI6eyJiYXIuem9uayI6e + yJiYXouYnV1Ijp7ImJhYSI6eyJiYWEuYmVlIjp7InJvbGVzIjoibXlfbmVzdGVkX3N0 + cmluZ19yb2xlXzEsIG15X25lc3RlZF9zdHJpbmdfcm9sZV8yIn19fX19fQ.rzaLmcA2 + 0R291XuGYNNTM9ypGL3UD_GlVp3DmBtWrZI + ) |> Enum.join() + + resp = + Couch.get("/_session", + headers: [authorization: "Bearer #{token}"] + ) + + assert resp.body["userCtx"]["name"] == "1234567890" + assert resp.body["userCtx"]["roles"] == roles + assert resp.body["info"]["authenticated"] == "jwt" + end + def test_roles_with_bad_input() do token = ~w( eyJ0eXAiOiJKV1QiLCJraWQiOiJteWp3dHRlc3RrZXkiLCJhbGciOiJIUzI1NiJ9. |