From 0afb977aec1170ad270ec2c17c3583e9f55bdba1 Mon Sep 17 00:00:00 2001 From: Hong Ooi Date: Tue, 12 Jan 2021 04:16:49 +1100 Subject: [PATCH] 1.3.0 update (#34) * change org * fix 'organizations' and 'consumers' endpoints * test fixups * document * better expiry calc * set_expiry_time logic fix * add grant_type to token request for device code * change org back * update licensing, version no * bump to 1.3.0 --- .github/workflows/check-standard.yaml | 6 +- DESCRIPTION | 6 +- LICENSE | 2 +- NEWS.md | 5 ++ R/AzureToken.R | 33 ++++++---- R/initfuncs.R | 1 + R/jwt.R | 12 ++-- R/normalize.R | 8 +-- R/token.R | 14 ++++- man/AzureR_dir.Rd | 2 +- man/AzureToken.Rd | 22 ++----- man/authorization.Rd | 20 +++++-- man/get_azure_token.Rd | 86 ++++++++++++++++++++------- man/guid.Rd | 4 +- tests/testthat/test11_v1_token_misc.R | 8 ++- tests/testthat/test21_v2_token_misc.R | 22 +++++-- 16 files changed, 170 insertions(+), 81 deletions(-) diff --git a/.github/workflows/check-standard.yaml b/.github/workflows/check-standard.yaml index d2dfb7e..5a5d308 100644 --- a/.github/workflows/check-standard.yaml +++ b/.github/workflows/check-standard.yaml @@ -6,7 +6,7 @@ name: R-CMD-check jobs: R-CMD-check: - if: github.repository_owner == 'Azure' + if: github.repository_owner != 'cloudyr' runs-on: ${{ matrix.config.os }} name: ${{ matrix.config.os }} (${{ matrix.config.r }}) @@ -29,7 +29,7 @@ jobs: fetch-depth: 0 # required for mirroring, see https://stackoverflow.com/a/64272409/474349 - name: Copy to Cloudyr - if: runner.os == 'Linux' && github.ref == 'refs/heads/master' + if: runner.os == 'Linux' && github.ref == 'refs/heads/master' && github.repository_owner == 'Azure' env: token: "${{ secrets.ghPat }}" # git config hack required, see https://stackoverflow.com/q/64270867/474349 @@ -91,7 +91,7 @@ jobs: path: check - name: Update Cloudyr drat - if: success() && runner.os == 'Linux' && github.ref == 'refs/heads/master' + if: success() && runner.os == 'Linux' && github.ref == 'refs/heads/master' && github.repository_owner == 'Azure' env: token: "${{ secrets.ghPat }}" run: | diff --git a/DESCRIPTION b/DESCRIPTION index aa86b12..228c910 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: AzureAuth Title: Authentication Services for Azure Active Directory -Version: 1.2.5 +Version: 1.3.0 Authors@R: c( person("Hong", "Ooi", , "hongooi73@gmail.com", role = c("aut", "cre")), person("httr development team", role="ctb", comment="Original OAuth listener code"), @@ -31,5 +31,5 @@ Suggests: shiny, AzureRMR, AzureGraph -Roxygen: list(markdown=TRUE) -RoxygenNote: 6.1.1 +Roxygen: list(markdown=TRUE, r6=FALSE) +RoxygenNote: 7.1.1 diff --git a/LICENSE b/LICENSE index 0aece56..9f5d8b1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2019 +YEAR: 2019-2021 COPYRIGHT HOLDER: Microsoft diff --git a/NEWS.md b/NEWS.md index 8eb2086..2bd9a2a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# AzureAuth 1.3.0 + +- Allow obtaining tokens for the `organizations` and `consumers` generic tenants, in addition to `common`. +- More robust handling of expiry time calculation for AAD v2.0 authentication. + # AzureAuth 1.2.5 - Change maintainer email address. diff --git a/R/AzureToken.R b/R/AzureToken.R index f5f642c..44c41c2 100644 --- a/R/AzureToken.R +++ b/R/AzureToken.R @@ -58,12 +58,14 @@ public=list( if(use_cache) private$load_cached_credentials() + # time of initial request for token: in case we need to set expiry time manually + request_time <- Sys.time() if(is.null(self$credentials)) { res <- private$initfunc(auth_info) self$credentials <- process_aad_response(res) } - private$set_expiry_time() + private$set_expiry_time(request_time) if(private$use_cache) self$cache() @@ -102,6 +104,7 @@ public=list( refresh=function() { + request_time <- Sys.time() res <- if(!is.null(self$credentials$refresh_token)) { body <- list(grant_type="refresh_token", @@ -127,7 +130,7 @@ public=list( } self$credentials <- creds - private$set_expiry_time() + private$set_expiry_time(request_time) if(private$use_cache) self$cache() @@ -164,22 +167,30 @@ private=list( self$refresh() }, - set_expiry_time=function() + set_expiry_time=function(request_time) { # v2.0 endpoint doesn't provide an expires_on field, set it here if(is.null(self$credentials$expires_on)) { - expiry <- try(as.character(decode_jwt(self$credentials$access_token)$payload$exp), silent=TRUE) + expiry <- try(decode_jwt(self$credentials$access_token)$payload$exp, silent=TRUE) if(inherits(expiry, "try-error")) + expiry <- try(decode_jwt(self$credentials$id_token)$payload$exp, silent=TRUE) + if(inherits(expiry, "try-error")) + expiry <- NA + + expires_in <- if(!is.null(self$credentials$expires_in)) + as.numeric(self$credentials$expires_in) + else NA + + request_time <- floor(as.numeric(request_time)) + expires_on <- request_time + expires_in + + self$credentials$expires_on <- if(is.na(expiry) && is.na(expires_on)) { - expiry <- try(as.character(decode_jwt(self$credentials$id_token)$payload$exp), silent=TRUE) - if(inherits(expiry, "try-error")) - { - warning("Expiry date not found", call.=FALSE) - expiry <- NA - } + warning("Could not set expiry time, using default validity period of 1 hour") + as.character(as.numeric(request_time + 3600)) } - self$credentials$expires_on <- expiry + else as.character(as.numeric(min(expiry, expires_on, na.rm=TRUE))) } }, diff --git a/R/initfuncs.R b/R/initfuncs.R index ead394a..413d079 100644 --- a/R/initfuncs.R +++ b/R/initfuncs.R @@ -47,6 +47,7 @@ poll_for_token <- function(url, body, interval, period) { interval <- as.numeric(interval) ntries <- as.numeric(period) %/% interval + body$grant_type <- "urn:ietf:params:oauth:grant-type:device_code" message("Waiting for device code in browser...\nPress Esc/Ctrl + C to abort") for(i in seq_len(ntries)) diff --git a/R/jwt.R b/R/jwt.R index 9c270f6..029cfa1 100644 --- a/R/jwt.R +++ b/R/jwt.R @@ -30,8 +30,10 @@ decode_jwt <- function(token, ...) #' @export decode_jwt.AzureToken <- function(token, type=c("access", "id"), ...) { - type <- match.arg(type) - decode_jwt(token$credentials[[paste0(type, "_token")]]) + type <- paste0(match.arg(type), "_token") + if(is.null(token$credentials[[type]])) + stop(type, " not found", call.=FALSE) + decode_jwt(token$credentials[[type]]) } @@ -39,8 +41,10 @@ decode_jwt.AzureToken <- function(token, type=c("access", "id"), ...) #' @export decode_jwt.Token <- function(token, type=c("access", "id"), ...) { - type <- match.arg(type) - decode_jwt(token$credentials[[paste0(type, "_token")]]) + type <- paste0(match.arg(type), "_token") + if(is.null(token$credentials[[type]])) + stop(type, " not found", call.=FALSE) + decode_jwt(token$credentials[[type]]) } diff --git a/R/normalize.R b/R/normalize.R index fddd3b1..b640f0f 100644 --- a/R/normalize.R +++ b/R/normalize.R @@ -9,8 +9,8 @@ #' 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` +#' 3. Otherwise, if it is one of the generic tenants "common", "organizations" or "consumers", return it +#' 4. Otherwise, append ".onmicrosoft.com" to it #' #' These functions are vectorised. See the link below for the GUID formats they accept. #' @@ -59,11 +59,11 @@ normalize_tenant <- function(tenant) tenant <- tolower(tenant) # check if supplied a guid; if not, check if a fqdn; - # if not, check if 'common'; if not, append '.onmicrosoft.com' + # if not, check if 'common', 'organizations' or 'consumers'; if not, append '.onmicrosoft.com' guid <- is_guid(tenant) tenant[guid] <- normalize_guid(tenant[guid]) - name <- !guid & (tenant != "common") & !grepl(".", tenant, fixed=TRUE) + name <- !guid & !(tenant %in% c("common", "organizations", "consumers")) & !grepl(".", tenant, fixed=TRUE) tenant[name] <- paste0(tenant[name], ".onmicrosoft.com") tenant diff --git a/R/token.R b/R/token.R index 11a7c04..bf23496 100644 --- a/R/token.R +++ b/R/token.R @@ -3,14 +3,14 @@ #' Use these functions to authenticate with Azure Active Directory (AAD). #' #' @param 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. -#' @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 tenant Your tenant. This can be a name ("myaadtenant"), a fully qualified domain name ("myaadtenant.onmicrosoft.com" or "mycompanyname.com"), or a GUID. It can also be one of the generic tenants "common", "organizations" or "consumers"; see 'Generic tenants' below. #' @param app The client/app ID to use to authenticate with. #' @param password For most authentication flows, this is the password for the _app_ where needed, also known as the client secret. For the resource owner grant, this is your personal account password. See 'Details' below. #' @param username Your AAD username, if using the resource owner grant. See 'Details' below. #' @param certificate A file containing the certificate for authenticating with (including the private key), an Azure Key Vault certificate object, or a call to the `cert_assertion` function to build a client assertion with a certificate. See 'Certificate authentication' 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. Can also be a full URL, eg `https://mydomain.b2clogin.com/mydomain/other/path/names/oauth2` (this is relevant mainly for Azure B2C logins). -#' @param version The AAD version, either 1 or 2. +#' @param version The AAD version, either 1 or 2. Authenticating with a personal account as opposed to a work or school account requires AAD 2.0. #' @param authorize_args An optional list of further parameters for the AAD authorization endpoint. These will be included in the request URI as query parameters. Only used if `auth_type="authorization_code"`. #' @param token_args An optional list of further parameters for the token endpoint. These will be included in the body of the request for `get_azure_token`, or as URI query parameters for `get_managed_token`. #' @param use_cache If TRUE and cached credentials exist, use them instead of obtaining a new token. Set this to FALSE to bypass the cache. @@ -54,6 +54,16 @@ #' - A certificate object from the AzureKeyVault package, representing a cert stored in the Key Vault service. #' - A call to the `cert_assertion()` function to customise details of the requested token, eg the duration, expiry date, custom claims, etc. See the examples below. #' +#' @section Generic tenants: +#' +#' There are 3 generic values that can be used as tenants when authenticating: +#' +#' | Tenant | Description | +#' | ------ | ----------- | +#' | `common` | Allows users with both personal Microsoft accounts and work/school accounts from Azure AD to sign into the application. | +#' | `organizations` | Allows only users with work/school accounts from Azure AD to sign into the application. | +#' | `consumers` | Allows only users with personal Microsoft accounts (MSA) to sign into the application. | +#' #' @section OpenID Connect: #' `get_azure_token` can be used to obtain ID tokens along with regular OAuth access tokens, when using an interactive authentication flow (authorization_code or device_code). The behaviour depends on the AAD version: #' - AAD v1.0 will return an ID token as well as the access token by default; you don't have to do anything extra. However, AAD v1.0 will not _refresh_ the ID token when it expires; you must reauthenticate to get a new one. To ensure you don't pull the cached version of the credentials, specify `use_cache=FALSE` in the calls to `get_azure_token`. diff --git a/man/AzureR_dir.Rd b/man/AzureR_dir.Rd index ee5fcb4..2893a51 100644 --- a/man/AzureR_dir.Rd +++ b/man/AzureR_dir.Rd @@ -13,7 +13,7 @@ A string containing the data directory. Data directory for AzureR packages } \details{ -AzureAuth can save your authentication credentials 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}. On Unix/Linux, it will be in \code{~/.local/share/AzureR}, and on MacOS, it will be in \code{~/Library/Application Support/AzureR}. The working directory is not touched (which significantly lessens the risk of accidentally introducing cached tokens into source control). This directory is also used by other AzureR packages, notably AzureRMR (for storing Resource Manager logins) and AzureGraph (for Microsoft Graph logins). +AzureAuth can save your authentication credentials in a user-specific directory, using the rappdirs package. On recent Windows versions, this will usually be in the location \verb{C:\\\\Users\\\\(username)\\\\AppData\\\\Local\\\\AzureR}. On Unix/Linux, it will be in \verb{~/.local/share/AzureR}, and on MacOS, it will be in \verb{~/Library/Application Support/AzureR}. The working directory is not touched (which significantly lessens the risk of accidentally introducing cached tokens into source control). This directory is also used by other AzureR packages, notably AzureRMR (for storing Resource Manager logins) and AzureGraph (for Microsoft Graph logins). On package startup, if this directory does not exist, AzureAuth 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. The prompt only appears in an interactive session; if AzureAuth is loaded in a batch script, the directory is not created if it doesn't already exist. } diff --git a/man/AzureToken.Rd b/man/AzureToken.Rd index d856727..d18cb83 100644 --- a/man/AzureToken.Rd +++ b/man/AzureToken.Rd @@ -10,24 +10,11 @@ \alias{AzureTokenResOwner} \alias{AzureTokenManaged} \title{Azure OAuth authentication} -\format{An R6 object representing an Azure Active Directory token and its associated credentials. \code{AzureToken} is the base class, and the others inherit from it.} -\usage{ -AzureToken - -AzureTokenAuthCode - -AzureTokenDeviceCode - -AzureTokenClientCreds - -AzureTokenOnBehalfOf - -AzureTokenResOwner - -AzureTokenManaged +\format{ +An R6 object representing an Azure Active Directory token and its associated credentials. \code{AzureToken} is the base class, and the others inherit from it. } \description{ -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()}}. +Azure OAuth 2.0 token classes, with an interface based on the \link[httr:Token-class]{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}{ @@ -40,6 +27,5 @@ Azure OAuth 2.0 token classes, with an interface based on the \link[httr:Token2. } \seealso{ -\link{get_azure_token}, \link[httr:Token]{httr::Token} +\link{get_azure_token}, \link[httr:Token-class]{httr::Token} } -\keyword{datasets} diff --git a/man/authorization.Rd b/man/authorization.Rd index 3ac90fe..8628d38 100644 --- a/man/authorization.Rd +++ b/man/authorization.Rd @@ -5,11 +5,23 @@ \alias{get_device_creds} \title{Standalone OAuth authorization functions} \usage{ -build_authorization_uri(resource, tenant, app, username = NULL, ..., - aad_host = "https://login.microsoftonline.com/", version = 1) +build_authorization_uri( + resource, + tenant, + app, + username = NULL, + ..., + aad_host = "https://login.microsoftonline.com/", + version = 1 +) -get_device_creds(resource, tenant, app, - aad_host = "https://login.microsoftonline.com/", version = 1) +get_device_creds( + resource, + tenant, + app, + aad_host = "https://login.microsoftonline.com/", + version = 1 +) } \arguments{ \item{resource, tenant, app, aad_host, version}{See the corresponding arguments for \link{get_azure_token}.} diff --git a/man/get_azure_token.Rd b/man/get_azure_token.Rd index 1589def..3961cc2 100644 --- a/man/get_azure_token.Rd +++ b/man/get_azure_token.Rd @@ -15,17 +15,40 @@ \usage{ get_managed_token(resource, token_args = list(), use_cache = TRUE) -get_azure_token(resource, tenant, app, password = NULL, - username = NULL, certificate = NULL, auth_type = NULL, - aad_host = "https://login.microsoftonline.com/", version = 1, - authorize_args = list(), token_args = list(), use_cache = TRUE, - on_behalf_of = NULL, auth_code = NULL, device_creds = NULL) +get_azure_token( + resource, + tenant, + app, + password = NULL, + username = NULL, + certificate = NULL, + auth_type = NULL, + aad_host = "https://login.microsoftonline.com/", + version = 1, + authorize_args = list(), + token_args = list(), + use_cache = TRUE, + on_behalf_of = NULL, + auth_code = NULL, + device_creds = NULL +) -delete_azure_token(resource, tenant, app, password = NULL, - username = NULL, certificate = NULL, auth_type = NULL, - aad_host = "https://login.microsoftonline.com/", version = 1, - authorize_args = list(), token_args = list(), on_behalf_of = NULL, - hash = NULL, confirm = TRUE) +delete_azure_token( + resource, + tenant, + app, + password = NULL, + username = NULL, + certificate = NULL, + auth_type = NULL, + aad_host = "https://login.microsoftonline.com/", + version = 1, + authorize_args = list(), + token_args = list(), + on_behalf_of = NULL, + hash = NULL, + confirm = TRUE +) load_azure_token(hash) @@ -33,10 +56,20 @@ clean_token_directory(confirm = TRUE) list_azure_tokens() -token_hash(resource, tenant, app, password = NULL, username = NULL, - certificate = NULL, auth_type = NULL, - aad_host = "https://login.microsoftonline.com/", version = 1, - authorize_args = list(), token_args = list(), on_behalf_of = NULL) +token_hash( + resource, + tenant, + app, + password = NULL, + username = NULL, + certificate = NULL, + auth_type = NULL, + aad_host = "https://login.microsoftonline.com/", + version = 1, + authorize_args = list(), + token_args = list(), + on_behalf_of = NULL +) is_azure_token(object) @@ -51,7 +84,7 @@ is_azure_v2_token(object) \item{use_cache}{If TRUE and cached credentials exist, use them instead of obtaining a new token. Set this to FALSE to bypass the cache.} -\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{tenant}{Your tenant. This can be a name ("myaadtenant"), a fully qualified domain name ("myaadtenant.onmicrosoft.com" or "mycompanyname.com"), or a GUID. It can also be one of the generic tenants "common", "organizations" or "consumers"; see 'Generic tenants' below.} \item{app}{The client/app ID to use to authenticate with.} @@ -63,9 +96,9 @@ is_azure_v2_token(object) \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. Can also be a full URL, eg \code{https://mydomain.b2clogin.com/mydomain/other/path/names/oauth2} (this is relevant mainly for Azure B2C logins).} +\item{aad_host}{URL for your AAD host. For the public Azure cloud, this is \verb{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud. Can also be a full URL, eg \verb{https://mydomain.b2clogin.com/mydomain/other/path/names/oauth2} (this is relevant mainly for Azure B2C logins).} -\item{version}{The AAD version, either 1 or 2.} +\item{version}{The AAD version, either 1 or 2. Authenticating with a personal account as opposed to a work or school account requires AAD 2.0.} \item{authorize_args}{An optional list of further parameters for the AAD authorization endpoint. These will be included in the request URI as query parameters. Only used if \code{auth_type="authorization_code"}.} @@ -89,7 +122,7 @@ Use these functions to authenticate with Azure Active Directory (AAD). \code{get_managed_token} is a specialised function to acquire tokens for a \emph{managed identity}. This is an Azure service, such as a VM or container, that has been assigned its own identity and can be granted access permissions like a regular user. The advantage of managed identities over the other authentication methods (see below) is that you don't have to store a secret password, which improves security. Note that \code{get_managed_token} can only be used from within the managed identity itself. -The \code{resource} arg should be a single URL or GUID for AAD v1.0. For AAD v2.0, it should be a vector of \emph{scopes}, where each scope consists of a URL or GUID along with a path that designates the type of access requested. If a v2.0 scope doesn't have a path, \code{get_azure_token} will append the \code{/.default} path with a warning. A special scope is \code{offline_access}, which requests a refresh token from AAD along with the access token: without this scope, you will have to reauthenticate if you want to refresh the token. +The \code{resource} arg should be a single URL or GUID for AAD v1.0. For AAD v2.0, it should be a vector of \emph{scopes}, where each scope consists of a URL or GUID along with a path that designates the type of access requested. If a v2.0 scope doesn't have a path, \code{get_azure_token} will append the \verb{/.default} path with a warning. A special scope is \code{offline_access}, which requests a refresh token from AAD along with the access token: without this scope, you will have to reauthenticate if you want to refresh the token. The \code{auth_code} and \code{device_creds} arguments are intended for use in embedded scenarios, eg when AzureAuth is loaded from within a Shiny web app. They enable the flow authorization step to be separated from the token acquisition step, which is necessary within an app; you can generally ignore these arguments when using AzureAuth interactively or as part of an R script. See the help for \link{build_authorization_uri} for examples on their use. @@ -124,6 +157,17 @@ OAuth tokens can be authenticated via an SSL/TLS certificate, which is considere } } +\section{Generic tenants}{ + + +There are 3 generic values that can be used as tenants when authenticating:\tabular{ll}{ + Tenant \tab Description \cr + \code{common} \tab Allows users with both personal Microsoft accounts and work/school accounts from Azure AD to sign into the application. \cr + \code{organizations} \tab Allows only users with work/school accounts from Azure AD to sign into the application. \cr + \code{consumers} \tab Allows only users with personal Microsoft accounts (MSA) to sign into the application. \cr +} +} + \section{OpenID Connect}{ \code{get_azure_token} can be used to obtain ID tokens along with regular OAuth access tokens, when using an interactive authentication flow (authorization_code or device_code). The behaviour depends on the AAD version: @@ -135,7 +179,7 @@ OAuth tokens can be authenticated via an SSL/TLS certificate, which is considere \section{Caching}{ -AzureAuth caches tokens based on all the inputs to \code{get_azure_token} as listed above. Tokens are cached in a custom, user-specific directory, created with the rappdirs package. On recent Windows versions, this will usually be in the location \code{C:\\Users\\(username)\\AppData\\Local\\AzureR}. On Linux, it will be in \code{~/.config/AzureR}, and on MacOS, it will be in \code{~/Library/Application Support/AzureR}. 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). +AzureAuth caches tokens based on all the inputs to \code{get_azure_token} as listed above. Tokens are cached in a custom, user-specific directory, created with the rappdirs package. On recent Windows versions, this will usually be in the location \verb{C:\\\\Users\\\\(username)\\\\AppData\\\\Local\\\\AzureR}. On Linux, it will be in \verb{~/.config/AzureR}, and on MacOS, it will be in \verb{~/Library/Application Support/AzureR}. 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. @@ -148,7 +192,7 @@ To delete \emph{all} cached tokens, use \code{clean_token_directory}. A token object can be refreshed by calling its \code{refresh()} method. If the token's credentials contain a refresh token, this is used; otherwise a new access token is obtained by reauthenticating. -Note that in AAD, a refresh token can be used to obtain an access token for any resource or scope that you have permissions for. Thus, for example, you could use a refresh token issued on a request for \code{https://management.azure.com} to obtain a new access token for \code{https://graph.microsoft.com} (assuming you've been granted permission). +Note that in AAD, a refresh token can be used to obtain an access token for any resource or scope that you have permissions for. Thus, for example, you could use a refresh token issued on a request for \verb{https://management.azure.com} to obtain a new access token for \verb{https://graph.microsoft.com} (assuming you've been granted permission). To obtain an access token for a new resource, change the object's \code{resource} (for an AAD v1.0 token) or \code{scope} field (for an AAD v2.0 token) before calling \code{refresh()}. If you \emph{also} want to retain the token for the old resource, you should call the \code{clone()} method first to create a copy. See the examples below. } @@ -276,7 +320,7 @@ tok2$refresh() } } \seealso{ -\link{AzureToken}, \link[httr:oauth2.0_token]{httr::oauth2.0_token}, \link[httr:Token]{httr::Token}, \link{cert_assertion}, +\link{AzureToken}, \link[httr:oauth2.0_token]{httr::oauth2.0_token}, \link[httr:Token-class]{httr::Token}, \link{cert_assertion}, \link{build_authorization_uri}, \link{get_device_creds} \href{https://docs.microsoft.com/en-us/azure/active-directory/develop/}{Azure Active Directory for developers}, diff --git a/man/guid.Rd b/man/guid.Rd index 9227679..67329a3 100644 --- a/man/guid.Rd +++ b/man/guid.Rd @@ -32,8 +32,8 @@ A tenant can be identified either by a GUID, or its name, or a fully-qualified d \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} +\item Otherwise, if it is one of the generic tenants "common", "organizations" or "consumers", return it +\item Otherwise, append ".onmicrosoft.com" to it } These functions are vectorised. See the link below for the GUID formats they accept. diff --git a/tests/testthat/test11_v1_token_misc.R b/tests/testthat/test11_v1_token_misc.R index bc576f8..777bb06 100644 --- a/tests/testthat/test11_v1_token_misc.R +++ b/tests/testthat/test11_v1_token_misc.R @@ -10,6 +10,7 @@ cert_file <- Sys.getenv("AZ_TEST_CERT_FILE") web_app <- Sys.getenv("AZ_TEST_WEB_APP_ID") web_app_pwd <- Sys.getenv("AZ_TEST_WEB_APP_PASSWORD") userpwd <- Sys.getenv("AZ_TEST_USERPWD") +admin_username <- Sys.getenv("AZ_TEST_ADMINUSERNAME") if(tenant == "" || app == "" || username == "" || password == "" || native_app == "" || cert_app == "" || cert_file == "" || web_app == "" || web_app_pwd == "" || userpwd == "") @@ -37,12 +38,13 @@ test_that("Providing optional args works", res <- "https://management.azure.com/" # login hint - aut_tok <- get_azure_token(res, tenant, native_app, username=username, auth_type="authorization_code") + aut_tok <- get_azure_token(res, tenant, native_app, username=admin_username, auth_type="authorization_code") expect_true(is_azure_token(aut_tok)) expect_identical(res, decode_jwt(aut_tok)$payload$aud) expect_null( - delete_azure_token(res, tenant, native_app, username=username, auth_type="authorization_code", confirm=FALSE)) + delete_azure_token(res, tenant, native_app, username=admin_username, auth_type="authorization_code", + confirm=FALSE)) }) @@ -116,7 +118,7 @@ test_that("Webapp authentication works", expect_identical(tok2$auth_type, "client_credentials") expect_identical(res, decode_jwt(tok2)$payload$aud) - tok3 <- get_azure_token(res, tenant, web_app, password=web_app_pwd, username=username, + tok3 <- get_azure_token(res, tenant, web_app, password=web_app_pwd, username=admin_username, auth_type="authorization_code") expect_true(is_azure_token(tok2)) expect_identical(res, decode_jwt(tok3)$payload$aud) diff --git a/tests/testthat/test21_v2_token_misc.R b/tests/testthat/test21_v2_token_misc.R index dddbb08..08443f6 100644 --- a/tests/testthat/test21_v2_token_misc.R +++ b/tests/testthat/test21_v2_token_misc.R @@ -10,6 +10,7 @@ cert_file <- Sys.getenv("AZ_TEST_CERT_FILE") web_app <- Sys.getenv("AZ_TEST_WEB_APP_ID") web_app_pwd <- Sys.getenv("AZ_TEST_WEB_APP_PASSWORD") userpwd <- Sys.getenv("AZ_TEST_USERPWD") +admin_username <- Sys.getenv("AZ_TEST_ADMINUSERNAME") if(tenant == "" || app == "" || username == "" || password == "" || native_app == "" || cert_app == "" || cert_file == "" || web_app == "" || web_app_pwd == "" || userpwd == "") @@ -38,13 +39,14 @@ test_that("Providing optional args works", res <- "https://management.azure.com/.default" resbase <- "https://management.azure.com" - aut_tok <- get_azure_token(res, tenant, native_app, username=username, auth_type="authorization_code", version=2) + aut_tok <- get_azure_token(res, tenant, native_app, username=admin_username, auth_type="authorization_code", + version=2) expect_true(is_azure_token(aut_tok)) expect_identical(resbase, decode_jwt(aut_tok)$payload$aud) expect_null( - delete_azure_token(res, tenant, native_app, username=username, auth_type="authorization_code", version=2, - confirm=FALSE)) + delete_azure_token(res, tenant, native_app, username=admin_username, auth_type="authorization_code", version=2, + confirm=FALSE)) }) @@ -150,7 +152,7 @@ test_that("Webapp authentication works", expect_identical(tok2$auth_type, "client_credentials") expect_identical(resbase, decode_jwt(tok2)$payload$aud) - tok3 <- get_azure_token(res, tenant, web_app, password=web_app_pwd, username=username, + tok3 <- get_azure_token(res, tenant, web_app, password=web_app_pwd, username=admin_username, auth_type="authorization_code", version=2) expect_true(is_azure_token(tok2)) expect_identical(resbase, decode_jwt(tok3)$payload$aud) @@ -185,3 +187,15 @@ test_that("Refreshing with changed resource works", tok$refresh() expect_identical(decode_jwt(tok)$payload$aud, "https://graph.microsoft.com") }) + + +test_that("Consumers tenant works", +{ + res <- "https://graph.microsoft.com/.default" + res2 <- "offline_access" + res3 <- "openid" + + tok <- get_azure_token(c(res, res2, res3), "consumers", cli_app, version=2) + expect_error(decode_jwt(tok)) + expect_identical(decode_jwt(tok, "id")$payload$tid, "9188040d-6c67-4c5b-b112-36a304b66dad") +})