How To Access Federated Api Using Saml2 And Adfs

##########################################################################
# modified from: https://aws.amazon.com/blogs/security/how-to-implement-federated-api-and-cli-access-using-saml-2-0-and-ad-fs/
##########################################################################

##########################################################################
# Packages required 
##########################################################################
library(httr)
library(xml2)
# getPass is used within the function when password isn't provided
# base64enc is used to decode samlassertion
##########################################################################

# wrapper to access 
adfs_saml2 <- function(adfs_url, region, username = NULL, password = NULL){

  # Get the federated credentials from the user
  if(is.null(username)) username = readline("Username:")
  if(is.null(password)) password = getPass::getPass()

  set_config(config(ssl_verifypeer = 0L))
  # get first page
  formresponse = GET(adfs_url)

  on.exit(handle_reset(adfs_url))

  idpauthformsubmiturl = formresponse$url

  formsoup  = content(formresponse)

  payload = list()
  for (inputtag in xml_find_all(formsoup, ".//INPUT|.//input")){
    name = if(is.na(xml_attr(inputtag, "name"))) "" else xml_attr(inputtag, "name")
    value = if(is.na(xml_attr(inputtag, "value"))) "" else xml_attr(inputtag, "value")
    if(grepl("user", tolower(name)))
      payload[[name]] = username
    else if(grepl("email", tolower(name)))
      payload[[name]] = username
    else if(grepl("pass", tolower(name)))
      payload[[name]] = password
    else
      payload[[name]] = value
  }

  for (inputtag in xml_find_all(formsoup,('.//FORM|.//form'))){
    action = xml_attr(inputtag, 'action')
    loginid = xml_attr(inputtag, 'id')
    if (!is.na(action) && loginid == "loginForm"){
      parsedurl = parse_url(adfs_url)
      idpauthformsubmiturl = paste0(parsedurl$scheme,"://",parsedurl$hostname,action)
    }
  }

  Cookies = unlist(as.list(formresponse$cookies))

  # get samlassertion
  response = POST(idpauthformsubmiturl, body = payload, set_cookies(.cookies = Cookies), encode="form")

  soup = content(response)
  SAMLResponse = NA
  for (inputtag in xml_find_all(soup,('.//input'))){
    name = if(!is.na(xml_attr(inputtag, 'name'))) xml_attr(inputtag, 'name') else ""
    if(name == 'SAMLResponse')
      SAMLResponse = xml_attr(inputtag, 'value')
  }

  if(is.na(SAMLResponse)) stop('Response did not contain a valid SAML assertion')

  # decode saml assertion so that can get roles for user to choose from
  root = readBin(base64enc::base64decode(SAMLResponse), "character") 
  saml2attribute = read_xml(root)

  # get correct level to iterate over
  child3 = xml_children(xml_children(xml_children(saml2attribute)))

  # get aws roles
  awsroles= list()
  for(saml in child3){
    if(isTRUE(xml_attr(saml, "Name") == "https://aws.amazon.com/SAML/Attributes/Role"))
      awsroles = c(awsroles, xml_text(xml_children(saml)))
  }

  # safe way to present roles to user (hides saml roles)
  aws_saml_role = lapply(seq_along(awsroles), function(i){
    aws_role_split = unlist(strsplit(awsroles[[i]], ","))
    saml = grepl("saml-provider", aws_role_split)
    names(aws_role_split) = c("role_arn", "principal_arn")[as.numeric(saml)+1]
    aws_role_split}
  )

  # asks user which role to choose
  writeLines("Please choose the role you would like to assume:")
  for(i in seq_along(aws_saml_role)){
    writeLines(sprintf("[%i]: %s",i , aws_saml_role[[i]]["role_arn"]))
  }
  choosen_role = as.numeric(readline("Selection: "))
  aws_saml_role=aws_saml_role[[choosen_role]]

  list(SamlRole = aws_saml_role, SAMLResponse = SAMLResponse)
}

##########################################################################
# Variables 

# region: The default AWS region that this script will connect 
# to for all API calls 
region = 'us-west-2' 


# idpentryurl: The initial URL that starts the authentication process. 
idpentryurl = 'https://<fqdn>/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices' 

##########################################################################

##########################################################################
# connect to adfs and get saml2 assertion
obj = adfs_saml2(idpentryurl, region)


cred= paws::sts()$assume_role_with_saml(RoleArn = obj$SamlRole["role_arn"], 
                                        PrincipalArn = obj$SamlRole["principal_arn"],
                                        SAMLAssertion = obj$SAMLResponse)

##########################################################################

# Use the AWS STS token to list all of the S3 buckets
config = list(
  credentials = list(
    creds = list(
      access_key_id = cred$Credentials$AccessKeyId,
      secret_access_key = cred$Credentials$SecretAccessKey,
      session_token = cred$Credentials$SessionToken
    )
  )
)

# connect to AWS S3 using temporary SAML credentials
S3= paws::s3(config)

# list all s3 buckets
sapply(S3$list_buckets()$Buckets, function(x) x$Name)