Skip to content
2 changes: 1 addition & 1 deletion 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.2.1
Version: 0.2.2
Authors@R: c(
person(given = "Roberto",
family = "Villegas-Diaz",
Expand Down
9 changes: 9 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
# dsROCrate 0.2.2

## Bug fixes

* Fixed an issue in `safe_output()` where Opal audit logs containing no `AGGREGATE` events could cause the audit pipeline to fail when constructing the calls table.
* `safe_output()` now handles audit sessions with logs but no `AGGREGATE` calls gracefully, returning an empty calls table rather than attempting to operate on `NULL`.
* Fixed an issue where an RO-Crate without an explicit Opal profile attribute could cause safe_output() to fail when filtering audit logs. The default "default" Opal profile is now used when no profile is supplied.
* Improved robustness of provenance extraction for audit sessions with incomplete or limited audit activity.

# dsROCrate 0.2.1

## Bug Fixes
Expand Down
15 changes: 14 additions & 1 deletion R/audit.R
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,15 @@
#' @param ... Additional arguments.
#' @param intent Additional object with governance bundle/specification of the
#' intent of a project. It takes the same types as `x`.
#' @param project String with project name(s) from which to extra Safe Project
#' @param profile String with profile name (used for OBiBa's Opal backend).
#' @param project String with project name(s) from which to extract Safe Project
#' details.
#' @param resources Vector of strings with the names of the resources, part of
#' `project`. Optional, if not provided, all the resources associated to
#' `project` will be included in the RO-Crate.
#' @param tables Vector of strings with the names of the tables/datasets, part
#' of `project`. Optional, if not provided, all the tables/datasets
#' associated to `project` will be included in the RO-Crate.
#' @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
Expand Down Expand Up @@ -142,7 +149,10 @@ audit.opal <- function(
x,
...,
intent = NULL,
profile = "default",
project = NULL,
resources = NULL,
tables = NULL,
user = NULL,
logs_from = -Inf,
logs_to = Inf,
Expand All @@ -154,7 +164,10 @@ audit.opal <- function(
# call next method
main_audit <- audit_engine(
x,
profile = profile,
project = c(intent_lst$main_audit_args$project, project),
resources = resources,
tables = tables,
user = c(intent_lst$main_audit_args$user, user),
logs_from = logs_from,
logs_to = logs_to,
Expand Down
29 changes: 22 additions & 7 deletions R/audit_engine.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@
#' governance archive file, representing the intent of a project and
#' associated governance details.
#' @param ... Other optional arguments, see full documentation for details.
#' @param project String with project name(s) from which to extra Safe Project
#' @param profile String with profile name (used for OBiBa's Opal backend).
#' @param project String with project name(s) from which to extract Safe Project
#' details.
#' @param resources Vector of strings with the names of the resources, part of
#' `project`. Optional, if not provided, all the resources associated to
#' `project` will be included in the RO-Crate.
#' @param tables Vector of strings with the names of the tables/datasets, part
#' of `project`. Optional, if not provided, all the tables/datasets
#' associated to `project` will be included in the RO-Crate.
#' @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
Expand Down Expand Up @@ -61,7 +68,10 @@ audit_engine.cr8tor <- function(x, ...) {
audit_engine.opal <- function(
x,
...,
profile = "default",
project = NULL,
resources = NULL,
tables = NULL,
user = NULL,
logs_from = -Inf,
logs_to = Inf,
Expand All @@ -70,11 +80,16 @@ audit_engine.opal <- function(
# local bindings
name <- NULL

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

# validate backend
validate_backend(x, ...)
# initialise empty RO-Crate with audit settings
crate <- x |>
dsROCrate::init(
profile = profile,
project = project,
resources = resources,
tables = tables,
user = user,
path = path
)

# if `project` is missing, then error
if (is.null(project)) {
Expand Down Expand Up @@ -165,7 +180,7 @@ audit_engine.opal <- function(
purrr::reduce(rocrateR::remove_entity, .init = crate)

# Safe Settings ----
crate <- safe_setting(x, rocrate = crate)
crate <- safe_setting(crate, connection = x)

# Safe Outputs ----
crate <- safe_people_tbl$name |>
Expand Down
4 changes: 2 additions & 2 deletions R/backend-opal.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ backend_options.opal <- function(x, ...) {
}

#' @export
backend_packages.opal <- function(x, ...) {
opalr::dsadmin.package_descriptions(x, ...)
backend_packages.opal <- function(x, ..., profile = "default") {
opalr::dsadmin.package_descriptions(x, profile = profile, ...)
}

#' @export
Expand Down
30 changes: 8 additions & 22 deletions R/safe_output.R
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ safe_output.opal <- function(
`@timestamp` <- backend <- logger_name <- safe_people_id <- username <- NULL
ds_action <- ds_eval <- ds_id <- ds_function <- ds_symbol <- ds_table <- NULL
asset <- action <- is_placeholder <- kind <- symbol_id <- timestamp <- NULL
expr <- fx <- log_id <- r_cmd <- session <- symbol <- NULL
ds_profile <- expr <- fx <- log_id <- r_cmd <- session <- symbol <- NULL

if (is.null(profile)) {
profile <- "default"
}

# create formatted versions of input dates
logs_from_is_valid <- FALSE
Expand Down Expand Up @@ -217,12 +221,14 @@ safe_output.opal <- function(
# parse logs
userlogs_tbl <- backend_logs(x) |>
tibble::as_tibble() |>
dplyr::bind_rows(tibble::tibble(ds_profile = "-999999")) |>
dplyr::mutate(
`@timestamp` = as.POSIXct(`@timestamp`, format = "%Y-%m-%dT%H:%M:%S")
) |>
# filter logs
dplyr::filter(`@timestamp` >= logs_from, `@timestamp` <= logs_to) |>
dplyr::filter(logger_name == "datashield.user") |>
dplyr::filter(ds_profile == profile) |>
dplyr::filter(username %in% user)

userlogs <- NULL
Expand Down Expand Up @@ -350,27 +356,7 @@ safe_output.opal <- function(
})

# convert list of calls into tibble
calls_tbl <- purrr::map(calls_lst, \(x) {
tibble::tibble(
timestamp = format(x$created_at, '%Y-%m-%dT%H:%M:%S'),
action = "AGGREGATE",
user = x$user,
r_cmd = x$original,
fx = paste0(x$package, x$namespace, x$fx),
args = list(x$args),
symbol = NA,
table = x$args |>
purrr::map(function(x) {
if (!inherits(x, "safe_reference")) {
return(NA_character_)
}
resolve_symbol_asset(x$symbol_id, registry)
}),
session = x$session,
profile = x$profile
)
}) |>
purrr::list_c()
calls_tbl <- calls_to_tbl(calls_lst, registry)

# combine function calls with symbol's registry
calls_symbols_tbl <- calls_tbl |>
Expand Down
2 changes: 1 addition & 1 deletion R/safe_setting.R
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ safe_setting.opal <- function(

# computational environment ----
# extract information about R packages installed in the environment
pkg_tbl <- backend_packages(x) |>
pkg_tbl <- backend_packages(x, profile = profile) |>
tibble::as_tibble()
pkg_entities <- pkg_tbl |>
purrr::pmap(function(Package, Version, Description, Author, ...) {
Expand Down
50 changes: 50 additions & 0 deletions R/utils-safe_output.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
#' Convert log calls into tibble
#'
#' @param calls List with log calls.
#' @param registry Symbol registry.
#'
#' @returns Tibble with parsed log calls.
#' @keywords internal
#'
#' @noRd
calls_to_tbl <- function(calls, registry) {
if (length(calls) == 0L) {
return(
tibble::tibble(
timestamp = character(),
action = character(),
user = character(),
r_cmd = character(),
fx = character(),
args = list(),
symbol = character(),
table = character(),
session = character(),
profile = character()
)
)
}

purrr::map(calls, \(x) {
tibble::tibble(
timestamp = format(x$created_at, "%Y-%m-%dT%H:%M:%S"),
action = "AGGREGATE",
user = x$user,
r_cmd = x$original,
fx = paste0(x$package, x$namespace, x$fx),
args = list(x$args),
symbol = NA_character_,
table = x$args |>
purrr::map(function(x) {
if (!inherits(x, "safe_reference")) {
return(NA_character_)
}
resolve_symbol_asset(x$symbol_id, registry)
}),
session = x$session,
profile = x$profile
)
}) |>
purrr::list_rbind()
}

#' Extract Safe Output entity(ies)
#'
#' @inheritParams safe_data
Expand Down
15 changes: 14 additions & 1 deletion man/audit.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading