Skip to content

Commit

Permalink
change aws_role, aws_group, aws_user to return similarly formatted re…
Browse files Browse the repository at this point in the history
…sults

- add create and delete methods for role
- add create and delete methods for group
- policies: add fxn as_policy_arn to coerce/check policy names/arns before using them
- policies: use the as_policy_arn fxn throughout policy fxns where applicable
- import two new pkgs: memoise, withr
  • Loading branch information
sckott committed Dec 7, 2023
1 parent 6c8bf39 commit d91e2bd
Show file tree
Hide file tree
Showing 23 changed files with 682 additions and 95 deletions.
1 change: 1 addition & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
^pkgdown$
^.lintr$
^README\.Rmd$
^data-raw$
8 changes: 6 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ Imports:
fs,
s3fs (>= 0.1.3),
cli,
glue
glue,
memoise
Suggests:
roxyglobals,
testthat (>= 3.0.0),
vcr (>= 0.6.0)
vcr (>= 0.6.0),
withr
Config/roxyglobals/filename: globals.R
Config/roxyglobals/unique: FALSE
Config/testthat/edition: 3
Remotes:
sckott/s3fs@sckott/file_download_vec
Depends:
R (>= 2.10)
8 changes: 8 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

export(as_policy_arn)
export(aws_bucket_acl_get)
export(aws_bucket_acl_modify)
export(aws_bucket_create)
Expand All @@ -16,6 +17,8 @@ export(aws_file_download)
export(aws_file_exists)
export(aws_file_upload)
export(aws_group)
export(aws_group_create)
export(aws_group_delete)
export(aws_groups)
export(aws_iam_client)
export(aws_policies)
Expand All @@ -24,6 +27,8 @@ export(aws_policy_attach)
export(aws_policy_detach)
export(aws_policy_exists)
export(aws_role)
export(aws_role_create)
export(aws_role_delete)
export(aws_roles)
export(aws_user)
export(aws_user_access_key)
Expand All @@ -35,7 +40,10 @@ export(billing)
export(s3_path)
export(set_s3_interface)
importFrom(cli,cli_inform)
importFrom(dplyr,bind_rows)
importFrom(dplyr,filter)
importFrom(dplyr,mutate)
importFrom(dplyr,pull)
importFrom(fs,file_exists)
importFrom(fs,fs_bytes)
importFrom(glue,glue)
Expand Down
2 changes: 2 additions & 0 deletions R/globals.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
utils::globalVariables(c(
".", # <aws_group>
".", # <aws_policy>
"PolicyName", # <as_policy_arn>
"Arn", # <as_policy_arn>
".", # <aws_role>
"PasswordLastUsed", # <user_list_tidy>
"CreateDate", # <tidy_generator>
Expand Down
43 changes: 39 additions & 4 deletions R/groups.R
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ aws_groups <- function(username = NULL, ...) {
#'
#' @export
#' @param name (character) the group name
#' @return A list with two elements:
#' - group: tibble with information about the group
#' - users: tibble with users in the group
#' @return a named list with slots for:
#' - group: information about the group (tibble)
#' - users: users in the group (tibble)
#' - policies (character)
#' - attached_policies (tibble)
#' @details see docs <https://www.paws-r-sdk.com/docs/iam_get_group/>
#' @autoglobal
#' @examples \dontrun{
Expand All @@ -46,6 +48,39 @@ aws_group <- function(name) {
x <- env64$iam$get_group(name)
list(
group = x$Group %>% list(.) %>% group_list_tidy(),
users = x$Users %>% user_list_tidy()
users = x$Users %>% user_list_tidy(),
policies = policies("group", name),
attached_policies = policies_attached("group", name)
)
}

#' Create a group
#'
#' @export
#' @param name (character) A group name. required
#' @param path (character) The path for the group name. optional.
#' If it is not included, it defaults to a slash (/).
#' @return A tibble with information about the group created
#' @details See <https://www.paws-r-sdk.com/docs/iam_create_group/>
#' docs for details on the parameters
#' @examples \dontrun{
#' aws_group_create("testgroup")
#' }
aws_group_create <- function(name, path = NULL) {
env64$iam$create_group(Path = path, GroupName = name) %>%
group_list_tidy()
}

#' Delete a group
#'
#' @export
#' @inheritParams aws_group_create
#' @return an empty list
#' @details See <https://www.paws-r-sdk.com/docs/iam_delete_group/>
#' docs for more details
#' @examples \dontrun{
#' aws_group_delete(name = "testgroup")
#' }
aws_group_delete <- function(name) {
env64$iam$delete_group(name)
}
106 changes: 92 additions & 14 deletions R/policies.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,43 @@ policy_list_tidy <- function(x) {
tidy_generator(vars)(x)
}

all_policies <- memoise::memoise(function(...) {
if (Sys.getenv("TESTING64", FALSE)) {
return(policies_sample)
}
paginate_aws(env64$iam$list_policies, "Policies", ...) %>% policy_list_tidy()
})

#' List policies
#'
#' @export
#' @param ... parameters passed on to the `paws`
#' [list_users](https://www.paws-r-sdk.com/docs/iam_list_policies/) method
#' @param refresh (logical) refresh results? default: `FALSE`. to invalidate
#' cache and refresh policy data, set `refresh=TRUE`
#' @param ... parameters passed on to
#' [list_policies](https://www.paws-r-sdk.com/docs/iam_list_policies/)
#' @details uses `memoise` internally to cache results to speed up all
#' subsequent calls to the function
#' @return A tibble with information about policies
#' @examples \dontrun{
#' aws_policies()
#' aws_policies()
#' aws_policies(refresh = TRUE)
#' }
aws_policies <- function(...) {
paginate_aws(env64$iam$list_policies, "Policies") %>%
policy_list_tidy()
aws_policies <- function(refresh = FALSE, ...) {
if (refresh) memoise::forget(all_policies)
all_policies(...)
}

#' Get a policy
#'
#' @export
#' @param name (character) the policy name
#' @param name (character) a policy name or ARN
#' @return a tibble with policy details
#' @details see docs <https://www.paws-r-sdk.com/docs/iam_get_policy/>
#' @autoglobal
#' @examples \dontrun{
#' aws_policy(name = "ReadOnlyAccess")
#' aws_policy("ReadOnlyAccess")
#' aws_policy("arn:aws:iam::aws:policy/ReadOnlyAccess")
#' }
aws_policy <- function(name) {
env64$iam$get_policy(as_policy_arn(name))$Policy %>%
Expand All @@ -47,30 +61,71 @@ aws_policy <- function(name) {
#' Get a policy
#'
#' @export
#' @param name (character) the policy name
#' @param name (character) a policy name
#' @return a tibble with policy details
#' @details see docs <https://www.paws-r-sdk.com/docs/iam_get_policy/>
#' @examples \dontrun{
#' aws_policy(name = "ReadOnlyAccess")
#' aws_policy_exists("ReadOnlyAccess")
#' }
aws_policy_exists <- function(name) {
!is.null(purrr::safely(aws_policy)(name)$result)
}

#' Convert a policy name to a policy ARN
#'
#' @export
#' @importFrom dplyr filter pull
#' @param name (character) a policy name or arn
#' @note uses exact matching; fails with error if there's no match;
#' beware as there is no validation is done of a user input policy arn
#' @return a policy ARN
#' @autoglobal
#' @examples \dontrun{
#' as_policy_arn("ReadOnlyAccess")
#' as_policy_arn("arn:aws:iam::aws:policy/ReadOnlyAccess")
#' as_policy_arn("AmazonRDSDataFullAccess")
#' # as_policy_arn("Blarp")
#' # as_policy_arn(letters)
#' # as_policy_arn(5)
#' }
as_policy_arn <- function(name) {
glue::glue("arn:aws:iam::aws:policy/{name}")
stopifnot(is.character(name))
stopifnot(length(name) == 1)
if (grepl("^arn:", name)) {
return(name)
}
pols <- aws_policies()
if (!name %in% pols$PolicyName) {
stop(glue::glue(
"'{name}' not associated with a known policy\n",
"this function only checks against the policies listed with the\n",
"https://www.paws-r-sdk.com/docs/iam_list_policies/ method"
))
}
pols %>%
filter(PolicyName == name) %>%
pull(Arn)
}

call_x_method <- function(x) {
fun <- switch(entity_type(x),
user = aws_user,
role = aws_role,
group = aws_group
)
fun(entity_value(x))
}

#' Attach a policy to a user, group, or role
#'
#' @export
#' @param .x result of a call to create or get method for user,
#' group, or role
#' @param policy (character) a policy name
#' @param policy (character) a policy name or ARN
#' @return A tibble with information about policies
#' @examples \dontrun{
#' aws_user() %>%
#' aws_policy_attach("AmazonRDSDataFullAccess")
#' aws_policy("AmazonRDSDataFullAccess")
#' aws_user() %>% aws_policy_attach("AmazonRDSDataFullAccess")
#' aws_user()$attached_policies
#'
#' # aws_role("OrganizationAccountSecurityRole") %>%
Expand All @@ -79,14 +134,15 @@ as_policy_arn <- function(name) {
aws_policy_attach <- function(.x, policy) {
method <- glue::glue("attach_{entity_type(.x)}_policy")
env64$iam[[method]](entity_value(.x), as_policy_arn(policy))
call_x_method(.x)
}

#' Detach a policy from a user, group, or role
#'
#' @export
#' @param .x result of a call to create or get method for user,
#' group, or role
#' @param policy (character) a policy name
#' @param policy (character) a policy name or ARN
#' @return A tibble with information about policies
#' @examples \dontrun{
#' aws_user() %>%
Expand All @@ -99,6 +155,7 @@ aws_policy_attach <- function(.x, policy) {
aws_policy_detach <- function(.x, policy) {
method <- glue::glue("detach_{entity_type(.x)}_policy")
env64$iam[[method]](entity_value(.x), as_policy_arn(policy))
call_x_method(.x)
}

# FIXME: this is probably fragile
Expand Down Expand Up @@ -126,3 +183,24 @@ entity_value <- function(x) {
x[[1]][[names(x[[1]])[1]]]
}
}

#' @param which (character) one of role, user, or group
#' @param name (character) the name of a role, user or group
#' @return a tibble
#' @noRd
#' @keywords internal
policies <- function(which, name) {
method <- glue::glue("list_{which}_policies")
env64$iam[[method]](name)$PolicyNames
}
#' @importFrom dplyr bind_rows
#' @param which (character) one of role, user, or group
#' @param name (character) the name of a role, user or group
#' @return a tibble
#' @noRd
#' @keywords internal
policies_attached <- function(which, name) {
method <- glue::glue("list_attached_{which}_policies")
res <- env64$iam[[method]](name)
res$AttachedPolicies %>% bind_rows()
}
Loading

0 comments on commit d91e2bd

Please sign in to comment.