Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
76ef034
Increment version number to 0.0.2.9000
villegar Apr 27, 2026
5b6f0ff
Replace calls to opalr::opal.datasources() with opalr::opal.projects(…
villegar May 12, 2026
35b2617
Add missing @export tag for some generic methods
villegar May 12, 2026
0ebd0c3
Update roxygen2 version
villegar May 12, 2026
f721f2c
New build
villegar May 12, 2026
de55c62
Update URL to the DataSHIELD dev install and add a test deployment se…
villegar May 14, 2026
5fc295b
Remove unused function (get_project_details) and associated documenta…
villegar May 19, 2026
40b576b
Remove unused function (get_project_tables) and associated documentat…
villegar May 19, 2026
6795daa
Remove unused function (get_table_permissions) and associated documen…
villegar May 19, 2026
a317a71
Remove the normalise_permission function
villegar May 19, 2026
3a29c26
Remove the update_project_datasets function, plus associated document…
villegar May 19, 2026
e48d7c0
Add new internal function, is_opal_audit_con, which validates if a gi…
villegar May 19, 2026
90c8ffe
Update definition of is_opal_admin_con to lineup with is_opal_audit_con
villegar May 19, 2026
5374995
New build
villegar May 19, 2026
9d88f8b
Remove call to validate_opal_con within is_opal_admin_con and is_opal…
villegar May 19, 2026
85675a9
Update test description
villegar May 19, 2026
4a05abc
Update verification of connection for audit or admin rights
villegar May 19, 2026
7b0f631
Add new function (check_permissions) to verify if a given connection …
villegar May 20, 2026
710dfae
Add call to the check_permissions function
villegar May 20, 2026
4771a83
Add vignette with extended instructions on how to configure audit per…
villegar May 20, 2026
9ecd735
Add link to the vignette with details for the audit permissions
villegar May 20, 2026
a61ee9d
New build
villegar May 20, 2026
df69100
Add params definition from audit_engine
villegar May 20, 2026
a8a1d17
Add @noRd tag to internal functions to address issue that includes th…
villegar May 20, 2026
664e56b
Minor fix to handle scenarios where the ds_table column is missing
villegar May 22, 2026
2ef9043
Update documentation to include instructions for setting up audit use…
villegar May 22, 2026
a8fc607
Set minimum version of opalr to 3.6.1, as this version fixed a bug wi…
villegar May 29, 2026
4d0f437
Add new function to validate_backend_version and S3 generics
villegar May 29, 2026
30bc668
Migrate validate_opal_con to S3 generic function validate_con
villegar Jun 1, 2026
444cec0
Add new internal function validate_backend to run all the checks requ…
villegar Jun 1, 2026
cc006bf
Replace opal-test server by opal-demo, as the former is running an ol…
villegar Jun 1, 2026
9f2b247
Replace multiple calls to separate functions for validation of backen…
villegar Jun 1, 2026
205c840
New build
villegar Jun 1, 2026
0dc94ca
Update filter to exclude permissions fro admin and auditor users
villegar Jun 1, 2026
ae5782a
Update filter for user logs
villegar Jun 1, 2026
4eee0f1
Add missing local bindings
villegar Jun 1, 2026
d38c559
Bump version for release
villegar Jun 1, 2026
f342b19
Include changes based on the issues addressed for this release
villegar Jun 1, 2026
583fb76
Bump version to minor release
villegar Jun 1, 2026
0179417
Update URL for example
villegar Jun 1, 2026
4547a3b
Minor change to skip checking local URL, as this resulted on a NOTE w…
villegar Jun 1, 2026
03c1dea
Remove additional field
villegar Jun 1, 2026
367b01b
Remove dev version header
villegar Jun 1, 2026
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
6 changes: 3 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: dsROCrate
Title: 'DataSHIELD' RO-Crate Governance Functions
Version: 0.0.2
Version: 0.1.0
Authors@R: c(
person(given = "Roberto",
family = "Villegas-Diaz",
Expand Down Expand Up @@ -39,13 +39,12 @@ Config/testthat/edition: 3
Encoding: UTF-8
Language: en-GB
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
Imports:
digest,
dplyr,
DSMolgenisArmadillo,
jsonlite,
opalr,
opalr (>= 3.6.1),
purrr,
RcppTOML,
rmarkdown,
Expand All @@ -59,3 +58,4 @@ Depends:
VignetteBuilder: knitr
URL: https://github.com/FederatedMethods/dsROCrate
BugReports: https://github.com/FederatedMethods/dsROCrate/issues
Config/roxygen2/version: 8.0.0
9 changes: 9 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ S3method(audit,opal)
S3method(audit,rocrate)
S3method(audit_engine,cr8tor)
S3method(audit_engine,opal)
S3method(check_permissions,default)
S3method(check_permissions,opal)
S3method(extract_safe_data,opal)
S3method(extract_safe_data,rocrate)
S3method(extract_safe_output,opal)
Expand All @@ -31,8 +33,10 @@ S3method(flatten_safe_setting,rocrate)
S3method(init,opal)
S3method(init,rocrate)
S3method(parse_user_profiles,ArmadilloCredentials)
S3method(parse_user_profiles,opal)
S3method(print,cr8tor_bundle)
S3method(project_exists,ArmadilloCredentials)
S3method(project_exists,opal)
S3method(report,character)
S3method(report,default)
S3method(report,list)
Expand All @@ -59,8 +63,13 @@ S3method(safe_setting,cr8tor)
S3method(safe_setting,default)
S3method(safe_setting,opal)
S3method(safe_setting,rocrate)
S3method(validate_backend_version,default)
S3method(validate_backend_version,opal)
S3method(validate_con,default)
S3method(validate_con,opal)
export(armadillo_login)
export(audit)
export(check_permissions)
export(init)
export(report)
export(safe_data)
Expand Down
33 changes: 32 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
# dsROCrate (development version)
# dsROCrate 0.1.0

## New Features

* Added `check_permissions()` support for backend connections, allowing users to verify whether a connection has sufficient privileges to run `safe_*` auditing functions before execution (#15).
* Permission validation now provides actionable feedback, including guidance on how to configure Opal users and roles when required permissions are missing (#15).
* Added Opal version validation during backend checks to ensure compatibility with APIs required for auditing and permission verification workflows (#22).

## Improvements

* Refactored backend validation into an extensible S3 framework, providing a cleaner architecture for supporting additional backends in future releases (#23).
* Introduced a unified backend validation workflow that standardises connection, version and permission checks across auditing functions (#23).
* Replaced use of `opalr::opal.datasources()` with `opalr::opal.projects()`, reducing the permissions required for metadata extraction and improving compatibility with auditor accounts (#21).
* Updated auditing workflows to support Opal's **Audit System** role, reducing the need for administrator-level accounts when generating RO-Crates and audit reports (#20).

## Bug Fixes

* Fixed generation of orphaned permission entities in RO-Crates by excluding administrator and auditor permissions from dataset permission records where corresponding user entities are not included (#19).
* Improved consistency between `safe_data()` and `safe_people()` permission handling (#19).

## Documentation

* Updated deployment vignette to reference the FederatedMethods-maintained DataSHIELD Docker deployment resources and repository structure (#16).
* Updated user documentation to reflect support for Opal's **Audit System** role and revised permission requirements (#20).
* Reviewed documentation for internal helper functions and adjusted visibility of internal APIs where appropriate (#17).
* Added documentation and examples for backend permission validation workflows (#15).

## Internal Changes

* Improved separation of concerns between connection validation, backend capability checks and permission validation (#22, #23).
* Refined internal validation infrastructure to simplify future backend integrations.
* General maintenance, testing and documentation updates.

# dsROCrate 0.0.2

Expand Down
12 changes: 11 additions & 1 deletion R/audit.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,17 @@
#' @param ... Additional arguments.
#' @param intent Additional object with governance bundle/specification of the
#' intent of a project. It takes the same types as `x`.
#' @inheritParams audit_engine
#' @param project String with project name(s) from which to extra Safe Project
#' details.
#' @param user String with the user name for which to extract Safe People
#' details.
#' @param logs_from Lower limit timestamp to filter out the outputs generated
#' (default: `-Inf`, everything up to `logs_to`)
#' @param logs_to Upper limit timestamp to filter out the outputs generated
#' (default: `Inf`, everything from `logs_from` onwards).
#' @param path String with path pointing to the root of the RO-Crate. This will
#' be used to store log files. If not provided, logs will be stored within
#' the RO-Crate returned by this function.
#'
#' @returns RO-Crate with audit details.
#' @export
Expand Down
29 changes: 22 additions & 7 deletions R/audit_engine.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
#'
#' @returns Audit RO-Crate with 5 Safes Components.
#' @keywords internal
#' @noRd
audit_engine <- function(x, ...) {
UseMethod("audit_engine")
}

#' @rdname audit_engine
#' @export
audit_engine.cr8tor <- function(x, ...) {
# extract individual components from cr8tor bundle
Expand All @@ -46,7 +46,6 @@ audit_engine.cr8tor <- function(x, ...) {
as_rocrate_audit(audit)
}

#' @rdname audit_engine
#' @export
audit_engine.opal <- function(
x,
Expand All @@ -58,21 +57,21 @@ audit_engine.opal <- function(
path = NULL
) {
# local bindings
name <- principal <- NULL
name <- permission <- principal <- NULL

# create RO-Create with the 5 safes profile
crate <- rocrateR::rocrate_5s()

# validate Opal connection
is_opal_admin_con(x)
# validate backend
validate_backend(x, ...)

# if `project` is missing, then ~extract all project names~ error
if (is.null(project)) {
stop("A `project` name is required!", call. = FALSE)
}

# extract all data sources to verify `project` contains a valid value.
ds <- opalr::opal.datasources(x)
# extract list with all projects to verify `project` contains a valid value
ds <- opalr::opal.projects(x)
server_prjs <- ds[, "name"]
idx <- project %in% server_prjs
if (!all(idx)) {
Expand All @@ -93,6 +92,22 @@ audit_engine.opal <- function(
# exclude system administrators from the report
dplyr::filter(!(tolower(name) %in% c("admin", "administrator")))

# if any users were found, then verify if they are admin/auditors and exclude
if (nrow(safe_people_tbl)) {
# extract system permissions
sys_perms_tbl <- opalr::oadmin.system_perm(x)
safe_people_tbl <- tryCatch(
{
safe_people_tbl |>
dplyr::left_join(sys_perms_tbl, by = c("name" = "subject")) |>
dplyr::filter(!(permission %in% c("administrate", "audit")))
},
error = function(e) {
tibble::tibble()
}
)
}

if (!is.null(user)) {
safe_people_tbl <- safe_people_tbl |>
dplyr::filter(tolower(name) %in% user)
Expand Down
69 changes: 69 additions & 0 deletions R/check_permissions.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#' Check backend connection permissions
#'
#' Validates whether a backend connection has sufficient permissions for
#' `{dsROCrate}` audit operations.
#'
#' Currently, audit or administrator permissions are required.
#'
#' @param x A backend connection object.
#' @param ... Additional arguments passed to methods.
#'
#' @returns
#' Returns `TRUE` invisibly if the connection has sufficient permissions.
#'
#' @export
#'
#' @seealso
#' \code{vignette("audit-permissions", package = "dsROCrate")}
check_permissions <- function(x, ...) {
UseMethod("check_permissions")
}

#' @export
check_permissions.default <- function(x, ...) {
stop(
sprintf(
paste0(
"No `check_permissions()` method exists for objects of class: %s.\n",
"Please provide a supported backend connection object."
),
paste(class(x), collapse = ", ")
),
call. = FALSE
)
}

#' @export
check_permissions.opal <- function(x, ...) {
is_admin <- FALSE
is_audit <- FALSE

is_admin <- tryCatch(
is_opal_admin_con(x),
error = function(e) FALSE
)

is_audit <- tryCatch(
is_opal_audit_con(x),
error = function(e) FALSE
)

if (isTRUE(is_admin) || isTRUE(is_audit)) {
return(invisible(TRUE))
}

stop(
paste(
"The supplied backend connection does not have sufficient permissions.",
"",
"{dsROCrate} requires elevated permissions to perform audit operations.",
"",
"Please see:",
" vignette('audit-permissions', package = 'dsROCrate')",
"",
"for backend-specific configuration instructions.",
sep = "\n"
),
call. = FALSE
)
}
4 changes: 2 additions & 2 deletions R/dsROCrate.R
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ init.opal <- function(
path = NULL,
user = NULL
) {
# x is a valid opal connection object
validate_opal_con(x)
# validate backend
validate_backend(x, ...)

# attach input arguments as attributes
attr(rocrate, "connection") <- x
Expand Down
4 changes: 2 additions & 2 deletions R/safe_data.R
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ safe_data.opal <- function(
# declare local bindings
created <- lastUpdate <- name <- new_dataset_entity <- subject <- NULL

# x is a valid opal connection object
validate_opal_con(x)
# validate backend
validate_backend(x, ...)

# match the assets to be included
include <- match.arg(include, several.ok = TRUE)
Expand Down
11 changes: 6 additions & 5 deletions R/safe_output.R
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ safe_output.opal <- function(
# local bindings
`@timestamp` <- backend <- logger_name <- safe_people_id <- username <- NULL
ds_action <- ds_eval <- ds_id <- ds_function <- ds_symbol <- ds_table <- NULL
is_placeholder <- NULL

# create formatted versions of input dates
logs_from_is_valid <- FALSE
Expand Down Expand Up @@ -134,11 +135,8 @@ safe_output.opal <- function(
format(as.POSIXct(logs_to), '%Y-%m-%d %H:%M:%S')
)

# x is a valid opal connection object
validate_opal_con(x)

# validate that connection user has administrative rights
is_opal_admin_con(x)
# validate backend
validate_backend(x, ...)

# verify if `user` is NULL, if so, retrieve information from the RO-crate
if (is.null(user)) {
Expand Down Expand Up @@ -271,6 +269,9 @@ safe_output.opal <- function(
## evaluated functions and tables/symbols mapped
userlogs_tbl_maps_evals <- userlogs_tbl |>
dplyr::filter(ds_action %in% c("ASSIGN", "AGGREGATE", "OPEN")) |>
# add place-holder column, for when ro records are found
dplyr::bind_rows(tibble::tibble(ds_table = NA, is_placeholder = TRUE)) |>
dplyr::filter(is.na(is_placeholder)) |>
# create derived `ds_eval` when `ds_action` = 'ASSIGN'
dplyr::mutate(
ds_eval = dplyr::coalesce(
Expand Down
4 changes: 2 additions & 2 deletions R/safe_people.R
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ safe_people.opal <- function(
resources = NULL,
tables = NULL
) {
# x is a valid opal connection object
validate_opal_con(x)
# validate backend
validate_backend(x, ...)

# attempt to retrieve project entity
project_id <- id_hash("#project:", project)
Expand Down
7 changes: 2 additions & 5 deletions R/safe_project.R
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,8 @@ safe_project.opal <- function(
# declare local bindings
created <- lastUpdate <- type <- NULL

# x is a valid opal connection object
validate_opal_con(x)

# validate that connection user has administrative rights
is_opal_admin_con(x)
# validate backend
validate_backend(x, ...)

# enforce that `project` is a single value
if (is.null(project)) {
Expand Down
7 changes: 2 additions & 5 deletions R/safe_setting.R
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,8 @@ safe_setting.opal <- function(
# local binding
Package <- NULL

# validate connection ----
# x is a valid opal connection object
validate_opal_con(x)
# validate that connection user has administrative rights
is_opal_admin_con(x)
# validate backend
validate_backend(x, ...)

# validate profile ----
if (!opalr::dsadmin.profile_exists(x, profile)) {
Expand Down
Loading
Loading