summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonny Berndt <ronny@apache.org>2023-02-20 16:10:04 +0100
committerGitHub <noreply@github.com>2023-02-20 16:10:04 +0100
commitb38885114414518c327bc411b46bdbf45c15a6d9 (patch)
tree6e90e42ac6b47e2f8768820ace9af80f5cf89780
parent9f8cf48e9ebd8cedf9ec393969b98e0b443d6749 (diff)
downloadcouchdb-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.erl9
-rw-r--r--src/docs/src/api/server/authn.rst25
-rw-r--r--src/docs/src/config/auth.rst2
-rw-r--r--test/elixir/test/jwt_roles_claim_test.exs40
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.