The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Tutorial

library(rAccess)

rAccess terminology

1. Access Unit

The smallest unit that you want to control access to. It could be a specific module in the app, a single UI element, a collection of UI elements, etc.

2. Access Panel

A container for a collection of Access Units. It can be used to implement deep layers of access control.

Roles and responibilities

App Developer is responsible to find an App Admin to provide Access units and panels.
App Developer will configure rAccess accordingly and implement the controlling logic in the app.
App Publisher will work with App Developer to deploy the app with correct configuration and add the identified App Admin through the rAccess interface.
App Admin is responsible to manage users assignments in rAccess interface.

Workflow

To use a rAccess security model, there are three distinct sets of actions that will be carried out by different roles.

Design phase :

Development phase :

Publishing phase :

Post-deployment :

Implementation

The rAccess package includes the ui and server module for the access control tab:

and these could be plugged into the ui and server of the main application.

module_iam_server() takes the arguments:

Once the access panels and access units are identified, the developer can create a new instance of the R6 object rAccess.

The parameters to the rAccess object can either be passed directly as arguments to the new instance of rAccess or could be defined within a configuration yaml file.

Adding rAccess config file

To add a config file template to your project directory use:

rAccess::use_config(file_name = "rAccess.yml")

Config file structure

module: rAccess
parameters:
  app_name:                 # application name *
  board_type:               # local/rconnect/s3 *   
  access_mode:              # default/single unit
  unit_display:             # dropdown/switch
  switch_size:              # default/mini/small/normal/large
  user_df: !expr tibble::tribble(          
    ~userid, ~username,
    "UserID", "User Name")        # Sample user_df
  use_rconnect_users: TRUE        # TRUE - combine rconnect userlist with user_df
  secure_mode: FALSE              # TRUE - Access will be denied to users with no access to atleast one panel/unit
  local_board_path:               # Path to save local pin_board when board_type = "local"    
  s3_bucket:                      # s3 bucket name when board_type = "s3"      
  s3_access_key:                  # s3 access key when board_type = "s3" 
  s3_secret_key:                  # s3 secret key when board_type = "s3" 
  verbose: FALSE                  # If TRUE Prints all changes in the log

data:                             # Study data path if available  
  datapath1:     
  datapath2: 

panel_str:                        # Panel structure to be defined by the developer
  - access_panel: ADMIN           # Every App should have an ADMIN panel.(mandatory)

  - access_panel:                 # Access Panel name *
    access_units:
      - unit:                     # Access unit name  *
      - unit:                     # Access unit name

  - access_panel:                 # Access Panel name *
    access_units:
      - unit:
        data:                     # datapath associated with access unit  
      - unit:                     
        data:                     # datapath associated with access unit

Parameters

Pin_board is created using the pins package to store the access data. It could be a local folder when running the app interactively. When deployed to connect servers, the S3 bucket or rsconnect pin_board must be used.

user_df <- tibble::tribble(
  ~userid, username,
  "UserId1", "User Name 1",
  "UserId2", "User Name 2",
  "UserId3", "User Name 3",
  "UserId4", "User Name 4",
  "UserId5", "User Name 5"
)

data

The study data path associated to the access units (if any) should be added in this section as dathapath1, datapath2, …

data: 
  datapath1: "testpath/data/path1"
  datapath2: "testpath/data/path2"

The datapaths will not be displayed anywhere in the access control UI but will be saved as part of the panel structure and developers can access them using panel_config. e.g. newIAM$panel_config

Panel Structure

The panel structure for each access panel is to maintained as given in the sample config file. Every access control module must have an ‘ADMIN’ panel and it has no units. This will be defined under panel_str as:

panel_str:
  - access_panel: ADMIN

All other access panels should follow the structure:

 - access_panel: "Access Panel 1" 
    access_units:
      - unit: "Unit 1"
        data: datapath1
      - unit: "Unit 2"
        data: datapath2

If there are multiple datapaths associated with an access unit then, it could be defined as:

- access_panel: "Access Panel 1" 
    access_units:
      - unit: "Unit 1"
        data: !expr c('datapath1', ''datapath2`)

Creating a new instance of rAccess with Config file

Once the configuration file is ready the user can create a new instance of rAccess as below:

newIAM <- rAccess$new(user = "UserID", config = "rAccess.yml")

When deploying apps, you should always use session$user as the user to ensure the app recognizes who is currently logged in.

Demo

demo_app.R without rAccess

library(shiny)

ui <- function() {
  navbarPage(
    "Demo!",
    tabPanel(
      "Plot",
      sidebarLayout(
        sidebarPanel(
          radioButtons(
            "plotType", "Plot type",
            c("Scatter" = "p", "Line" = "l")
          )
        ),
        mainPanel(
          plotOutput("plot")
        )
      )
    ),
    tabPanel(
      "Summary",
      verbatimTextOutput("summary")
    ),
    navbarMenu(
      "More",
      tabPanel(
        "Table",
        DT::dataTableOutput("table")
      ),
      tabPanel(
        "About",
        fluidRow(
          column(
            6,
            h1("this is a sample app")
          )
        )
      )
    )
  )
}

server <- function(input, output, session) {
  output$plot <- renderPlot({
    plot(cars, type = input$plotType)
  })

  output$summary <- renderPrint({
    summary(cars)
  })

  output$table <- DT::renderDataTable({
    DT::datatable(cars)
  })
}

shinyApp(ui, server)

demo_app.R with rAccess

library(DT)
#> 
#> Attaching package: 'DT'
#> The following objects are masked from 'package:shiny':
#> 
#>     dataTableOutput, renderDataTable
library(pins)
library(shiny)
library(rAccess)

ui <- navbarPage(
  id = "mainpage",
  title = "Demo!",
  tabPanel(
    "Plot",
    sidebarLayout(
      sidebarPanel(
        radioButtons(
          "plotType", "Plot type",
          c("Scatter" = "p", "Line" = "l")
        )
      ),
      mainPanel(
        plotOutput("plot")
      )
    )
  ),
  tabPanel(
    "Summary",
    verbatimTextOutput("summary")
  ),
  navbarMenu(
    "More",
    tabPanel(
      "Table",
      DT::dataTableOutput("table")
    ),
    tabPanel(
      "About",
      fluidRow(
        column(
          6,
          h1("this is a sample app")
        )
      )
    )
  ),
  tabPanel(
    "Access Control",
    rAccess::module_iam_ui("iam")
  )
)

server <- function(input, output, session) {
  # Add sample rAccess config file to the root directory
  if (!file.exists("sample_rAccess.yml")) {
    rAccess::use_config("sample_rAccess.yml")
  }

  # Create new instance of rAccess
  user_id <- ifelse(!exists("session$user"), "UserID", session$user)
  newIAM <- rAccess$new(
    user = user_id,
    config = "sample_rAccess.yml"
  )

  if (newIAM$no_admin() || newIAM$is_admin()) {
    showTab("mainpage", target = "Access Control")
  } else {
    hideTab("mainpage", target = "Access Control")
  }

  rAccess::module_iam_server("iam", newIAM)

  # Get panels with access
  user_access_list <- newIAM$get_user_accesslist()

  # Show/Hide: Access Control Panel
  if (newIAM$no_admin() || newIAM$is_admin()) {
    showTab("mainpage", target = "Access Control")
  } else {
    hideTab("mainpage", target = "Access Control")
    print("YOU DO NOT HAVE ADMIN ACCESS")
  }

  # Show/Hide: plot tab
  if (!"plot" %in% user_access_list[["sum"]]) {
    hideTab("mainpage", target = "Plot")
  } else {
    showTab("mainpage", target = "Plot")
  }

  # Show/hide the Summary Tab
  if (!"summary" %in% user_access_list[["sum"]]) {
    hideTab("mainpage", target = "Summary")
  } else {
    showTab("mainpage", target = "Summary")
  }

  # Show/hide the Table tab
  if (!"view" %in% user_access_list[["data"]]) {
    hideTab("mainpage", target = "Table")
  } else {
    showTab("mainpage", target = "Table")
  }

  # ----------------------------------------------------------------------------

  output$plot <- renderPlot({
    plot(cars, type = input$plotType)
  })

  output$summary <- renderPrint({
    summary(cars)
  })

  output$table <- DT::renderDataTable({
    DT::datatable(cars)
  })
}

shinyApp(ui, server)

Sample config file

module: rAccess
parameters:
  app_name: demoApp                
  board_type: local                  
  access_mode: default             
  unit_display: 'dropdown'      
  switch_size: 
  user_df: !expr tibble::tribble(                   
    ~userid, ~username,
    "UserID", "User Name 1",
    "UserID2", "User Name 2",
  use_rconnect_users: TRUE        
  local_board_path:                   
  s3_bucket:                      
  s3_access_key:                  
  s3_secret_key:                  
  verbose: FALSE

data:
  datapath1:     
  datapath2: 

panel_str:              
  - access_panel: ADMIN 

  - access_panel: sum
    access_units:
      - unit: plot 
      - unit: summary

  - access_panel: data
    access_units:
      - unit: view    

Audit trail functionalities

rAccess package also offers additional functions for performing audit trail activities.

pin_board <- pins::board_folder(path = "./data/")
get_accesshistory(pin_board, "demo")
#> $`2023-06-29 08:44:20 Edited by NHarida1`
#>   AccessPanel              UserName   UserID  plot summary  view ADMIN   sum
#> 1       ADMIN Nandukrishnan Haridas NHarida1 FALSE   FALSE FALSE  TRUE  TRUE
#> 2       ADMIN         Peyman Eshghi  PEshghi FALSE   FALSE FALSE  TRUE  TRUE
#> 3        data Nandukrishnan Haridas NHarida1  TRUE   FALSE FALSE FALSE FALSE
#> 4        data         Peyman Eshghi  PEshghi  TRUE   FALSE FALSE FALSE FALSE
#> 5         sum         Peyman Eshghi  PEshghi  TRUE    TRUE FALSE FALSE FALSE
#> 6         sum Nandukrishnan Haridas NHarida1  TRUE    TRUE FALSE FALSE FALSE
#>    data
#> 1  TRUE
#> 2  TRUE
#> 3 FALSE
#> 4 FALSE
#> 5 FALSE
#> 6 FALSE
get_accesslist(pin_board, "demo", datemin = "2023-06-29")
#> $`2023-06-29 08:44:20`
#>   AccessPanel              UserName   UserID  plot summary  view ADMIN   sum
#> 1       ADMIN Nandukrishnan Haridas NHarida1 FALSE   FALSE FALSE  TRUE  TRUE
#> 2       ADMIN         Peyman Eshghi  PEshghi FALSE   FALSE FALSE  TRUE  TRUE
#> 3        data Nandukrishnan Haridas NHarida1  TRUE   FALSE FALSE FALSE FALSE
#> 4        data         Peyman Eshghi  PEshghi  TRUE   FALSE FALSE FALSE FALSE
#> 5         sum         Peyman Eshghi  PEshghi  TRUE    TRUE FALSE FALSE FALSE
#> 6         sum Nandukrishnan Haridas NHarida1  TRUE    TRUE FALSE FALSE FALSE
#>    data
#> 1  TRUE
#> 2  TRUE
#> 3 FALSE
#> 4 FALSE
#> 5 FALSE
#> 6 FALSE
get_admins(pin_board, "demo", "ADMIN")
#> NHarida1 - Nandukrishnan Haridas          PEshghi - Peyman Eshghi 
#>                       "NHarida1"                        "PEshghi"
get_board(pin_board, "demo")
#> $access_panels
#> [1] "ADMIN" "data"  "sum"  
#> 
#> $access_units
#> [1] "plot"    "summary" "view"   
#> 
#> $access_df
#>   AccessPanel              UserName   UserID  plot summary  view ADMIN   sum
#> 1       ADMIN Nandukrishnan Haridas NHarida1 FALSE   FALSE FALSE  TRUE  TRUE
#> 2       ADMIN         Peyman Eshghi  PEshghi FALSE   FALSE FALSE  TRUE  TRUE
#> 3        data Nandukrishnan Haridas NHarida1  TRUE   FALSE FALSE FALSE FALSE
#> 4        data         Peyman Eshghi  PEshghi  TRUE   FALSE FALSE FALSE FALSE
#> 5         sum         Peyman Eshghi  PEshghi  TRUE    TRUE FALSE FALSE FALSE
#> 6         sum Nandukrishnan Haridas NHarida1  TRUE    TRUE FALSE FALSE FALSE
#>    data
#> 1  TRUE
#> 2  TRUE
#> 3 FALSE
#> 4 FALSE
#> 5 FALSE
#> 6 FALSE
#> 
#> $access_list
#> list()

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.