Merge pull request #10 from cloudyr/import_auth

This commit is contained in:
Hong Ooi 2019-02-07 12:43:51 +11:00 коммит произвёл GitHub
Родитель 209e2620f0 f461980ccf
Коммит 52dc74ba75
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 70 добавлений и 1030 удалений

Просмотреть файл

@ -15,15 +15,16 @@ VignetteBuilder: knitr
Depends:
R (>= 3.3)
Imports:
AzureAuth,
utils,
httr (>= 1.3),
openssl,
jsonlite,
R6,
rappdirs
R6
Suggests:
knitr,
testthat,
httpuv
Roxygen: list(markdown=TRUE)
RoxygenNote: 6.1.0.9000
Remotes: cloudyr/AzureAuth

Просмотреть файл

@ -1,7 +1,6 @@
# Generated by roxygen2: do not edit by hand
export(AzureRMR_dir)
export(AzureToken)
export(AzureR_dir)
export(az_resource)
export(az_resource_group)
export(az_rm)
@ -30,6 +29,5 @@ export(is_url)
export(list_azure_logins)
export(list_azure_tokens)
export(named_list)
export(normalize_guid)
export(normalize_tenant)
import(AzureAuth)
importFrom(utils,modifyList)

Просмотреть файл

@ -4,14 +4,8 @@
* Allow authentication without having to create a service principal first, by leveraging the Azure CLI cross-platform app. It's still recommended to create your own SP for authentication, if possible.
* New `create_azure_login`, `get_azure_login` and `delete_azure_login` functions to handle ARM authentication. While directly calling `az_rm$new()` will still work, it's recommended to use `create_azure_login` and `get_azure_login` going forward. Login credentials will be saved and reused for subsequent sessions (see below).
* `get_azure_token` significantly revamped. It now supports four authentication methods for obtaining AAD tokens:
- Client credentials (what you would use with a "web app" registered service principal)
- Authorization code (for a "native" service principal)
- Device code
- With a username and password (resource owner grant)
* `get_azure_token` will now cache AAD tokens and refresh them for subsequent sessions. Tokens are cached in a user-specific configuration directory, using the rappdirs package (unlike httr, which saves them in a special file in the R working directory).
* By default, use the latest _stable_ API version when interacting with resources. `az_resource$set_api_version` gains a new argument `stable_only` which defaults to `TRUE`; set this to `FALSE` if you want the latest preview version.
* Token acquisition logic will shortly move to a new package, to allow it to be used by other packages independently of the Resource Manager interface.
* Token acquisition logic substantially enhanced and moved to a new package, [AzureAuth](https://github.com/cloudyr/AzureAuth). `get_azure_token` now supports four authentication methods for obtaining tokens (`client_credentials`, `authorization_code`, `device_code` and `resource_owner`). Tokens are also automatically cached and retrieved for use in subsequent sessions, without needing the user to reauthenticate. See the AzureAuth documentation for more details.
## Other changes

Просмотреть файл

@ -1,12 +1,14 @@
#' @import AzureAuth
#' @importFrom utils modifyList
NULL
.onLoad <- function(libname, pkgname)
{
azure_api_version="2018-05-01"
options(azure_api_version=azure_api_version)
make_AzureRMR_dir()
make_AzureR_dir()
invisible(NULL)
}
@ -17,40 +19,18 @@ NULL
# create a directory for saving creds -- ask first, to satisfy CRAN requirements
make_AzureRMR_dir <- function()
make_AzureR_dir <- function()
{
AzureRMR_dir <- AzureRMR_dir()
if(!dir.exists(AzureRMR_dir) && interactive())
AzureR_dir <- AzureR_dir()
if(!dir.exists(AzureR_dir) && interactive())
{
yn <- readline(paste0(
"AzureRMR can cache Azure Active Directory tokens and Resource Manager logins in the directory:\n\n",
AzureRMR_dir, "\n\n",
"AzureRMR can cache Azure Resource Manager logins in the directory:\n\n",
AzureR_dir, "\n\n",
"This saves you having to re-authenticate with Azure in future sessions. Create this directory? (Y/n) "))
if(tolower(substr(yn, 1, 1)) == "n")
return(invisible(NULL))
dir.create(AzureRMR_dir, recursive=TRUE)
dir.create(AzureR_dir, recursive=TRUE)
}
}
#' Data directory for AzureRMR
#'
#' @details
#' AzureRMR can store authentication credentials and OAuth tokens in a user-specific directory, using the rappdirs package. On recent Windows versions, this will usually be in the location `C:\\Users\\(username)\\AppData\\Local\\AzureR\\AzureRMR`. On Unix/Linux, it will be in `~/.local/share/AzureRMR`, and on MacOS, it will be in `~/Library/Application Support/AzureRMR`. The working directory is not touched (which significantly lessens the risk of accidentally introducing cached tokens into source control).
#'
#' On package startup, if this directory does not exist, AzureRMR will prompt you for permission to create it. It's recommended that you allow the directory to be created, as otherwise you will have to reauthenticate with Azure every time. Note that many cloud engineering tools, including the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest), save authentication credentials in this way.
#'
#' @return
#' A string containing the data directory.
#'
#' @seealso
#' [get_azure_token], [get_azure_login]
#'
#' [rappdirs::user_data_dir]
#'
#' @export
AzureRMR_dir <- function()
{
rappdirs::user_data_dir(appname="AzureRMR", appauthor="AzureR", roaming=FALSE)
}

Просмотреть файл

@ -1,520 +0,0 @@
#' Azure OAuth authentication
#'
#' Azure OAuth 2.0 token class, inheriting from the [Token2.0 class][httr::Token2.0] in httr. Rather than calling the initialization method directly, tokens should be created via [get_azure_token()].
#'
#' @docType class
#' @section Methods:
#' - `refresh`: Refreshes the token. For expired Azure tokens using client credentials, refreshing really means requesting a new token.
#' - `validate`: Checks if the token is still valid. For Azure tokens using client credentials, this just checks if the current time is less than the token's expiry time.
#' - `hash`: Computes an MD5 hash on selected fields of the token. Used internally for identification purposes when caching.
#' - `cache`: Stores the token on disk for use in future sessions.
#'
#' @seealso
#' [get_azure_token], [httr::Token]
#'
#' @format An R6 object of class `AzureToken`.
#' @export
AzureToken <- R6::R6Class("AzureToken", inherit=httr::Token2.0,
public=list(
# need to do hacky init to support explicit re-authentication instead of using a refresh token
initialize=function(endpoint, app, user_params, use_device=FALSE, client_credentials=TRUE)
{
params <- list(scope=NULL, user_params=user_params, type=NULL, use_oob=FALSE, as_header=TRUE,
use_basic_auth=FALSE, config_init=list(),
client_credentials=client_credentials, use_device=use_device)
# if this is an existing object, don't use cached value
# avoids infinite loop when refresh() calls initialize()
tokenfile <- file.path(AzureRMR_dir(), token_hash(endpoint, app, params))
if(file.exists(tokenfile) && !isTRUE(private$initialized))
{
message("Loading cached token")
token <- readRDS(tokenfile)
self$app <- token$app
self$endpoint <- token$endpoint
self$params <- token$params
self$cache_path <- token$cache_path
self$private_key <- token$private_key
self$credentials <- token$credentials
private$initialized <- TRUE
return(self$refresh())
}
private$initialized <- TRUE
# use httr initialize for authorization_code, client_credentials methods
if(!use_device && is.null(user_params$username))
return(super$initialize(app=app, endpoint=endpoint, params=params, cache_path=FALSE))
self$app <- app
self$endpoint <- endpoint
self$params <- params
self$cache_path <- NULL
self$private_key <- NULL
# use our own init functions for device_code, resource_owner methods
if(use_device)
private$init_with_device(user_params)
else private$init_with_username(user_params)
if(dir.exists(AzureRMR_dir()))
saveRDS(self, tokenfile)
self
},
# overrides httr::Token method
hash=function()
{
token_hash(self$endpoint, self$app, self$params)
},
# overrides httr::Token method
cache=function()
{
if(dir.exists(AzureRMR_dir()))
{
filename <- file.path(AzureRMR_dir(), self$hash())
saveRDS(self, filename)
}
invisible(NULL)
},
# overrides httr::Token2.0 method
can_refresh=function()
{
TRUE # always can refresh
},
# overrides httr::Token2.0 method
validate=function()
{
if(!is.null(self$endpoint$validate))
return(super$validate())
expdate <- as.POSIXct(as.numeric(self$credentials$expires_on), origin="1970-01-01")
curdate <- Sys.time()
curdate < expdate
},
# overrides httr::Token2.0 method
refresh=function()
{
# use a refresh token if it exists
# don't call superclass method b/c of different caching logic
if(!is.null(self$credentials$refresh_token))
{
body <- list(
refresh_token=self$credentials$refresh_token,
client_id=self$app$key,
client_secret=self$app$secret,
grant_type="refresh_token"
)
body <- modifyList(body, self$params$user_params)
access_uri <- sub("devicecode$", "token", self$endpoint$access)
res <- httr::POST(access_uri, body=body, encode="form")
if(httr::status_code(res) >= 300)
{
delete_azure_token(hash=self$hash(), confirm=FALSE)
stop("Unable to refresh", call.=FALSE)
}
self$credentials <- utils::modifyList(self$credentials, httr::content(res))
}
else # re-authenticate if no refresh token
{
# save the hash so we can delete the cached token on failure (initialize can modify state)
hash <- self$hash()
res <- try(self$initialize(self$endpoint, self$app, self$params$user_params,
use_device=self$params$use_device,
client_credentials=self$params$client_credentials), silent=TRUE)
if(inherits(res, "try-error"))
{
delete_azure_token(hash=hash, confirm=FALSE)
stop("Unable to reauthenticate", call.=FALSE)
}
}
self$cache()
self
},
print=function()
{
cat(format_auth_header(self))
invisible(self)
}
),
private=list(
initialized=NULL,
# device code authentication: after sending initial request, loop until server indicates code has been received
# after init_oauth2.0, oauth2.0_access_token
init_with_device=function(user_params)
{
# must be in an interactive session to use devicecode; should not affect cached tokens
if(!interactive())
stop("Must be in an interactive session to use device code authentication", call.=FALSE)
creds <- httr::oauth2.0_access_token(self$endpoint, self$app, code=NULL, user_params=user_params,
redirect_uri=NULL)
cat(creds$message, "\n") # tell user to enter the code
req_params <- list(client_id=self$app$key, grant_type="device_code", code=creds$device_code)
req_params <- utils::modifyList(user_params, req_params)
access_uri <- sub("devicecode$", "token", self$endpoint$access)
message("Waiting for device code in browser...\nPress Esc/Ctrl + C to abort")
interval <- as.numeric(creds$interval)
ntries <- as.numeric(creds$expires_in) %/% interval
for(i in seq_len(ntries))
{
Sys.sleep(interval)
res <- httr::POST(access_uri, httr::add_headers(`Cache-Control`="no-cache"), encode="form",
body=req_params)
status <- httr::status_code(res)
cont <- httr::content(res)
if(status == 400 && cont$error == "authorization_pending")
{
# do nothing
}
else if(status >= 300)
httr::stop_for_status(res)
else break
}
if(status >= 300)
stop("Unable to authenticate")
message("Authentication complete.")
self$credentials <- cont
NULL
},
# resource owner authentication: send username/password
init_with_username=function(user_params)
{
body <- list(
resource=user_params$resource,
client_id=self$app$key,
grant_type="password",
username=user_params$username,
password=user_params$password)
res <- httr::POST(self$endpoint$access, httr::add_headers(`Cache-Control`="no-cache"), encode="form",
body=body)
httr::stop_for_status(res, task="get an access token")
self$credentials <- httr::content(res)
NULL
}
))
#' Manage Azure Active Directory OAuth 2.0 tokens
#'
#' These functions extend the OAuth functionality in httr for use with Azure Active Directory (AAD).
#'
#' @param resource URL for your resource host. For Resource Manager in the public Azure cloud, this is `https://management.azure.com/`.
#' @param tenant Your tenant. This can be a name ("myaadtenant"), a fully qualified domain name ("myaadtenant.onmicrosoft.com" or "mycompanyname.com"), or a GUID.
#' @param app The client/app ID to use to authenticate with.
#' @param password The password, either for the app, or your username if supplied. See 'Details' below.
#' @param username Your AAD username, if using the resource owner grant. See 'Details' below.
#' @param auth_type The authentication type. See 'Details' below.
#' @param aad_host URL for your AAD host. For the public Azure cloud, this is `https://login.microsoftonline.com/`. Change this if you are using a government or private cloud.
#'
#' @details
#' `get_azure_token` does much the same thing as [httr::oauth2.0_token()], but customised for Azure. It obtains an OAuth token, first by checking if a cached value exists on disk, and if not, acquiring it from the AAD server. `delete_azure_token` deletes a cached token, and `list_azure_tokens` lists currently cached tokens.
#'
#' Note that tokens are only cached if you allowed AzureRMR to create a data directory at package startup.
#'
#' @section Authentication methods:
#' The OAuth authentication type can be one of four possible values: "authorization_code", "client_credentials", "device_code", or "resource_owner". The first two are provided by the [httr::Token2.0] token class, while the last two are provided by the AzureToken class which extends httr::Token2.0. Here is a short description of these methods.
#'
#' 1. Using the authorization_code method is a 3-step process. First, `get_azure_token` contacts the AAD authorization endpoint to obtain a temporary access code. It then contacts the AAD access endpoint, passing it the code. The access endpoint sends back a login URL which `get_azure_token` opens in your browser, where you can enter your credentials. Once this is completed, the endpoint returns the OAuth token via a HTTP redirect URI.
#'
#' 2. The device_code method is similar in concept to authorization_code, but is meant for situations where you are unable to browse the Internet -- for example if you don't have a browser installed or your computer has input constraints. First, `get_azure_token` contacts the AAD devicecode endpoint, which responds with a login URL and an access code. You then visit the URL and enter the code, possibly using a different computer. Meanwhile, `get_azure_token` polls the AAD access endpoint for a token, which is provided once you have successfully entered the code.
#'
#' 3. The client_credentials method is much simpler than the above methods, requiring only one step. `get_azure_token` contacts the access endpoint, passing it the app secret (which you supplied in the `password` argument). Assuming the secret is valid, the endpoint then returns the OAuth token.
#'
#' 4. The resource_owner method also requires only one step. In this method, `get_azure_token` passes your (personal) username and password to the AAD access endpoint, which validates your credentials and returns the token.
#'
#' If the authentication method is not specified, it is chosen based on the presence or absence of the `password` and `username` arguments:
#'
#' - Password and username present: resource_owner.
#' - Password and username absent: authorization_code if the httpuv package is installed, device_code otherwise
#' - Password present, username absent: client_credentials
#' - Password absent, username present: error
#'
#' The httpuv package must be installed to use the authorization_code method, as this requires a web server to listen on the (local) redirect URI. See [httr::oauth2.0_token] for more information; note that Azure does not support the `use_oob` feature of the httr OAuth 2.0 token class.
#'
#' Similarly, since the authorization_code method opens a browser to load the AAD authorization page, your machine must have an Internet browser installed that can be run from inside R. In particular, if you are using a Linux [Data Science Virtual Machine](https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/) in Azure, you may run into difficulties; use one of the other methods instead.
#'
#' @section Caching:
#' AzureRMR differs from httr in its handling of token caching in a number of ways. First, caching is based on all the inputs to `get_azure_token` as listed above. Second, it defines its own directory for cached tokens, using the rappdirs package. On recent Windows versions, this will usually be in the location `C:\\Users\\(username)\\AppData\\Local\\AzureR\\AzureRMR`. On Linux, it will be in `~/.config/AzureRMR`, and on MacOS, it will be in `~/Library/Application Support/AzureRMR`. Note that a single directory is used for all tokens, and the working directory is not touched (which significantly lessens the risk of accidentally introducing cached tokens into source control).
#'
#' To list all cached tokens on disk, use `list_azure_tokens`. This returns a list of token objects, named according to their MD5 hashes.
#'
#' To delete a cached token, use `delete_azure_token`. This takes the same inputs as `get_azure_token`, or you can specify the MD5 hash directly in the `hash` argument.
#'
#' To delete _all_ cached tokens, use `clean_token_directory`.
#'
#' @section Value:
#' For `get_azure_token`, an object of class `AzureToken` representing the AAD token. For `list_azure_tokens`, a list of such objects retrieved from disk.
#'
#' @seealso
#' [AzureToken], [httr::oauth2.0_token], [httr::Token],
#'
#' [OAuth authentication for Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code),
#' [Device code flow on OAuth.com](https://www.oauth.com/oauth2-servers/device-flow/token-request/),
#' [OAuth 2.0 RFC](https://tools.ietf.org/html/rfc6749) for the gory details on how OAuth works
#'
#' @examples
#' \dontrun{
#'
#' # authenticate with Azure Resource Manager:
#' # no user credentials are supplied, so this will use the authorization_code
#' # method if httpuv is installed, and device_code if not
#' arm_token <- get_azure_token(
#' resource="https://management.azure.com/",
#' tenant="myaadtenant.onmicrosoft.com",
#' app="app_id")
#'
#' # you can force a specific authentication method with the auth_type argument
#' arm_token <- get_azure_token(
#' resource="https://management.azure.com/",
#' tenant="myaadtenant.onmicrosoft.com",
#' app="app_id",
#' auth_type="device_code")
#'
#' # to use the client_credentials method, supply the app secret as the password
#' arm_token <- get_azure_token(
#' resource="https://management.azure.com/",
#' tenant="myaadtenant.onmicrosoft.com",
#' app="app_id",
#' password="app_secret")
#'
#' # authenticate with Azure storage
#' storage_token <- get_azure_token(
#' resource="https://storage.azure.com/",
#' tenant="myaadtenant.onmicrosoft.com",
#' app="app_id")
#'
#' # authenticate to your resource with the resource_owner method: provide your username and password
#' owner_token <- get_azure_token(
#' resource="https://myresource/",
#' tenant="myaadtenant",
#' app="app_id",
#' username="user",
#' password="abcdefg")
#'
#' # list saved tokens
#' list_azure_tokens()
#'
#' # delete a saved token from disk
#' delete_azure_token(
#' resource="https://myresource/",
#' tenant="myaadtenant",
#' app="app_id",
#' username="user",
#' password="abcdefg")
#'
#' # delete a saved token by specifying its MD5 hash
#' delete_azure_token(hash="7ea491716e5b10a77a673106f3f53bfd")
#'
#' }
#' @export
get_azure_token <- function(resource, tenant, app, password=NULL, username=NULL, auth_type=NULL,
aad_host="https://login.microsoftonline.com/")
{
tenant <- normalize_tenant(tenant)
app <- normalize_guid(app)
base_url <- construct_path(aad_host, tenant)
if(is.null(auth_type))
auth_type <- select_auth_type(password, username)
# fail if authorization_code selected but httpuv not available
if(auth_type == "authorization_code" && system.file(package="httpuv") == "")
stop("httpuv package must be installed to use authorization_code method", call.=FALSE)
switch(auth_type,
client_credentials=
auth_with_client_creds(base_url, app, password, resource),
device_code=
auth_with_device(base_url, app, resource),
authorization_code=
auth_with_code(base_url, app, resource),
resource_owner=
auth_with_username(base_url, app, password, username, resource),
stop("Invalid auth_type argument", call.=FALSE))
}
auth_with_client_creds <- function(base_url, app, password, resource)
{
endp <- httr::oauth_endpoint(base_url=base_url, authorize="oauth2/authorize", access="oauth2/token")
app <- httr::oauth_app("azure", key=app, secret=password, redirect_uri=NULL)
AzureToken$new(endp, app, user_params=list(resource=resource), use_device=FALSE, client_credentials=TRUE)
}
auth_with_device <- function(base_url, app, resource)
{
endp <- httr::oauth_endpoint(base_url=base_url, authorize="oauth2/authorize", access="oauth2/devicecode")
app <- httr::oauth_app("azure", key=app, secret=NULL)
AzureToken$new(endp, app, user_params=list(resource=resource), use_device=TRUE, client_credentials=FALSE)
}
auth_with_code <- function(base_url, app, resource)
{
endp <- httr::oauth_endpoint(base_url=base_url, authorize="oauth2/authorize", access="oauth2/token")
app <- httr::oauth_app("azure", key=app, secret=NULL)
AzureToken$new(endp, app, user_params=list(resource=resource), use_device=FALSE, client_credentials=FALSE)
}
auth_with_username <- function(base_url, app, password, username, resource)
{
endp <- httr::oauth_endpoint(base_url=base_url, authorize="oauth2/authorize", access="oauth2/token")
app <- httr::oauth_app("azure", key=app, secret=NULL)
AzureToken$new(endp, app, user_params=list(resource=resource, username=username, password=password),
use_device=FALSE, client_credentials=FALSE)
}
# select authentication method based on input arguments and presence of httpuv
select_auth_type <- function(password, username)
{
got_pwd <- !is.null(password)
got_user <- !is.null(username)
if(got_pwd && got_user)
"resource_owner"
else if(!got_pwd && !got_user)
{
if(system.file(package="httpuv") == "")
{
message("httpuv not installed, defaulting to device code authentication")
"device_code"
}
else "authorization_code"
}
else if(got_pwd && !got_user)
"client_credentials"
else stop("Can't select authentication method", call.=FALSE)
}
#' @param hash The MD5 hash of this token, computed from the above inputs. Used by `delete_azure_token` to identify a cached token to delete.
#' @param confirm For `delete_azure_token`, whether to prompt for confirmation before deleting a token.
#' @rdname get_azure_token
#' @export
delete_azure_token <- function(resource, tenant, app, password=NULL, username=NULL, auth_type=NULL,
aad_host="https://login.microsoftonline.com/",
hash=NULL,
confirm=TRUE)
{
if(!dir.exists(AzureRMR_dir()))
return(invisible(NULL))
if(is.null(hash))
hash <- token_hash_from_original_args(resource, tenant, app, password, username, auth_type, aad_host)
if(confirm && interactive())
{
yn <- readline(paste0("Do you really want to delete this Azure Active Directory token? (y/N) "))
if(tolower(substr(yn, 1, 1)) != "y")
return(invisible(NULL))
}
file.remove(file.path(AzureRMR_dir(), hash))
invisible(NULL)
}
#' @rdname get_azure_token
#' @export
clean_token_directory <- function(confirm=TRUE)
{
if(!dir.exists(AzureRMR_dir()))
return(invisible(NULL))
if(confirm && interactive())
{
yn <- readline(paste0("Do you really want to delete ALL saved Azure Active Directory tokens? (y/N) "))
if(tolower(substr(yn, 1, 1)) != "y")
return(invisible(NULL))
}
toks <- dir(AzureRMR_dir(), pattern="^[0-9a-f]{32}$", full.names=TRUE)
file.remove(toks)
invisible(NULL)
}
#' @rdname get_azure_token
#' @export
list_azure_tokens <- function()
{
tokens <- dir(AzureRMR_dir(), pattern="[0-9a-f]{32}", full.names=TRUE)
lst <- lapply(tokens, function(fname)
{
x <- try(readRDS(fname), silent=TRUE)
if(is_azure_token(x))
x
else NULL
})
names(lst) <- basename(tokens)
lst[!sapply(lst, is.null)]
}
token_hash <- function(endpoint, app, params)
{
msg <- serialize(list(endpoint, app, params), NULL, version=2)
paste(openssl::md5(msg[-(1:14)]), collapse="")
}
token_hash_from_original_args <- function(resource, tenant, app, password=NULL, username=NULL, auth_type=NULL,
aad_host="https://login.microsoftonline.com/")
{
# reconstruct the hash for the token object from the inputs
tenant <- normalize_tenant(tenant)
app <- normalize_guid(app)
base_url <- construct_path(aad_host, tenant)
if(is.null(auth_type))
auth_type <- select_auth_type(password, username)
base_url <- construct_path(aad_host, tenant)
use_device <- auth_type == "device_code"
client_credentials <- auth_type == "client_credentials"
endp <- httr::oauth_endpoint(base_url=base_url,
authorize="oauth2/authorize",
access=if(use_device) "oauth2/devicecode" else "oauth2/token")
app <- httr::oauth_app("azure", app,
secret=if(client_credentials) password else NULL,
redirect_uri=if(client_credentials) NULL else httr::oauth_callback())
user_params <- list(resource=resource)
if(auth_type == "resource_owner")
user_params <- c(user_params, password=NULL, username=NULL)
params <- list(scope=NULL, user_params=user_params, type=NULL, use_oob=FALSE, as_header=TRUE,
use_basic_auth=FALSE, config_init=list(),
client_credentials=client_credentials, use_device=use_device)
token_hash(endp, app, params)
}

Просмотреть файл

@ -50,7 +50,7 @@
#' If the AzureRMR data directory for saving credentials does not exist, `get_azure_login` will throw an error.
#'
#' @seealso
#' [az_rm], [get_azure_token],
#' [az_rm], [AzureAuth::get_azure_token],
#'
#' [Azure Resource Manager overview](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview),
#' [REST API reference](https://docs.microsoft.com/en-us/rest/api/resources/)
@ -91,7 +91,7 @@ create_azure_login <- function(tenant, app=.az_cli_app_id, password=NULL, userna
if(!is.null(conf$aad_host)) aad_host <- conf$aad_host
}
hash <- token_hash_from_original_args(
hash <- token_hash(
resource=host,
tenant=tenant,
app=app,
@ -100,7 +100,7 @@ create_azure_login <- function(tenant, app=.az_cli_app_id, password=NULL, userna
auth_type=auth_type,
aad_host=aad_host
)
tokenfile <- file.path(AzureRMR_dir(), hash)
tokenfile <- file.path(AzureR_dir(), hash)
if(file.exists(tokenfile))
{
message("Deleting existing Azure Active Directory token for this set of credentials")
@ -126,7 +126,7 @@ create_azure_login <- function(tenant, app=.az_cli_app_id, password=NULL, userna
#' @export
get_azure_login <- function(tenant, selection=NULL, refresh=TRUE)
{
if(!dir.exists(AzureRMR_dir()))
if(!dir.exists(AzureR_dir()))
stop("AzureRMR data directory does not exist; cannot load saved logins")
tenant <- normalize_tenant(tenant)
@ -142,7 +142,7 @@ get_azure_login <- function(tenant, selection=NULL, refresh=TRUE)
else if(is.null(selection))
{
tokens <- lapply(this_login, function(f)
readRDS(file.path(AzureRMR_dir(), f)))
readRDS(file.path(AzureR_dir(), f)))
choices <- sapply(tokens, function(token)
{
@ -169,7 +169,7 @@ get_azure_login <- function(tenant, selection=NULL, refresh=TRUE)
else if(is.character(selection))
this_login[which(this_login == selection)] # force an error if supplied hash doesn't match available logins
file <- file.path(AzureRMR_dir(), file)
file <- file.path(AzureR_dir(), file)
if(is_empty(file) || !file.exists(file))
stop("Azure Active Directory token not found for this login", call.=FALSE)
@ -188,7 +188,7 @@ get_azure_login <- function(tenant, selection=NULL, refresh=TRUE)
#' @export
delete_azure_login <- function(tenant, confirm=TRUE)
{
if(!dir.exists(AzureRMR_dir()))
if(!dir.exists(AzureR_dir()))
{
warning("AzureRMR data directory does not exist; no logins to delete")
return(invisible(NULL))
@ -221,7 +221,7 @@ list_azure_logins <- function()
{
sapply(tenant, function(hash)
{
file <- file.path(AzureRMR_dir(), hash)
file <- file.path(AzureR_dir(), hash)
az_rm$new(token=readRDS(file))
}, simplify=FALSE)
}, simplify=FALSE)
@ -232,7 +232,7 @@ list_azure_logins <- function()
load_arm_logins <- function()
{
file <- file.path(AzureRMR_dir(), "arm_logins.json")
file <- file.path(AzureR_dir(), "arm_logins.json")
if(!file.exists(file))
return(structure(list(), names=character(0)))
jsonlite::fromJSON(file)
@ -241,7 +241,7 @@ load_arm_logins <- function()
save_arm_logins <- function(logins)
{
if(!dir.exists(AzureRMR_dir()))
if(!dir.exists(AzureR_dir()))
{
message("AzureRMR data directory does not exist; login credentials not saved")
return(invisible(NULL))
@ -250,7 +250,7 @@ save_arm_logins <- function(logins)
if(is_empty(logins))
names(logins) <- character(0)
file <- file.path(AzureRMR_dir(), "arm_logins.json")
file <- file.path(AzureR_dir(), "arm_logins.json")
writeLines(jsonlite::toJSON(logins, auto_unbox=TRUE, pretty=TRUE), file)
invisible(NULL)
}

7
R/is.R
Просмотреть файл

@ -45,10 +45,3 @@ is_template <- function(object)
R6::is.R6(object) && inherits(object, "az_template")
}
#' @rdname is
#' @export
is_azure_token <- function(object)
{
R6::is.R6(object) && inherits(object, "AzureToken")
}

Просмотреть файл

@ -1,87 +0,0 @@
#' Normalize GUID and tenant values
#'
#' These functions are used by `get_azure_token` to recognise and properly format tenant and app IDs.
#'
#' @param tenant For `normalize_tenant`, a string containing an Azure Active Directory tenant. This can be a name ("myaadtenant"), a fully qualified domain name ("myaadtenant.onmicrosoft.com" or "mycompanyname.com"), or a valid GUID.
#' @param x For `is_guid`, a character string; for `normalize_guid`, a string containing a _validly formatted_ GUID.
#'
#' @details
#' A tenant can be identified either by a GUID, or its name, or a fully-qualified domain name (FQDN). The rules for normalizing a tenant are:
#' 1. If `tenant` is recognised as a valid GUID, return its canonically formatted value
#' 2. Otherwise, if it is a FQDN, return it
#' 3. Otherwise, if it is not the string "common", append ".onmicrosoft.com" to it
#' 4. Otherwise, return the value of `tenant`
#'
#' See the link below for GUID formats recognised by these functions.
#'
#' @return
#' For `is_guid`, whether the argument is a validly formatted GUID.
#'
#' For `normalize_guid`, the GUID in canonical format. If the argument is not recognised as a GUID, it throws an error.
#'
#' For `normalize_tenant`, the normalized ID or name of the tenant.
#'
#' @seealso
#' [get_azure_token]
#'
#' [Parsing rules for GUIDs in .NET](https://docs.microsoft.com/en-us/dotnet/api/system.guid.parse]). `is_guid` and `normalize_guid` recognise the "N", "D", "B" and "P" formats.
#'
#' @examples
#'
#' is_guid("72f988bf-86f1-41af-91ab-2d7cd011db47") # TRUE
#' is_guid("{72f988bf-86f1-41af-91ab-2d7cd011db47}") # TRUE
#' is_guid("72f988bf-86f1-41af-91ab-2d7cd011db47}") # FALSE (unmatched brace)
#' is_guid("microsoft") # FALSE
#'
#' # all of these return the same value
#' normalize_guid("72f988bf-86f1-41af-91ab-2d7cd011db47")
#' normalize_guid("{72f988bf-86f1-41af-91ab-2d7cd011db47}")
#' normalize_guid("(72f988bf-86f1-41af-91ab-2d7cd011db47)")
#' normalize_guid("72f988bf86f141af91ab2d7cd011db47")
#'
#' normalize_tenant("microsoft") # returns 'microsoft.onmicrosoft.com'
#' normalize_tenant("microsoft.com") # returns 'microsoft.com'
#' normalize_tenant("72f988bf-86f1-41af-91ab-2d7cd011db47") # returns the GUID
#'
#' @export
#' @rdname guid
normalize_tenant <- function(tenant)
{
# check if supplied a guid; if not, check if a fqdn;
# if not, check if 'common'; if not, append '.onmicrosoft.com'
if(is_guid(tenant))
return(normalize_guid(tenant))
if(!grepl("\\.", tenant) && tenant != "common")
tenant <- paste(tenant, "onmicrosoft.com", sep=".")
tenant
}
#' @export
#' @rdname guid
normalize_guid <- function(x)
{
if(!is_guid(x))
stop("Not a GUID", call.=FALSE)
x <- sub("^[({]?([-0-9a-f]+)[})]$", "\\1", x)
x <- gsub("-", "", x)
return(paste(
substr(x, 1, 8),
substr(x, 9, 12),
substr(x, 13, 16),
substr(x, 17, 20),
substr(x, 21, 32), sep="-"))
}
#' @export
#' @rdname guid
is_guid <- function(x)
{
grepl("^[0-9a-f]{32}$", x) ||
grepl("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", x) ||
grepl("^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}$", x) ||
grepl("^\\([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\)$", x)
}

20
R/reexport_AzureAuth.R Normal file
Просмотреть файл

@ -0,0 +1,20 @@
#' @export
AzureAuth::clean_token_directory
#' @export
AzureAuth::delete_azure_token
#' @export
AzureAuth::get_azure_token
#' @export
AzureAuth::is_azure_token
#' @export
AzureAuth::is_guid
#' @export
AzureAuth::list_azure_tokens
#' @export
AzureAuth::AzureR_dir

Просмотреть файл

@ -4,7 +4,7 @@
![Downloads](https://cranlogs.r-pkg.org/badges/AzureRMR)
[![Travis Build Status](https://travis-ci.org/cloudyr/AzureRMR.png?branch=master)](https://travis-ci.org/cloudyr/AzureRMR)
AzureRMR is a package for interacting with Azure Active Directory and Azure Resource Manager: obtain AAD authentication tokens, list subscriptions, manage resource groups, deploy and delete templates and resources. It calls the Resource Manager [REST API](https://docs.microsoft.com/en-us/rest/api/resources) directly, so you don't need to have PowerShell or Python installed.
AzureRMR is a package for interacting with Azure Resource Manager: list subscriptions, manage resource groups, deploy and delete templates and resources. It calls the Resource Manager [REST API](https://docs.microsoft.com/en-us/rest/api/resources) directly, so you don't need to have PowerShell or Python installed. Azure Active Directory OAuth tokens are obtained using the [AzureAuth](https://github.com/cloudyr/AzureAuth) package.
You can install the development version from GitHub, via `devtools::install_github("cloudyr/AzureRMR")`.

Просмотреть файл

@ -1,24 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/AzureRMR.R
\name{AzureRMR_dir}
\alias{AzureRMR_dir}
\title{Data directory for AzureRMR}
\usage{
AzureRMR_dir()
}
\value{
A string containing the data directory.
}
\description{
Data directory for AzureRMR
}
\details{
AzureRMR can store authentication credentials and OAuth tokens in a user-specific directory, using the rappdirs package. On recent Windows versions, this will usually be in the location \code{C:\\Users\\(username)\\AppData\\Local\\AzureR\\AzureRMR}. On Unix/Linux, it will be in \code{~/.local/share/AzureRMR}, and on MacOS, it will be in \code{~/Library/Application Support/AzureRMR}. The working directory is not touched (which significantly lessens the risk of accidentally introducing cached tokens into source control).
On package startup, if this directory does not exist, AzureRMR will prompt you for permission to create it. It's recommended that you allow the directory to be created, as otherwise you will have to reauthenticate with Azure every time. Note that many cloud engineering tools, including the \href{https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest}{Azure CLI}, save authentication credentials in this way.
}
\seealso{
\link{get_azure_token}, \link{get_azure_login}
\link[rappdirs:user_data_dir]{rappdirs::user_data_dir}
}

Просмотреть файл

@ -1,27 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/AzureToken.R
\docType{class}
\name{AzureToken}
\alias{AzureToken}
\title{Azure OAuth authentication}
\format{An R6 object of class \code{AzureToken}.}
\usage{
AzureToken
}
\description{
Azure OAuth 2.0 token class, inheriting from the \link[httr:Token2.0]{Token2.0 class} in httr. Rather than calling the initialization method directly, tokens should be created via \code{\link[=get_azure_token]{get_azure_token()}}.
}
\section{Methods}{
\itemize{
\item \code{refresh}: Refreshes the token. For expired Azure tokens using client credentials, refreshing really means requesting a new token.
\item \code{validate}: Checks if the token is still valid. For Azure tokens using client credentials, this just checks if the current time is less than the token's expiry time.
\item \code{hash}: Computes an MD5 hash on selected fields of the token. Used internally for identification purposes when caching.
\item \code{cache}: Stores the token on disk for use in future sessions.
}
}
\seealso{
\link{get_azure_token}, \link[httr:Token]{httr::Token}
}
\keyword{datasets}

Просмотреть файл

@ -103,7 +103,7 @@ az <- get_azure_login("myaadtenant")
}
}
\seealso{
\link{az_rm}, \link{get_azure_token},
\link{az_rm}, \link[AzureAuth:get_azure_token]{AzureAuth::get_azure_token},
\href{https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview}{Azure Resource Manager overview},
\href{https://docs.microsoft.com/en-us/rest/api/resources/}{REST API reference}

Просмотреть файл

@ -1,150 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/AzureToken.R
\name{get_azure_token}
\alias{get_azure_token}
\alias{delete_azure_token}
\alias{clean_token_directory}
\alias{list_azure_tokens}
\title{Manage Azure Active Directory OAuth 2.0 tokens}
\usage{
get_azure_token(resource, tenant, app, password = NULL,
username = NULL, auth_type = NULL,
aad_host = "https://login.microsoftonline.com/")
delete_azure_token(resource, tenant, app, password = NULL,
username = NULL, auth_type = NULL,
aad_host = "https://login.microsoftonline.com/", hash = NULL,
confirm = TRUE)
clean_token_directory(confirm = TRUE)
list_azure_tokens()
}
\arguments{
\item{resource}{URL for your resource host. For Resource Manager in the public Azure cloud, this is \code{https://management.azure.com/}.}
\item{tenant}{Your tenant. This can be a name ("myaadtenant"), a fully qualified domain name ("myaadtenant.onmicrosoft.com" or "mycompanyname.com"), or a GUID.}
\item{app}{The client/app ID to use to authenticate with.}
\item{password}{The password, either for the app, or your username if supplied. See 'Details' below.}
\item{username}{Your AAD username, if using the resource owner grant. See 'Details' below.}
\item{auth_type}{The authentication type. See 'Details' below.}
\item{aad_host}{URL for your AAD host. For the public Azure cloud, this is \code{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud.}
\item{hash}{The MD5 hash of this token, computed from the above inputs. Used by \code{delete_azure_token} to identify a cached token to delete.}
\item{confirm}{For \code{delete_azure_token}, whether to prompt for confirmation before deleting a token.}
}
\description{
These functions extend the OAuth functionality in httr for use with Azure Active Directory (AAD).
}
\details{
\code{get_azure_token} does much the same thing as \code{\link[httr:oauth2.0_token]{httr::oauth2.0_token()}}, but customised for Azure. It obtains an OAuth token, first by checking if a cached value exists on disk, and if not, acquiring it from the AAD server. \code{delete_azure_token} deletes a cached token, and \code{list_azure_tokens} lists currently cached tokens.
Note that tokens are only cached if you allowed AzureRMR to create a data directory at package startup.
}
\section{Authentication methods}{
The OAuth authentication type can be one of four possible values: "authorization_code", "client_credentials", "device_code", or "resource_owner". The first two are provided by the \link[httr:Token2.0]{httr::Token2.0} token class, while the last two are provided by the AzureToken class which extends httr::Token2.0. Here is a short description of these methods.
\enumerate{
\item Using the authorization_code method is a 3-step process. First, \code{get_azure_token} contacts the AAD authorization endpoint to obtain a temporary access code. It then contacts the AAD access endpoint, passing it the code. The access endpoint sends back a login URL which \code{get_azure_token} opens in your browser, where you can enter your credentials. Once this is completed, the endpoint returns the OAuth token via a HTTP redirect URI.
\item The device_code method is similar in concept to authorization_code, but is meant for situations where you are unable to browse the Internet -- for example if you don't have a browser installed or your computer has input constraints. First, \code{get_azure_token} contacts the AAD devicecode endpoint, which responds with a login URL and an access code. You then visit the URL and enter the code, possibly using a different computer. Meanwhile, \code{get_azure_token} polls the AAD access endpoint for a token, which is provided once you have successfully entered the code.
\item The client_credentials method is much simpler than the above methods, requiring only one step. \code{get_azure_token} contacts the access endpoint, passing it the app secret (which you supplied in the \code{password} argument). Assuming the secret is valid, the endpoint then returns the OAuth token.
\item The resource_owner method also requires only one step. In this method, \code{get_azure_token} passes your (personal) username and password to the AAD access endpoint, which validates your credentials and returns the token.
}
If the authentication method is not specified, it is chosen based on the presence or absence of the \code{password} and \code{username} arguments:
\itemize{
\item Password and username present: resource_owner.
\item Password and username absent: authorization_code if the httpuv package is installed, device_code otherwise
\item Password present, username absent: client_credentials
\item Password absent, username present: error
}
The httpuv package must be installed to use the authorization_code method, as this requires a web server to listen on the (local) redirect URI. See \link[httr:oauth2.0_token]{httr::oauth2.0_token} for more information; note that Azure does not support the \code{use_oob} feature of the httr OAuth 2.0 token class.
Similarly, since the authorization_code method opens a browser to load the AAD authorization page, your machine must have an Internet browser installed that can be run from inside R. In particular, if you are using a Linux \href{https://azure.microsoft.com/en-us/services/virtual-machines/data-science-virtual-machines/}{Data Science Virtual Machine} in Azure, you may run into difficulties; use one of the other methods instead.
}
\section{Caching}{
AzureRMR differs from httr in its handling of token caching in a number of ways. First, caching is based on all the inputs to \code{get_azure_token} as listed above. Second, it defines its own directory for cached tokens, using the rappdirs package. On recent Windows versions, this will usually be in the location \code{C:\\Users\\(username)\\AppData\\Local\\AzureR\\AzureRMR}. On Linux, it will be in \code{~/.config/AzureRMR}, and on MacOS, it will be in \code{~/Library/Application Support/AzureRMR}. Note that a single directory is used for all tokens, and the working directory is not touched (which significantly lessens the risk of accidentally introducing cached tokens into source control).
To list all cached tokens on disk, use \code{list_azure_tokens}. This returns a list of token objects, named according to their MD5 hashes.
To delete a cached token, use \code{delete_azure_token}. This takes the same inputs as \code{get_azure_token}, or you can specify the MD5 hash directly in the \code{hash} argument.
To delete \emph{all} cached tokens, use \code{clean_token_directory}.
}
\section{Value}{
For \code{get_azure_token}, an object of class \code{AzureToken} representing the AAD token. For \code{list_azure_tokens}, a list of such objects retrieved from disk.
}
\examples{
\dontrun{
# authenticate with Azure Resource Manager:
# no user credentials are supplied, so this will use the authorization_code
# method if httpuv is installed, and device_code if not
arm_token <- get_azure_token(
resource="https://management.azure.com/",
tenant="myaadtenant.onmicrosoft.com",
app="app_id")
# you can force a specific authentication method with the auth_type argument
arm_token <- get_azure_token(
resource="https://management.azure.com/",
tenant="myaadtenant.onmicrosoft.com",
app="app_id",
auth_type="device_code")
# to use the client_credentials method, supply the app secret as the password
arm_token <- get_azure_token(
resource="https://management.azure.com/",
tenant="myaadtenant.onmicrosoft.com",
app="app_id",
password="app_secret")
# authenticate with Azure storage
storage_token <- get_azure_token(
resource="https://storage.azure.com/",
tenant="myaadtenant.onmicrosoft.com",
app="app_id")
# authenticate to your resource with the resource_owner method: provide your username and password
owner_token <- get_azure_token(
resource="https://myresource/",
tenant="myaadtenant",
app="app_id",
username="user",
password="abcdefg")
# list saved tokens
list_azure_tokens()
# delete a saved token from disk
delete_azure_token(
resource="https://myresource/",
tenant="myaadtenant",
app="app_id",
username="user",
password="abcdefg")
# delete a saved token by specifying its MD5 hash
delete_azure_token(hash="7ea491716e5b10a77a673106f3f53bfd")
}
}
\seealso{
\link{AzureToken}, \link[httr:oauth2.0_token]{httr::oauth2.0_token}, \link[httr:Token]{httr::Token},
\href{https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code}{OAuth authentication for Azure Active Directory},
\href{https://www.oauth.com/oauth2-servers/device-flow/token-request/}{Device code flow on OAuth.com},
\href{https://tools.ietf.org/html/rfc6749}{OAuth 2.0 RFC} for the gory details on how OAuth works
}

Просмотреть файл

@ -1,63 +0,0 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/normalize.R
\name{normalize_tenant}
\alias{normalize_tenant}
\alias{normalize_guid}
\alias{is_guid}
\title{Normalize GUID and tenant values}
\usage{
normalize_tenant(tenant)
normalize_guid(x)
is_guid(x)
}
\arguments{
\item{tenant}{For \code{normalize_tenant}, a string containing an Azure Active Directory tenant. This can be a name ("myaadtenant"), a fully qualified domain name ("myaadtenant.onmicrosoft.com" or "mycompanyname.com"), or a valid GUID.}
\item{x}{For \code{is_guid}, a character string; for \code{normalize_guid}, a string containing a \emph{validly formatted} GUID.}
}
\value{
For \code{is_guid}, whether the argument is a validly formatted GUID.
For \code{normalize_guid}, the GUID in canonical format. If the argument is not recognised as a GUID, it throws an error.
For \code{normalize_tenant}, the normalized ID or name of the tenant.
}
\description{
These functions are used by \code{get_azure_token} to recognise and properly format tenant and app IDs.
}
\details{
A tenant can be identified either by a GUID, or its name, or a fully-qualified domain name (FQDN). The rules for normalizing a tenant are:
\enumerate{
\item If \code{tenant} is recognised as a valid GUID, return its canonically formatted value
\item Otherwise, if it is a FQDN, return it
\item Otherwise, if it is not the string "common", append ".onmicrosoft.com" to it
\item Otherwise, return the value of \code{tenant}
}
See the link below for GUID formats recognised by these functions.
}
\examples{
is_guid("72f988bf-86f1-41af-91ab-2d7cd011db47") # TRUE
is_guid("{72f988bf-86f1-41af-91ab-2d7cd011db47}") # TRUE
is_guid("72f988bf-86f1-41af-91ab-2d7cd011db47}") # FALSE (unmatched brace)
is_guid("microsoft") # FALSE
# all of these return the same value
normalize_guid("72f988bf-86f1-41af-91ab-2d7cd011db47")
normalize_guid("{72f988bf-86f1-41af-91ab-2d7cd011db47}")
normalize_guid("(72f988bf-86f1-41af-91ab-2d7cd011db47)")
normalize_guid("72f988bf86f141af91ab2d7cd011db47")
normalize_tenant("microsoft") # returns 'microsoft.onmicrosoft.com'
normalize_tenant("microsoft.com") # returns 'microsoft.com'
normalize_tenant("72f988bf-86f1-41af-91ab-2d7cd011db47") # returns the GUID
}
\seealso{
\link{get_azure_token}
\href{https://docs.microsoft.com/en-us/dotnet/api/system.guid.parse]}{Parsing rules for GUIDs in .NET}. \code{is_guid} and \code{normalize_guid} recognise the "N", "D", "B" and "P" formats.
}

Просмотреть файл

@ -6,7 +6,6 @@
\alias{is_resource_group}
\alias{is_resource}
\alias{is_template}
\alias{is_azure_token}
\title{Informational functions}
\usage{
is_azure_login(object)
@ -18,8 +17,6 @@ is_resource_group(object)
is_resource(object)
is_template(object)
is_azure_token(object)
}
\arguments{
\item{object}{An R object.}

22
man/reexports.Rd Normal file
Просмотреть файл

@ -0,0 +1,22 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/reexport_AzureAuth.R
\docType{import}
\name{reexports}
\alias{reexports}
\alias{clean_token_directory}
\alias{delete_azure_token}
\alias{get_azure_token}
\alias{is_azure_token}
\alias{is_guid}
\alias{list_azure_tokens}
\alias{AzureR_dir}
\title{Objects exported from other packages}
\keyword{internal}
\description{
These objects are imported from other packages. Follow the links
below to see their documentation.
\describe{
\item{AzureAuth}{\code{\link[AzureAuth]{clean_token_directory}}, \code{\link[AzureAuth]{delete_azure_token}}, \code{\link[AzureAuth]{get_azure_token}}, \code{\link[AzureAuth]{is_azure_token}}, \code{\link[AzureAuth]{is_guid}}, \code{\link[AzureAuth]{list_azure_tokens}}, \code{\link[AzureAuth]{AzureR_dir}}}
}}

Просмотреть файл

@ -1,94 +0,0 @@
context("AzureToken")
test_that("normalize_tenant, normalize_guid work",
{
guid <- "abcdefab-1234-5678-9012-abcdefabcdef"
expect_identical(normalize_guid(guid), guid)
guid2 <- paste0("{", guid, "}")
expect_identical(normalize_guid(guid2), guid)
guid3 <- paste0("(", guid, ")")
expect_identical(normalize_guid(guid3), guid)
guid4 <- gsub("-", "", guid, fixed=TRUE)
expect_identical(normalize_guid(guid4), guid)
# improperly formatted GUID will be treated as a name
guid5 <- paste0("(", guid)
expect_false(is_guid(guid5))
expect_error(normalize_guid(guid5))
expect_identical(normalize_tenant(guid5), paste0(guid5, ".onmicrosoft.com"))
expect_identical(normalize_tenant("common"), "common")
expect_identical(normalize_tenant("mytenant"), "mytenant.onmicrosoft.com")
expect_identical(normalize_tenant("mytenant.com"), "mytenant.com")
# iterating normalize shouldn't change result
expect_identical(normalize_tenant(normalize_tenant("mytenant")), "mytenant.onmicrosoft.com")
})
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
app <- Sys.getenv("AZ_TEST_APP_ID")
password <- Sys.getenv("AZ_TEST_PASSWORD")
subscription <- Sys.getenv("AZ_TEST_SUBSCRIPTION")
native_app <- Sys.getenv("AZ_TEST_NATIVE_APP_ID")
if(tenant == "" || app == "" || password == "" || subscription == "" || native_app == "")
skip("Authentication tests skipped: ARM credentials not set")
if(system.file(package="httpuv") == "")
skip("Authentication tests skipped: httpuv must be installed")
# not a perfect test: will fail to detect Linux DSVM issue
if(!interactive())
skip("Authentication tests skipped: must be an interactive session")
test_that("Authentication works",
{
suppressWarnings(file.remove(dir(AzureRMR:::AzureRMR_dir(), full.names=TRUE)))
res <- "https://management.azure.com/"
# obtain new tokens
aut_tok <- get_azure_token(res, tenant, native_app, auth_type="authorization_code")
expect_true(is_azure_token(aut_tok))
expect_identical(aut_tok$hash(), "b29ef592fa435a4fd92672daf8726bae")
ccd_tok <- get_azure_token(res, tenant, app, password=password)
expect_true(is_azure_token(ccd_tok))
expect_identical(ccd_tok$hash(), "c75c266d9c578af29e24d3f22013ebf6")
dev_tok <- get_azure_token(res, tenant, native_app, auth_type="device_code")
expect_true(is_azure_token(dev_tok))
expect_identical(dev_tok$hash(), "37cbd9fec7c15b5a47edc1ea6f2f2747")
aut_expire <- as.numeric(aut_tok$credentials$expires_on)
ccd_expire <- as.numeric(ccd_tok$credentials$expires_on)
dev_expire <- as.numeric(dev_tok$credentials$expires_on)
Sys.sleep(5)
# refresh/reauthenticate
aut_tok$refresh()
ccd_tok$refresh()
dev_tok$refresh()
expect_true(as.numeric(aut_tok$credentials$expires_on) > aut_expire)
expect_true(as.numeric(ccd_tok$credentials$expires_on) > ccd_expire)
expect_true(as.numeric(dev_tok$credentials$expires_on) > dev_expire)
# load cached tokens: should not get repeated login prompts/screens
aut_tok2 <- get_azure_token(res, tenant, native_app, auth_type="authorization_code")
expect_true(is_azure_token(aut_tok2))
expect_identical(aut_tok2$hash(), "b29ef592fa435a4fd92672daf8726bae")
ccd_tok2 <- get_azure_token(res, tenant, app, password=password)
expect_true(is_azure_token(ccd_tok2))
expect_identical(ccd_tok2$hash(), "c75c266d9c578af29e24d3f22013ebf6")
dev_tok2 <- get_azure_token(res, tenant, native_app, auth_type="device_code")
expect_true(is_azure_token(dev_tok2))
expect_identical(dev_tok2$hash(), "37cbd9fec7c15b5a47edc1ea6f2f2747")
expect_null(delete_azure_token(res, tenant, native_app, auth_type="authorization_code", confirm=FALSE))
expect_null(delete_azure_token(res, tenant, app, password=password, confirm=FALSE))
expect_null(delete_azure_token(res, tenant, native_app, auth_type="device_code", confirm=FALSE))
})