From a90b9b2185d21e2bdc9f1c2830afa700b6fa1f2a Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Thu, 19 Dec 2024 15:01:27 -0500 Subject: [PATCH 1/6] respond with 403 (not 401) when password good but no perms #10340 --- .../iq/dataverse/api/AbstractApiBean.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java index 3c1074b75bb..8a88ff042ab 100644 --- a/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java +++ b/src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java @@ -831,6 +831,18 @@ protected Response badRequest(String msg, Map fieldErrors) { .build(); } + /** + * In short, your password is fine but you don't have permission. + * + * "The 403 (Forbidden) status code indicates that the server understood the + * request but refuses to authorize it. A server that wishes to make public + * why the request has been forbidden can describe that reason in the + * response payload (if any). + * + * If authentication credentials were provided in the request, the server + * considers them insufficient to grant access." -- + * https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.3 + */ protected Response forbidden( String msg ) { return error( Status.FORBIDDEN, msg ); } @@ -852,9 +864,17 @@ protected Response permissionError( PermissionException pe ) { } protected Response permissionError( String message ) { - return unauthorized( message ); + return forbidden( message ); } + /** + * In short, bad password. + * + * "The 401 (Unauthorized) status code indicates that the request has not + * been applied because it lacks valid authentication credentials for the + * target resource." -- + * https://datatracker.ietf.org/doc/html/rfc7235#section-3.1 + */ protected Response unauthorized( String message ) { return error( Status.UNAUTHORIZED, message ); } From bc7cb235ecf93b56786d210c9633c27dc3f3c2e8 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Thu, 19 Dec 2024 15:15:41 -0500 Subject: [PATCH 2/6] add release note #10340 --- doc/release-notes/10340-forbidden.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 doc/release-notes/10340-forbidden.md diff --git a/doc/release-notes/10340-forbidden.md b/doc/release-notes/10340-forbidden.md new file mode 100644 index 00000000000..0910646c7bd --- /dev/null +++ b/doc/release-notes/10340-forbidden.md @@ -0,0 +1,7 @@ +### API Now Returns 403 Forbidden for Permission Checks + +Dataverse was returning 401 Unauthorized when a permission check failed. This has been corrected to return 403 Forbidden in these cases. That is, the API token is known to be good (401 otherwise) but the user lacks permission (403 is now sent). See also #10340 and #11116. + +### Backward Incompatible Changes + +See "API Now Returns 403 Forbidden for Permission Checks" above. From caa00fb166c9197f154663dd52af54c78da88be1 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 20 Dec 2024 13:52:43 -0500 Subject: [PATCH 3/6] add test to exercise change from unauth to forbidden #10340 --- .../java/edu/harvard/iq/dataverse/api/RolesIT.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/java/edu/harvard/iq/dataverse/api/RolesIT.java b/src/test/java/edu/harvard/iq/dataverse/api/RolesIT.java index d15fda3a1a1..7e0a4714b1f 100644 --- a/src/test/java/edu/harvard/iq/dataverse/api/RolesIT.java +++ b/src/test/java/edu/harvard/iq/dataverse/api/RolesIT.java @@ -4,6 +4,7 @@ import io.restassured.RestAssured; import io.restassured.path.json.JsonPath; import io.restassured.response.Response; +import static jakarta.ws.rs.core.Response.Status.FORBIDDEN; import java.util.logging.Logger; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -69,7 +70,15 @@ public void testCreateDeleteRoles() { body = addBuiltinRoleResponse.getBody().asString(); status = JsonPath.from(body).getString("status"); assertEquals("OK", status); - + + Response createNoPermsUser = UtilIT.createRandomUser(); + createNoPermsUser.prettyPrint(); + String noPermsapiToken = UtilIT.getApiTokenFromResponse(createNoPermsUser); + + Response noPermsResponse = UtilIT.viewDataverseRole("testRole", noPermsapiToken); + noPermsResponse.prettyPrint(); + noPermsResponse.then().assertThat().statusCode(FORBIDDEN.getStatusCode()); + Response viewDataverseRoleResponse = UtilIT.viewDataverseRole("testRole", apiToken); viewDataverseRoleResponse.prettyPrint(); body = viewDataverseRoleResponse.getBody().asString(); From f99d67e2b6bf9d3464a840ef8c5ee1fa5f96945e Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 20 Dec 2024 14:25:53 -0500 Subject: [PATCH 4/6] add change from 401 to 403 for /api/roles, improve docs #10340 --- doc/sphinx-guides/source/api/changelog.rst | 1 + doc/sphinx-guides/source/api/native-api.rst | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/sphinx-guides/source/api/changelog.rst b/doc/sphinx-guides/source/api/changelog.rst index 162574e7799..5ae152aeace 100644 --- a/doc/sphinx-guides/source/api/changelog.rst +++ b/doc/sphinx-guides/source/api/changelog.rst @@ -11,6 +11,7 @@ v6.6 ---- - **/api/metadatablocks** is no longer returning duplicated metadata properties and does not omit metadata properties when called. +- **/api/roles**: :ref:`show-role` now properly returns 403 Forbidden instead of 401 Unauthorized when you pass a working API token that doesn't have the right permission. v6.5 ---- diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index dabca195e37..570b6308a64 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -4563,12 +4563,22 @@ Create Role Roles can be created globally (:ref:`create-global-role`) or for individual Dataverse collections (:ref:`create-role-in-collection`). +.. _show-role: + Show Role ~~~~~~~~~ -Shows the role with ``id``:: +You must have ``ManageDataversePermissions`` to be able to show a role that was created using :ref:`create-role-in-collection`. Global roles (:ref:`create-global-role`) only be shown with a superuser API token. + +A curl example using an ``ID``: + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export ID=11 - GET http://$SERVER/api/roles/$id + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/roles/$ID" Delete Role ~~~~~~~~~~~ From a2cb8ae72c83b1d3be4e1d50148e1db4574639f5 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Fri, 20 Dec 2024 14:30:48 -0500 Subject: [PATCH 5/6] make release note more accurate, only one endpoint affected #10340 --- doc/release-notes/10340-forbidden.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/doc/release-notes/10340-forbidden.md b/doc/release-notes/10340-forbidden.md index 0910646c7bd..5997f717d64 100644 --- a/doc/release-notes/10340-forbidden.md +++ b/doc/release-notes/10340-forbidden.md @@ -1,7 +1,3 @@ -### API Now Returns 403 Forbidden for Permission Checks - -Dataverse was returning 401 Unauthorized when a permission check failed. This has been corrected to return 403 Forbidden in these cases. That is, the API token is known to be good (401 otherwise) but the user lacks permission (403 is now sent). See also #10340 and #11116. - ### Backward Incompatible Changes -See "API Now Returns 403 Forbidden for Permission Checks" above. +The [Show Role](https://dataverse-guide--11116.org.readthedocs.build/en/11116/api/native-api.html#show-role) API endpoint was returning 401 Unauthorized when a permission check failed. This has been corrected to return 403 Forbidden instead. That is, the API token is known to be good (401 otherwise) but the user lacks permission (403 is now sent). See also the [API Changelog](https://dataverse-guide--11116.org.readthedocs.build/en/11116/api/changelog.html), #10340, and #11116. From d4f91892803c6a04e3749524ed0e6c5db2b9ab91 Mon Sep 17 00:00:00 2001 From: Philip Durbin Date: Wed, 8 Jan 2025 21:30:10 -0500 Subject: [PATCH 6/6] add docs on showing a role using an alias, other tweaks #10340 - missing JSON header added - smart quotes changed to straight - more consistency - typos fixed --- doc/sphinx-guides/source/api/native-api.rst | 43 +++++++++++++++------ 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/doc/sphinx-guides/source/api/native-api.rst b/doc/sphinx-guides/source/api/native-api.rst index 570b6308a64..a822f1dcb2b 100644 --- a/doc/sphinx-guides/source/api/native-api.rst +++ b/doc/sphinx-guides/source/api/native-api.rst @@ -424,13 +424,13 @@ Creates a new role under Dataverse collection ``id``. Needs a json file with the export SERVER_URL=https://demo.dataverse.org export ID=root - curl -H "X-Dataverse-key:$API_TOKEN" -X POST "$SERVER_URL/api/dataverses/$ID/roles" --upload-file roles.json + curl -H "X-Dataverse-key:$API_TOKEN" -H "Content-type:application/json" -X POST "$SERVER_URL/api/dataverses/$ID/roles" --upload-file roles.json The fully expanded example above (without environment variables) looks like this: .. code-block:: bash - curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST -H "Content-type:application/json" "https://demo.dataverse.org/api/dataverses/root/roles" --upload-file roles.json + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Content-type:application/json" -X POST "https://demo.dataverse.org/api/dataverses/root/roles" --upload-file roles.json For ``roles.json`` see :ref:`json-representation-of-a-role` @@ -4549,8 +4549,8 @@ The JSON representation of a role (``roles.json``) looks like this:: { "alias": "sys1", - "name": “Restricted System Role”, - "description": “A person who may only add datasets.”, + "name": "Restricted System Role", + "description": "A person who may only add datasets.", "permissions": [ "AddDataset" ] @@ -4568,9 +4568,25 @@ Roles can be created globally (:ref:`create-global-role`) or for individual Data Show Role ~~~~~~~~~ -You must have ``ManageDataversePermissions`` to be able to show a role that was created using :ref:`create-role-in-collection`. Global roles (:ref:`create-global-role`) only be shown with a superuser API token. +You must have ``ManageDataversePermissions`` to be able to show a role that was created using :ref:`create-role-in-collection`. Global roles (:ref:`create-global-role`) can only be shown with a superuser API token. -A curl example using an ``ID``: +An example using a role alias: + +.. code-block:: bash + + export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + export SERVER_URL=https://demo.dataverse.org + export ALIAS=sys1 + + curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/roles/:alias?alias=$ALIAS" + +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/roles/:alias?alias=sys1" + +An example using a role id: .. code-block:: bash @@ -4580,10 +4596,16 @@ A curl example using an ``ID``: curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/roles/$ID" +The fully expanded example above (without environment variables) looks like this: + +.. code-block:: bash + + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/roles/11" + Delete Role ~~~~~~~~~~~ -A curl example using an ``ID`` +An example using a role id: .. code-block:: bash @@ -4599,13 +4621,13 @@ The fully expanded example above (without environment variables) looks like this curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/roles/24" -A curl example using a Role alias ``ALIAS`` +An example using a role alias: .. code-block:: bash export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx export SERVER_URL=https://demo.dataverse.org - export ALIAS=roleAlias + export ALIAS=sys1 curl -H "X-Dataverse-key:$API_TOKEN" -X DELETE "$SERVER_URL/api/roles/:alias?alias=$ALIAS" @@ -4613,8 +4635,7 @@ The fully expanded example above (without environment variables) looks like this .. code-block:: bash - curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/roles/:alias?alias=roleAlias" - + curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/roles/:alias?alias=sys1" Explicit Groups ---------------