Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correct /api/roles API to respond with 403 Forbidden (not 401 Unauthorized) when auth is good but no permission #11116

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/release-notes/10340-forbidden.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Backward Incompatible Changes

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.
1 change: 1 addition & 0 deletions doc/sphinx-guides/source/api/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----
Expand Down
14 changes: 12 additions & 2 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/AbstractApiBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,18 @@ protected Response badRequest(String msg, Map<String, String> 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 );
}
Expand All @@ -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 );
}
Expand Down
11 changes: 10 additions & 1 deletion src/test/java/edu/harvard/iq/dataverse/api/RolesIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Loading