зеркало из https://github.com/Azure/AzureRMR.git
fully implement token caching
This commit is contained in:
Родитель
dd698633f3
Коммит
24018e92fd
|
@ -10,20 +10,25 @@ export(call_azure_rm)
|
|||
export(call_azure_url)
|
||||
export(create_azure_login)
|
||||
export(delete_azure_login)
|
||||
export(delete_azure_token)
|
||||
export(format_auth_header)
|
||||
export(format_public_fields)
|
||||
export(format_public_methods)
|
||||
export(get_azure_login)
|
||||
export(get_azure_token)
|
||||
export(is_azure_login)
|
||||
export(is_azure_token)
|
||||
export(is_empty)
|
||||
export(is_guid)
|
||||
export(is_resource)
|
||||
export(is_resource_group)
|
||||
export(is_subscription)
|
||||
export(is_template)
|
||||
export(is_url)
|
||||
export(list_azure_logins)
|
||||
export(list_azure_tokens)
|
||||
export(named_list)
|
||||
export(normalize_guid)
|
||||
export(normalize_tenant)
|
||||
export(refresh_azure_logins)
|
||||
importFrom(utils,modifyList)
|
||||
|
|
152
R/AzureToken.R
152
R/AzureToken.R
|
@ -8,7 +8,7 @@
|
|||
#' - `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.
|
||||
#'
|
||||
#' @section Caching:
|
||||
#' This class never caches its tokens, unlike httr::Token2.0.
|
||||
#' Unlike httr::Token2.0, caching for Azure tokens is handled outside the class. Tokens are automatically cached by the `get_azure_token` function, and can be (manually) deleted with the `delete_azure_token` function. Calling `AzureToken$new()` directly will always acquire a new token from the server.
|
||||
#'
|
||||
#' @seealso
|
||||
#' [get_azure_token], [httr::Token]
|
||||
|
@ -46,7 +46,7 @@ public=list(
|
|||
# overrides httr::Token method: caching done outside class
|
||||
hash=function()
|
||||
{
|
||||
NULL
|
||||
stop("Caching not handled by AzureToken class")
|
||||
},
|
||||
|
||||
# overrides httr::Token2.0 method
|
||||
|
@ -142,9 +142,9 @@ private=list(
|
|||
))
|
||||
|
||||
|
||||
#' Generate an Azure OAuth token
|
||||
#' Manage Azure Active Directory OAuth 2.0 tokens
|
||||
#'
|
||||
#' This extends the OAuth functionality in httr for use with Azure Active Directory (AAD).
|
||||
#' These functions extend the OAuth functionality in httr for use with Azure Active Directory (AAD).
|
||||
#'
|
||||
#' @param resource_host 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.
|
||||
|
@ -155,17 +155,18 @@ private=list(
|
|||
#' @param aad_host URL for your AAD host. For the public Azure cloud, this is `https://login.microsoftonline.com/`.
|
||||
#'
|
||||
#' @details
|
||||
#' This function does much the same thing as [httr::oauth2.0_token()], but customised for Azure.
|
||||
#' `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.
|
||||
#'
|
||||
#' @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.
|
||||
#'
|
||||
#' - 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.
|
||||
#' 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.
|
||||
#'
|
||||
#' - 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.
|
||||
#' 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.
|
||||
#'
|
||||
#' - 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.
|
||||
#' 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.
|
||||
#'
|
||||
#' - 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.
|
||||
#' 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:
|
||||
#'
|
||||
|
@ -178,6 +179,20 @@ private=list(
|
|||
#'
|
||||
#' 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.
|
||||
#'
|
||||
#' - It moves caching of OAuth tokens out of the token class, and into the `get_azure_token` function. Caching is based on all the inputs to `get_azure_token` as listed above. Directly calling the AzureToken class constructor will always acquire a new token from the server.
|
||||
#'
|
||||
#' - It defines its own directory for caching 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, unlike httr, and the working directory is not touched (which 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.
|
||||
#'
|
||||
#' @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],
|
||||
#'
|
||||
|
@ -220,15 +235,32 @@ private=list(
|
|||
#' owner_token <- get_azure_token(
|
||||
#' resource_host="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_host="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_host, tenant, app, password=NULL, username=NULL, auth_type=NULL,
|
||||
aad_host="https://login.microsoftonline.com/")
|
||||
{
|
||||
tenant <- normalize_tenant(tenant)
|
||||
if(is_guid(app))
|
||||
app <- normalize_guid(app)
|
||||
base_url <- construct_path(aad_host, tenant)
|
||||
|
||||
if(is.null(auth_type))
|
||||
|
@ -241,8 +273,10 @@ get_azure_token <- function(resource_host, tenant, app, password=NULL, username=
|
|||
# load saved token if available
|
||||
tokenfile <- file.path(config_dir(),
|
||||
token_hash(resource_host, tenant, app, password, username, auth_type, aad_host))
|
||||
|
||||
if(file.exists(tokenfile))
|
||||
{
|
||||
message("Loading saved token")
|
||||
token <- readRDS(tokenfile)
|
||||
token$refresh()
|
||||
}
|
||||
|
@ -324,6 +358,57 @@ select_auth_type <- function(password, username)
|
|||
}
|
||||
|
||||
|
||||
#' @param hash The MD5 hash of this token, computed from the above inputs. Used by `delete_azure_token` for identification purposes.
|
||||
#' @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_host, tenant, app, password=NULL, username=NULL, auth_type=NULL,
|
||||
aad_host="https://login.microsoftonline.com/",
|
||||
hash=NULL,
|
||||
confirm=TRUE)
|
||||
{
|
||||
if(is.null(hash))
|
||||
{
|
||||
tenant <- normalize_tenant(tenant)
|
||||
if(is_guid(app))
|
||||
app <- normalize_guid(app)
|
||||
base_url <- construct_path(aad_host, tenant)
|
||||
|
||||
if(is.null(auth_type))
|
||||
auth_type <- select_auth_type(password, username)
|
||||
|
||||
hash <- token_hash(resource_host, 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(config_dir(), hash))
|
||||
invisible(NULL)
|
||||
}
|
||||
|
||||
|
||||
#' @rdname get_azure_token
|
||||
#' @export
|
||||
list_azure_tokens <- function()
|
||||
{
|
||||
tokens <- dir(config_dir(), full.names=TRUE)
|
||||
lst <- lapply(tokens, function(fname)
|
||||
{
|
||||
x <- readRDS(fname)
|
||||
if(is_azure_token(x))
|
||||
x
|
||||
else NULL
|
||||
})
|
||||
names(lst) <- basename(tokens)
|
||||
lst[!sapply(lst, is.null)]
|
||||
}
|
||||
|
||||
|
||||
token_hash <- function(resource_host, tenant, app, password, username, auth_type, aad_host)
|
||||
{
|
||||
msg <- serialize(list(resource_host, tenant, app, password, username, auth_type, aad_host), NULL, version=2)
|
||||
|
@ -331,52 +416,3 @@ token_hash <- function(resource_host, tenant, app, password, username, auth_type
|
|||
}
|
||||
|
||||
|
||||
#' Normalizes a tenant
|
||||
#'
|
||||
#' @param tenant 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.
|
||||
#'
|
||||
#' @details
|
||||
#' This function is used by `get_azure_token` to recognise a tenant input. 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 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`
|
||||
#'
|
||||
#' @return
|
||||
#' 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]). `normalize_tenant` recognises the "N", "D", "B" and "P" formats for GUIDs.
|
||||
#' @export
|
||||
normalize_tenant <- function(tenant)
|
||||
{
|
||||
# see https://docs.microsoft.com/en-us/dotnet/api/system.guid.parse
|
||||
# for possible input formats for GUIDs
|
||||
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)
|
||||
}
|
||||
|
||||
# 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))
|
||||
{
|
||||
tenant <- sub("^[({]?([-0-9a-f]+)[})]$", "\\1", tenant)
|
||||
tenant <- gsub("-", "", tenant)
|
||||
return(paste(
|
||||
substr(tenant, 1, 8),
|
||||
substr(tenant, 9, 12),
|
||||
substr(tenant, 13, 16),
|
||||
substr(tenant, 17, 20),
|
||||
substr(tenant, 21, 32), sep="-"))
|
||||
}
|
||||
|
||||
if(!grepl("\\.", tenant) && tenant != "common")
|
||||
tenant <- paste(tenant, "onmicrosoft.com", sep=".")
|
||||
tenant
|
||||
}
|
||||
|
||||
|
|
16
R/az_login.R
16
R/az_login.R
|
@ -55,8 +55,8 @@ config_dir <- function()
|
|||
#' @rdname azure_login
|
||||
#' @export
|
||||
create_azure_login <- function(tenant, app, password=NULL, username=NULL, auth_type=NULL,
|
||||
host="https://management.azure.com/", aad_host="https://login.microsoftonline.com/",
|
||||
config_file=NULL, ...)
|
||||
host="https://management.azure.com/", aad_host="https://login.microsoftonline.com/",
|
||||
config_file=NULL, ...)
|
||||
{
|
||||
if(!is.null(config_file))
|
||||
{
|
||||
|
@ -70,6 +70,8 @@ create_azure_login <- function(tenant, app, password=NULL, username=NULL, auth_t
|
|||
}
|
||||
|
||||
tenant <- normalize_tenant(tenant)
|
||||
if(is_guid(app))
|
||||
app <- normalize_guid(app)
|
||||
message("Creating Azure Resource Manager login for tenant '", tenant, "'")
|
||||
client <- az_rm$new(tenant, app, password, username, auth_type, host, aad_host, config_file, ...)
|
||||
save_client(client, tenant)
|
||||
|
@ -123,9 +125,15 @@ delete_azure_login <- function(tenant, confirm=TRUE)
|
|||
list_azure_logins <- function()
|
||||
{
|
||||
tenants <- dir(config_dir(), full.names=TRUE)
|
||||
lst <- lapply(tenants, readRDS)
|
||||
lst <- lapply(tenants, function(fname)
|
||||
{
|
||||
x <- readRDS(fname)
|
||||
if(is_azure_login(x))
|
||||
x
|
||||
else NULL
|
||||
})
|
||||
names(lst) <- basename(tenants)
|
||||
lst
|
||||
lst[!sapply(lst, is.null)]
|
||||
}
|
||||
|
||||
|
||||
|
|
8
R/is.R
8
R/is.R
|
@ -6,6 +6,14 @@
|
|||
#'
|
||||
#' @return
|
||||
#' A boolean.
|
||||
#' @rdname is
|
||||
#' @export
|
||||
is_azure_login <- function(object)
|
||||
{
|
||||
R6::is.R6(object) && inherits(object, "az_rm")
|
||||
}
|
||||
|
||||
|
||||
#' @rdname is
|
||||
#' @export
|
||||
is_subscription <- function(object)
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
#' 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 `normalize_guid`, the canonically formatted GUID. Note that if `normalize_guid` is given an improperly formatted GUID, its output is undefined; you should always test a string with `is_guid` before passing it to `normalize_guid`.
|
||||
#'
|
||||
#' 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)
|
||||
{
|
||||
# see https://docs.microsoft.com/en-us/dotnet/api/system.guid.parse
|
||||
# for possible input formats for GUIDs
|
||||
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)
|
||||
}
|
||||
|
||||
# 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))
|
||||
{
|
||||
tenant <- sub("^[({]?([-0-9a-f]+)[})]$", "\\1", tenant)
|
||||
tenant <- gsub("-", "", tenant)
|
||||
return(normalize_guid(tenant))
|
||||
}
|
||||
|
||||
if(!grepl("\\.", tenant) && tenant != "common")
|
||||
tenant <- paste(tenant, "onmicrosoft.com", sep=".")
|
||||
tenant
|
||||
}
|
||||
|
||||
|
||||
#' @export
|
||||
#' @rdname guid
|
||||
normalize_guid <- function(x)
|
||||
{
|
||||
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)
|
||||
}
|
|
@ -21,7 +21,7 @@ Azure OAuth 2.0 token class, inheriting from the \link[httr:Token2.0]{Token2.0 c
|
|||
|
||||
\section{Caching}{
|
||||
|
||||
This class never caches its tokens, unlike httr::Token2.0.
|
||||
Unlike httr::Token2.0, caching for Azure tokens is handled outside the class. Tokens are automatically cached by the \code{get_azure_token} function, and can be (manually) deleted with the \code{delete_azure_token} function. Calling \code{AzureToken$new()} directly will always acquire a new token from the server.
|
||||
}
|
||||
|
||||
\seealso{
|
||||
|
|
|
@ -2,11 +2,20 @@
|
|||
% Please edit documentation in R/AzureToken.R
|
||||
\name{get_azure_token}
|
||||
\alias{get_azure_token}
|
||||
\title{Generate an Azure OAuth token}
|
||||
\alias{delete_azure_token}
|
||||
\alias{list_azure_tokens}
|
||||
\title{Manage Azure Active Directory OAuth 2.0 tokens}
|
||||
\usage{
|
||||
get_azure_token(resource_host, tenant, app, password = NULL,
|
||||
username = NULL, auth_type = NULL,
|
||||
aad_host = "https://login.microsoftonline.com/")
|
||||
|
||||
delete_azure_token(resource_host, tenant, app, password = NULL,
|
||||
username = NULL, auth_type = NULL,
|
||||
aad_host = "https://login.microsoftonline.com/", hash = NULL,
|
||||
confirm = TRUE)
|
||||
|
||||
list_azure_tokens()
|
||||
}
|
||||
\arguments{
|
||||
\item{resource_host}{URL for your resource host. For Resource Manager in the public Azure cloud, this is \code{https://management.azure.com/}.}
|
||||
|
@ -22,15 +31,21 @@ get_azure_token(resource_host, tenant, app, password = NULL,
|
|||
\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/}.}
|
||||
|
||||
\item{hash}{The MD5 hash of this token, computed from the above inputs. Used by \code{delete_azure_token} for identification purposes.}
|
||||
|
||||
\item{confirm}{For \code{delete_azure_token}, whether to prompt for confirmation before deleting a token.}
|
||||
}
|
||||
\description{
|
||||
This extends the OAuth functionality in httr for use with Azure Active Directory (AAD).
|
||||
These functions extend the OAuth functionality in httr for use with Azure Active Directory (AAD).
|
||||
}
|
||||
\details{
|
||||
This function does much the same thing as \code{\link[httr:oauth2.0_token]{httr::oauth2.0_token()}}, but customised for Azure.
|
||||
\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.
|
||||
}
|
||||
\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.
|
||||
\itemize{
|
||||
\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.
|
||||
|
@ -49,6 +64,25 @@ The httpuv package must be installed to use the authorization_code method, as th
|
|||
|
||||
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.
|
||||
\itemize{
|
||||
\item It moves caching of OAuth tokens out of the token class, and into the \code{get_azure_token} function. Caching is based on all the inputs to \code{get_azure_token} as listed above. Directly calling the AzureToken class constructor will always acquire a new token from the server.
|
||||
\item It defines its own directory for caching 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, unlike httr, and the working directory is not touched (which 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.
|
||||
}
|
||||
|
||||
\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{
|
||||
|
||||
|
@ -84,9 +118,24 @@ storage_token <- get_azure_token(
|
|||
owner_token <- get_azure_token(
|
||||
resource_host="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_host="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{
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
% 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{normalize_guid}, the canonically formatted GUID. Note that if \code{normalize_guid} is given an improperly formatted GUID, its output is undefined; you should always test a string with \code{is_guid} before passing it to \code{normalize_guid}.
|
||||
|
||||
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.
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/is.R
|
||||
\name{is_subscription}
|
||||
\name{is_azure_login}
|
||||
\alias{is_azure_login}
|
||||
\alias{is_subscription}
|
||||
\alias{is_resource_group}
|
||||
\alias{is_resource}
|
||||
|
@ -8,6 +9,8 @@
|
|||
\alias{is_azure_token}
|
||||
\title{Informational functions}
|
||||
\usage{
|
||||
is_azure_login(object)
|
||||
|
||||
is_subscription(object)
|
||||
|
||||
is_resource_group(object)
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/AzureToken.R
|
||||
\name{normalize_tenant}
|
||||
\alias{normalize_tenant}
|
||||
\title{Normalizes a tenant}
|
||||
\usage{
|
||||
normalize_tenant(tenant)
|
||||
}
|
||||
\arguments{
|
||||
\item{tenant}{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.}
|
||||
}
|
||||
\value{
|
||||
The normalized ID or name of the tenant.
|
||||
}
|
||||
\description{
|
||||
Normalizes a tenant
|
||||
}
|
||||
\details{
|
||||
This function is used by \code{get_azure_token} to recognise a tenant input. 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 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}
|
||||
}
|
||||
}
|
||||
\seealso{
|
||||
\link{get_azure_token},
|
||||
|
||||
\href{https://docs.microsoft.com/en-us/dotnet/api/system.guid.parse]}{Parsing rules for GUIDs in .NET}. \code{normalize_tenant} recognises the "N", "D", "B" and "P" formats for GUIDs.
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
context("AzureToken")
|
||||
|
||||
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")
|
||||
|
||||
if(tenant == "" || app == "" || password == "" || subscription == "")
|
||||
skip("Authentication tests skipped: ARM credentials not set")
|
||||
|
||||
|
||||
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 <- sub("-", "", guid, fixed=TRUE)
|
||||
expect_identical(normalize_guid(guid4), guid)
|
||||
|
||||
# improperly formatted GUID will be treated as a name
|
||||
guid5 <- paste0("(", guid)
|
||||
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")
|
||||
})
|
||||
|
||||
test_that("Authentication works",
|
||||
{
|
||||
suppressWarnings(delete_azure_token("http://management.azure.com/", tenant, app, password, confirm=FALSE))
|
||||
|
||||
token <- get_azure_token("http://management.azure.com/", tenant, app, password)
|
||||
expect_true(is_azure_token(token))
|
||||
|
||||
toklist <- list_azure_tokens()
|
||||
expect_true(length(toklist) > 0)
|
||||
|
||||
expect_null(delete_azure_token("http://management.azure.com/", tenant, app, password, confirm=FALSE))
|
||||
})
|
Загрузка…
Ссылка в новой задаче