diff --git a/NAMESPACE b/NAMESPACE index 020d429..9a8b9aa 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -42,6 +42,7 @@ export(azureGetBlob) export(azureGetTokenClientCredential) export(azureGetTokenDeviceCode) export(azureGetTokenDeviceCodeFetch) +export(azureGetTokenRefreshToken) export(azureHDIConf) export(azureHiveSQL) export(azureHiveStatus) diff --git a/R/AzureAuthenticate.R b/R/AzureAuthenticate.R index a38b96e..91cf0b0 100644 --- a/R/AzureAuthenticate.R +++ b/R/AzureAuthenticate.R @@ -40,8 +40,15 @@ azureAuthenticateOnAuthType <- function(azureActiveContext, authType, resource, if (is.null(resource) || nchar(resource) == 0) stop("Unspecified Resource. Please specify a valid resource.") + # before using the preferred auth type, check if access token can be fetched with a valid refresh token + refreshToken <- azureActiveContext$RefreshToken + if (!is.null(refreshToken) && nchar(refreshToken) > 0) { + authType <- "RefreshToken" + } + result <- switch( authType, + RefreshToken = azureGetTokenRefreshToken(azureActiveContext), ClientCredential = azureGetTokenClientCredential(azureActiveContext, resource = resource, verbose = verbose), DeviceCode = azureGetTokenDeviceCode(azureActiveContext, resource = resource, verbose = verbose), FALSE @@ -55,7 +62,7 @@ azureAuthenticateOnAuthType <- function(azureActiveContext, authType, resource, return(result) } -#' Get Azure token +#' Get Azure token using client credentials #' #' @inheritParams setAzureContext #' @param resource Specify the resource with which the toke is obtained @@ -79,14 +86,16 @@ azureGetTokenClientCredential <- function(azureActiveContext, tenantID, clientID assert_that(is_tenant_id(tenantID)) assert_that(is_client_id(clientID)) assert_that(is_authKey(authKey)) + # TODO: check resource? + verbosity <- set_verbosity(verbose) URLGT <- paste0("https://login.microsoftonline.com/", tenantID, "/oauth2/token?api-version=1.0") authKeyEncoded <- URLencode(authKey, reserved = TRUE) resourceEncoded <- URLencode(resource, reserved = TRUE) - bodyGT <- paste0("grant_type=client_credentials&resource=", resourceEncoded, "&client_id=", - clientID, "&client_secret=", authKeyEncoded) + bodyGT <- paste0("grant_type=client_credentials", "&client_id=", clientID, "&client_secret=", authKeyEncoded, + "&resource=", resourceEncoded) r <- httr::POST(URLGT, add_headers( @@ -109,7 +118,7 @@ azureGetTokenClientCredential <- function(azureActiveContext, tenantID, clientID return(TRUE) } -#' Authenticates against Azure Active directory application using DeviceCode flow. +#' Get Azure token using DeviceCode. #' #' @inheritParams setAzureContext #' @param verbose Print Tracing information (Default False) @@ -152,16 +161,26 @@ azureGetTokenDeviceCode <- function(azureActiveContext, tenantID, clientID, reso stopWithAzureError(r) j1 <- content(r, "parsed", encoding = "UTF-8") - print(class(j1)) # display the message to user so that he can take appropriate action showDeviceCodeMessageToUser(j1) + userCode <- j1$user_code deviceCode <- j1$device_code + verificationURL <- j1$verification_url + messageToUser <- j1$message + expiresIn <- j1$expires_in + pollingInterval <- j1$interval -# wait_for_azure( -# sa_name %in% azureListSA(asc)$storageAccount -# ) + iteration <- 0 + waiting <- TRUE + while (iteration < 50 && waiting) { + Sys.sleep(pollingInterval) + if(azureGetTokenDeviceCodeFetch(azureActiveContext, tenantID, clientID, deviceCode, resource, verbose)) { + waiting <- FALSE + } + iteration <- iteration + 1 + } return(TRUE) } @@ -188,20 +207,20 @@ azureGetTokenDeviceCodeFetch <- function(azureActiveContext, tenantID, clientID, if (missing(tenantID)) tenantID <- azureActiveContext$tenantID if (missing(clientID)) clientID <- azureActiveContext$clientID - if (missing(deviceCode)) deviceCode <- azureActiveContext$authKey + if (missing(deviceCode)) deviceCode <- azureActiveContext$deviceCode if (missing(resource)) resource <- azureActiveContext$resource assert_that(is_tenant_id(tenantID)) assert_that(is_client_id(clientID)) + # TODO: check device code? verbosity <- set_verbosity(verbose) URLGT <- paste0("https://login.microsoftonline.com/", tenantID, "/oauth2/token?api-version=1.0") - - authKeyEncoded <- URLencode(authKey, reserved = TRUE) + deviceCodeEncoded <- URLencode(deviceCode, reserved = TRUE) resourceEncoded <- URLencode(resource, reserved = TRUE) - bodyGT <- paste0("grant_type=client_credentials&resource=", resourceEncoded, "&client_id=", - clientID, "&client_secret=", authKeyEncoded) + bodyGT <- paste0("grant_type=device_code&code=", deviceCodeEncoded, + "&client_id=", clientID, "&resource=", resourceEncoded) r <- httr::POST(URLGT, add_headers( @@ -209,18 +228,77 @@ azureGetTokenDeviceCodeFetch <- function(azureActiveContext, tenantID, clientID, `Content-type` = "application/x-www-form-urlencoded")), body = bodyGT, verbosity) + # handle special error case + if(status_code(r) == 400) { + rr <- content(r) + if (rr$error == "authorization_pending") { + print("polled AAD for token, got authorization_pending (still waiting for user to complete login)") + return(FALSE) + } + } stopWithAzureError(r) j1 <- content(r, "parsed", encoding = "UTF-8") azToken <- paste("Bearer", j1$access_token) + azRefreshToken <- j1$refresh_token azureActiveContext$Token <- azToken azureActiveContext$tenantID <- tenantID azureActiveContext$clientID <- clientID - azureActiveContext$authKey <- authKey azureActiveContext$EXPIRY <- Sys.time() + 3598 azureActiveContext$resource <- resource + azureActiveContext$RefreshToken <- azRefreshToken + return(TRUE) +} + +#' Get Azure token using RefreshToken +#' +#' @inheritParams setAzureContext +#' @param resource Specify the resource with which the token is obtained +#' @param verbose Print Tracing information (Default False) +#' +#' @note See \url{https://azure.microsoft.com/en-us/documentation/articles/resource-group-create-service-principal-portal/} for instructions to set up an Active Directory application +#' @references \url{https://azure.microsoft.com/en-us/documentation/articles/resource-group-create-service-principal-portal/} +#' +#' @return If successful, returns TRUE +#' @family Azure resource functions +#' +#' @export +azureGetTokenRefreshToken <- function(azureActiveContext, tenantID, refreshToken, verbose = FALSE) { + assert_that(is.azureActiveContext(azureActiveContext)) + + if (missing(tenantID)) tenantID <- azureActiveContext$tenantID + if (missing(refreshToken)) refreshToken <- azureActiveContext$RefreshToken + + assert_that(is_tenant_id(tenantID)) + assert_that(is_client_id(clientID)) + # TODO: need another validate function + #assert_that(is_authKey(refreshToken)) + + verbosity <- set_verbosity(verbose) + + URLGT <- paste0("https://login.microsoftonline.com/", tenantID, "/oauth2/token?api-version=1.0") + refreshTokenEncoded <- URLencode(refreshToken, reserved = TRUE) + bodyGT <- paste0("grant_type=refresh_token&refresh_token=", refreshTokenEncoded) + + r <- httr::POST(URLGT, + add_headers( + .headers = c(`Content-type` = "application/x-www-form-urlencoded")), + body = bodyGT, + verbosity) + stopWithAzureError(r) + + j1 <- content(r, "parsed", encoding = "UTF-8") + + azToken <- paste("Bearer", j1$access_token) + azRefreshToken <- j1$refresh_token + + azureActiveContext$Token <- azToken + azureActiveContext$tenantID <- tenantID + azureActiveContext$clientID <- clientID + azureActiveContext$EXPIRY <- Sys.time() + 3598 + azureActiveContext$RefreshToken <- azRefreshToken return(TRUE) } diff --git a/R/AzureContextObject.R b/R/AzureContextObject.R index 605e06b..8990d48 100644 --- a/R/AzureContextObject.R +++ b/R/AzureContextObject.R @@ -42,6 +42,8 @@ createAzureContext <- function(tenantID, clientID, authKey, configFile, authType #' @param azureActiveContext A container used for caching variables used by `AzureSMR`, created by [createAzureContext()] #' @param tenantID The tenant ID provided during creation of the Active Directory application / service principal #' @param clientID The client ID provided during creation of the Active Directory application / service principal +#' @param authType Auth type for getting token: "ClientCredential", "DeviceCode" +#' @param resource Resource to use for getting token #' @param authKey The authentication key provided during creation of the Active Directory application / service principal #' @param subscriptionID Subscription ID. This is obtained automatically by [azureAuthenticate()] when only a single subscriptionID is available via Active Directory #' @param resourceGroup Name of the resource group @@ -55,8 +57,6 @@ createAzureContext <- function(tenantID, clientID, authKey, configFile, authType #' @param hdiPassword HDInsight admin password. See [azureCreateHDI()] #' @param container Storage container name. See [azureListStorageContainers()] #' @param kind HDinsight kind: "hadoop","spark" or "rserver". See [azureCreateHDI()] -#' @param authType Auth type for getting token: "ClientCredential", "DeviceCode" -#' @param resource Resource to use for getting token #' #' @family azureActiveContext functions #' @export diff --git a/man/azureAuthenticate.Rd b/man/azureAuthenticate.Rd index ce08c1e..635edd1 100644 --- a/man/azureAuthenticate.Rd +++ b/man/azureAuthenticate.Rd @@ -40,5 +40,6 @@ Other Azure resource functions: \code{\link{azureAuthenticateOnAuthType}}, \code{\link{azureCheckToken}}, \code{\link{azureGetTokenClientCredential}}, \code{\link{azureGetTokenDeviceCodeFetch}}, - \code{\link{azureGetTokenDeviceCode}} + \code{\link{azureGetTokenDeviceCode}}, + \code{\link{azureGetTokenRefreshToken}} } diff --git a/man/azureAuthenticateOnAuthType.Rd b/man/azureAuthenticateOnAuthType.Rd index 9701750..202f938 100644 --- a/man/azureAuthenticateOnAuthType.Rd +++ b/man/azureAuthenticateOnAuthType.Rd @@ -27,5 +27,6 @@ Other Azure resource functions: \code{\link{azureAuthenticate}}, \code{\link{azureCheckToken}}, \code{\link{azureGetTokenClientCredential}}, \code{\link{azureGetTokenDeviceCodeFetch}}, - \code{\link{azureGetTokenDeviceCode}} + \code{\link{azureGetTokenDeviceCode}}, + \code{\link{azureGetTokenRefreshToken}} } diff --git a/man/azureCheckToken.Rd b/man/azureCheckToken.Rd index a555dff..af39f7a 100644 --- a/man/azureCheckToken.Rd +++ b/man/azureCheckToken.Rd @@ -17,5 +17,6 @@ Other Azure resource functions: \code{\link{azureAuthenticateOnAuthType}}, \code{\link{azureAuthenticate}}, \code{\link{azureGetTokenClientCredential}}, \code{\link{azureGetTokenDeviceCodeFetch}}, - \code{\link{azureGetTokenDeviceCode}} + \code{\link{azureGetTokenDeviceCode}}, + \code{\link{azureGetTokenRefreshToken}} } diff --git a/man/azureGetTokenClientCredential.Rd b/man/azureGetTokenClientCredential.Rd index c3960e2..39a11b6 100644 --- a/man/azureGetTokenClientCredential.Rd +++ b/man/azureGetTokenClientCredential.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/AzureAuthenticate.R \name{azureGetTokenClientCredential} \alias{azureGetTokenClientCredential} -\title{Get Azure token} +\title{Get Azure token using client credentials} \usage{ azureGetTokenClientCredential(azureActiveContext, tenantID, clientID, authKey, resource, verbose = FALSE) @@ -24,7 +24,7 @@ azureGetTokenClientCredential(azureActiveContext, tenantID, clientID, authKey, If successful, returns TRUE } \description{ -Get Azure token +Get Azure token using client credentials } \note{ See \url{https://azure.microsoft.com/en-us/documentation/articles/resource-group-create-service-principal-portal/} for instructions to set up an Active Directory application @@ -37,5 +37,6 @@ Other Azure resource functions: \code{\link{azureAuthenticateOnAuthType}}, \code{\link{azureAuthenticate}}, \code{\link{azureCheckToken}}, \code{\link{azureGetTokenDeviceCodeFetch}}, - \code{\link{azureGetTokenDeviceCode}} + \code{\link{azureGetTokenDeviceCode}}, + \code{\link{azureGetTokenRefreshToken}} } diff --git a/man/azureGetTokenDeviceCode.Rd b/man/azureGetTokenDeviceCode.Rd index 40f8444..d16cd10 100644 --- a/man/azureGetTokenDeviceCode.Rd +++ b/man/azureGetTokenDeviceCode.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/AzureAuthenticate.R \name{azureGetTokenDeviceCode} \alias{azureGetTokenDeviceCode} -\title{Authenticates against Azure Active directory application using DeviceCode flow.} +\title{Get Azure token using DeviceCode.} \usage{ azureGetTokenDeviceCode(azureActiveContext, tenantID, clientID, resource, verbose = FALSE) @@ -22,7 +22,7 @@ azureGetTokenDeviceCode(azureActiveContext, tenantID, clientID, resource, If successful, returns TRUE } \description{ -Authenticates against Azure Active directory application using DeviceCode flow. +Get Azure token using DeviceCode. } \note{ See \url{https://azure.microsoft.com/en-us/documentation/articles/resource-group-create-service-principal-portal/} for instructions to set up an Active Directory application @@ -35,5 +35,6 @@ Other Azure resource functions: \code{\link{azureAuthenticateOnAuthType}}, \code{\link{azureAuthenticate}}, \code{\link{azureCheckToken}}, \code{\link{azureGetTokenClientCredential}}, - \code{\link{azureGetTokenDeviceCodeFetch}} + \code{\link{azureGetTokenDeviceCodeFetch}}, + \code{\link{azureGetTokenRefreshToken}} } diff --git a/man/azureGetTokenDeviceCodeFetch.Rd b/man/azureGetTokenDeviceCodeFetch.Rd index 2c579f6..208d25b 100644 --- a/man/azureGetTokenDeviceCodeFetch.Rd +++ b/man/azureGetTokenDeviceCodeFetch.Rd @@ -35,5 +35,6 @@ Other Azure resource functions: \code{\link{azureAuthenticateOnAuthType}}, \code{\link{azureAuthenticate}}, \code{\link{azureCheckToken}}, \code{\link{azureGetTokenClientCredential}}, - \code{\link{azureGetTokenDeviceCode}} + \code{\link{azureGetTokenDeviceCode}}, + \code{\link{azureGetTokenRefreshToken}} } diff --git a/man/azureGetTokenRefreshToken.Rd b/man/azureGetTokenRefreshToken.Rd new file mode 100644 index 0000000..91e6117 --- /dev/null +++ b/man/azureGetTokenRefreshToken.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/AzureAuthenticate.R +\name{azureGetTokenRefreshToken} +\alias{azureGetTokenRefreshToken} +\title{Get Azure token using RefreshToken} +\usage{ +azureGetTokenRefreshToken(azureActiveContext, tenantID, refreshToken, + verbose = FALSE) +} +\arguments{ +\item{azureActiveContext}{A container used for caching variables used by \code{AzureSMR}, created by \code{\link[=createAzureContext]{createAzureContext()}}} + +\item{tenantID}{The tenant ID provided during creation of the Active Directory application / service principal} + +\item{verbose}{Print Tracing information (Default False)} + +\item{resource}{Specify the resource with which the token is obtained} +} +\value{ +If successful, returns TRUE +} +\description{ +Get Azure token using RefreshToken +} +\note{ +See \url{https://azure.microsoft.com/en-us/documentation/articles/resource-group-create-service-principal-portal/} for instructions to set up an Active Directory application +} +\references{ +\url{https://azure.microsoft.com/en-us/documentation/articles/resource-group-create-service-principal-portal/} +} +\seealso{ +Other Azure resource functions: \code{\link{azureAuthenticateOnAuthType}}, + \code{\link{azureAuthenticate}}, + \code{\link{azureCheckToken}}, + \code{\link{azureGetTokenClientCredential}}, + \code{\link{azureGetTokenDeviceCodeFetch}}, + \code{\link{azureGetTokenDeviceCode}} +}