Skip to content

Commit b290c14

Browse files
committed
retrieve_credential_local() can select user
close #364
1 parent 00aad21 commit b290c14

9 files changed

+97
-22
lines changed

NEWS.md

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Upcoming Versions
66
* `sanitize_token()` now allows lowercase characters --in addition to uppercase characters & digits. (#347, @jmbarbone)
77
* `redcap_metadata_read()` now uses json (instead of csv) to transfer the dictionary between server & client. This accommodates super-wide dictionaries with 35k+ variables. The user shouldn't notice a difference, and still will receive a data.frame. (#335, @januz & @datalorax)
88
* Include a few more `testthat::skip_on_cran()` calls to comply with CRAN's ["fail gracefully"](https://cran.r-project.org/web/packages/policies.html) policy. Similarly, skip remaining examples that depend on external resources. (#352)
9+
* `retrieve_credential_local()` can now user `username` to identify the desired credential row (#364)
910

1011
### Test Suite
1112

R/helpers-testing.R

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
retrieve_credential_testing <- function(project_id = 153L) {
1+
retrieve_credential_testing <- function (project_id = 153L, username = NA_character_) {
22
checkmate::assert_integer(project_id, lower = 1, len = 1, any.missing = FALSE)
33
REDCapR::retrieve_credential_local(
44
path_credential = system.file("misc/example.credentials", package="REDCapR"),
5-
project_id = project_id
5+
project_id = project_id,
6+
username = username
67
)
78
}
89

R/project-dag-write.R

+1-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ populate_project_dag_write <- function(batch = FALSE) {
1212
# nocov end
1313
}
1414

15-
credential <- retrieve_credential_testing(2545L)
16-
# credential$token <- "123CA040BDA500E5CADB144D610FA3D0"
15+
credential <- retrieve_credential_testing(2545L, "admin")
1716

1817
project <- REDCapR::redcap_project$new(
1918
redcap_uri = credential$redcap_uri,

R/retrieve-credential.R

+25-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
#' project_id,
1313
#' check_url = TRUE,
1414
#' check_username = FALSE,
15-
#' check_token_pattern = TRUE
15+
#' check_token_pattern = TRUE,
16+
#' username = NA_character_
1617
#' )
1718
#'
1819
#' retrieve_credential_mssql(
@@ -37,6 +38,7 @@
3738
#' @param check_url A `logical` value indicates if the url in the credential
3839
#' file should be checked to have approximately the correct form. Defaults
3940
#' to TRUE.
41+
#' [REDCapR::retrieve_credential_local()].
4042
#' @param check_username A `logical` value indicates if the username in the
4143
#' credential file should be checked against the username returned by R.
4244
#' Defaults to FALSE.
@@ -46,6 +48,8 @@
4648
#' local machine that points to the desired MSSQL database. Required.
4749
#' @param channel An *optional* connection handle as returned by
4850
#' [DBI::dbConnect()]. See Details below. Optional.
51+
#' @param username A character value used to retrieve a credential.
52+
#' See the Notes below. Optional.
4953
#'
5054
#' @return A list of the following elements are returned from
5155
#' `retrieve_credential_local()` and `retrieve_credential_mssql()`:
@@ -82,13 +86,25 @@
8286
#' 6. Double-check the file is secured and not accessible by other users.
8387
#'
8488
#' @note
89+
#'
90+
#' *Storing credentials on a server is preferred*
91+
#'
8592
#' Although we strongly encourage storing all the tokens on a central server
8693
#' (*e.g.*, see the `retrieve_credential_mssql()` function and the
8794
#' "SecurityDatabase" vignette), there are times when this approach is not
8895
#' feasible and the token must be stored locally. Please contact us
8996
#' if your institution is using something other than SQL Server, and
9097
#' would like help adapting this approach to your infrastructure.
9198
#'
99+
#' *Stored credentials locally*
100+
#'
101+
#' When storing credentials locally, typically the credential file
102+
#' should be dedicated to just one user. Occasionally it makes sense to store
103+
#' tokens for multiple users --usually it's for the purpose of testing.
104+
#'
105+
#' The `username` field is connected only in the local credential file.
106+
#' It does not need to be the same as the official username in REDCap.
107+
#'
92108
#' @author Will Beasley
93109
#'
94110
#' @examples
@@ -113,11 +129,13 @@ retrieve_credential_local <- function(
113129
project_id,
114130
check_url = TRUE,
115131
check_username = FALSE,
116-
check_token_pattern = TRUE
132+
check_token_pattern = TRUE,
133+
username = NA_character_
117134
) {
118135

119136
checkmate::assert_character(path_credential , any.missing=FALSE, len=1, pattern="^.{1,}$")
120137
checkmate::assert_file_exists(path_credential )
138+
checkmate::assert_character(username , any.missing=TRUE, len=1, pattern="^.{1,}$")
121139

122140
col_types <- readr::cols_only(
123141
redcap_uri = readr::col_character(),
@@ -154,6 +172,11 @@ retrieve_credential_local <- function(
154172
# Select only the records with a matching project id.
155173
ds_credential <- ds_credentials[ds_credentials$project_id == project_id, ]
156174

175+
# If specified, select only the records with a matching username.
176+
if (!is.na(username)) {
177+
ds_credential <- ds_credentials[ds_credentials$username == username, ]
178+
}
179+
157180
# Check that one and only one record matches the project id.
158181
if (nrow(ds_credential) == 0L) {
159182
stop(

inst/misc/example.credentials

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ redcap_uri,username,project_id,token,comment
2020
"https://bbmc.ouhsc.edu/redcap/api/","myusername","1400","F187271FC6FD72C3BFCE37990A6BF6A7","Repeating Instruments"
2121
"https://bbmc.ouhsc.edu/redcap/api/","myusername","1425","221E86DABFEEA233067C6889991B7FBB","Potentially problematic dictionary"
2222
"https://bbmc.ouhsc.edu/redcap/api/","myusername","1490","457C24AB91B7FCF5B1A7DA67E70E24C7","simple write metadata"
23-
"https://bbmc.ouhsc.edu/redcap/api/","myusername","2545","0BF11B9CB01F0B8F8EE203B7E07DEFD9","DAG Write"
23+
"https://bbmc.ouhsc.edu/redcap/api/","admin" ,"2545","0BF11B9CB01F0B8F8EE203B7E07DEFD9","DAG Write -admin"
24+
"https://bbmc.ouhsc.edu/redcap/api/","user-dag1" ,"2545","C79DB3836373478986928303B52E74DF","DAG Write -group A"
2425
"https://bbmc.ouhsc.edu/redcap/api/","myusername","2593","1C31398F332FCACA4C0A7B93B18D5CD4","super-wide #2--5,785 columns"
2526
"https://bbmc.ouhsc.edu/redcap/api/","myusername","2597","5C1526186C4D04AE0A0630743E69B53C","super-wide #3--35,000 columns"
2627
"https://bbmc.ouhsc.edu/redcap/api/","myusername","2603","56F43A10D01D6578A46393394D76D88F","Repeating Instruments --Sparse"

man/retrieve_credential.Rd

+18-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-retrieve-credential-local.R

+29
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ path <- system.file("misc/example.credentials", package="REDCapR")
44
pid_read <- 153L # This project is for testing only reading from the server.
55
pid_longitudinal <- 212L # This project is for testing reading longitudinal projects.
66
pid_write <- 213L # This project is for testing reading & writing.
7+
pid_dag <- 2545L #This project is for testing DAGs.
78

89
test_that("Good Credentials", {
910
expected_read_redcap_uri <- "https://bbmc.ouhsc.edu/redcap/api/"
@@ -46,6 +47,34 @@ test_that("Good Credentials", {
4647
expect_equal(credential_write$token , expected_write_token)
4748
expect_equal(credential_write$comment , expected_write_comment)
4849
})
50+
test_that("Multiple users", {
51+
expected_admin_redcap_uri <- "https://bbmc.ouhsc.edu/redcap/api/"
52+
expected_admin_username <- "admin"
53+
expected_admin_project_id <- pid_dag
54+
expected_admin_token <- "0BF11B9CB01F0B8F8EE203B7E07DEFD9"
55+
expected_admin_comment <- "DAG Write -admin"
56+
57+
expected_user_redcap_uri <- "https://bbmc.ouhsc.edu/redcap/api/"
58+
expected_user_username <- "user-dag1"
59+
expected_user_project_id <- pid_dag
60+
expected_user_token <- "C79DB3836373478986928303B52E74DF"
61+
expected_user_comment <- "DAG Write -group A"
62+
63+
credential_admin <- retrieve_credential_testing(pid_read, expected_admin_username)
64+
credential_user <- retrieve_credential_testing(pid_read, expected_user_username)
65+
66+
expect_equal(credential_admin$redcap_uri , expected_admin_redcap_uri)
67+
expect_equal(credential_admin$username , expected_admin_username)
68+
expect_equal(credential_admin$project_id , expected_admin_project_id)
69+
expect_equal(credential_admin$token , expected_admin_token)
70+
expect_equal(credential_admin$comment , expected_admin_comment)
71+
72+
expect_equal(credential_user$redcap_uri , expected_user_redcap_uri)
73+
expect_equal(credential_user$username , expected_user_username)
74+
expect_equal(credential_user$project_id , expected_user_project_id)
75+
expect_equal(credential_user$token , expected_user_token)
76+
expect_equal(credential_user$comment , expected_user_comment)
77+
})
4978

5079
test_that("Missing file", {
5180
expected_message <- "Assertion on 'path_credential' failed: File does not exist: 'misc/missing.credentials'."

tests/testthat/test-write-dag.R

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
library(testthat)
22
update_expectation <- FALSE
3+
credential_admin <- retrieve_credential_testing(2545L, "admin")
4+
credential_user <- retrieve_credential_testing(2545L, "user-dag1")
5+
url <- credential_admin$redcap_uri
6+
7+
testthat::expect_equal(url, credential_user$redcap_uri)
38

49
test_that("Smoke Test", {
510
testthat::skip_on_cran()
@@ -17,11 +22,10 @@ test_that("default", {
1722
path_expected_after <- "test-data/specific-redcapr/write-dag/after.R"
1823
start_clean_result <- REDCapR:::clean_start_dag_write(batch=FALSE)
1924
project <- start_clean_result$redcap_project
20-
token_for_dag_user <- "C79DB3836373478986928303B52E74DF"
2125

2226
expected_outcome_message <- "\\d+ records and \\d+ columns were read from REDCap in \\d+(\\.\\d+\\W|\\W)seconds\\."
2327
expect_message(
24-
returned_object <- redcap_read_oneshot(redcap_uri=project$redcap_uri, token=token_for_dag_user),
28+
returned_object <- redcap_read_oneshot(url, credential_user$token),
2529
regexp = expected_outcome_message
2630
)
2731

@@ -42,8 +46,8 @@ test_that("default", {
4246
# ds_updated$record_id <- sub("^\\d+-(\\d+)$", "\\1", ds_updated$record_id)
4347
# ds_updated$redcap_data_access_group <- NULL
4448

45-
redcap_write_oneshot(ds_updated, project$redcap_uri, token_for_dag_user)
46-
returned_object <- redcap_read_oneshot(redcap_uri=project$redcap_uri, token=project$token)
49+
redcap_write_oneshot(ds_updated, url, credential_user$token)
50+
returned_object <- redcap_read_oneshot(url, credential_admin$token)
4751

4852
if (update_expectation) save_expected(returned_object$data, path_expected_after)
4953
expected_data_frame <- retrieve_expected(path_expected_after)
@@ -63,34 +67,34 @@ test_that("reassign subject to a different dag", {
6367

6468
# Step 1: Initialize the project
6569
start_clean_result <- REDCapR:::clean_start_dag_write(batch=FALSE)
66-
url <- start_clean_result$redcap_project$redcap_uri
67-
token_for_admin <- start_clean_result$redcap_project$token
68-
token_for_dag_user <- "C79DB3836373478986928303B52E74DF"
70+
# url <- start_clean_result$redcap_project$redcap_uri
71+
# token_for_admin <- start_clean_result$redcap_project$token
72+
# token_for_dag_user <- "C79DB3836373478986928303B52E74DF"
6973

7074
# Step 2a: Retrieve the dataset as admin. The 3 subjects' DAGs are 'daga', 'daga', & 'dagb'
71-
ds_admin_1 <- redcap_read_oneshot(url, token_for_admin, export_data_access_groups=T)$data
75+
ds_admin_1 <- redcap_read_oneshot(url, credential_admin$token, export_data_access_groups=T)$data
7276
expect_equal(nrow(ds_admin_1), 3L)
7377
expect_equal(ds_admin_1$record_id , c("331-1", "331-2", "332-3"))
7478
expect_equal(ds_admin_1$redcap_data_access_group, c("daga", "daga", "dagb" ))
7579

7680
# Step 2b: Retrieve the dataset as user. Only the first two subjects are visible to DAG-A users initially.
77-
ds_user_1 <- redcap_read_oneshot(url, token_for_dag_user)$data
81+
ds_user_1 <- redcap_read_oneshot(url, credential_user$token)$data
7882
expect_equal(nrow(ds_user_1), 2L)
7983
expect_equal(ds_user_1$record_id, c("331-1", "331-2"))
8084

8185
#Step 3: Reassign the 2nd subject and upload to server
8286
ds_admin_1$redcap_data_access_group[2] <- "dagb"
83-
redcap_write_oneshot(ds_admin_1, url, token_for_admin)
87+
redcap_write_oneshot(ds_admin_1, url, credential_admin$token)
8488

8589
# Step 4a: Retrieve the dataset as admin. Should the 2nd row automatically change from '331-2' to '332-2'?
86-
ds_admin_2 <- redcap_read_oneshot(url, token_for_admin, export_data_access_groups=T)$data
90+
ds_admin_2 <- redcap_read_oneshot(url, credential_admin$token, export_data_access_groups=T)$data
8791
expect_equal(nrow(ds_admin_2), 3L)
8892
expect_equal(ds_admin_2$record_id , c("331-1", "331-2", "332-3"))
8993
# expect_equal(ds_admin_2$record_id , c("331-1", "332-2", "332-3"))
9094
expect_equal(ds_admin_2$redcap_data_access_group, c("daga", "dagb", "dagb" ))
9195

9296
# Step 4b: Retrieve the dataset as user. Now only one subject is visible to DAG-A users.
93-
ds_user_2 <- redcap_read_oneshot(url, token_for_dag_user)$data
97+
ds_user_2 <- redcap_read_oneshot(url, credential_user$token)$data
9498
expect_equal(nrow(ds_user_2), 1L)
9599
expect_equal(ds_user_2$record_id, c("331-1"))
96100
})

utility/refresh.R

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ pkgdown::build_site()
3232

3333
devtools::run_examples(); #dev.off() #This overwrites the NAMESPACE file too
3434
# devtools::run_examples(, "redcap_read.Rd")
35+
# pkgload::load_all()
3536
test_results_checked <- devtools::test()
3637
test_results_checked <- devtools::test(filter = "column")
37-
test_results_checked <- devtools::test(filter = "validate.*$")
38+
test_results_checked <- devtools::test(filter = "write-dag")
3839
withr::local_envvar(ONLYREADTESTS = "true")
3940
test_results_checked <- devtools::test(filter = "write-batch")
4041

0 commit comments

Comments
 (0)