This commit is contained in:
hong-revo 2019-02-09 04:29:40 +11:00
Родитель 61698f49a4
Коммит 33b7275777
7 изменённых файлов: 66 добавлений и 23 удалений

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

@ -45,7 +45,7 @@ public=list(
if(self$version == 1)
self$resource <- resource
else self$scope <- paste0(resource, collapse=" ")
else self$scope <- sapply(resource, verify_v2_scope, USE.NAMES=FALSE)
private$initfunc <- switch(self$auth_type,
authorization_code=init_authcode,
@ -73,7 +73,12 @@ public=list(
# notify user if interactive auth and no refresh token
if(self$auth_type %in% c("authorization_code", "device_code") && is.null(self$credentials$refresh_token))
message("Server did not provide a refresh token. To refresh, you will have to reauthenticate.")
{
if(self$version == 1)
message("Server did not provide a refresh token: please reauthenticate to refresh.")
else message("Server did not provide a refresh token: you will have to reauthenticate to refresh.\n",
"Add the 'offline_access' scope to obtain a refresh token.")
}
self$cache()
self
@ -157,7 +162,7 @@ private=list(
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=self$scope)
else c(body, self$authorize_args, scope=paste(self$scope, collapse=" "))
},
# member function to be filled in by initialize()

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

@ -12,7 +12,7 @@ format_auth_header <- function(token)
res <- if(token$version == 1)
paste("resource", token$resource)
else paste("scope", token$scope)
else paste("scope", paste(token$scope, collapse=" "))
version <- if(token$version == 1) "v1.0" else "v2.0"

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

@ -11,7 +11,7 @@ init_authcode <- function()
response_type="code",
redirect_uri="http://localhost:1410/",
resource=self$resource,
scope=self$scope,
scope=paste(self$scope, collapse=" "),
client_secret=self$client$client_secret,
login_hint=self$client$login_hint,
state=paste0(sample(letters, 20), collapse="") # random nonce

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

@ -233,7 +233,8 @@ token_hash <- function(resource, tenant, app, password=NULL, username=NULL, cert
scope <- NULL
else
{
scope <- paste0(resource, collapse=" ")
# ignore warnings about invalid scopes when computing hash
scope <- suppressWarnings(sapply(resource, verify_v2_scope, USE.NAMES=FALSE))
resource <- NULL
}

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

@ -81,6 +81,8 @@ process_aad_response <- function(res)
}
# need to capture bad scopes before requesting auth code
# v2.0 endpoint will show error page rather than redirecting, causing get_azure_token to wait forever
verify_v2_scope <- function(scope)
{
# some OpenID scopes get a pass
@ -95,7 +97,7 @@ verify_v2_scope <- function(scope)
# is it a URI or GUID?
valid_uri <- grepl("^https?://", scope)
valid_guid <- is_guid(sub("/.+$", "", scope))
valid_guid <- is_guid(sub("/.*$", "", scope))
if(!valid_uri && !valid_guid)
stop("Invalid scope (must be a URI or GUID): ", scope, call.=FALSE)
@ -105,17 +107,17 @@ verify_v2_scope <- function(scope)
uri <- httr::parse_url(scope)
if(uri$path == "")
{
warning("No path supplied for scope ", scope, "; setting to /.default")
warning("No path supplied for scope ", scope, "; setting to /.default", call.=FALSE)
uri$path <- ".default"
scope <- httr::build_url(uri)
}
}
else
{
path <- sub("^[^/]+/", "", scope)
path <- sub("^[^/]+/?", "", scope)
if(path == "")
{
warning("No path supplied for scope ", scope, "; setting to /.default")
warning("No path supplied for scope ", scope, "; setting to /.default", call.=FALSE)
scope <- sub("//", "/", paste0(scope, "/.default"))
}
}

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

@ -24,3 +24,31 @@ test_that("normalize_tenant, normalize_guid work",
expect_identical(normalize_tenant(normalize_tenant("mytenant")), "mytenant.onmicrosoft.com")
})
test_that("verify_v2_scope works",
{
expect_silent(AzureAuth:::verify_v2_scope("https://resource.com/.default"))
# supported OpenID scope
expect_silent(AzureAuth:::verify_v2_scope("offline_access"))
# unsupported OpenID scope
expect_error(AzureAuth:::verify_v2_scope("address"))
# no scope path
expect_warning(newscope <- AzureAuth:::verify_v2_scope("https://resource"))
expect_equal(newscope, "https://resource/.default")
expect_warning(newscope <- AzureAuth:::verify_v2_scope("https://resource/"))
expect_equal(newscope, "https://resource/.default")
# GUIDs
expect_silent(AzureAuth:::verify_v2_scope("12345678901234567890123456789012/.default"))
expect_warning(newscope <- AzureAuth:::verify_v2_scope("12345678901234567890123456789012"))
expect_equal(newscope, "12345678901234567890123456789012/.default")
expect_warning(newscope <- AzureAuth:::verify_v2_scope("12345678901234567890123456789012/"))
expect_equal(newscope, "12345678901234567890123456789012/.default")
# not a URI or GUID
expect_error(AzureAuth:::verify_v2_scope("resource"))
expect_error(AzureAuth:::verify_v2_scope("resource/.default"))
})

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

@ -16,6 +16,7 @@ if(system.file(package="httpuv") == "")
if(!interactive())
skip("Authentication tests skipped: must be an interactive session")
# should get 2 authcode and 2 devcode prompts here
test_that("v2.0 simple authentication works",
{
suppressWarnings(file.remove(dir(AzureR_dir(), full.names=TRUE)))
@ -54,25 +55,13 @@ test_that("v2.0 simple authentication works",
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", version=2)
expect_true(is_azure_token(aut_tok2))
expect_identical(aut_tok2$hash(), aut_hash)
ccd_tok2 <- get_azure_token(res, tenant, app, password=password, version=2)
expect_true(is_azure_token(ccd_tok2))
expect_identical(ccd_tok2$hash(), ccd_hash)
dev_tok2 <- get_azure_token(res, tenant, native_app, auth_type="device_code", version=2)
expect_true(is_azure_token(dev_tok2))
expect_identical(dev_tok2$hash(), dev_hash)
expect_null(delete_azure_token(res, tenant, native_app, auth_type="authorization_code", version=2, confirm=FALSE))
expect_null(delete_azure_token(res, tenant, app, password=password, version=2, confirm=FALSE))
expect_null(delete_azure_token(res, tenant, native_app, auth_type="device_code", version=2, confirm=FALSE))
})
# should only get 1 authcode and 1 devcode prompt here
test_that("v2.0 refresh with offline scope works",
{
res <- "https://management.azure.com/.default"
@ -96,12 +85,20 @@ test_that("v2.0 refresh with offline scope works",
expect_true(as.numeric(aut_tok$credentials$expires_on) > aut_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(c(res, res2), tenant, native_app, auth_type="authorization_code", version=2)
expect_true(is_azure_token(aut_tok2))
dev_tok2 <- get_azure_token(c(res, res2), tenant, native_app, auth_type="device_code", version=2)
expect_true(is_azure_token(dev_tok2))
expect_null(
delete_azure_token(c(res, res2), tenant, native_app, auth_type="authorization_code", version=2, confirm=FALSE))
expect_null(delete_azure_token(c(res, res2), tenant, native_app, auth_type="device_code", version=2, confirm=FALSE))
})
# should get 1 authcode screen here
test_that("Providing optional args works",
{
res <- "https://management.azure.com/.default"
@ -119,3 +116,13 @@ test_that("Providing optional args works",
confirm=FALSE))
})
test_that("Dubious requests handled gracefully",
{
badres <- "resource"
expect_error(get_azure_token(badres, tenant, app, password=password, version=2))
nopath <- "https://management.azure.com"
expect_warning(tok <- get_azure_token(nopath, tenant, app, password=password, version=2))
expect_equal(tok$scope, "https://management.azure.com/.default")
})