diff --git a/DESCRIPTION b/DESCRIPTION index 832cc87..dd42b96 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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", @@ -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, @@ -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 diff --git a/NAMESPACE b/NAMESPACE index b8a79c3..adeeb38 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -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) @@ -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) @@ -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) diff --git a/NEWS.md b/NEWS.md index bc7280e..a4e189d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 diff --git a/R/audit.R b/R/audit.R index 69ecea9..fc88fd7 100644 --- a/R/audit.R +++ b/R/audit.R @@ -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 diff --git a/R/audit_engine.R b/R/audit_engine.R index f05ab10..f554ffd 100644 --- a/R/audit_engine.R +++ b/R/audit_engine.R @@ -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 @@ -46,7 +46,6 @@ audit_engine.cr8tor <- function(x, ...) { as_rocrate_audit(audit) } -#' @rdname audit_engine #' @export audit_engine.opal <- function( x, @@ -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)) { @@ -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) diff --git a/R/check_permissions.R b/R/check_permissions.R new file mode 100644 index 0000000..a3eb91b --- /dev/null +++ b/R/check_permissions.R @@ -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 + ) +} diff --git a/R/dsROCrate.R b/R/dsROCrate.R index 0fd90c3..c8bcc33 100644 --- a/R/dsROCrate.R +++ b/R/dsROCrate.R @@ -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 diff --git a/R/safe_data.R b/R/safe_data.R index 213cafa..738f02a 100644 --- a/R/safe_data.R +++ b/R/safe_data.R @@ -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) diff --git a/R/safe_output.R b/R/safe_output.R index 7a8a9cc..112f6bf 100644 --- a/R/safe_output.R +++ b/R/safe_output.R @@ -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 @@ -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)) { @@ -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( diff --git a/R/safe_people.R b/R/safe_people.R index 4426eb5..6203557 100644 --- a/R/safe_people.R +++ b/R/safe_people.R @@ -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) diff --git a/R/safe_project.R b/R/safe_project.R index c7cd4f8..760645a 100644 --- a/R/safe_project.R +++ b/R/safe_project.R @@ -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)) { diff --git a/R/safe_setting.R b/R/safe_setting.R index c0d5aca..b729044 100644 --- a/R/safe_setting.R +++ b/R/safe_setting.R @@ -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)) { diff --git a/R/utils-connection.R b/R/utils-connection.R index 3517549..ea4a579 100644 --- a/R/utils-connection.R +++ b/R/utils-connection.R @@ -10,12 +10,13 @@ #' @family Armadillo #' @usage #' \S4method{parse_user_profiles}{armadillo}(x, ..., user) +#' @noRd parse_user_profiles <- function(x, ...) { UseMethod("parse_user_profiles") } # S3 methods ---- -#' @rdname parse_user_profiles +#' @export #' @family Opal parse_user_profiles.opal <- function(x, ..., user) { # local bindings @@ -45,8 +46,6 @@ parse_user_profiles.opal <- function(x, ..., user) { } # S4 methods ---- -#' @method parse_user_profiles ArmadilloCredentials -#' @rdname parse_user_profiles #' @export parse_user_profiles.ArmadilloCredentials <- function(x, ..., user) { message("PLACEHOLDER!") @@ -65,20 +64,13 @@ parse_user_profiles.ArmadilloCredentials <- function(x, ..., user) { #' `project` does not exist in the given server. #' #' @keywords internal -#' @aliases project_exists,armadillo-method -#' @family Armadillo -#' @usage -#' \S4method{project_exists}{armadillo}( -#' x, -#' ..., -#' project -#' ) +#' @noRd project_exists <- function(x, ...) { UseMethod("project_exists") } # S3 methods ---- -#' @rdname project_exists +#' @export #' @family Opal project_exists.opal <- function(x, ..., project) { if (!opalr::opal.project_exists(x, project)) { @@ -93,8 +85,6 @@ project_exists.opal <- function(x, ..., project) { } # S4 methods ---- -#' @method project_exists ArmadilloCredentials -#' @rdname project_exists #' @export #' @family Armadillo project_exists.ArmadilloCredentials <- @@ -113,3 +103,96 @@ project_exists.ArmadilloCredentials <- ) } } + +#' Validate backend +#' +#' Validate backend: including connection status, backend version and check the +#' user permissions. +#' +#' @param x DataSHIELD backend connection object. +#' @param ... Optional params. +#' +#' @returns Nothing, call for its side effect. +#' @keywords internal +#' @noRd +validate_backend <- function(x, ...) { + # validate connection object + validate_con(x, ...) + # validate backend version + validate_backend_version(x, ...) + # validate that the connection user has administrative or audit privileges + check_permissions(x, ...) + + invisible(TRUE) +} + +#' Validate backend version +#' +#' @param x DataSHIELD backend connection object. +#' @param ... Unused. +#' @param minimum String with minimum version. +#' +#' @returns Logical value indicating if backend version is valid. +#' @keywords internal +#' @noRd +validate_backend_version <- function(x, ...) { + UseMethod("validate_backend_version") +} + +#' @export +validate_backend_version.default <- function(x, ...) { + invisible(TRUE) +} + +#' @export +validate_backend_version.opal <- function(x, ..., minimum = "5.7.2") { + if (utils::compareVersion(x$version, minimum) < 0) { + stop( + sprintf( + "Opal >= %s is required, but server version is %s.", + minimum, + x$version + ), + call. = FALSE + ) + } + invisible(TRUE) +} + +#' Validate backend connection +#' +#' @param x DataSHIELD backend connection object. +#' @param ... Unused. +#' +#' @returns Nothing, call for its side effect. +#' @keywords internal +#' @noRd +validate_con <- function(x, ...) { + UseMethod("validate_con") +} + +#' @export +validate_con.default <- function(x, ...) { + stop( + sprintf( + "Unsupported connection type: %s", + paste(class(x), collapse = ", ") + ), + call. = FALSE + ) +} + +#' @export +validate_con.opal <- function(x, ...) { + tryCatch( + { + status <- xptr::is_null_xptr(x$handle$handle) + if (status) { + stop("The given connection is not valid!", call. = FALSE) + } + }, + error = function(e) { + stop("The given connection is not valid!", call. = FALSE) + } + ) +} diff --git a/R/utils-cr8tor.R b/R/utils-cr8tor.R index 9b681e6..028cc6f 100644 --- a/R/utils-cr8tor.R +++ b/R/utils-cr8tor.R @@ -759,6 +759,7 @@ link_people_to_root <- function(rc, usernames) { #' #' @return Object of class `cr8tor`. #' @keywords internal +#' @noRd load_cr8tor_bundle <- function(x, ...) { tmp <- tempfile("cr8tor_") dir.create(tmp, recursive = TRUE, showWarnings = FALSE) diff --git a/R/utils-date.R b/R/utils-date.R index a2b16e4..d9cc0f3 100644 --- a/R/utils-date.R +++ b/R/utils-date.R @@ -6,6 +6,7 @@ #' @returns Boolean value to indicate if the given string is a valid POSIXct #' string. #' @keywords internal +#' @noRd is_valid_posixct <- function(x, tz = "UTC") { tryCatch( { diff --git a/R/utils-opal.R b/R/utils-opal.R index c3149c7..d049085 100644 --- a/R/utils-opal.R +++ b/R/utils-opal.R @@ -18,6 +18,9 @@ add_asset_permissions_to_crate <- function( assets_tbl, id_lookup ) { + # retrieve users (if any), safe_people.* should be executed first + safe_people_tbl <- flatten_safe_people(rocrate) + for (i in seq_len(nrow(assets_tbl))) { asset <- assets_tbl[i, ] @@ -31,6 +34,12 @@ add_asset_permissions_to_crate <- function( next } + # exclude admin and auditors permissions + if (nrow(safe_people_tbl)) { + perms <- perms |> + dplyr::filter(person %in% safe_people_tbl$name) + } + # iterate through person permissions for an asset for (j in seq_len(nrow(perms))) { person <- perms$person[j] @@ -110,11 +119,7 @@ build_asset_entities <- function(assets_tbl, project_id, asset_id_suffix) { url = url, dateCreated = safe_time(created), dateModified = safe_time(updated), - isPartOf = list(`@id` = project_id) #, - # extra = list( - # assetKind = asset_type, - # backend = meta - # ) + isPartOf = list(`@id` = project_id) ) } ) @@ -173,19 +178,11 @@ get_project_assets <- function(x, project, type = c("tables", "resources")) { name = vapply(prj, \(x) x$name %||% "", ""), description = vapply(prj, \(x) x$description %||% "", ""), created = vapply(prj, \(x) safe_time(x$timestamps$created), character(1)), - # prj, - # \(x) as.POSIXct(x$timestamps$created, tz = "UTC") %||% NA_real_, - # character(1) - # ), updated = vapply( prj, \(x) safe_time(x$timestamps$lastUpdate), character(1) ), - # prj, - # \(x) as.POSIXct(x$timestamps$lastUpdate, tz = "UTC") %||% NA_real_, - # character(1) - # ), url = vapply(prj, \(x) x$link %||% NA_character_, ""), meta = vector("list", length(prj)) ) @@ -202,15 +199,7 @@ get_project_assets <- function(x, project, type = c("tables", "resources")) { name = vapply(res, \(x) x$name %||% "", ""), description = vapply(res, \(x) x$description %||% "", ""), created = vapply(res, \(x) safe_time(x$created), character(1)), - # res, - # \(x) as.POSIXct(x$created, tz = "UTC") %||% NA_real_, - # character(1) - # ), updated = vapply(res, \(x) safe_time(x$updated), character(1)), - # res, - # \(x) as.POSIXct(x$updated, tz = "UTC") %||% NA_real_, - # character(1) - # ), url = vapply(res, function(x) x$resource$url %||% NA_character_, ""), meta = parse_resource_params( vapply(res, `[[`, "", "parameters") @@ -219,137 +208,13 @@ get_project_assets <- function(x, project, type = c("tables", "resources")) { } } -#' Get project details (including tables) -#' -#' @inheritParams get_table_permissions -#' -#' @returns Data frame with project and tables associated -#' @keywords internal -#' -#' @family Opal -get_project_details <- function(x, project) { - project |> - lapply(function(p) { - tryCatch( - { - # check if the given project exists - project_exists(x, project = p) - - # retrieve tables for the given project - project_tables <- get_project_tables(x, p) - # check if any tables were found attached to the project - if (length(project_tables) < 1) { - return(tibble::tibble(project = p, table = NA)) - } - tibble::tibble( - project = p, - table = project_tables - ) - }, - error = function(e) { - return(tibble::tibble(project = p, table = NA)) - } - ) - }) |> - dplyr::bind_rows() |> - dplyr::filter(!is.na(table)) -} - -#' Get project tables -#' -#' Wrapper for [opalr::opal.project()]. -#' -#' @inheritParams get_table_permissions -#' -#' @returns List of project tables -#' @keywords internal -#' -#' @family Opal -get_project_tables <- function(x, project) { - # verify if project exists - project_exists(x, project = project) - - # extract table names associated to `project` - project_tables <- opalr::opal.project(x, project) |> - getElement("datasource") |> - getElement("table") |> - unlist() - - # verify if `project_tables` is missing or NULL, if so, print warning message - if (all(is.na(project_tables)) || all(is.null(project_tables))) { - warning( - "The given `project`, does not have any tables associated!", - call. = FALSE - ) - - # return empty list, invisibly - return(invisible(list())) - } - - # return project tables - return(project_tables) -} - -#' Get table permissions -#' -#' Wrapper for the [opalr::opal.table_perm()] function. -#' -#' @inheritParams validate_opal_con -#' @param project String with project name. -#' @param tables String (or vector of strings) with table names for the given -#' project. -#' -#' @returns Data frame with permissions for each table in `tables`. -#' @keywords internal -#' -#' @family Opal -get_table_permissions <- function(x, project, tables) { - seq_along(tables) |> - lapply(function(j) { - tryCatch( - { - tibble::tibble( - project = project, - table = tables[j], - # get permissions for each dataset inside each project - opalr::opal.table_perm(x, project, tables[j]) - ) - }, - error = function(e) { - if (grepl("HTTP 403", e$message)) { - stop( - "The provided connection does not have access to retrieve ", - "table permissions!", - call. = FALSE - ) - } else if (grepl("HTTP 404", e$message)) { - warning( - "Error when retrieving permissions for ", - paste0(project, ".", tables[j], "!"), - call. = FALSE - ) - } else { - warning(e$message, call. = FALSE) - } - - # return empty tibble, only with `project` and `table` details - return(tibble::tibble( - project = project, - table = tables[j] - )) - } - ) - }) |> - # combine results from the permissions for each table - dplyr::bind_rows() -} - #' Flatten user permission entities #' -#' @param x List with entities, generated by [user_perm_entity()]. +#' @param x List with entities, generated by `user_perm_entity()`. #' #' @returns Tibble with properties of the entities. #' @keywords internal +#' @noRd flatten_user_perm_entity <- function(x) { # local bindings agent <- object <- asset_id <- person_id <- NULL @@ -419,24 +284,84 @@ infer_table_resource_lineage <- function(assets_tbl) { #' Verify if connection was created by an administrative user #' -#' @inheritParams validate_opal_con +#' @inheritParams validate_con #' #' @returns Boolean flag to indicate whether the given connection was created #' by an administrative user. #' @keywords internal +#' +#' @noRd is_opal_admin_con <- function(x) { - # validate connection - validate_opal_con(x) + # local binding + aux <- NULL + + # condition 1: admin users have access to `opalr::oadmin.user_exists` + cond1 <- tryCatch( + { + aux <- opalr::oadmin.user_exists(x, x$username) + TRUE + }, + error = function(e) { + FALSE + } + ) + + # condition 2: admin users have access to `opalr::dsadmin.profile_exists` + cond2 <- tryCatch( + { + aux <- opalr::dsadmin.profile_exists(x, "default") + TRUE + }, + error = function(e) { + FALSE + } + ) - # extract the user profile, `uprofile` from the connection object - uprofile <- getElement(x, "uprofile") + # check all the conditions are met + if (all(cond1, cond2)) { + return(TRUE) + } else { + return(FALSE) + } +} + +#' Verify if connection was created by an auditor user +#' +#' @inheritParams validate_con +#' +#' @returns Boolean flag to indicate whether the given connection was created +#' by an administrative user. +#' @keywords internal +#' +#' @noRd +is_opal_audit_con <- function(x) { + # local binding + aux <- NULL - # extract logged in user's groups - groups <- getElement(uprofile, "groups") |> - sapply(unlist) + # condition 1: auditor users don't have access to `opalr::oadmin.user_exists` + cond1 <- tryCatch( + { + aux <- opalr::oadmin.user_exists(x, x$username) + FALSE + }, + error = function(e) { + TRUE + } + ) - # check if the user has admin in their groups - if ("admin" %in% groups) { + # condition 2: auditor users have access to `opalr::dsadmin.profile_exists` + cond2 <- tryCatch( + { + aux <- opalr::dsadmin.profile_exists(x, "default") + TRUE + }, + error = function(e) { + FALSE + } + ) + + # check all the conditions are met + if (all(cond1, cond2)) { return(TRUE) } else { return(FALSE) @@ -488,14 +413,6 @@ map_asset_type <- function(asset_type, meta, url) { "DigitalDocument" } -normalise_permission <- function(p) { - allowed <- c("view", "view-values", "edit", "edit-values", "administrate") - if (p %in% allowed) { - return(p) - } - "view" -} - #' Parse project resources parametres #' #' @param params_json JSON object with resource params. @@ -526,49 +443,6 @@ safe_time <- function(x) { ) } -#' Update datasets linked to a project -#' -#' Update datasets linked to a project (`hasPart`) -#' -#' @inheritParams safe_data -#' @param ds_ids Vector with `@id`s of the datasets to be linked to `project` -#' -#' @returns Update RO-Crate with updated project entity -#' @keywords internal -update_project_datasets <- function(rocrate, project, ds_ids) { - # attempt to retrieve the project entities to link up the IDs to the project - # this only valid if safe_project is called before safe_data - suppressWarnings({ - project_ents <- rocrate |> - .get_entity(type = "Project") |> - # only keep entity for `project` - Filter(f = \(x) getElement(x, "name") == project) - }) - - # if any entity was found, then filter to keep those for which their @id - # starts with `project_id_suffix` as set by `safe_project()`: - if (length(project_ents) == 1 && !is.null(project)) { - # extract the `hasPart` section - has_part <- project_ents |> - sapply("[[", "hasPart") |> - unlist() - - # update the `hasPart` section - rocrate <- rocrate |> - rocrateR::add_entity_value( - id = project_ents[[1]]["@id"], - key = "hasPart", - value = c(has_part, ds_ids) |> - unique() |> - lapply(\(id) list(`@id` = id)), - overwrite = TRUE - ) - } - - # return update RO-Crate - return(rocrate) -} - #' @noRd user_asset_perm_entities <- function( person, @@ -602,6 +476,7 @@ user_asset_perm_entities <- function( #' #' @returns List of [rocrateR::entity] objects #' @keywords internal +#' @noRd user_perm_entity <- function( person, person_id, @@ -668,23 +543,3 @@ user_perm_entity <- function( purrr::pmap(permission_entities_tbl, rocrateR::entity) # rocrateR::entity(as.list(permission_entities_tbl)) } - -#' Validate OBiBa's Opal connection -#' -#' @param x Connection to OBiBa's Opal server (see [opalr::opal.login()]). -#' -#' @returns Nothing, call for its side effect. -#' @keywords internal -validate_opal_con <- function(x) { - tryCatch( - { - status <- xptr::is_null_xptr(x$handle$handle) - if (status) { - stop("The given connection is not valid!", call. = FALSE) - } - }, - error = function(e) { - stop("The given connection is not valid!", call. = FALSE) - } - ) -} diff --git a/R/utils-rocrate.R b/R/utils-rocrate.R index 845aff3..3b2e978 100644 --- a/R/utils-rocrate.R +++ b/R/utils-rocrate.R @@ -5,6 +5,7 @@ #' #' @returns Update `rocrate` object. #' @keywords internal +#' @noRd load_content <- function(rocrate, roc_path) { # get 'File' entities with missing `content` (if any) file_ents <- rocrate |> diff --git a/R/utils-rocrateR.R b/R/utils-rocrateR.R index 4183e15..b1df0dc 100644 --- a/R/utils-rocrateR.R +++ b/R/utils-rocrateR.R @@ -10,6 +10,7 @@ #' #' @returns Updated RO-Crate object. #' @keywords internal +#' @noRd append_entity_ref <- function(rocrate, id, key, ref_id) { entity <- rocrateR::get_entity(rocrate, id = id)[[1]] diff --git a/R/utils-safe_data.R b/R/utils-safe_data.R index 5b3765d..feb3cd1 100644 --- a/R/utils-safe_data.R +++ b/R/utils-safe_data.R @@ -2,20 +2,21 @@ #' #' @inheritParams safe_data #' @param ... Other optional arguments. See the full documentation +#' @param rocrate (Optional) RO-Crate object to update with Safe Data details. +#' @param id (Optional) Vector with `@id` strings for Safe Data entity(ies) +#' to be extracted from the given RO-Crate, `x`. #' #' @returns RO-Crate with Safe Data entity(ies). -#' @rdname extract_safe_data #' @keywords internal +#' @noRd extract_safe_data <- function(x, ...) { UseMethod("extract_safe_data") } -#' @param rocrate (Optional) RO-Crate object to update with Safe Data details. -#' @rdname extract_safe_data #' @export extract_safe_data.opal <- function(x, ..., rocrate = rocrateR::rocrate_5s()) { - # extract all data sources - ds <- opalr::opal.datasources(x) + # extract list with all projects + ds <- opalr::opal.projects(x) # extract project names and ignore NAs projects <- ds$name[!is.na(ds$name) & !is.null(ds$name)] @@ -30,9 +31,6 @@ extract_safe_data.opal <- function(x, ..., rocrate = rocrateR::rocrate_5s()) { ) } -#' @param id (Optional) Vector with `@id` strings for Safe Data entity(ies) -#' to be extracted from the given RO-Crate, `x`. -#' @rdname extract_safe_data #' @export extract_safe_data.rocrate <- function( x, @@ -76,24 +74,22 @@ extract_safe_data.rocrate <- function( #' Flatten object with Safe Data details #' #' @param x Object (e.g., RO-Crate) with Safe Data details. This can be -#' generated with the [extract_safe_data()] function. +#' generated with the `extract_safe_data()` function. #' @param id Vector of strings with the `@id`s for the datasets to be extracted. #' If not provided, extract all entities with `@type = 'Dataset'`. #' #' @returns Data frame with Safe Data details. -#' @rdname flatten_safe_data #' @keywords internal +#' @noRd flatten_safe_data <- function(x, ...) { UseMethod("flatten_safe_data") } -#' @rdname flatten_safe_data #' @export flatten_safe_data.default <- function(x, ...) { return(tibble::tibble()) } -#' @rdname flatten_safe_data #' @export flatten_safe_data.rocrate <- function( x, diff --git a/R/utils-safe_output.R b/R/utils-safe_output.R index 90519cb..e637a9d 100644 --- a/R/utils-safe_output.R +++ b/R/utils-safe_output.R @@ -1,18 +1,19 @@ #' Extract Safe Output entity(ies) #' #' @inheritParams safe_data +#' @inheritParams safe_output #' @param ... Other optional arguments. See the full documentation +#' @param rocrate (Optional) RO-Crate object to update with Safe Output details. +#' @param id (Optional) Vector with `@id` strings for Safe Output entity(ies) +#' to be extracted from the given RO-Crate, `x`. #' #' @returns RO-Crate with Safe Output entity(ies). -#' @rdname extract_safe_output #' @keywords internal +#' @noRd extract_safe_output <- function(x, ...) { UseMethod("extract_safe_output") } -#' @param rocrate (Optional) RO-Crate object to update with Safe Output details. -#' @inheritParams safe_output -#' @rdname extract_safe_output #' @export extract_safe_output.opal <- function( x, @@ -37,10 +38,6 @@ extract_safe_output.opal <- function( return(rocrate) } -#' @param id (Optional) Vector with `@id` strings for Safe Output entity(ies) -#' to be extracted from the given RO-Crate, `x`. -#' @inheritParams safe_output -#' @rdname extract_safe_output #' @export extract_safe_output.rocrate <- function( x, @@ -94,24 +91,22 @@ extract_safe_output.rocrate <- function( #' Flatten object with Safe Output details #' #' @param x Object (e.g., RO-Crate) with Safe Output details. This can be -#' generated with the [extract_safe_output()] function. +#' generated with the `extract_safe_output()` function. #' @param id Vector of strings with the `@id`s for the outputs to be extracted. #' If not provided, extract all entities with `@type = 'File'`. #' #' @returns Data frame with object mappings and functions from safe outputs. -#' @rdname flatten_safe_output #' @keywords internal +#' @noRd flatten_safe_output <- function(x, ...) { UseMethod("flatten_safe_output") } -#' @rdname flatten_safe_output #' @export flatten_safe_output.default <- function(x, ...) { return(tibble::tibble()) } -#' @rdname flatten_safe_output #' @export flatten_safe_output.rocrate <- function(x, ..., id = NULL) { tryCatch( diff --git a/R/utils-safe_people.R b/R/utils-safe_people.R index 827da6f..b03e172 100644 --- a/R/utils-safe_people.R +++ b/R/utils-safe_people.R @@ -2,16 +2,17 @@ #' #' @inheritParams safe_data #' @param ... Other optional arguments. See the full documentation +#' @param rocrate (Optional) RO-Crate object to update with Safe People details. +#' @param id (Optional) Vector with `@id` strings for Safe People entity(ies) +#' to be extracted from the given RO-Crate, `x`. #' #' @returns RO-Crate with Safe People entity(ies). -#' @rdname extract_safe_people #' @keywords internal +#' @noRd extract_safe_people <- function(x, ...) { UseMethod("extract_safe_people") } -#' @param rocrate (Optional) RO-Crate object to update with Safe People details. -#' @rdname extract_safe_people #' @export extract_safe_people.opal <- function(x, ..., rocrate = rocrateR::rocrate_5s()) { # set local binding @@ -39,9 +40,6 @@ extract_safe_people.opal <- function(x, ..., rocrate = rocrateR::rocrate_5s()) { return(rocrate) } -#' @param id (Optional) Vector with `@id` strings for Safe People entity(ies) -#' to be extracted from the given RO-Crate, `x`. -#' @rdname extract_safe_people #' @export extract_safe_people.rocrate <- function( x, @@ -104,25 +102,23 @@ extract_safe_people.rocrate <- function( #' Flatten object with Safe People details #' #' @param x Object (e.g., RO-Crate) with Safe People details. This can be -#' generated with the [extract_safe_data()] function. +#' generated with the `extract_safe_people()` function. #' @param ... Other optional arguments (not in used). #' @param id Vector of strings with the `@id`s for the users to be extracted. #' If not provided, extract all entities with `@type = 'Person'`. #' #' @returns Data frame with safe people details. -#' @rdname flatten_safe_people #' @keywords internal +#' @noRd flatten_safe_people <- function(x, ...) { UseMethod("flatten_safe_people") } -#' @rdname flatten_safe_people #' @export flatten_safe_people.default <- function(x, ...) { return(tibble::tibble()) } -#' @rdname flatten_safe_people #' @export flatten_safe_people.rocrate <- function(x, ..., id = NULL) { # local bindings diff --git a/R/utils-safe_project.R b/R/utils-safe_project.R index 1437d1e..8f58a22 100644 --- a/R/utils-safe_project.R +++ b/R/utils-safe_project.R @@ -2,25 +2,26 @@ #' #' @inheritParams safe_data #' @param ... Other optional arguments. See the full documentation +#' @param rocrate (Optional) RO-Crate object to update with Safe Project +#' details. +#' @param id (Optional) Vector with `@id` strings for Safe Project entity(ies) +#' to be extracted from the given RO-Crate, `x`. #' #' @returns List with Safe Project entity(ies). -#' @rdname extract_safe_project #' @keywords internal +#' @noRd extract_safe_project <- function(x, ...) { UseMethod("extract_safe_project") } -#' @param rocrate (Optional) RO-Crate object to update with Safe Project -#' details. -#' @rdname extract_safe_project #' @export extract_safe_project.opal <- function( x, ..., rocrate = rocrateR::rocrate_5s() ) { - # extract all data sources - ds <- opalr::opal.datasources(x) + # extract list with all projects + ds <- opalr::opal.projects(x) # cycle through the data source (x) and extract project details for (i in seq_len(nrow(ds))) { @@ -37,9 +38,6 @@ extract_safe_project.opal <- function( return(rocrate) } -#' @param id (Optional) Vector with `@id` strings for Safe Project entity(ies) -#' to be extracted from the given RO-Crate, `x`. -#' @rdname extract_safe_project #' @export extract_safe_project.rocrate <- function( x, @@ -102,26 +100,24 @@ extract_safe_project.rocrate <- function( #' Flatten object with Safe Project details #' #' @param x Object (e.g., RO-Crate) with Safe Project details. This -#' can be generated with the [extract_safe_project()] function. +#' can be generated with the `extract_safe_project()` function. #' @param ... Other optional arguments (not in used). #' @param y Object (e.g., RO-Crate) with Safe Data details. This can be -#' generated with the [extract_safe_data()] function. If not provided, it +#' generated with the `extract_safe_data()` function. If not provided, it #' uses the `x` by default. #' #' @returns Data frame with safe project details. #' @keywords internal -#' @rdname flatten_safe_project +#' @noRd flatten_safe_project <- function(x, ...) { UseMethod("flatten_safe_project") } -#' @rdname flatten_safe_project #' @export flatten_safe_project.default <- function(x, ...) { return(tibble::tibble()) } -#' @rdname flatten_safe_project #' @export flatten_safe_project.rocrate <- function(x, ..., y = x) { tryCatch( diff --git a/R/utils-safe_setting.R b/R/utils-safe_setting.R index 19f6ebe..807ea8e 100644 --- a/R/utils-safe_setting.R +++ b/R/utils-safe_setting.R @@ -2,16 +2,16 @@ #' #' @inheritParams safe_data #' @param ... Other optional arguments. See the full documentation +#' @param rocrate (Optional) RO-Crate object to update with Safe Setting details. +#' @param id (Optional) Vector with `@id` strings for Safe Setting entity(ies). #' #' @returns RO-Crate with Safe Setting entity(ies). -#' @rdname extract_safe_setting #' @keywords internal +#' @noRd extract_safe_setting <- function(x, ...) { UseMethod("extract_safe_setting") } -#' @param rocrate (Optional) RO-Crate object to update with Safe Setting details. -#' @rdname extract_safe_setting #' @export extract_safe_setting.opal <- function( x, @@ -25,9 +25,6 @@ extract_safe_setting.opal <- function( return(rocrate) } -#' @param id (Optional) Vector with `@id` strings for Safe Setting entity(ies) -#' to be extracted from the given RO-Crate, `x`. -#' @rdname extract_safe_setting #' @export extract_safe_setting.rocrate <- function( x, @@ -107,25 +104,23 @@ extract_safe_setting.rocrate <- function( #' Flatten object with Safe Setting details #' #' @param x Object (e.g., RO-Crate) with Safe Setting details. This can be -#' generated with the [extract_safe_setting()] function. +#' generated with the `extract_safe_setting()` function. #' @param id Vector of strings with the `@id`s for the settings to be extracted. #' If not provided, extract all entities with `@type = 'PropertyValue'` or #' `@type = 'SoftwareApplication'`. #' #' @returns Data frame with Safe Settings. -#' @rdname flatten_safe_setting #' @keywords internal +#' @noRd flatten_safe_setting <- function(x, ...) { UseMethod("flatten_safe_setting") } -#' @rdname flatten_safe_setting #' @export flatten_safe_setting.default <- function(x, ...) { return(tibble::tibble()) } -#' @rdname flatten_safe_setting #' @export flatten_safe_setting.rocrate <- function(x, ..., id = NULL) { tryCatch( diff --git a/R/utils-tibble.R b/R/utils-tibble.R index e88e425..08c7e6d 100644 --- a/R/utils-tibble.R +++ b/R/utils-tibble.R @@ -5,6 +5,7 @@ #' #' @returns Unfilled vector. #' @keywords internal +#' @noRd #' @source https://github.com/tidyverse/tidyr/issues/250#issuecomment-344984802 unfill_vec <- function(x, val = "") { same <- x == dplyr::lag(x) @@ -18,6 +19,7 @@ unfill_vec <- function(x, val = "") { #' #' @returns Filled vector. #' @keywords internal +#' @noRd refill_vec <- function(x, val = "") { for (i in seq_along(x)) { if (i > 1 && (is.na(x[i]) || x[i] == val)) { diff --git a/inst/CITATION b/inst/CITATION index 8a0c5fc..8c81ab9 100644 --- a/inst/CITATION +++ b/inst/CITATION @@ -24,6 +24,5 @@ bibentry( ), year = "2026", note = "Zenodo report associated with the dsROCrate R package", - doi = "10.5281/zenodo.19366020", - url = "https://zenodo.org/doi/10.5281/zenodo.19366020" + doi = "10.5281/zenodo.19366020" ) diff --git a/man/append_entity_ref.Rd b/man/append_entity_ref.Rd deleted file mode 100644 index 79db726..0000000 --- a/man/append_entity_ref.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-rocrateR.R -\name{append_entity_ref} -\alias{append_entity_ref} -\title{Append entity reference} -\usage{ -append_entity_ref(rocrate, id, key, ref_id) -} -\arguments{ -\item{rocrate}{RO-Crate object.} - -\item{id}{String with \verb{@id} of the entity.} - -\item{key}{String with property/key of the entity.} - -\item{ref_id}{String with reference \verb{@id}.} -} -\value{ -Updated RO-Crate object. -} -\description{ -Adds a reference to an existing property while -preserving existing values and avoiding duplicates. -} -\keyword{internal} diff --git a/man/audit_engine.Rd b/man/audit_engine.Rd deleted file mode 100644 index f566f63..0000000 --- a/man/audit_engine.Rd +++ /dev/null @@ -1,53 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/audit_engine.R -\name{audit_engine} -\alias{audit_engine} -\alias{audit_engine.cr8tor} -\alias{audit_engine.opal} -\title{Audit Engine} -\usage{ -audit_engine(x, ...) - -\method{audit_engine}{cr8tor}(x, ...) - -\method{audit_engine}{opal}( - x, - ..., - project = NULL, - user = NULL, - logs_from = -Inf, - logs_to = Inf, - path = NULL -) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}). Alternatively, a governance -archive file, representing the intent of a project and associated -governance details.} - -\item{...}{Other optional arguments, see full documentation for details.} - -\item{project}{String with project name(s) from which to extra Safe Project -details.} - -\item{user}{String with the user name for which to extract Safe People -details.} - -\item{logs_from}{Lower limit timestamp to filter out the outputs generated -(default: \code{-Inf}, everything up to \code{logs_to})} - -\item{logs_to}{Upper limit timestamp to filter out the outputs generated -(default: \code{Inf}, everything from \code{logs_from} onwards).} - -\item{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.} -} -\value{ -Audit RO-Crate with 5 Safes Components. -} -\description{ -Internal function to create audits for various back-ends. -} -\keyword{internal} diff --git a/man/check_permissions.Rd b/man/check_permissions.Rd new file mode 100644 index 0000000..52076d7 --- /dev/null +++ b/man/check_permissions.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/check_permissions.R +\name{check_permissions} +\alias{check_permissions} +\title{Check backend connection permissions} +\usage{ +check_permissions(x, ...) +} +\arguments{ +\item{x}{A backend connection object.} + +\item{...}{Additional arguments passed to methods.} +} +\value{ +Returns \code{TRUE} invisibly if the connection has sufficient permissions. +} +\description{ +Validates whether a backend connection has sufficient permissions for +\code{{dsROCrate}} audit operations. +} +\details{ +Currently, audit or administrator permissions are required. +} +\seealso{ +\code{vignette("audit-permissions", package = "dsROCrate")} +} diff --git a/man/dsROCrate-package.Rd b/man/dsROCrate-package.Rd index 5e69fe9..a352c93 100644 --- a/man/dsROCrate-package.Rd +++ b/man/dsROCrate-package.Rd @@ -23,6 +23,7 @@ Useful links: Authors: \itemize{ + \item Roberto Villegas-Diaz \email{r.villegas-diaz@outlook.com} (\href{https://orcid.org/0000-0001-5036-8661}{ORCID}) \item Becca Wilson (\href{https://orcid.org/0000-0003-2294-593X}{ORCID}) \item Olly Butters (\href{https://orcid.org/0000-0003-0354-8461}{ORCID}) \item Stuart Wheater (\href{https://orcid.org/0009-0003-2419-1964}{ORCID}) diff --git a/man/extract_safe_data.Rd b/man/extract_safe_data.Rd deleted file mode 100644 index 32853fb..0000000 --- a/man/extract_safe_data.Rd +++ /dev/null @@ -1,43 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_data.R -\name{extract_safe_data} -\alias{extract_safe_data} -\alias{extract_safe_data.opal} -\alias{extract_safe_data.rocrate} -\title{Extract Safe Data entity(ies)} -\usage{ -extract_safe_data(x, ...) - -\method{extract_safe_data}{opal}(x, ..., rocrate = rocrateR::rocrate_5s()) - -\method{extract_safe_data}{rocrate}( - x, - ..., - id = NULL, - asset_id_suffix = "#asset:", - rocrate = rocrateR::rocrate_5s() -) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}), an RO-Crate -(\link[rocrateR:rocrate]{rocrate} class) or a string with the path to an -RO-Crate.} - -\item{...}{Other optional arguments. See the full documentation} - -\item{rocrate}{(Optional) RO-Crate object to update with Safe Data details.} - -\item{id}{(Optional) Vector with \verb{@id} strings for Safe Data entity(ies) -to be extracted from the given RO-Crate, \code{x}.} - -\item{asset_id_suffix}{String with ID suffix for the tables/datasets -entities in the RO-Crate (default: \code{"#asset:"}).} -} -\value{ -RO-Crate with Safe Data entity(ies). -} -\description{ -Extract Safe Data entity(ies) -} -\keyword{internal} diff --git a/man/extract_safe_output.Rd b/man/extract_safe_output.Rd deleted file mode 100644 index 424fb16..0000000 --- a/man/extract_safe_output.Rd +++ /dev/null @@ -1,62 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_output.R -\name{extract_safe_output} -\alias{extract_safe_output} -\alias{extract_safe_output.opal} -\alias{extract_safe_output.rocrate} -\title{Extract Safe Output entity(ies)} -\usage{ -extract_safe_output(x, ...) - -\method{extract_safe_output}{opal}( - x, - ..., - path = NULL, - user = NULL, - logs_to = Sys.time(), - logs_from = logs_to - 24 * 60^2, - rocrate = rocrateR::rocrate_5s() -) - -\method{extract_safe_output}{rocrate}( - x, - ..., - id = NULL, - user = NULL, - rocrate = rocrateR::rocrate_5s() -) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}), an RO-Crate -(\link[rocrateR:rocrate]{rocrate} class) or a string with the path to an -RO-Crate.} - -\item{...}{Other optional arguments. See the full documentation} - -\item{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.} - -\item{user}{List (or \link[rocrateR:entity]{entity} object) with details for -the Safe People, it must include \verb{@id} and \code{name} entries. Alternatively, -this can be a string with the \code{name} of the current user.} - -\item{logs_to}{Upper limit timestamp to filter out the outputs generated -(default: \code{Sys.time()}, current system time).} - -\item{logs_from}{Lower limit timestamp to filter out the outputs generated -(default: \code{Sys.time() - 24 * 60 ^ 2}, last 24 hours).} - -\item{rocrate}{(Optional) RO-Crate object to update with Safe Output details.} - -\item{id}{(Optional) Vector with \verb{@id} strings for Safe Output entity(ies) -to be extracted from the given RO-Crate, \code{x}.} -} -\value{ -RO-Crate with Safe Output entity(ies). -} -\description{ -Extract Safe Output entity(ies) -} -\keyword{internal} diff --git a/man/extract_safe_people.Rd b/man/extract_safe_people.Rd deleted file mode 100644 index 3c47308..0000000 --- a/man/extract_safe_people.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_people.R -\name{extract_safe_people} -\alias{extract_safe_people} -\alias{extract_safe_people.opal} -\alias{extract_safe_people.rocrate} -\title{Extract Safe People entity(ies)} -\usage{ -extract_safe_people(x, ...) - -\method{extract_safe_people}{opal}(x, ..., rocrate = rocrateR::rocrate_5s()) - -\method{extract_safe_people}{rocrate}(x, ..., id = NULL, rocrate = rocrateR::rocrate_5s()) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}), an RO-Crate -(\link[rocrateR:rocrate]{rocrate} class) or a string with the path to an -RO-Crate.} - -\item{...}{Other optional arguments. See the full documentation} - -\item{rocrate}{(Optional) RO-Crate object to update with Safe People details.} - -\item{id}{(Optional) Vector with \verb{@id} strings for Safe People entity(ies) -to be extracted from the given RO-Crate, \code{x}.} -} -\value{ -RO-Crate with Safe People entity(ies). -} -\description{ -Extract Safe People entity(ies) -} -\keyword{internal} diff --git a/man/extract_safe_project.Rd b/man/extract_safe_project.Rd deleted file mode 100644 index 88f55cf..0000000 --- a/man/extract_safe_project.Rd +++ /dev/null @@ -1,35 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_project.R -\name{extract_safe_project} -\alias{extract_safe_project} -\alias{extract_safe_project.opal} -\alias{extract_safe_project.rocrate} -\title{Extract Safe Project entity(ies)} -\usage{ -extract_safe_project(x, ...) - -\method{extract_safe_project}{opal}(x, ..., rocrate = rocrateR::rocrate_5s()) - -\method{extract_safe_project}{rocrate}(x, ..., id = NULL, rocrate = rocrateR::rocrate_5s()) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}), an RO-Crate -(\link[rocrateR:rocrate]{rocrate} class) or a string with the path to an -RO-Crate.} - -\item{...}{Other optional arguments. See the full documentation} - -\item{rocrate}{(Optional) RO-Crate object to update with Safe Project -details.} - -\item{id}{(Optional) Vector with \verb{@id} strings for Safe Project entity(ies) -to be extracted from the given RO-Crate, \code{x}.} -} -\value{ -List with Safe Project entity(ies). -} -\description{ -Extract Safe Project entity(ies) -} -\keyword{internal} diff --git a/man/extract_safe_setting.Rd b/man/extract_safe_setting.Rd deleted file mode 100644 index 54c005e..0000000 --- a/man/extract_safe_setting.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_setting.R -\name{extract_safe_setting} -\alias{extract_safe_setting} -\alias{extract_safe_setting.opal} -\alias{extract_safe_setting.rocrate} -\title{Extract Safe Setting entity(ies)} -\usage{ -extract_safe_setting(x, ...) - -\method{extract_safe_setting}{opal}(x, ..., rocrate = rocrateR::rocrate_5s()) - -\method{extract_safe_setting}{rocrate}(x, ..., id = NULL, rocrate = rocrateR::rocrate_5s()) -} -\arguments{ -\item{x}{This can be a connection to a 'DataSHIELD' server (e.g., object with -the \code{opal} class, see \code{\link[opalr:opal.login]{opalr::opal.login()}}), an RO-Crate -(\link[rocrateR:rocrate]{rocrate} class) or a string with the path to an -RO-Crate.} - -\item{...}{Other optional arguments. See the full documentation} - -\item{rocrate}{(Optional) RO-Crate object to update with Safe Setting details.} - -\item{id}{(Optional) Vector with \verb{@id} strings for Safe Setting entity(ies) -to be extracted from the given RO-Crate, \code{x}.} -} -\value{ -RO-Crate with Safe Setting entity(ies). -} -\description{ -Extract Safe Setting entity(ies) -} -\keyword{internal} diff --git a/man/flatten_safe_data.Rd b/man/flatten_safe_data.Rd deleted file mode 100644 index 1ae4c16..0000000 --- a/man/flatten_safe_data.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_data.R -\name{flatten_safe_data} -\alias{flatten_safe_data} -\alias{flatten_safe_data.default} -\alias{flatten_safe_data.rocrate} -\title{Flatten object with Safe Data details} -\usage{ -flatten_safe_data(x, ...) - -\method{flatten_safe_data}{default}(x, ...) - -\method{flatten_safe_data}{rocrate}(x, ..., id = NULL, asset_id_suffix = "#asset:") -} -\arguments{ -\item{x}{Object (e.g., RO-Crate) with Safe Data details. This can be -generated with the \code{\link[=extract_safe_data]{extract_safe_data()}} function.} - -\item{id}{Vector of strings with the \verb{@id}s for the datasets to be extracted. -If not provided, extract all entities with \verb{@type = 'Dataset'}.} -} -\value{ -Data frame with Safe Data details. -} -\description{ -Flatten object with Safe Data details -} -\keyword{internal} diff --git a/man/flatten_safe_output.Rd b/man/flatten_safe_output.Rd deleted file mode 100644 index 3725f78..0000000 --- a/man/flatten_safe_output.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_output.R -\name{flatten_safe_output} -\alias{flatten_safe_output} -\alias{flatten_safe_output.default} -\alias{flatten_safe_output.rocrate} -\title{Flatten object with Safe Output details} -\usage{ -flatten_safe_output(x, ...) - -\method{flatten_safe_output}{default}(x, ...) - -\method{flatten_safe_output}{rocrate}(x, ..., id = NULL) -} -\arguments{ -\item{x}{Object (e.g., RO-Crate) with Safe Output details. This can be -generated with the \code{\link[=extract_safe_output]{extract_safe_output()}} function.} - -\item{id}{Vector of strings with the \verb{@id}s for the outputs to be extracted. -If not provided, extract all entities with \verb{@type = 'File'}.} -} -\value{ -Data frame with object mappings and functions from safe outputs. -} -\description{ -Flatten object with Safe Output details -} -\keyword{internal} diff --git a/man/flatten_safe_people.Rd b/man/flatten_safe_people.Rd deleted file mode 100644 index e144789..0000000 --- a/man/flatten_safe_people.Rd +++ /dev/null @@ -1,30 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_people.R -\name{flatten_safe_people} -\alias{flatten_safe_people} -\alias{flatten_safe_people.default} -\alias{flatten_safe_people.rocrate} -\title{Flatten object with Safe People details} -\usage{ -flatten_safe_people(x, ...) - -\method{flatten_safe_people}{default}(x, ...) - -\method{flatten_safe_people}{rocrate}(x, ..., id = NULL) -} -\arguments{ -\item{x}{Object (e.g., RO-Crate) with Safe People details. This can be -generated with the \code{\link[=extract_safe_data]{extract_safe_data()}} function.} - -\item{...}{Other optional arguments (not in used).} - -\item{id}{Vector of strings with the \verb{@id}s for the users to be extracted. -If not provided, extract all entities with \verb{@type = 'Person'}.} -} -\value{ -Data frame with safe people details. -} -\description{ -Flatten object with Safe People details -} -\keyword{internal} diff --git a/man/flatten_safe_project.Rd b/man/flatten_safe_project.Rd deleted file mode 100644 index d0b9362..0000000 --- a/man/flatten_safe_project.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_project.R -\name{flatten_safe_project} -\alias{flatten_safe_project} -\alias{flatten_safe_project.default} -\alias{flatten_safe_project.rocrate} -\title{Flatten object with Safe Project details} -\usage{ -flatten_safe_project(x, ...) - -\method{flatten_safe_project}{default}(x, ...) - -\method{flatten_safe_project}{rocrate}(x, ..., y = x) -} -\arguments{ -\item{x}{Object (e.g., RO-Crate) with Safe Project details. This -can be generated with the \code{\link[=extract_safe_project]{extract_safe_project()}} function.} - -\item{...}{Other optional arguments (not in used).} - -\item{y}{Object (e.g., RO-Crate) with Safe Data details. This can be -generated with the \code{\link[=extract_safe_data]{extract_safe_data()}} function. If not provided, it -uses the \code{x} by default.} -} -\value{ -Data frame with safe project details. -} -\description{ -Flatten object with Safe Project details -} -\keyword{internal} diff --git a/man/flatten_safe_setting.Rd b/man/flatten_safe_setting.Rd deleted file mode 100644 index 9a9610d..0000000 --- a/man/flatten_safe_setting.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-safe_setting.R -\name{flatten_safe_setting} -\alias{flatten_safe_setting} -\alias{flatten_safe_setting.default} -\alias{flatten_safe_setting.rocrate} -\title{Flatten object with Safe Setting details} -\usage{ -flatten_safe_setting(x, ...) - -\method{flatten_safe_setting}{default}(x, ...) - -\method{flatten_safe_setting}{rocrate}(x, ..., id = NULL) -} -\arguments{ -\item{x}{Object (e.g., RO-Crate) with Safe Setting details. This can be -generated with the \code{\link[=extract_safe_setting]{extract_safe_setting()}} function.} - -\item{id}{Vector of strings with the \verb{@id}s for the settings to be extracted. -If not provided, extract all entities with \verb{@type = 'PropertyValue'} or -\verb{@type = 'SoftwareApplication'}.} -} -\value{ -Data frame with Safe Settings. -} -\description{ -Flatten object with Safe Setting details -} -\keyword{internal} diff --git a/man/flatten_user_perm_entity.Rd b/man/flatten_user_perm_entity.Rd deleted file mode 100644 index 5b38f71..0000000 --- a/man/flatten_user_perm_entity.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{flatten_user_perm_entity} -\alias{flatten_user_perm_entity} -\title{Flatten user permission entities} -\usage{ -flatten_user_perm_entity(x) -} -\arguments{ -\item{x}{List with entities, generated by \code{\link[=user_perm_entity]{user_perm_entity()}}.} -} -\value{ -Tibble with properties of the entities. -} -\description{ -Flatten user permission entities -} -\keyword{internal} diff --git a/man/get_project_details.Rd b/man/get_project_details.Rd deleted file mode 100644 index 240903d..0000000 --- a/man/get_project_details.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{get_project_details} -\alias{get_project_details} -\title{Get project details (including tables)} -\usage{ -get_project_details(x, project) -} -\arguments{ -\item{x}{Connection to OBiBa's Opal server (see \code{\link[opalr:opal.login]{opalr::opal.login()}}).} - -\item{project}{String with project name.} -} -\value{ -Data frame with project and tables associated -} -\description{ -Get project details (including tables) -} -\seealso{ -Other Opal: -\code{\link{get_project_tables}()}, -\code{\link{get_table_permissions}()}, -\code{\link{parse_user_profiles}()}, -\code{\link{project_exists}()} -} -\concept{Opal} -\keyword{internal} diff --git a/man/get_project_tables.Rd b/man/get_project_tables.Rd deleted file mode 100644 index 26567e7..0000000 --- a/man/get_project_tables.Rd +++ /dev/null @@ -1,28 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{get_project_tables} -\alias{get_project_tables} -\title{Get project tables} -\usage{ -get_project_tables(x, project) -} -\arguments{ -\item{x}{Connection to OBiBa's Opal server (see \code{\link[opalr:opal.login]{opalr::opal.login()}}).} - -\item{project}{String with project name.} -} -\value{ -List of project tables -} -\description{ -Wrapper for \code{\link[opalr:opal.project]{opalr::opal.project()}}. -} -\seealso{ -Other Opal: -\code{\link{get_project_details}()}, -\code{\link{get_table_permissions}()}, -\code{\link{parse_user_profiles}()}, -\code{\link{project_exists}()} -} -\concept{Opal} -\keyword{internal} diff --git a/man/get_table_permissions.Rd b/man/get_table_permissions.Rd deleted file mode 100644 index f1a2516..0000000 --- a/man/get_table_permissions.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{get_table_permissions} -\alias{get_table_permissions} -\title{Get table permissions} -\usage{ -get_table_permissions(x, project, tables) -} -\arguments{ -\item{x}{Connection to OBiBa's Opal server (see \code{\link[opalr:opal.login]{opalr::opal.login()}}).} - -\item{project}{String with project name.} - -\item{tables}{String (or vector of strings) with table names for the given -project.} -} -\value{ -Data frame with permissions for each table in \code{tables}. -} -\description{ -Wrapper for the \code{\link[opalr:opal.table_perm]{opalr::opal.table_perm()}} function. -} -\seealso{ -Other Opal: -\code{\link{get_project_details}()}, -\code{\link{get_project_tables}()}, -\code{\link{parse_user_profiles}()}, -\code{\link{project_exists}()} -} -\concept{Opal} -\keyword{internal} diff --git a/man/is_opal_admin_con.Rd b/man/is_opal_admin_con.Rd deleted file mode 100644 index fabd09d..0000000 --- a/man/is_opal_admin_con.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{is_opal_admin_con} -\alias{is_opal_admin_con} -\title{Verify if connection was created by an administrative user} -\usage{ -is_opal_admin_con(x) -} -\arguments{ -\item{x}{Connection to OBiBa's Opal server (see \code{\link[opalr:opal.login]{opalr::opal.login()}}).} -} -\value{ -Boolean flag to indicate whether the given connection was created -by an administrative user. -} -\description{ -Verify if connection was created by an administrative user -} -\keyword{internal} diff --git a/man/is_valid_posixct.Rd b/man/is_valid_posixct.Rd deleted file mode 100644 index 5b981e6..0000000 --- a/man/is_valid_posixct.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-date.R -\name{is_valid_posixct} -\alias{is_valid_posixct} -\title{Validate POSIXct string} -\usage{ -is_valid_posixct(x, tz = "UTC") -} -\arguments{ -\item{x}{POSIXct string.} - -\item{tz}{String with time zone,} -} -\value{ -Boolean value to indicate if the given string is a valid POSIXct -string. -} -\description{ -Validate POSIXct string -} -\keyword{internal} diff --git a/man/load_content.Rd b/man/load_content.Rd deleted file mode 100644 index 34d4e8c..0000000 --- a/man/load_content.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-rocrate.R -\name{load_content} -\alias{load_content} -\title{Load \code{content} from external files} -\usage{ -load_content(rocrate, roc_path) -} -\arguments{ -\item{rocrate}{Object with the \link[rocrateR:rocrate]{rocrate} class.} - -\item{roc_path}{String with path to the root of the RO-Crate.} -} -\value{ -Update \code{rocrate} object. -} -\description{ -Load \code{content} from external files -} -\keyword{internal} diff --git a/man/load_cr8tor_bundle.Rd b/man/load_cr8tor_bundle.Rd deleted file mode 100644 index 8378d49..0000000 --- a/man/load_cr8tor_bundle.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-cr8tor.R -\name{load_cr8tor_bundle} -\alias{load_cr8tor_bundle} -\title{Load cr8tor governance bundle / project archive} -\usage{ -load_cr8tor_bundle(x, ...) -} -\arguments{ -\item{x}{Path to cr8tor ZIP archive.} - -\item{...}{Unused.} -} -\value{ -Object of class \code{cr8tor}. -} -\description{ -A cr8tor project archive contains: -\itemize{ -\item bagit/ → RO-Crate metadata layer. -\item resources/ → deployment & governance YAML specs. -\item config.toml → platform configuration. -} -} -\keyword{internal} diff --git a/man/parse_user_profiles.Rd b/man/parse_user_profiles.Rd deleted file mode 100644 index 2611227..0000000 --- a/man/parse_user_profiles.Rd +++ /dev/null @@ -1,40 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-connection.R -\name{parse_user_profiles} -\alias{parse_user_profiles} -\alias{parse_user_profiles,armadillo-method} -\alias{parse_user_profiles.opal} -\alias{parse_user_profiles.ArmadilloCredentials} -\title{Parse user profiles} -\usage{ -\S4method{parse_user_profiles}{armadillo}(x, ..., user) - -\method{parse_user_profiles}{opal}(x, ..., user) - -\method{parse_user_profiles}{ArmadilloCredentials}(x, ..., user) -} -\arguments{ -\item{x}{Connection object to backend for DataSHIELD server (e.g., Opal).} - -\item{...}{Optional arguments, unused.} -} -\value{ -Data frame with given \code{user}'s profile details, as captured on the -server pointed by \code{x}. -} -\description{ -Parse user profiles -} -\seealso{ -Other Armadillo: -\code{\link{project_exists}()} - -Other Opal: -\code{\link{get_project_details}()}, -\code{\link{get_project_tables}()}, -\code{\link{get_table_permissions}()}, -\code{\link{project_exists}()} -} -\concept{Armadillo} -\concept{Opal} -\keyword{internal} diff --git a/man/project_exists.Rd b/man/project_exists.Rd deleted file mode 100644 index f3d537f..0000000 --- a/man/project_exists.Rd +++ /dev/null @@ -1,50 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-connection.R -\name{project_exists} -\alias{project_exists} -\alias{project_exists,armadillo-method} -\alias{project_exists.opal} -\alias{project_exists.ArmadilloCredentials} -\title{Verify if project exists} -\usage{ -\S4method{project_exists}{armadillo}( - x, - ..., - project -) - -\method{project_exists}{opal}(x, ..., project) - -\method{project_exists}{ArmadilloCredentials}(x, ..., project) -} -\arguments{ -\item{x}{Connection object to backend for DataSHIELD server (e.g., Opal).} - -\item{...}{Optional arguments, unused.} - -\item{project}{String with project name to be verified.} -} -\value{ -Nothing, call for its side effect. Stop execution of script if -\code{project} does not exist in the given server. -} -\description{ -Wrapper for the \code{\link[opalr:opal.project_exists]{opalr::opal.project_exists()}} and -\code{\link[MolgenisArmadillo:armadillo.list_projects]{MolgenisArmadillo::armadillo.list_projects()}} functions. -} -\seealso{ -Other Armadillo: -\code{\link{parse_user_profiles}()} - -Other Opal: -\code{\link{get_project_details}()}, -\code{\link{get_project_tables}()}, -\code{\link{get_table_permissions}()}, -\code{\link{parse_user_profiles}()} - -Other Armadillo: -\code{\link{parse_user_profiles}()} -} -\concept{Armadillo} -\concept{Opal} -\keyword{internal} diff --git a/man/refill_vec.Rd b/man/refill_vec.Rd deleted file mode 100644 index b8703c0..0000000 --- a/man/refill_vec.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-tibble.R -\name{refill_vec} -\alias{refill_vec} -\title{Fill vector} -\usage{ -refill_vec(x, val = "") -} -\arguments{ -\item{x}{Input vector.} - -\item{val}{Default value.} -} -\value{ -Filled vector. -} -\description{ -Fill vector -} -\keyword{internal} diff --git a/man/unfill_vec.Rd b/man/unfill_vec.Rd deleted file mode 100644 index 553d9e6..0000000 --- a/man/unfill_vec.Rd +++ /dev/null @@ -1,23 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-tibble.R -\name{unfill_vec} -\alias{unfill_vec} -\title{Unfill vector} -\source{ -https://github.com/tidyverse/tidyr/issues/250#issuecomment-344984802 -} -\usage{ -unfill_vec(x, val = "") -} -\arguments{ -\item{x}{Input vector.} - -\item{val}{Default value.} -} -\value{ -Unfilled vector. -} -\description{ -Unfill vector -} -\keyword{internal} diff --git a/man/update_project_datasets.Rd b/man/update_project_datasets.Rd deleted file mode 100644 index e4ea6cc..0000000 --- a/man/update_project_datasets.Rd +++ /dev/null @@ -1,24 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{update_project_datasets} -\alias{update_project_datasets} -\title{Update datasets linked to a project} -\usage{ -update_project_datasets(rocrate, project, ds_ids) -} -\arguments{ -\item{rocrate}{RO-Crate object. Optional, if \code{x} is either an RO-Crate -object or a path to a valid RO-Crate. If so, then \code{connection} is -required (default: \code{rocrateR::rocrate_5s()}).} - -\item{project}{String with the name of the \link[=safe_project]{Safe Project}.} - -\item{ds_ids}{Vector with \verb{@id}s of the datasets to be linked to \code{project}} -} -\value{ -Update RO-Crate with updated project entity -} -\description{ -Update datasets linked to a project (\code{hasPart}) -} -\keyword{internal} diff --git a/man/user_perm_entity.Rd b/man/user_perm_entity.Rd deleted file mode 100644 index fac1c4c..0000000 --- a/man/user_perm_entity.Rd +++ /dev/null @@ -1,29 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{user_perm_entity} -\alias{user_perm_entity} -\title{Create user/person permission entities} -\usage{ -user_perm_entity(person, person_id, asset, asset_id, permission, ...) -} -\arguments{ -\item{person}{String with person name/username.} - -\item{person_id}{String with person \verb{@id}.} - -\item{asset}{String with dataset/table/resource name.} - -\item{asset_id}{String with dataset/table \verb{@id}.} - -\item{permission}{String with permission ('view', 'view-values', 'edit', -'edit-values' OR 'administrate').} - -\item{...}{Other additional values.} -} -\value{ -List of \link[rocrateR:entity]{rocrateR::entity} objects -} -\description{ -Create user/person permission entities -} -\keyword{internal} diff --git a/man/validate_opal_con.Rd b/man/validate_opal_con.Rd deleted file mode 100644 index 45b54e1..0000000 --- a/man/validate_opal_con.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils-opal.R -\name{validate_opal_con} -\alias{validate_opal_con} -\title{Validate OBiBa's Opal connection} -\usage{ -validate_opal_con(x) -} -\arguments{ -\item{x}{Connection to OBiBa's Opal server (see \code{\link[opalr:opal.login]{opalr::opal.login()}}).} -} -\value{ -Nothing, call for its side effect. -} -\description{ -Validate OBiBa's Opal connection -} -\keyword{internal} diff --git a/tests/testthat/test-utils-opal.R b/tests/testthat/test-utils-opal.R index dcc6835..678ad46 100644 --- a/tests/testthat/test-utils-opal.R +++ b/tests/testthat/test-utils-opal.R @@ -1,14 +1,14 @@ -test_that("validate_opal_con works with real Opal connection", { +test_that("validate_con works with real Opal connection", { # open connection to OBiBa's Opal demo server opal_con <- opal_demo_con() - expect_no_error(validate_opal_con(opal_con)) + expect_no_error(validate_con(opal_con)) # close connection to OBiBa's Opal demo server opalr::opal.logout(opal_con) }) -test_that("is_opal_admin_con detects admin group correctly", { +test_that("is_opal_admin_con detects admin connection correctly", { # open connection to OBiBa's Opal demo server opal_con <- opal_demo_con() @@ -21,137 +21,15 @@ test_that("is_opal_admin_con detects admin group correctly", { opalr::opal.logout(opal_con) }) -test_that("get_project_tables retrieves real tables from demo server", { - # open connection to OBiBa's Opal demo server - opal_con <- opal_demo_con() - project <- attr(opal_con, "PROJECT") - - tables <- get_project_tables(opal_con, project) - - expect_type(tables, "character") - expect_true(length(tables) >= 1) - expect_true(all(nzchar(tables))) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("get_project_details works end-to-end with demo Opal project", { - # open connection to OBiBa's Opal demo server - opal_con <- opal_demo_con() - project <- attr(opal_con, "PROJECT") - - res <- get_project_details(opal_con, project) - - expect_s3_class(res, "data.frame") - expect_true("project" %in% names(res)) - expect_true("table" %in% names(res)) - expect_true(nrow(res) >= 1) - expect_true(all(res$project == project)) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("get_table_permissions errors for non-admin connection", { - # open connection to OBiBa's Opal demo server - opal_con <- opal_demo_con(admin = FALSE) - project <- attr(opal_con, "PROJECT") - tables <- attr(opal_con, "TABLES") - - expect_error( - get_table_permissions(opal_con, project, tables), - "The provided connection does not have access to retrieve table permissions!" - ) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("get_table_permissions throws a warning for an invalid project", { - # open connection to OBiBa's Opal demo server - opal_con <- opal_demo_con() - tables <- attr(opal_con, "TABLES") - - expect_warning( - get_table_permissions(opal_con, "INVALID PROJECT", tables), - "404" - ) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("get_table_permissions retrieves real permissions", { - # open connection to OBiBa's Opal demo server - opal_con <- opal_demo_con() - project <- attr(opal_con, "PROJECT") - tables <- attr(opal_con, "TABLES") - - res <- get_table_permissions(opal_con, project, tables) - - expect_s3_class(res, "data.frame") - expect_true(all(c("project", "table") %in% names(res))) - expect_equal(unique(res$project), project) - expect_equal(unique(res$table), tables) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("get_project_details handles non-existent project gracefully", { - opal_con <- opal_demo_con() - - res <- get_project_details(opal_con, "THIS_PROJECT_DOES_NOT_EXIST_123") - - expect_s3_class(res, "data.frame") - expect_equal(nrow(res), 0) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - -test_that("validate_opal_con errors on invalid connection object", { +test_that("validate_con errors on invalid connection object", { bad_con <- list(handle = list(handle = NULL)) expect_error( - validate_opal_con(bad_con), - "connection is not valid" + validate_con(bad_con), + "Unsupported connection type" ) }) -test_that("update_project_datasets updates project entities of an RO-Crate", { - # open connection to OBiBa's Opal demo server - opal_con <- opal_demo_con() - - # create basic RO-Crate with a project - rocrate <- rocrateR::rocrate_5s() |> - dsROCrate::safe_project( - connection = opal_con, - project = attr(opal_con, "PROJECT") - ) - - # update project datasets - expect_no_error( - rocrate <- rocrate |> - update_project_datasets(project = attr(opal_con, "PROJECT"), ds_ids = 1:5) - ) - - # extract `hasPart` for the project entity - expect_no_error( - has_part <- rocrateR::get_entity(rocrate, type = "Project") |> - sapply(getElement, name = "hasPart") |> - sapply(getElement, name = "@id") |> - unlist() - ) - - expect_equal(length(has_part), 5) - expect_equal(has_part, 1:5) - - # close connection to OBiBa's Opal demo server - opalr::opal.logout(opal_con) -}) - test_that("user_perm_entity works for all values of 'permission'", { input_tbl <- tibble::tibble( person = "dsuser", diff --git a/vignettes/audit-permissions.Rmd b/vignettes/audit-permissions.Rmd new file mode 100644 index 0000000..fadbe87 --- /dev/null +++ b/vignettes/audit-permissions.Rmd @@ -0,0 +1,133 @@ +--- +title: "Configuring Audit Permissions" +output: rmarkdown::html_vignette +vignette: > + %\VignetteIndexEntry{Configuring Audit Permissions} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>" +) +``` + +# Overview + +`{dsROCrate}` requires elevated permissions when auditing backend systems. + +Depending on the backend implementation, this may require: + +- administrator privileges, +- audit privileges, +- or backend-specific read access to system metadata. + +Without these permissions, `{dsROCrate}` may be unable to retrieve +the metadata required for audit and RO-Crate generation workflows. + +This vignette describes: + +- why elevated permissions are required, +- how permission validation works, +- and how to configure supported backends. + + +# Why are elevated permissions required? + +`dsROCrate` performs metadata and configuration inspection operations +against supported backend systems. + +These operations may include access to: + +- system configuration metadata, +- authentication configuration, +- datasource metadata, +- project metadata, +- audit-relevant server settings. + +Standard user accounts may not have sufficient privileges for these +operations. + + +# Supported backends + +Currently supported backend permission configurations: + +- Opal + +Future backend-specific instructions may include: + +- Armadillo + + +# Opal + +**NOTE:** Opal 5.7+ is required, as that's when the _audit_ role was introduced. + +## Required permissions + +For Opal backends, the connected user must have at least one of: + +- administrator privileges, or +- the `Audit system` permission. + +## Configuring an Opal audit user + +### Step 1: Log in as an administrator + +Log in to the Opal web interface using an administrator account. + +### Step 2: Create a user (optional) + +If a dedicated audit user does not already exist: + +1. Open the **Administration** tab. +2. Under **Data Access**, select **Users and Groups**. +3. Create a new user account for auditing purposes. + +### Step 3: Grant audit permissions + +1. Return to the **Administration** tab. +2. Under **System**, select **General Settings**. +3. Open the **Permissions** section. +4. Click the **+** button. +5. Select: + - **Add user permission** +6. Enter the username of the audit user. +7. Select: + - **Audit system** +8. Click **Submit**. + + +-------------------------------------------------------------------------------- + +# Example usage + +```{r eval = FALSE} +con <- opalr::opal.login( + username = "audit_user", + password = "secret", + url = "https://opal-demo.obiba.org" +) + +dsROCrate::check_permissions(con) +``` + + +# Troubleshooting + +## Insufficient permissions + +Ensure the connected user has the permissions required for the +target backend. + +See the backend-specific sections in this vignette for details. + +## Testing Opal permissions directly + +```{r eval = FALSE} +dsROCrate::is_opal_admin_con(con) +dsROCrate::is_opal_audit_con(con) +``` diff --git a/vignettes/deploy-local-datashield-server-with-opal.Rmd b/vignettes/deploy-local-datashield-server-with-opal.Rmd index 7326054..b25ecad 100644 --- a/vignettes/deploy-local-datashield-server-with-opal.Rmd +++ b/vignettes/deploy-local-datashield-server-with-opal.Rmd @@ -34,17 +34,17 @@ Here we will spawn a local instance of [DataSHIELD](https://datashield.org) with configured Docker on your computer; however, if that's not the case, visit their [get started with Docker](https://www.docker.com/get-started/) page. -#### Set up +#### Setup The easiest way to deploy DataSHIELD with docker is by cloning the following -repo: [OllyButters/datashield_pcr](https://github.com/OllyButters/datashield_pcr). +repo: [FederatedMethods/datashield_dev_install](https://github.com/FederatedMethods/datashield_dev_install). Here, you will find a step by step guide, including a very useful -[`docker-compose.yml`](https://github.com/OllyButters/datashield_pcr/blob/main/docker/docker-compose.yml) +[`docker-compose.yml`](https://github.com/FederatedMethods/datashield_dev_install/blob/main/docker/docker-compose.yml) file, which you can use out of the box. If you are running Linux or macOS, you can run the following commands: ```sh -git clone https://github.com/OllyButters/datashield_pcr +git clone https://github.com/FederatedMethods/datashield_dev_install cd datashield_pcr/docker docker compose up -d ``` @@ -59,7 +59,7 @@ following: #### Open connection -By default, the [`docker-compose.yml`](https://github.com/OllyButters/datashield_pcr/blob/main/docker/docker-compose.yml) +By default, the [`docker-compose.yml`](https://github.com/FederatedMethods/datashield_dev_install/blob/main/docker/docker-compose.yml) file in the repo above defines a demo user, `demo_user`, with the following password: `Demo_password1!` (edit the variables `OPAL_DEMO_USER_NAME` and `OPAL_DEMO_USER_PASSWORD` accordingly). Here, we will open a connection to our @@ -74,3 +74,53 @@ TABLES <- c("CNSIM1") SERVER <- "https://opal-demo.obiba.org" PROFILE <- "demo" ``` + +-------- + +#### Test deployment + +1. On a web browser, navigate to `http://localhost:8880` +and use the credentials on the `docker-compose.yml` to log in (e.g., `administrator` and `password`). + +2. Inside RStudio (or your preferred IDE), you can test the connection to the local server with the following command (note that you might need to update the `username` and `password`): + + ```r + opalr::opal.login( + username = "administrator", + password = "password", + url = "https://localhost:8843/" + ) + ``` + + You should see something like the following: + ```bash + url: https://localhost:8843 + name: localhost + version: 5.2.0 + username: administrator + ``` + +3. (Optional) Next, you can attempt running some commands with `{dsBaseClient}`: + + ```r + # for setup only + DSOpal::Opal() + # create new login object + builder <- DSI::newDSLoginBuilder() + builder$append(server = "study1", + url = "https://localhost:8843/", + user = "administrator", + password = "password", + driver = "OpalDriver") + logindata <- builder$build() + conns <- DSI::datashield.login(logins = logindata) + + # assign table to symbol, `test` + DSI::datashield.assign.table(conns["study1"], + symbol = "test", + table = "DEMO.CNSIM1", + errors.print = TRUE) + + dsBaseClient::ds.ls(datasources = conns["study1"]) + dsBaseClient::ds.summary("test") + ``` diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd index e680850..219aaf8 100644 --- a/vignettes/getting-started.Rmd +++ b/vignettes/getting-started.Rmd @@ -76,7 +76,6 @@ library(dsROCrate) # show all the lines in the RO-Crate oopt <- options(max_lines = Inf) -on.exit(options(oopt), add = TRUE) ``` This tutorial assumes that you have an internet connection and can access @@ -93,6 +92,15 @@ vignette("deploy-local-datashield-server-with-opal", package = "dsROCrate") ## 1. Creating your first RO-Crate +> **ⓘ** Here we are using an administrator account, as this one is +available by default on the demo server; however, we recommend you create a +dedicated auditing account with the `Audit system` permission level. See the +vignette: +> +> `vignette('audit-permissions', package = 'dsROCrate')` +> +> for further details. + ### 1.1. Connect to an Opal server Here we will use OBiBa's Opal demo server: https://opal-demo.obiba.org/ which @@ -200,9 +208,9 @@ print(basic_rocrate) # note that the output will be truncated #### Safe Setting To add details for Safe Setting, use the function `dsROCrate::safe_setting()`. -**⚠️NOTE:** The `dsROCrate::safe_setting` function requires administrator -privileges, so here, we will have to log in with administrator credentials (if -you used a non-administrator account previously). +**ⓘ️ NOTE:** The `dsROCrate::safe_setting` function requires administrator +or auditor privileges, so here, we will have to log in with administrator +credentials (if you used a non-administrator/auditor account previously). ```{r, eval = FALSE} # close previous connection @@ -230,9 +238,9 @@ Currently, only log files from the operations executed by the user within a specific period. Set the period using `logs_from` and `logs_to`. Additionally, a list of functions executed by the user are extracted in a separate file/entity. -**⚠️NOTE:** Similar to `dsROCrate::safe_setting`, the `dsROCrate::safe_output` -function requires of administrator rights, so here, we will have to log in with -administrator credentials: +**ⓘ NOTE:** Similar to `dsROCrate::safe_setting`, the `dsROCrate::safe_output` +function requires of administrator or auditor privileges, so here, we will have to log in with +administrator/auditor credentials: ```{r, eval = FALSE} # close previous connection @@ -250,8 +258,8 @@ o <- opalr::opal.login( ##### DataSHIELD operations -**⚠️NOTE:** Before extracting logs, ensure there is recent activity on the -server for testing purposes. This can be done using the following commands: +**ⓘ NOTE:** Before extracting logs, ensure there is recent activity on +the server for testing purposes. This can be done using the following commands: ###### Setup You will need the following packages: @@ -279,7 +287,7 @@ conns <- DSI::datashield.login(logins = logindata) ``` ###### Simulate some operations -```{r, eval = TRUE} +```{r, eval = FALSE} ## assign data DSI::datashield.assign.table(conns["study1"], symbol = "dsROCrate_test", @@ -287,11 +295,51 @@ DSI::datashield.assign.table(conns["study1"], errors.print = TRUE) dsBaseClient::ds.ls(datasources = conns["study1"]) +#> $study1 +#> $study1$environment.searched +#> [1] "R_GlobalEnv" +#> +#> $study1$objects.found +#> [1] "dsROCrate_test" dsBaseClient::ds.summary("dsROCrate_test") -``` - -```{r, echo = FALSE, warning = FALSE, message = FALSE} +#> $study1 +#> $study1$class +#> [1] "data.frame" +#> +#> $study1$`number of rows` +#> [1] 2163 +#> +#> $study1$`number of columns` +#> [1] 11 +#> +#> $study1$`variables held` +#> [1] "LAB_TSC" "LAB_TRIG" "LAB_HDL" +#> [4] "LAB_GLUC_ADJUSTED" "PM_BMI_CONTINUOUS" "DIS_CVA" +#> [7] "MEDI_LPD" "DIS_DIAB" "DIS_AMI" +#> [10] "GENDER" "PM_BMI_CATEGORICAL" +``` + +```{r, echo = FALSE, warning = FALSE, message = FALSE, results='hide'} # check if there are any logs available, if not simulate some operations +dsuser_logs_tbl <- opalr::opal.login( + username = "administrator", + password = "password", + url = SERVER +) |> + opalr::dsadmin.log() |> + dplyr::bind_rows(tibble::tibble(username = NA, ds_action = NA)) |> + dplyr::filter(!is.na(username), username == "dsuser") |> + dplyr::filter(ds_action %in% c("ASSIGN", "AGGREGATE", "OPEN")) + +if (nrow(dsuser_logs_tbl) < 1) { + ## assign data + DSI::datashield.assign.table(conns["study1"], + symbol = "dsROCrate_test", + table = paste0(PROJECT, ".", TABLES[1]), + errors.print = TRUE) + dsBaseClient::ds.ls(datasources = conns["study1"]) + dsBaseClient::ds.summary("dsROCrate_test") +} ``` --------- @@ -305,11 +353,11 @@ lines_before_safe_outputs <- rocrate_lines(basic_rocrate) ```{r safe_outputs} basic_rocrate <- o |> dsROCrate::safe_output(rocrate = basic_rocrate, - logs_from = Sys.time() - 60, # capture the last minute + logs_from = Sys.time() - 8.64E4, # capture the last 24 hours logs_to = Sys.time()) ``` -```{r, out.lines=-(lines_before_safe_outputs + 1)} +```{r, out.lines=-(lines_before_safe_outputs + 6)} print(basic_rocrate) # note that the output will be truncated ``` @@ -325,7 +373,7 @@ The resulting RO-Crate can be stored into an RO-Crate bag/archive with the function `rocrateR::bag_rocrate`: ```{r} -# create temp directory +# create temp directory (only for demo purposes) tmp_path_bag <- file.path(tempdir(), "dsROCrate-getting-started") dir.create(tmp_path_bag, showWarnings = FALSE) @@ -351,9 +399,13 @@ path_to_rocrate_bag |> unlink(tmp_path_bag, recursive = TRUE, force = TRUE) ``` -
+```{r, echo = FALSE} +options(oopt) +oopt <- options(max_lines = 40) +``` + ## 2. Auditing RO-Crates and servers ### 2.1. Audit People @@ -457,7 +509,7 @@ study_crate_v1 <- "opal_test" = opalr::opal.login( username = USERNAME, password = USERPASS, - url = "https://opal-test.obiba.org" + url = "https://opal-demo.obiba.org" ), "opal_demo" = opalr::opal.login( username = USERNAME,