Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
253a034
basic roleassignment auditing for assign, revoke
qqmyers Apr 8, 2025
a7f4b61
add indexes
qqmyers Apr 8, 2025
bf0beed
missing import
qqmyers Apr 8, 2025
47e9805
refresh assignment to get id
qqmyers Apr 8, 2025
1d3a111
history panel
qqmyers Apr 8, 2025
2fbde58
typo
qqmyers Apr 8, 2025
9da8ee9
file RAs
qqmyers Apr 9, 2025
fe1f3e7
minor fixes
qqmyers Apr 9, 2025
a1d5c8a
sync tables except def point
qqmyers Apr 9, 2025
52e1877
aggregate multiple def pts, show tool tip with list
qqmyers Apr 9, 2025
8b93f30
fixes
qqmyers Apr 9, 2025
d16eb77
remove param
qqmyers Apr 10, 2025
c622259
use final map to avoid duplicates
qqmyers Apr 10, 2025
334d8e0
use h:outputFormat
qqmyers Apr 10, 2025
04ac9ba
Drop refresh
qqmyers Apr 14, 2025
c7251f5
move RA to postFlush in create
qqmyers Apr 14, 2025
614697b
move audit code to service
qqmyers Apr 14, 2025
d896af5
update history when assignment made
qqmyers Apr 16, 2025
06042f3
handle dataverse, no filePIDs, more update history
qqmyers Apr 16, 2025
1bb5fe2
handle null assign dates (revoked perms that already existed)
qqmyers Apr 16, 2025
e687a87
update fixes
qqmyers Apr 16, 2025
1864d0d
update sorting
qqmyers Apr 18, 2025
65dc679
sortable, deferred load
qqmyers Apr 18, 2025
c87220e
cleanup/drop deferred load (wasn't working)
qqmyers Apr 18, 2025
cb57117
make updating RA history conditional on flag being set.
qqmyers Apr 28, 2025
04b37f2
missing widgetVar
qqmyers Apr 28, 2025
b6b86ea
Revert "make updating RA history conditional on flag being set."
qqmyers Apr 28, 2025
d3ab76b
update to fix 500 error re not finding ra history
qqmyers Apr 28, 2025
af6600f
word wrapping on perms form
qqmyers Apr 29, 2025
4fca21b
missing import
qqmyers Jul 7, 2025
8c0bfbf
refactor getting RA history
qqmyers Jul 7, 2025
859d0ab
api call
qqmyers Jul 7, 2025
1c87cef
remove unused imports
qqmyers Jul 16, 2025
a6a1ab1
api call with i18n and csv option
qqmyers Jul 16, 2025
c2c54dc
dataset/dataverse button to download csv
qqmyers Jul 16, 2025
b1b4503
refactor, apis for all three types
qqmyers Jul 17, 2025
0989e35
download csv buttons
qqmyers Jul 17, 2025
db6e94b
strings for csv headers and button
qqmyers Jul 17, 2025
478084a
Merge remote-tracking branch 'IQSS/develop' into RoleAccessHistory
qqmyers Jul 18, 2025
7e468f4
try signed URLs for CSV buttons, fix files reporting/api
qqmyers Jul 21, 2025
13ac931
use getUserIdentifier()
qqmyers Jul 23, 2025
7ed23bf
add missing strings
qqmyers Jul 24, 2025
94235c7
update files page, use new filenames
qqmyers Jul 24, 2025
aad80c4
fix file url generation - authUser
qqmyers Jul 24, 2025
233f3e4
add produces annotation
qqmyers Jul 24, 2025
c0897a9
move script to end of body
qqmyers Jul 24, 2025
59c0680
handle commas w/multiple def pts
qqmyers Jul 24, 2025
eda9fe8
Merge remote-tracking branch 'IQSS/develop' into RoleAccessHistory
qqmyers Jul 24, 2025
c167307
reuse strings
qqmyers Jul 24, 2025
f10dcbb
handle missing assignment info
qqmyers Jul 24, 2025
0803726
fix comma handling
qqmyers Jul 24, 2025
715badb
change api paths to mirror /assignment endpoints
qqmyers Jul 24, 2025
baf5e18
change flag name, class names to remove audit
qqmyers Jul 25, 2025
696f009
more audit changes
qqmyers Jul 25, 2025
ec2042d
documentation
qqmyers Jul 25, 2025
dfba6a7
change revokeRole to add OnDataverse, add dv roleassignment/history call
qqmyers Jul 25, 2025
bc97830
dataverse-level test
qqmyers Jul 25, 2025
fd4983c
Change to combine up to the same minute
qqmyers Jul 25, 2025
94f4ff2
dataset, files ra history tests, fix csv for dataverse test
qqmyers Jul 25, 2025
ceaf855
local tests
qqmyers Jul 25, 2025
8ad46ff
IT tests (for use when the feature is on)
qqmyers Jul 25, 2025
77972b2
doc updates with info on response formats
qqmyers Jul 25, 2025
a348b98
change table, get rid of audit word
qqmyers Jul 25, 2025
6b2b76f
Merge remote-tracking branch 'IQSS/develop' into RoleAccessHistory
qqmyers Jul 25, 2025
a1a85b4
update flag name in xhtml
qqmyers Jul 25, 2025
06453fc
Merge remote-tracking branch 'IQSS/develop' into RoleAccessHistory
qqmyers Aug 27, 2025
71a6e4e
Merge remote-tracking branch 'IQSS/develop' into RoleAccessHistory
qqmyers Sep 11, 2025
c7c7e70
Apply suggestions from code review
qqmyers Sep 11, 2025
62f976e
updates per review
qqmyers Sep 11, 2025
8e12e43
Merge remote-tracking branch 'IQSS/develop' into RoleAccessHistory
qqmyers Sep 25, 2025
bd2ab9b
Merge remote-tracking branch 'IQSS/develop' into RoleAccessHistory
qqmyers Oct 23, 2025
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
17 changes: 17 additions & 0 deletions doc/release-notes/11612-RAHistory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Role Assignment History Tracking

Dataverse can now track the history of role assignments, allowing administrators to see who assigned or revoked roles, when these actions occurred, and which roles were involved. This feature helps with auditing and understanding permission changes over time.

## Key components of this feature:

- **Feature Flag**: The functionality can be enabled/disabled via the `ROLE_ASSIGNMENT_HISTORY` feature flag (default is `off`)
- **UI Integration**: New history panels on permission management pages showing the complete history of role assignments/revocations
- **CSV Export**: Administrators can download the role assignment history for a given collection or dataset (or files in a dataset) as a CSV file directly from the new panels
- **API Access**: New API endpoints provide access to role assignment history in both JSON and CSV formats:
- `/api/dataverses/{identifier}/assignments/history`
- `/api/datasets/{identifier}/assignments/history`
- `/api/datasets/{identifier}/files/assignments/history`

All return JSON by default but will return an internationalized CSV if an `Accept: text/csv` header is adde

For more information, see #11612
201 changes: 201 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,78 @@ The fully expanded example above (without environment variables) looks like this

curl -H "X-Dataverse-key: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X POST "https://demo.dataverse.org/api/dataverses/1/templates" --upload-file dataverse-template.json


Dataverse Role Assignment History
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Get the history of role assignments for a collection. This API call returns a list of role assignments and revocations for the specified dataset.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=1

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: application/json" "$SERVER_URL/api/dataverses/$ID/assignments/history"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: application/json" "https://demo.dataverse.org/api/dataverses/3/assignments/history"

You can also use the collection alias instead of the numeric id:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export DV_ALIAS=dvAlias

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: application/json" "$SERVER_URL/api/dataverses/$DV_ALIAS/assignments/history"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: application/json" "https://demo.dataverse.org/api/datasets/dvAlias/assignments/history"

The response is a JSON array of role assignment history entries with the following structure for each entry:

.. code-block:: json

{
"definedOn": "1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elsewhere we have both definitionPointId and definitionPointName:

src/test/java/edu/harvard/iq/dataverse/api/DeactivateUsersIT.java
83:                .body("data.traces.roleAssignments.items[0].definitionPointName", equalTo(dataverseAlias))
84:                .body("data.traces.roleAssignments.items[0].definitionPointId", equalTo(dataverseId))

Should we consider showing both instead of just the id?

At the very least, should we consider switching to definitionPointId, for consistency? It appears above and in the following JsonPrinter method:

public static JsonObjectBuilder json(RoleAssignment ra) {
    return jsonObjectBuilder()
            .add("id", ra.getId())
            .add("assignee", ra.getAssigneeIdentifier())
            .add("roleId", ra.getRole().getId())
            .add("roleName", ra.getRole().getName())
            .add("_roleAlias", ra.getRole().getAlias())
            .add("privateUrlToken", ra.getPrivateUrlToken())
            .add("definitionPointId", ra.getDefinitionPoint().getId());
}

Obviously, internal consistency across the JSON and CSV formats for this feature is desired, as well, so we should update both if we make any updates.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure - 'both' only exist for dataverses (id, alias) whereas datasets/files could use (id, PID (if available)), and the file-level call can actually return a comma-separated list of file ids in the definedOn field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Maybe we can revisit this when we build this feature in the SPA. Or maybe we won't have to. 🤷

"assigneeIdentifier": "@user1",
"roleName": "Admin",
"assignedBy": "@dataverseAdmin",
"assignedAt": "2023-01-01T12:00:00Z",
"revokedBy": null,
"revokedAt": null
}

For revoked assignments, the "revokedBy" and "revokedAt" fields will contain values instead of null.

To retrieve the history in CSV format, change the Accept header to "text/csv":

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=3

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: text/csv" "$SERVER_URL/api/dataverses/$ID/assignments/history"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: text/csv" "https://demo.dataverse.org/api/dataverses/3/assignments/history"

The CSV response has column headers mirroring the JSON entries. They are internationalized (when internationalization is configured).

Note: This feature requires the "role-assignment-history" feature flag to be enabled (see :ref:`feature-flags`).

Datasets
--------

Expand Down Expand Up @@ -4115,6 +4187,135 @@ Upon success, the API will return a JSON response with a success message and the
The API call will report a 400 (BAD REQUEST) error if any of the files specified do not exist or are not in the latest version of the specified dataset.
The ``fileIds`` in the JSON payload should be an array of file IDs that you want to delete from the dataset.

.. _api-dataset-role-assignment-history:

Dataset Role Assignment History
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Get the history of role assignments for a dataset. This API call returns a list of role assignments and revocations for the specified dataset.

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=3

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: application/json" "$SERVER_URL/api/datasets/$ID/assignments/history"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: application/json" "https://demo.dataverse.org/api/datasets/3/assignments/history"

You can also use the persistent identifier instead of the numeric id:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/ABCDEF

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: application/json" "$SERVER_URL/api/datasets/:persistentId/assignments/history?persistentId=$PERSISTENT_IDENTIFIER"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: application/json" "https://demo.dataverse.org/api/datasets/:persistentId/assignments/history?persistentId=doi:10.5072/FK2/ABCDEF"

The response is a JSON array of role assignment history entries with the following structure for each entry:

.. code-block:: json

{
"definedOn": "3",
"assigneeIdentifier": "@user1",
"roleName": "Admin",
"assignedBy": "@dataverseAdmin",
"assignedAt": "2023-01-01T12:00:00Z",
"revokedBy": null,
"revokedAt": null
}

For revoked assignments, the "revokedBy" and "revokedAt" fields will contain values instead of null.

To retrieve the history in CSV format, change the Accept header to "text/csv":

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=3

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: text/csv" "$SERVER_URL/api/datasets/$ID/assignments/history"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: text/csv" "https://demo.dataverse.org/api/datasets/3/assignments/history"

The CSV response has column headers mirroring the JSON entries. They are internationalized (when internationalization is configured).

Note: This feature requires the "role-assignment-history" feature flag to be enabled (see :ref:`feature-flags`).

Dataset Files Role Assignment History
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Get the history of role assignments for the files in a dataset. This API call returns a list of role assignments and revocations for all files in the specified dataset.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - this is just when assignments have been made or revoked. Whether a request is granted/denied is related but separate (denied would not have any RA history entries).


.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=3

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: application/json" "$SERVER_URL/api/datasets/$ID/files/assignments/history"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: application/json" "https://demo.dataverse.org/api/datasets/3/files/assignments/history"

You can also use the persistent identifier instead of the numeric id:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export PERSISTENT_IDENTIFIER=doi:10.5072/FK2/ABCDEF

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: application/json" "$SERVER_URL/api/datasets/:persistentId/files/assignments/history?persistentId=$PERSISTENT_IDENTIFIER"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: application/json" "https://demo.dataverse.org/api/datasets/:persistentId/files/assignments/history?persistentId=doi:10.5072/FK2/ABCDEF"

The JSON response for this call is the same as for the /api/datasets/{id}/assignments/history call above with the exception that definedOn will be a comma separated list of one or more file ids.

To retrieve the history in CSV format, change the Accept header to "text/csv":

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=3

curl -H "X-Dataverse-key:$API_TOKEN" -H "Accept: text/csv" "$SERVER_URL/api/datasets/files/$ID/assignments/history"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -H "Accept: text/csv" "https://demo.dataverse.org/api/datasets/3/files/assignments/history"

The CSV response for this call is the same as for the /api/datasets/{id}/assignments/history call above with the exception that definedOn will be a comma separated list of one or more file ids.

Note: This feature requires the "role-assignment-history" feature flag to be enabled (see :ref:`feature-flags`).

Files
-----
Expand Down
3 changes: 3 additions & 0 deletions doc/sphinx-guides/source/installation/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3821,6 +3821,9 @@ please find all known feature flags below. Any of these flags can be activated u
* - enable-pid-failure-log
- Turns on creation of a monthly log file (logs/PIDFailures_<yyyy-MM>.log) showing failed requests for dataset/file PIDs. Can be used directly or with scripts at https://github.com/gdcc/dataverse-recipes/python/pid_reports to alert admins.
- ``Off``
* - role-assignment-history
- Turns on tracking/display of role assignments and revocations for collections, datasets, and files
- ``Off``

**Note:** Feature flags can be set via any `supported MicroProfile Config API source`_, e.g. the environment variable
``DATAVERSE_FEATURE_XXX`` (e.g. ``DATAVERSE_FEATURE_API_SESSION_AUTH=1``). These environment variables can be set in your shell before starting Payara. If you are using :doc:`Docker for development </container/dev-usage>`, you can set them in the `docker compose <https://docs.docker.com/compose/environment-variables/set-environment-variables/>`_ file.
Expand Down
4 changes: 3 additions & 1 deletion doc/sphinx-guides/source/user/dataverse-management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,16 @@ Clicking on Permissions will bring you to this page:

|image3|

When you access a Dataverse collection's permissions page, you will see three sections:
When you access a Dataverse collection's permissions page, you will see three or four sections:

**Permissions:** Here you can decide the requirements that determine which types of users can add datasets and sub Dataverse collections to your Dataverse collection, and what permissions they'll be granted when they do so.

**Users/Groups:** Here you can assign roles to specific users or groups, determining which actions they are permitted to take on your Dataverse collection. You can also reference a list of all users who have roles assigned to them for your Dataverse collection and remove their roles if you please.

**Roles:** Here you can reference a full list of roles that can be assigned to users of your Dataverse collection. Each role lists the permissions that it offers.

**Role Assignment History** If enabled, you'll be able to see the history of when roles have been assigned and revoked and by whom.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we link to the feature flag here?


Please note that even on a newly created Dataverse collection, you may see user and groups have already been granted role(s) if your installation has ``:InheritParentRoleAssignments`` set. For more on this setting, see the :doc:`/installation/config` section of the Installation Guide.

Setting Access Configurations
Expand Down
Loading