Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add credential management #43

Merged
merged 7 commits into from
May 26, 2022
Merged

Add credential management #43

merged 7 commits into from
May 26, 2022

Conversation

ChemiKyle
Copy link
Contributor

@ChemiKyle ChemiKyle commented Apr 12, 2022

Closes #41

Still needs work to make it feel less hacky.


Examples

These might belong in prebuilt ETLs, but I'm putting them here for now as I'm using blue team as guinea pigs for initial testing.

API Token Scraping and Fabricating

The following two snippets require .env files populated with credentials for a production instance prod.env and one for a local instance local_1134.env. Here is my local_1134.env file:

INSTANCE=local_1134
TIME_ZONE=America/New_York

# REDCap API endpoint, REDCap webroot + "/api/"
URI=https://localhost:11134/api/

# REDCap DB Credentials
REDCAP_DB_NAME=redcap
REDCAP_DB_HOST=127.0.0.1
REDCAP_DB_USER=redcap
REDCAP_DB_PASSWORD=redcap123
REDCAP_DB_PORT=3306

Scraping a server's API tokens and putting them in a local sqlite DB for use

library(redcapcustodian)
library(DBI)
library(tidyverse)
library(dotenv)

# fetching all extant API tokens and adding them to storage #################

dir.create("credentials")
# creates file if one does not exist
file_conn <- DBI::dbConnect(RSQLite::SQLite(), "credentials/credentials.db")

credentials_sql <- "CREATE TABLE IF NOT EXISTS `credentials` (
  `redcap_uri` TEXT NOT NULL,
  `server_short_name` varchar(128) NOT NULL,
  `username` varchar(191) NOT NULL,
  `project_id` int(10) NOT NULL,
  `project_display_name` TEXT NOT NULL,
  `project_short_name` varchar(128) DEFAULT NULL,
  `token` varchar(64) NOT NULL,
  `comment` varchar(256) DEFAULT NULL
);
"

dbExecute(file_conn, credentials_sql)

load_dot_env("prod.env")

username <- "your_redcap_username"
source_conn <- connect_to_redcap_db()
source_credentials <- scrape_user_api_tokens(source_conn, username)

# alter credentials to match local schema
source_credentials_upload <- source_credentials %>%
  mutate(
    redcap_uri = Sys.getenv("URI"),
    server_short_name = tolower(Sys.getenv("INSTANCE"))
  ) %>%
  # remove duplicates
  anti_join(
    tbl(file_conn, "credentials") %>%
      collect()
  )

dbAppendTable(file_conn, "credentials", source_credentials_upload)

Creating API tokens for all local projects

library(redcapcustodian)
library(DBI)
library(tidyverse)
library(dotenv)

file_conn <- DBI::dbConnect(RSQLite::SQLite(), "credentials/credentials.db")

load_dot_env("local_1134.env")

# note, this will close any other connections
target_conn <- connect_to_redcap_db()

local_credentials <- scrape_user_api_tokens(target_conn, "admin")

# specify a subset of project_ids rather than making tokens for all
target_pids <- tbl(target_conn, "redcap_projects") %>%
  select(project_id) %>%
  filter(project_id > 15) %>%
  filter(!project_id %in% local(local_credentials$project_id)) %>%
  collect() %>%
  pull(project_id)

# create tokens
for(pid in target_pids) {
  set_project_api_token(target_conn, "admin", pid)
}

# gather newly created tokens and insert them into local storage
local_credentials <- scrape_user_api_tokens(target_conn, "admin")

local_credentials_upload <- local_credentials %>%
  mutate(
    redcap_uri = Sys.getenv("URI"),
    server_short_name = tolower(Sys.getenv("INSTANCE"))
  ) %>%
  # remove duplicates
  anti_join(
    tbl(file_conn, "credentials") %>%
      collect()
  )

dbAppendTable(file_conn, "credentials", local_credentials_upload)

Syncing a production project to your local instance

library(redcapcustodian)
library(tidyverse)

file_conn <- DBI::dbConnect(RSQLite::SQLite(), "credentials/credentials.db")

source_credentials <- tbl(file_conn, "credentials") %>%
  filter(username == "your_production_username") %>%
  filter(server_short_name == "prod") %>%
  collect() %>%
  filter(
    str_detect(project_display_name, "The big important project")
  ) %>%
  unnest()

local_credentials <- tbl(file_conn, "credentials") %>%
  filter(username == "admin") %>%
  filter(server_short_name == "local_1134") %>%
  collect() %>%
  # adjust url to make REDCapR's validation processes happy
  mutate(redcap_uri = str_replace(redcap_uri, "https", "http")) %>%
  mutate(redcap_uri = str_replace(redcap_uri, "localhost", "127.0.0.1")) %>%
  filter(
    str_detect(project_display_name, "The big important project")
  ) %>%
  unnest()

sync_metadata(source_credentials, local_credentials, strip_action_tags = TRUE)

# metadata may be too large, in which case you'll see an error message like this:
# The REDCapR write/import metadata operation was not successful.  The error message was:\n\t\t\t\t<div class=\"red\" style=\"margin:20px 0px;max-width:700px;\">\n\t\t\t\t\t<b>REDCap crashed due to an unexpected PHP fatal error!</b><br><br>\n\t\t\t\t\t<b>Error message:</b> Uncaught Error: Xdebug has detected a possible infinite loop, and aborted your script with a stack depth of '256' frames in /var/www/html/redcap_v11.3.4/Classes/LogicParser.php:280\
# solution: https://stackoverflow.com/a/65997576/7418735
# alter the following file and set an arbitrarily large stack frame limit, at least equal to the number of fields in your data dictionary
# <your-rdc-instance>/redcap-overrides/web/php/80-xdebug.ini
# xdebug.max_nesting_level=<high-value>

@ChemiKyle ChemiKyle linked an issue Apr 12, 2022 that may be closed by this pull request
Comment on lines +15 to +23
#' source_credentials <- REDCapR::retrieve_credential_local(
#' path_credential = "source_credentials.csv",
#' project_id = 31
#' )
#'
#' target_credentials <- REDCapR::retrieve_credential_local(
#' path_credential = "target_credentials.csv",
#' project_id = 25
#' )
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Need to find a better way to let users know there is more than one way to do this

@ChemiKyle
Copy link
Contributor Author

Functions that use credentials objects should error out if they contain more than 1 row.

@pbchase pbchase self-requested a review May 25, 2022 20:25
@pbchase pbchase merged commit b53c94a into ctsit:develop May 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support Credential Management to Enable Multi-instance Operations
2 participants