This commit is contained in:
hong-revo 2019-02-13 22:00:52 +11:00
Родитель 731691f1d7 a6912f49d1
Коммит ba250fe15f
8 изменённых файлов: 143 добавлений и 62 удалений

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

@ -2,11 +2,15 @@
export(AzureR_dir)
export(AzureToken)
export(AzureTokenV1)
export(AzureTokenV2)
export(clean_token_directory)
export(delete_azure_token)
export(format_auth_header)
export(get_azure_token)
export(is_azure_token)
export(is_azure_v1_token)
export(is_azure_v2_token)
export(is_guid)
export(list_azure_tokens)
export(normalize_guid)

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

@ -1,39 +1,39 @@
#' Azure OAuth authentication
#'
#' Azure OAuth 2.0 token class, with an interface based on the [Token2.0 class][httr::Token2.0] in httr. Rather than calling the initialization method directly, tokens should be created via [get_azure_token()].
#' Azure OAuth 2.0 token classes, with an interface based on the [Token2.0 class][httr::Token2.0] in httr. Rather than calling the initialization methods 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.
#' - `refresh`: Refreshes the token. For expired tokens without an associated refresh token, refreshing really means requesting a new token.
#' - `validate`: Checks if the token is still valid. If there is no associated refresh token, this just checks if the current time is less than the token's expiry time.
#' - `hash`: Computes an MD5 hash on the input fields of the object. 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`.
#' @format An R6 object representing an Azure Active Directory token and its associated credentials. The `AzureTokenV1` class is for AAD v1.0 tokens, and the `AzureTokenV2` class is for AAD v2.0 tokens. Objects of the AzureToken class should not be created directly.
#' @export
AzureToken <- R6::R6Class("AzureToken",
public=list(
version=NULL,
aad_host=NULL,
tenant=NULL,
auth_type=NULL,
client=NULL,
resource=NULL,
scope=NULL,
authorize_args=NULL,
token_args=NULL,
credentials=list(), # returned token details from host
initialize=function(resource, tenant, app, password=NULL, username=NULL, certificate=NULL, auth_type=NULL,
aad_host="https://login.microsoftonline.com/", version=1,
initialize=function(tenant, app, password=NULL, username=NULL, certificate=NULL, auth_type=NULL,
aad_host="https://login.microsoftonline.com/",
authorize_args=list(), token_args=list())
{
self$version <- normalize_aad_version(version)
# fail if this constructor is called directly
if(is.null(self$version))
stop("Do not call this constructor directly; use get_azure_token() instead")
self$aad_host <- aad_host
self$tenant <- normalize_tenant(tenant)
self$auth_type <- select_auth_type(password, username, certificate, auth_type)
@ -43,10 +43,7 @@ public=list(
self$authorize_args <- authorize_args
self$token_args <- token_args
if(self$version == 1)
self$resource <- resource
else self$scope <- sapply(resource, verify_v2_scope, USE.NAMES=FALSE)
# set the "real" init method based on auth type
private$initfunc <- switch(self$auth_type,
authorization_code=init_authcode,
device_code=init_devcode,
@ -122,7 +119,7 @@ public=list(
list(grant_type="refresh_token", refresh_token=self$credentials$refresh_token))
body <- private$build_access_body(body)
uri <- aad_endpoint(self$aad_host, self$tenant, self$version, "token")
uri <- private$aad_endpoint("token")
httr::POST(uri, body=body, encode="form")
}
else private$initfunc() # reauthenticate if no refresh token
@ -157,16 +154,74 @@ private=list(
self$credentials <- token$credentials
},
build_access_body=function(body=self$client)
{
stopifnot(is.list(self$token_args))
body <- if(self$version == 1)
c(body, self$authorize_args, resource=self$resource)
else c(body, self$authorize_args, scope=paste(self$scope, collapse=" "))
},
# member function to be filled in by initialize()
initfunc=NULL
))
#' @rdname AzureToken
#' @export
AzureTokenV1 <- R6::R6Class("AzureTokenV1", inherit=AzureToken,
public=list(
version=1, # for compatibility
resource=NULL,
initialize=function(resource, ...)
{
self$resource <- resource
super$initialize(...)
}
),
private=list(
build_access_body=function(body=self$client)
{
stopifnot(is.list(self$token_args))
c(body, self$authorize_args, resource=self$resource)
},
aad_endpoint=function(type)
{
uri <- httr::parse_url(self$aad_host)
uri$path <- file.path(self$tenant, "oauth2", type)
httr::build_url(uri)
}
))
#' @rdname AzureToken
#' @export
AzureTokenV2 <- R6::R6Class("AzureTokenV2", inherit=AzureToken,
public=list(
version=2, # for compatibility
scope=NULL,
initialize=function(resource, ...)
{
self$scope <- sapply(resource, verify_v2_scope, USE.NAMES=FALSE)
super$initialize(...)
}
),
private=list(
build_access_body=function(body=self$client)
{
stopifnot(is.list(self$token_args))
c(body, self$authorize_args, scope=paste(self$scope, collapse=" "))
},
aad_endpoint=function(type)
{
uri <- httr::parse_url(self$aad_host)
uri$path <- file.path(self$tenant, "oauth2/v2.0", type)
httr::build_url(uri)
}
))

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

@ -10,11 +10,16 @@ format_auth_header <- function(token)
expiry <- as.POSIXct(as.numeric(token$credentials$expires_on), origin="1970-01-01")
obtained <- expiry - as.numeric(token$credentials$expires_in)
res <- if(token$version == 1)
paste("resource", token$resource)
else paste("scope", paste(token$scope, collapse=" "))
version <- if(token$version == 1) "v1.0" else "v2.0"
if(is_azure_v1_token(token))
{
version <- "v1.0"
res <- paste("resource", token$resource)
}
else
{
version <- "v2.0"
res <- paste("scope", paste(token$scope, collapse=" "))
}
tenant <- token$tenant
if(tenant == "common")

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

@ -4,7 +4,7 @@ init_authcode <- function()
stop("httpuv package must be installed to use authorization_code method", call.=FALSE)
# browse to authorization endpoint to get code
auth_uri <- httr::parse_url(aad_endpoint(self$aad_host, self$tenant, self$version, "authorize") )
auth_uri <- httr::parse_url(private$aad_endpoint("authorize"))
opts <- utils::modifyList(list(
client_id=self$client$client_id,
@ -23,7 +23,7 @@ init_authcode <- function()
code <- listen_for_authcode(auth_uri, host, redirect$port)
# contact token endpoint for token
access_uri <- aad_endpoint(self$aad_host, self$tenant, self$version, "token")
access_uri <- private$aad_endpoint("token")
body <- c(self$client, code=code, redirect_uri=opts$redirect_uri)
httr::POST(access_uri, body=body, encode="form")
@ -33,7 +33,7 @@ init_authcode <- function()
init_devcode <- function()
{
# contact devicecode endpoint to get code
dev_uri <- aad_endpoint(self$aad_host, self$tenant, self$version, "devicecode")
dev_uri <- private$aad_endpoint("devicecode")
body <- private$build_access_body(list(client_id=self$client$client_id))
res <- httr::POST(dev_uri, body=body, encode="form")
@ -43,7 +43,7 @@ init_devcode <- function()
cat(creds$message, "\n")
# poll token endpoint for token
access_uri <- aad_endpoint(self$aad_host, self$tenant, self$version, "token")
access_uri <- private$aad_endpoint("token")
body <- c(self$client, code=creds$device_code)
poll_for_token(access_uri, body, creds$interval, creds$expires_in)
@ -53,7 +53,7 @@ init_devcode <- function()
init_clientcred <- function()
{
# contact token endpoint directly with client credentials
uri <- aad_endpoint(self$aad_host, self$tenant, self$version, "token")
uri <- private$aad_endpoint("token")
body <- private$build_access_body()
httr::POST(uri, body=body, encode="form")
@ -63,7 +63,7 @@ init_clientcred <- function()
init_resowner <- function()
{
# contact token endpoint directly with resource owner username/password
uri <- aad_endpoint(self$aad_host, self$tenant, self$version, "token")
uri <- private$aad_endpoint("token")
body <- private$build_access_body()
httr::POST(uri, body=body, encode="form")

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

@ -50,7 +50,7 @@
#' 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.
#' For `get_azure_token`, an object of class either `AzureTokenV1` or `AzureTokenV2` depending on whether the token is for AAD v1.0 or v2.0. For `list_azure_tokens`, a list of such objects retrieved from disk.
#'
#' @seealso
#' [AzureToken], [httr::oauth2.0_token], [httr::Token],
@ -109,8 +109,11 @@ get_azure_token <- function(resource, tenant, app, password=NULL, username=NULL,
aad_host="https://login.microsoftonline.com/", version=1,
authorize_args=list(), token_args=list())
{
AzureToken$new(resource, tenant, app, password, username, certificate, auth_type, aad_host, version,
authorize_args, token_args)
if(normalize_aad_version(version) == 1)
AzureTokenV1$new(resource, tenant, app, password, username, certificate, auth_type, aad_host,
authorize_args, token_args)
else AzureTokenV2$new(resource, tenant, app, password, username, certificate, auth_type, aad_host,
authorize_args, token_args)
}
@ -247,7 +250,13 @@ construct_path <- function(...)
}
#' @param object For `is_azure_token`, an R object.
is_empty <- function(x)
{
is.null(x) || length(x) == 0
}
#' @param object For `is_azure_token`, `is_azure_v1_token` and `is_azure_v2_token`, an R object.
#' @rdname get_azure_token
#' @export
is_azure_token <- function(object)
@ -256,7 +265,17 @@ is_azure_token <- function(object)
}
is_empty <- function(x)
#' @rdname get_azure_token
#' @export
is_azure_v1_token <- function(object)
{
is.null(x) || length(x) == 0
is_azure_token(object) && inherits(object, "AzureTokenV1")
}
#' @rdname get_azure_token
#' @export
is_azure_v2_token <- function(object)
{
is_azure_token(object) && inherits(object, "AzureTokenV2")
}

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

@ -35,20 +35,6 @@ aad_request_credentials <- function(app, password, username, certificate, auth_t
}
aad_endpoint <- function(aad_host, tenant, version=1, type=c("authorize", "token", "devicecode"))
{
type <- match.arg(type)
tenant <- normalize_tenant(tenant)
uri <- httr::parse_url(aad_host)
uri$path <- if(version == 1)
file.path(tenant, "oauth2", type)
else file.path(tenant, "oauth2/v2.0", type)
httr::build_url(uri)
}
normalize_aad_version <- function(v)
{
if(v == "v1.0")

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

@ -3,20 +3,26 @@
\docType{class}
\name{AzureToken}
\alias{AzureToken}
\alias{AzureTokenV1}
\alias{AzureTokenV2}
\title{Azure OAuth authentication}
\format{An R6 object of class \code{AzureToken}.}
\format{An R6 object representing an Azure Active Directory token and its associated credentials. The \code{AzureTokenV1} class is for AAD v1.0 tokens, and the \code{AzureTokenV2} class is for AAD v2.0 tokens. Objects of the AzureToken class should not be created directly.}
\usage{
AzureToken
AzureTokenV1
AzureTokenV2
}
\description{
Azure OAuth 2.0 token class, with an interface based on 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()}}.
Azure OAuth 2.0 token classes, with an interface based on the \link[httr:Token2.0]{Token2.0 class} in httr. Rather than calling the initialization methods 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{refresh}: Refreshes the token. For expired tokens without an associated refresh token, refreshing really means requesting a new token.
\item \code{validate}: Checks if the token is still valid. If there is no associated refresh token, this just checks if the current time is less than the token's expiry time.
\item \code{hash}: Computes an MD5 hash on the input fields of the object. Used internally for identification purposes when caching.
\item \code{cache}: Stores the token on disk for use in future sessions.
}
}

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

@ -7,6 +7,8 @@
\alias{list_azure_tokens}
\alias{token_hash}
\alias{is_azure_token}
\alias{is_azure_v1_token}
\alias{is_azure_v2_token}
\title{Manage Azure Active Directory OAuth 2.0 tokens}
\usage{
get_azure_token(resource, tenant, app, password = NULL,
@ -30,6 +32,10 @@ token_hash(resource, tenant, app, password = NULL, username = NULL,
authorize_args = list(), token_args = list())
is_azure_token(object)
is_azure_v1_token(object)
is_azure_v2_token(object)
}
\arguments{
\item{resource}{For AAD v1.0, the URL of your resource host, or a GUID. For AAD v2.0, a character vector of scopes, each consisting of a URL or GUID along with a path designating the access scope. See 'Details' below.}
@ -58,7 +64,7 @@ is_azure_token(object)
\item{confirm}{For \code{delete_azure_token}, whether to prompt for confirmation before deleting a token.}
\item{object}{For \code{is_azure_token}, an R object.}
\item{object}{For \code{is_azure_token}, \code{is_azure_v1_token} and \code{is_azure_v2_token}, an R object.}
}
\description{
These functions extend the OAuth functionality in httr for use with Azure Active Directory (AAD).
@ -103,7 +109,7 @@ 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.
For \code{get_azure_token}, an object of class either \code{AzureTokenV1} or \code{AzureTokenV2} depending on whether the token is for AAD v1.0 or v2.0. For \code{list_azure_tokens}, a list of such objects retrieved from disk.
}
\examples{