diff --git a/R/Microsoft365R.R b/R/Microsoft365R.R index 6aa72a2..c4ba1b3 100644 --- a/R/Microsoft365R.R +++ b/R/Microsoft365R.R @@ -5,6 +5,9 @@ utils::globalVariables(c("self", "private")) .onLoad <- function(libname, pkgname) { + # set Graph API to beta, for more powerful permissions + options(azure_graph_api_version="beta") + register_graph_class("site", ms_site, function(props) grepl("sharepoint", props$id, fixed=TRUE)) @@ -20,9 +23,12 @@ utils::globalVariables(c("self", "private")) add_methods() } -# authentication app ID +# default app ID .microsoft365r_app_id <- "d44a05d5-c6a5-4bbb-82d2-443123722380" +# CLI for Microsoft 365 app ID +.cli_microsoft365_app_id <- "31359c7f-bd7e-475c-86db-fdb8c937548e" + # helper function error_message <- get("error_message", getNamespace("AzureGraph")) diff --git a/R/client.R b/R/client.R index ced86dd..8d8331d 100644 --- a/R/client.R +++ b/R/client.R @@ -1,7 +1,7 @@ #' OneDrive and Sharepoint Online clients #' #' @param tenant For `business_onedrive` and `sharepoint_site`, the name of your Azure Active Directory (AAD) tenant. If not supplied, use the value of the `CLIMICROSOFT365_TENANT` environment variable, or "common" if that is unset. -#' @param app A custom app registration ID to use for authentication. If not supplied, use the value of the `CLIMICROSOFT365_AADAPPID` environment variable, or an internal app ID if that is unset. +#' @param app A custom app registration ID to use for authentication. For `personal_onedrive`, the default is to use Microsoft365R's internal app ID. For `business_onedrive` and `sharepoint_site`, see below. #' @param scopes The Microsoft Graph scopes (permissions) to obtain. #' @param site_url,site_id For `sharepoint_site`, the web URL and ID of the SharePoint site to retrieve. Supply one or the other, but not both. #' @param ... Optional arguments to be passed to `AzureGraph::create_graph_login`. @@ -10,20 +10,24 @@ #' #' When authenticating, you can pass optional arguments in `...` which will ultimately be received by `AzureAuth::get_azure_token`. In particular, if your machine doesn't have a web browser available to authenticate with (for example if you are in a remote RStudio Server session), pass `auth_type="device_code"` which is intended for such scenarios. #' -#' The default "common" tenant for `business_onedrive` and `sharepoint_site` attempts to detect your actual tenant from your saved credentials in your browser. This may not always succeed, for example if you have a personal account that is also a guest account in a tenant. In this case, supply the actual tenant name, either in the `tenant` argument or in the `CLIMICROSOFT365_TENANT` environment variable. The latter allows sharing authentication details with the CLI for Microsoft 365. +#' @section Authenticating to Microsoft 365 Business services: +#' Authenticating to Microsoft 365 Business services (SharePoint and OneDrive for Business) has some specific complexities. #' -#' For authentication purposes, Microsoft365R is registered as an app in the "aicatr" AAD tenant. Depending on your organisation's security policy, you may have to get an admin to grant it access to your tenant. +#' The default "common" tenant for `business_onedrive` and `sharepoint_site` attempts to detect your actual tenant from your saved credentials in your browser. This may not always succeed, for example if you have a personal account that is also a guest account in a tenant. In this case, supply the actual tenant name, either in the `tenant` argument or in the `CLIMICROSOFT365_TENANT` environment variable. The latter allows sharing authentication details with the [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/). #' -#' As an alternative to using the default app ID, you (or your admin) can create your own app registration. It should have a native redirect URI of `http://localhost:1410`, and the "public client" option should be enabled if you want to use the device code authentication flow. You can supply your app ID either via the `app` argument, or in the environment variable `CLIMICROSOFT365_AADAPPID`. +#' The default when authenticating to these services is for Microsoft365R to use its own internal app ID. Depending on your organisation's security policy, you may have to get an admin to grant it access to your tenant. As an alternative to the default app ID, you (or your admin) can create your own app registration: it should have a native redirect URI of `http://localhost:1410`, and the "public client" option should be enabled if you want to use the device code authentication flow. You can supply your app ID either via the `app` argument, or in the environment variable `CLIMICROSOFT365_AADAPPID`. #' -#' In addition, for SharePoint (only) it's possible to use the Azure CLI app ID to access document libraries and lists. See the examples below. Be warned, however, that this may attract the attention of your admin! +#' If creating your own app registration is impractical, there are a couple of ways to work around access issues by piggybacking on other well-known apps. Be warned that these solutions may draw the attention of your admin! +#' - If the R option `microsoft365r_use_cli_app_id` is set to a non-NULL value, authentication will be done using the app ID for the CLI for Microsoft 365. Technically this app still requires admin approval, but it is in widespread use and so may already be allowed in your organisation. +#' - For SharePoint (only) it's possible to use the Azure CLI app ID to access document libraries and lists. As a first-party Microsoft app the Azure CLI is available in every AAD tenant, but is not intended for working with Microsoft 365. #' #' @return -#' An object of class `ms_drive`. +#' For `personal_onedrive` and `business_onedrive`, an object of class `ms_drive`. For `sharepoint_site`, an object of class `ms_site`. #' @seealso -#' [ms_drive], [AzureGraph::create_graph_login] +#' [ms_drive], [ms_site], [AzureGraph::create_graph_login], [AzureAuth::get_azure_token] #' -#' [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/) -- where the `CLIMICROSOFT365_AADAPPID` and `CLIMICROSOFT365_TENANT` environment variables come from +#' [CLI for Microsoft 365](https://pnp.github.io/cli-microsoft365/) -- a commandline tool for managing Microsoft 365 +#' [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/what-is-azure-cli) #' @examples #' \dontrun{ #' @@ -43,15 +47,19 @@ #' business_onedrive(app="app_id") #' sharepoint_site("https://mycompany.sharepoint.com/sites/my-site-name", app="app_id") #' -#' # for SharePoint, a fallback is to use the Azure CLI app ID and the '.default' scope: +#' # using the app ID for the CLI for Microsoft 365: set a global option +#' options(microsoft365r_use_cli_app_id=TRUE) +#' business_onedrive() +#' sharepoint_site("https://mycompany.sharepoint.com/sites/my-site-name") +#' +#' # for SharePoint, it's possible to use the Azure CLI app ID: #' sharepoint_site("https://mycompany.sharepoint.com/sites/my-site-name", -#' app=AzureGraph:::.az_cli_app_id, -#' scopes=".default") +#' app=AzureGraph:::.az_cli_app_id) #' #' } #' @rdname client #' @export -personal_onedrive <- function(app=Sys.getenv("CLIMICROSOFT365_AADAPPID", .microsoft365r_app_id), +personal_onedrive <- function(app=.microsoft365r_app_id, scopes=c("Files.ReadWrite.All", "User.Read"), ...) { @@ -61,10 +69,11 @@ personal_onedrive <- function(app=Sys.getenv("CLIMICROSOFT365_AADAPPID", .micros #' @rdname client #' @export business_onedrive <- function(tenant=Sys.getenv("CLIMICROSOFT365_TENANT", "common"), - app=Sys.getenv("CLIMICROSOFT365_AADAPPID", .microsoft365r_app_id), - scopes=c("Files.ReadWrite.All", "User.Read"), + app=Sys.getenv("CLIMICROSOFT365_AADAPPID"), + scopes=".default", ...) { + app <- choose_app(app) do_login(tenant, app, scopes, ...)$get_user()$get_drive() } @@ -72,10 +81,11 @@ business_onedrive <- function(tenant=Sys.getenv("CLIMICROSOFT365_TENANT", "commo #' @export sharepoint_site <- function(site_url=NULL, site_id=NULL, tenant=Sys.getenv("CLIMICROSOFT365_TENANT", "common"), - app=Sys.getenv("CLIMICROSOFT365_AADAPPID", .microsoft365r_app_id), - scopes=c("Sites.ReadWrite.All", "User.Read"), + app=Sys.getenv("CLIMICROSOFT365_AADAPPID"), + scopes=".default", ...) { + app <- choose_app(app) do_login(tenant, app, scopes, ...)$get_sharepoint_site(site_url, site_id) } @@ -87,3 +97,15 @@ do_login <- function(tenant, app, scopes, ...) login <- create_graph_login(tenant, app=app, scopes=scopes, ...) login } + + +choose_app <- function(app) +{ + if(is.null(app) || app == "") + { + if(!is.null(getOption("microsoft365r_use_cli_app_id"))) + .cli_microsoft365_app_id + else .microsoft365r_app_id + } + else app +} diff --git a/README.md b/README.md index 4fe26df..f22d29c 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,12 @@ The first time you call one of the Microsoft365R functions (see below), it will For authentication purposes, the package is registered as an app in the 'aicatr' AAD tenant; depending on your organisation's security policy, you may have to get an admin to grant it access to your tenant. The default permissions requested are: -- For `personal_onedrive`: Files.ReadWrite.All, User.Read -- For `business_onedrive`: Files.ReadWrite.All, User.Read -- For `sharepoint_site`: Sites.ReadWrite.All, User.Read +- User.Read +- Files.ReadWrite.All +- Group.ReadWrite.All +- profile +- email +- openid If the environment variable `CLIMICROSOFT365_AADAPPID` is set, Microsoft365R will use its value as the app ID for authenticating instead. You can also specify the app ID as an argument when calling the functions. diff --git a/man/client.Rd b/man/client.Rd index 57b2c1a..ac6de62 100644 --- a/man/client.Rd +++ b/man/client.Rd @@ -6,21 +6,18 @@ \alias{sharepoint_site} \title{OneDrive and Sharepoint Online clients} \usage{ -personal_onedrive(app = Sys.getenv("CLIMICROSOFT365_AADAPPID", - .microsoft365r_app_id), scopes = c("Files.ReadWrite.All", "User.Read"), - ...) +personal_onedrive(app = .microsoft365r_app_id, + scopes = c("Files.ReadWrite.All", "User.Read"), ...) business_onedrive(tenant = Sys.getenv("CLIMICROSOFT365_TENANT", "common"), - app = Sys.getenv("CLIMICROSOFT365_AADAPPID", .microsoft365r_app_id), - scopes = c("Files.ReadWrite.All", "User.Read"), ...) + app = Sys.getenv("CLIMICROSOFT365_AADAPPID"), scopes = ".default", ...) sharepoint_site(site_url = NULL, site_id = NULL, tenant = Sys.getenv("CLIMICROSOFT365_TENANT", "common"), - app = Sys.getenv("CLIMICROSOFT365_AADAPPID", .microsoft365r_app_id), - scopes = c("Sites.ReadWrite.All", "User.Read"), ...) + app = Sys.getenv("CLIMICROSOFT365_AADAPPID"), scopes = ".default", ...) } \arguments{ -\item{app}{A custom app registration ID to use for authentication. If not supplied, use the value of the \code{CLIMICROSOFT365_AADAPPID} environment variable, or an internal app ID if that is unset.} +\item{app}{A custom app registration ID to use for authentication. For \code{personal_onedrive}, the default is to use Microsoft365R's internal app ID. For \code{business_onedrive} and \code{sharepoint_site}, see below.} \item{scopes}{The Microsoft Graph scopes (permissions) to obtain.} @@ -31,7 +28,7 @@ sharepoint_site(site_url = NULL, site_id = NULL, \item{site_url, site_id}{For \code{sharepoint_site}, the web URL and ID of the SharePoint site to retrieve. Supply one or the other, but not both.} } \value{ -An object of class \code{ms_drive}. +For \code{personal_onedrive} and \code{business_onedrive}, an object of class \code{ms_drive}. For \code{sharepoint_site}, an object of class \code{ms_site}. } \description{ OneDrive and Sharepoint Online clients @@ -40,15 +37,22 @@ OneDrive and Sharepoint Online clients \code{personal_onedrive}, \code{business_onedrive} and \code{sharepoint_site} provide easy access to OneDrive, OneDrive for Business, and SharePoint Online respectively. On first use, they will call your web browser to authenticate with Azure Active Directory, in a similar manner to other web apps. You will get a dialog box asking for permission to access your information. You only have to authenticate once per client; your credentials will be saved and reloaded in subsequent sessions. When authenticating, you can pass optional arguments in \code{...} which will ultimately be received by \code{AzureAuth::get_azure_token}. In particular, if your machine doesn't have a web browser available to authenticate with (for example if you are in a remote RStudio Server session), pass \code{auth_type="device_code"} which is intended for such scenarios. - -The default "common" tenant for \code{business_onedrive} and \code{sharepoint_site} attempts to detect your actual tenant from your saved credentials in your browser. This may not always succeed, for example if you have a personal account that is also a guest account in a tenant. In this case, supply the actual tenant name, either in the \code{tenant} argument or in the \code{CLIMICROSOFT365_TENANT} environment variable. The latter allows sharing authentication details with the CLI for Microsoft 365. - -For authentication purposes, Microsoft365R is registered as an app in the "aicatr" AAD tenant. Depending on your organisation's security policy, you may have to get an admin to grant it access to your tenant. - -As an alternative to using the default app ID, you (or your admin) can create your own app registration. It should have a native redirect URI of \verb{http://localhost:1410}, and the "public client" option should be enabled if you want to use the device code authentication flow. You can supply your app ID either via the \code{app} argument, or in the environment variable \code{CLIMICROSOFT365_AADAPPID}. - -In addition, for SharePoint (only) it's possible to use the Azure CLI app ID to access document libraries and lists. See the examples below. Be warned, however, that this may attract the attention of your admin! } +\section{Authenticating to Microsoft 365 Business services}{ + +Authenticating to Microsoft 365 Business services (SharePoint and OneDrive for Business) has some specific complexities. + +The default "common" tenant for \code{business_onedrive} and \code{sharepoint_site} attempts to detect your actual tenant from your saved credentials in your browser. This may not always succeed, for example if you have a personal account that is also a guest account in a tenant. In this case, supply the actual tenant name, either in the \code{tenant} argument or in the \code{CLIMICROSOFT365_TENANT} environment variable. The latter allows sharing authentication details with the \href{https://pnp.github.io/cli-microsoft365/}{CLI for Microsoft 365}. + +The default when authenticating to these services is for Microsoft365R to use its own internal app ID. Depending on your organisation's security policy, you may have to get an admin to grant it access to your tenant. As an alternative to the default app ID, you (or your admin) can create your own app registration: it should have a native redirect URI of \verb{http://localhost:1410}, and the "public client" option should be enabled if you want to use the device code authentication flow. You can supply your app ID either via the \code{app} argument, or in the environment variable \code{CLIMICROSOFT365_AADAPPID}. + +If creating your own app registration is impractical, there are a couple of ways to work around access issues by piggybacking on other well-known apps. Be warned that these solutions may draw the attention of your admin! +\itemize{ +\item If the R option \code{microsoft365r_use_cli_app_id} is set to a non-NULL value, authentication will be done using the app ID for the CLI for Microsoft 365. Technically this app still requires admin approval, but it is in widespread use and so may already be allowed in your organisation. +\item For SharePoint (only) it's possible to use the Azure CLI app ID to access document libraries and lists. As a first-party Microsoft app the Azure CLI is available in every AAD tenant, but is not intended for working with Microsoft 365. +} +} + \examples{ \dontrun{ @@ -68,15 +72,20 @@ site$get_drive()$list_items() business_onedrive(app="app_id") sharepoint_site("https://mycompany.sharepoint.com/sites/my-site-name", app="app_id") -# for SharePoint, a fallback is to use the Azure CLI app ID and the '.default' scope: +# using the app ID for the CLI for Microsoft 365: set a global option +options(microsoft365r_use_cli_app_id=TRUE) +business_onedrive() +sharepoint_site("https://mycompany.sharepoint.com/sites/my-site-name") + +# for SharePoint, it's possible to use the Azure CLI app ID: sharepoint_site("https://mycompany.sharepoint.com/sites/my-site-name", - app=AzureGraph:::.az_cli_app_id, - scopes=".default") + app=AzureGraph:::.az_cli_app_id) } } \seealso{ -\link{ms_drive}, \link[AzureGraph:graph_login]{AzureGraph::create_graph_login} +\link{ms_drive}, \link{ms_site}, \link[AzureGraph:graph_login]{AzureGraph::create_graph_login}, \link[AzureAuth:get_azure_token]{AzureAuth::get_azure_token} -\href{https://pnp.github.io/cli-microsoft365/}{CLI for Microsoft 365} -- where the \code{CLIMICROSOFT365_AADAPPID} and \code{CLIMICROSOFT365_TENANT} environment variables come from +\href{https://pnp.github.io/cli-microsoft365/}{CLI for Microsoft 365} -- a commandline tool for managing Microsoft 365 +\href{https://docs.microsoft.com/en-us/cli/azure/what-is-azure-cli}{Azure CLI} } diff --git a/tests/testthat/test02_business_onedrive.R b/tests/testthat/test02_business_onedrive.R index 9aa022a..0aee543 100644 --- a/tests/testthat/test02_business_onedrive.R +++ b/tests/testthat/test02_business_onedrive.R @@ -8,11 +8,10 @@ if(!interactive()) skip("OneDrive for Business tests skipped: must be in interactive session") tok <- try(AzureAuth::get_azure_token( - c("https://graph.microsoft.com/Files.ReadWrite.All", - "https://graph.microsoft.com/User.Read", + c("https://graph.microsoft.com/.default", "openid", "offline_access"), - tenant=tenant, app=.microsoft365r_app_id, version=2, use_cache=FALSE), + tenant=tenant, app=app, version=2, use_cache=FALSE), silent=TRUE) if(inherits(tok, "try-error")) skip("OneDrive for Business tests skipped: no access to tenant") @@ -53,6 +52,9 @@ test_that("OneDrive for Business works", expect_silent(item$update(name=basename(dest))) expect_identical(item$properties$name, basename(dest)) + # ODB requires that folders be empty before deleting them (!) + expect_silent(item$delete(confirm=FALSE)) + expect_silent(od$delete_item(newfolder, confirm=FALSE)) }) diff --git a/vignettes/Microsoft365R.Rmd b/vignettes/Microsoft365R.Rmd index 462a6f2..d2e2872 100644 --- a/vignettes/Microsoft365R.Rmd +++ b/vignettes/Microsoft365R.Rmd @@ -16,9 +16,12 @@ The first time you call one of the Microsoft365R functions (see below), it will For authentication purposes, the package is registered as an app in the 'aicatr' AAD tenant; depending on your organisation's security policy, you may have to get an admin to grant it access to your tenant. The default permissions requested are: -- For `personal_onedrive`: Files.ReadWrite.All, User.Read -- For `business_onedrive`: Files.ReadWrite.All, User.Read -- For `sharepoint_site`: Sites.ReadWrite.All, User.Read +- User.Read +- Files.ReadWrite.All +- Group.ReadWrite.All +- profile +- email +- openid If the environment variable `CLIMICROSOFT365_AADAPPID` is set, Microsoft365R will use its value as the app ID for authenticating instead. You can also specify the app ID as an argument when calling the functions.