* change org

* test commit

* Extensibility (#1)

* sharepoint site/drive

* use basename of src for download

* download method for drive

* add top-level get_sharepoint_site, get_drive

* refactoring

* fix glitch in test

* add more is_* functions

* start on list_files

* list_files working, add get_drives methods

* rationalise get_drive/document_library

* fine-tune drive/file detection

* add drive_item class

* tweak for personal onedrive

* add app for personal auth

* file uploading

* delete file

* drive items

* use AAD v2.0 as default

* class registry

* tweak login defaults

* document everything

* linting

* update description

* split ms_object and az_object

* change default for init_list_objects

* stub sharepoint list object

* basic list functionality

* break sharepoint stuff into new pkg

* fixes

* util fns from AzureRMR

* update news

* update docs

* basic testing

* add vignette

* fix vignette

* update news

* change org back

* also set default to aad v2 in ms_graph

* update doc

* bump version no

* rework get_graph_login
This commit is contained in:
Hong Ooi 2021-01-12 04:17:32 +11:00 коммит произвёл GitHub
Родитель c57654053e
Коммит 4d19dac5b0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
43 изменённых файлов: 942 добавлений и 290 удалений

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

@ -4,8 +4,6 @@
\.Rproj$
\.Rxproj$
^\.Rproj\.user$
.travis.yml
CONTRIBUTING.md
drat.sh
^LICENSE\.md$
^\.github$

6
.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: github.repository_owner == 'Azure' && runner.os == 'Linux' && github.ref == 'refs/heads/master'
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() && github.repository_owner == 'Azure' && runner.os == 'Linux' && github.ref == 'refs/heads/master'
env:
token: "${{ secrets.ghPat }}"
run: |

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

@ -1,6 +1,6 @@
Package: AzureGraph
Title: Simple Interface to 'Microsoft Graph'
Version: 1.1.2
Version: 1.2.0
Authors@R: c(
person("Hong", "Ooi", , "hongooi73@gmail.com", role = c("aut", "cre")),
person("Microsoft", role="cph")
@ -18,11 +18,13 @@ Imports:
httr (>= 1.3),
jsonlite,
openssl,
curl,
R6
Suggests:
AzureRMR,
vctrs,
knitr,
rmarkdown,
testthat
Roxygen: list(markdown=TRUE)
RoxygenNote: 6.1.1
Roxygen: list(markdown=TRUE, r6=FALSE, old_usage=TRUE)
RoxygenNote: 7.1.1

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

@ -1,2 +1,2 @@
YEAR: 2019
YEAR: 2019-2021
COPYRIGHT HOLDER: Microsoft

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

@ -2,6 +2,7 @@
export(az_app)
export(az_device)
export(az_directory_role)
export(az_group)
export(az_object)
export(az_service_principal)
@ -10,13 +11,21 @@ export(call_graph_endpoint)
export(call_graph_url)
export(create_graph_login)
export(delete_graph_login)
export(format_public_fields)
export(format_public_methods)
export(get_graph_login)
export(is_aad_object)
export(is_app)
export(is_directory_object)
export(is_directory_role)
export(is_empty)
export(is_group)
export(is_msgraph_object)
export(is_service_principal)
export(is_user)
export(list_graph_logins)
export(ms_graph)
export(ms_object)
export(named_list)
export(register_graph_class)
import(AzureAuth)
importFrom(utils,modifyList)

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

@ -1,3 +1,10 @@
# AzureGraph 1.2.0
- Internal refactoring to support future extensibility, including transferring some utility functions from AzureRMR to here.
- New "Extending AzureGraph" vignette, showing how to extend this package to represent other object types in Microsoft Graph.
- Switch to AAD v2.0 as the default for authenticating.
- Enhance `get_graph_login` to allow specifying scopes.
# AzureGraph 1.1.2
- Change maintainer email address.

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

@ -14,3 +14,5 @@ utils::globalVariables(c("self", "private"))
# default authentication app ID: leverage the az CLI
.az_cli_app_id <- "04b07795-8ddb-461a-bbee-02f9e1bf7b46"
# authentication app ID for personal accounts
.azurer_graph_app_id <- "5bb21e8a-06bf-4ac4-b613-110ac0e582c1"

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

@ -89,6 +89,7 @@ public=list(
initialize=function(token, tenant=NULL, properties=NULL, password=NULL)
{
self$type <- "application"
private$api_type <- "applications"
self$password <- password
super$initialize(token, tenant, properties)
},
@ -161,7 +162,7 @@ public=list(
list_owners=function(type=c("user", "group", "application", "servicePrincipal"))
{
res <- private$get_paged_list(self$do_operation("owners"))
private$init_list_objects(private$filter_list(res, type))
private$init_list_objects(res, type)
},
create_service_principal=function(...)

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

@ -1,6 +1,6 @@
#' Device in Azure Active Directory
#'
#' Base class representing a registered device.
#' Class representing a registered device.
#'
#' @docType class
#' @section Fields:
@ -24,7 +24,7 @@
#' [ms_graph], [az_user], [az_object]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta)
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @format An R6 object of class `az_device`, inheriting from `az_object`.
#' @export

55
R/az_dir_role.R Normal file
Просмотреть файл

@ -0,0 +1,55 @@
#' Directory role
#'
#' Class representing a role in Azure Active Directory.
#'
#' @docType class
#' @section Fields:
#' - `token`: The token used to authenticate with the Graph host.
#' - `tenant`: The Azure Active Directory tenant for this role.
#' - `type`: always "directory role" for a directory role object.
#' - `properties`: The item properties.
#' @section Methods:
#' - `new(...)`: Initialize a new object. Do not call this directly; see 'Initialization' below.
#' - `delete(confirm=TRUE)`: Delete this item. By default, ask for confirmation first.
#' - `update(...)`: Update the item's properties in Microsoft Graph.
#' - `do_operation(...)`: Carry out an arbitrary operation on the item.
#' - `sync_fields()`: Synchronise the R object with the item metadata in Microsoft Graph.
#'
#' @section Initialization:
#' Currently support for directory roles is limited. Objects of this class should not be initialized directly.
#'
#' @seealso
#' [ms_graph], [az_user]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @format An R6 object of class `az_directory_role`, inheriting from `az_object`.
#' @export
az_directory_role <- R6::R6Class("az_directory_role", inherit=az_object,
public=list(
initialize=function(token, tenant=NULL, properties=NULL)
{
self$type <- "directory role"
private$api_type <- "directoryRoles"
super$initialize(token, tenant, properties)
},
list_members=function()
{
res <- private$get_paged_list(self$do_operation("members"))
private$init_list_objects(res)
},
print=function(...)
{
cat("<Azure Active Directory role '", self$properties$displayName, "'>\n", sep="")
cat(" directory id:", self$properties$id, "\n")
cat(" description:", self$properties$description, "\n")
cat("---\n")
cat(format_public_methods(self))
invisible(self)
}
))

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

@ -1,6 +1,6 @@
#' Group in Azure Active Directory
#'
#' Base class representing an AAD group.
#' Class representing an AAD group.
#'
#' @docType class
#' @section Fields:
@ -26,7 +26,7 @@
#' [ms_graph], [az_app], [az_user], [az_object]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta)
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @examples
#' \dontrun{
@ -50,24 +50,32 @@ public=list(
initialize=function(token, tenant=NULL, properties=NULL)
{
self$type <- "group"
private$api_type <- "groups"
super$initialize(token, tenant, properties)
},
list_members=function(type=c("user", "group", "application", "servicePrincipal"))
{
res <- private$get_paged_list(self$do_operation("members"))
private$init_list_objects(private$filter_list(res, type))
private$init_list_objects(res, type)
},
list_owners=function(type=c("user", "group", "application", "servicePrincipal"))
{
res <- private$get_paged_list(self$do_operation("owners"))
private$init_list_objects(private$filter_list(res, type))
private$init_list_objects(res, type)
},
print=function(...)
{
cat("<Graph group '", self$properties$displayName, "'>\n", sep="")
group_type <- if("Unified" %in% self$properties$groupTypes)
"Microsoft 365"
else if(!self$properties$mailEnabled)
"Security"
else if(self$properties$securityEnabled)
"Mail-enabled security"
else "Distribution"
cat("<", group_type, " group '", self$properties$displayName, "'>\n", sep="")
cat(" directory id:", self$properties$id, "\n")
cat(" description:", self$properties$description, "\n")
cat("---\n")

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

@ -1,6 +1,6 @@
#' Azure Active Directory object
#'
#' Base class representing a directory object in Microsoft Graph.
#' Base class representing an Azure Active Directory object in Microsoft Graph.
#'
#' @docType class
#' @section Fields:
@ -24,55 +24,14 @@
#' [ms_graph], [az_app], [az_service_principal], [az_user], [az_group]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta)
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @format An R6 object of class `az_object`.
#' @format An R6 object of class `az_object`, inheriting from `ms_object`.
#' @export
az_object <- R6::R6Class("az_object",
az_object <- R6::R6Class("az_object", inherit=ms_object,
public=list(
token=NULL,
tenant=NULL,
type=NULL,
# app data from server
properties=NULL,
initialize=function(token, tenant=NULL, properties=NULL)
{
self$token <- token
self$tenant <- tenant
self$properties <- properties
},
update=function(...)
{
self$do_operation(body=list(...), encode="json", http_verb="PATCH")
self$properties <- self$do_operation()
self
},
sync_fields=function()
{
self$properties <- self$do_operation()
invisible(self)
},
delete=function(confirm=TRUE)
{
if(confirm && interactive())
{
msg <- sprintf("Do you really want to delete the %s '%s'?",
self$type, self$properties$displayName)
if(!get_confirmation(msg, FALSE))
return(invisible(NULL))
}
self$do_operation(http_verb="DELETE")
invisible(NULL)
},
list_object_memberships=function()
{
lst <- self$do_operation("getMemberObjects", body=list(securityEnabledOnly=TRUE),
@ -89,75 +48,13 @@ public=list(
unlist(private$get_paged_list(lst))
},
do_operation=function(op="", ...)
{
op <- construct_path(private$get_endpoint(), self$properties$id, op)
call_graph_endpoint(self$token, op, ...)
},
print=function(...)
{
cat("<Graph directory object '", self$properties$displayName, "'>\n", sep="")
cat("<Azure Active Directory object '", self$properties$displayName, "'>\n", sep="")
cat(" directory id:", self$properties$id, "\n")
cat("---\n")
cat(format_public_methods(self))
invisible(self)
}
),
private=list(
get_paged_list=function(lst, next_link_name="@odata.nextLink", value_name="value")
{
res <- lst[[value_name]]
while(!is_empty(lst[[next_link_name]]))
{
lst <- call_graph_url(self$token, lst[[next_link_name]])
res <- c(res, lst[[value_name]])
}
res
},
filter_list=function(lst, type=c("user", "group", "application", "servicePrincipal", "device"))
{
type <- paste0("#microsoft.graph.", type)
keep <- vapply(lst, function(obj) obj$`@odata.type` %in% type, FUN.VALUE=logical(1))
lst[keep]
},
init_list_objects=function(lst)
{
lapply(lst, function(obj)
{
switch(obj$`@odata.type`,
"#microsoft.graph.user"=
az_user$new(self$token, self$tenant, obj),
"#microsoft.graph.group"=
az_group$new(self$token, self$tenant, obj),
"#microsoft.graph.application"=
az_app$new(self$token, self$tenant, obj),
"#microsoft.graph.servicePrincipal"=
az_service_principal$new(self$token, self$tenant, obj),
"#microsoft.graph.device"=
az_device$new(self$token, self$tenant, obj),
{
warning("Unknown directory object type ", obj$`@odata.type`)
obj
}
)
})
},
get_endpoint=function()
{
switch(self$type,
"user"="users",
"group"="groups",
"application"="applications",
"service principal"="servicePrincipals",
"device"="devices",
stop("Unknown directory object type"))
}
))

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

@ -1,6 +1,6 @@
#' Service principal in Azure Active Directory
#'
#' Base class representing an AAD service principal.
#' Class representing an AAD service principal.
#'
#' @docType class
#' @section Fields:
@ -24,7 +24,7 @@
#' [ms_graph], [az_app], [az_object]
#'
#' [Azure Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta)
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @format An R6 object of class `az_service_principal`, inheriting from `az_object`.
#' @export
@ -35,6 +35,7 @@ public=list(
initialize=function(token, tenant=NULL, properties=NULL)
{
self$type <- "service principal"
private$api_type <- "servicePrincipals"
super$initialize(token, tenant, properties)
},

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

@ -1,6 +1,6 @@
#' User in Azure Active Directory
#'
#' Base class representing an AAD user account.
#' Class representing an AAD user account.
#'
#' @docType class
#' @section Fields:
@ -16,7 +16,7 @@
#' - `sync_fields()`: Synchronise the R object with the app data in Azure Active Directory.
#' - `list_group_memberships()`: Return the IDs of all groups this user is a member of.
#' - `list_object_memberships()`: Return the IDs of all groups, administrative units and directory roles this user is a member of.
#' - `list_direct_memberships(id_only=TRUE)`: List the groups this user is a direct member of. Set `id_only=TRUE` to return only a vector of group IDs (the default), or `id_only=FALSE` to return a list of group objects.
#' - `list_direct_memberships(id_only=TRUE)`: List the groups and directory roles this user is a direct member of. Set `id_only=TRUE` to return only a vector of IDs (the default), or `id_only=FALSE` to return a list of group objects.
#' - `list_owned_objects(type=c("user", "group", "application", "servicePrincipal"))`: List directory objects (groups/apps/service principals) owned by this user. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_created_objects(type=c("user", "group", "application", "servicePrincipal"))`: List directory objects (groups/apps/service principals) created by this user. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_owned_devices()`: List the devices owned by this user.
@ -30,7 +30,7 @@
#' [ms_graph], [az_app], [az_group], [az_device], [az_object]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta)
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @examples
#' \dontrun{
@ -64,6 +64,7 @@ public=list(
initialize=function(token, tenant=NULL, properties=NULL, password=NULL)
{
self$type <- "user"
private$api_type <- "users"
self$password <- password
super$initialize(token, tenant, properties)
},
@ -90,19 +91,19 @@ public=list(
list_owned_objects=function(type=c("user", "group", "application", "servicePrincipal"))
{
res <- private$get_paged_list(self$do_operation("ownedObjects"))
private$init_list_objects(private$filter_list(res, type))
private$init_list_objects(res, type)
},
list_created_objects=function(type=c("user", "group", "application", "servicePrincipal"))
{
res <- private$get_paged_list(self$do_operation("createdObjects"))
private$init_list_objects(private$filter_list(res, type))
private$init_list_objects(res, type)
},
list_owned_devices=function()
{
res <- private$get_paged_list(self$do_operation("ownedDevices"))
private$init_list_objects(private$filter_list(res))
private$init_list_objects(res, "device")
},
list_direct_memberships=function(id_only=TRUE)
@ -111,11 +112,7 @@ public=list(
if(id_only)
sapply(res, function(grp) grp$id)
else
{
names(res) <- sapply(res, function(grp) grp$displayName)
lapply(res, function(grp) az_group$new(self$token, self$tenant, grp))
}
else private$init_list_objects(res)
},
print=function(...)

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

@ -7,6 +7,7 @@
#' @param url A complete URL to send to the host.
#' @param http_verb The HTTP verb as a string, one of `GET`, `PUT`, `POST`, `DELETE`, `HEAD` or `PATCH`.
#' @param http_status_handler How to handle in R the HTTP status code of a response. `"stop"`, `"warn"` or `"message"` will call the appropriate handlers in httr, while `"pass"` ignores the status code.
#' @param simplify Whether to turn arrays of objects in the JSON response into data frames. Set this to `TRUE` if you are expecting the endpoint to return tabular data and you want a tabular result, as opposed to a list of objects.
#' @param auto_refresh Whether to refresh/renew the OAuth token if it is no longer valid.
#' @param body The body of the request, for `PUT`/`POST`/`PATCH`.
#' @param encode The encoding (really content-type) for the request body. The default value "json" means to serialize a list body into a JSON object. If you pass an already-serialized JSON object as the body, set `encode` to "raw".
@ -15,6 +16,8 @@
#' @details
#' These functions form the low-level interface between R and Microsoft Graph. `call_graph_endpoint` forms a URL from its arguments and passes it to `call_graph_url`.
#'
#' If `simplify` is `TRUE`, `call_graph_url` will exploit the ability of `jsonlite::fromJSON` to convert arrays of objects into R data frames. This can be useful for REST calls that return tabular data. However, it can also cause problems for _paged_ lists, where each page will be turned into a separate data frame; as the individual objects may not have the same fields, the resulting data frames will also have differing columns. This will cause base R's `rbind` to fail when binding the pages together. When processing paged lists, AzureGraph will use `vctrs::vec_rbind` instead of `rbind` when the vctrs package is available; `vec_rbind` does not have this problem. For safety, you should only set `simplify=TRUE` when vctrs is installed.
#'
#' @return
#' If `http_status_handler` is one of `"stop"`, `"warn"` or `"message"`, the status code of the response is checked. If an error is not thrown, the parsed content of the response is returned with the status code attached as the "status" attribute.
#'
@ -39,7 +42,7 @@ call_graph_endpoint <- function(token, operation, ..., options=list(),
call_graph_url <- function(token, url, ..., body=NULL, encode="json",
http_verb=c("GET", "DELETE", "PUT", "POST", "HEAD", "PATCH"),
http_status_handler=c("stop", "warn", "message", "pass"),
auto_refresh=TRUE)
simplify=FALSE, auto_refresh=TRUE)
{
headers <- process_headers(token, url, auto_refresh)
@ -54,7 +57,7 @@ call_graph_url <- function(token, url, ..., body=NULL, encode="json",
# do actual API call
res <- httr::VERB(match.arg(http_verb), url, headers, ..., body=body, encode=encode)
process_response(res, match.arg(http_status_handler))
process_response(res, match.arg(http_status_handler), simplify)
}
@ -79,11 +82,11 @@ process_headers <- function(token, host, auto_refresh)
}
process_response <- function(response, handler)
process_response <- function(response, handler, simplify)
{
if(handler != "pass")
{
cont <- httr::content(response)
cont <- httr::content(response, simplifyVector=simplify)
handler <- get(paste0(handler, "_for_status"), getNamespace("httr"))
handler(response, paste0("complete operation. Message:\n",
sub("\\.$", "", error_message(cont))))
@ -128,36 +131,6 @@ construct_path <- function(...)
}
# same as AzureRMR::named_list, do not export to avoid conflicts
named_list <- function(lst=NULL, name_fields="name")
{
if(is_empty(lst))
return(structure(list(), names=character(0)))
lst_names <- sapply(name_fields, function(n) sapply(lst, `[[`, n))
if(length(name_fields) > 1)
{
dim(lst_names) <- c(length(lst_names) / length(name_fields), length(name_fields))
lst_names <- apply(lst_names, 1, function(nn) paste(nn, collapse="/"))
}
names(lst) <- lst_names
dups <- duplicated(tolower(names(lst)))
if(any(dups))
{
duped_names <- names(lst)[dups]
warning("Some names are duplicated: ", paste(unique(duped_names), collapse=" "), call.=FALSE)
}
lst
}
# same as AzureRMR::is_empty, do not export to avoid conflicts
is_empty <- function(x)
{
length(x) == 0
}
# display confirmation prompt, return TRUE/FALSE (no NA)
get_confirmation <- function(msg, default=TRUE)
{

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

@ -1,5 +1,15 @@
# copied from AzureRMR:: do not export
#' Format a Microsoft Graph or Azure object
#'
#' Miscellaneous functions for printing Microsoft Graph and Azure R6 objects
#'
#' @param env An R6 object's environment for printing.
#' @param exclude Objects in `env` to exclude from the printout.
#'
#' @details
#' These are utilities to aid in printing R6 objects created by this package or its descendants. They are not meant to be called by the user.
#'
#' @rdname format
#' @export
format_public_fields <- function(env, exclude=character(0))
{
objnames <- ls(env)
@ -7,7 +17,7 @@ format_public_fields <- function(env, exclude=character(0))
objnames <- setdiff(objnames, c(exclude, std_fields))
maxwidth <- as.integer(0.8 * getOption("width"))
objconts <- sapply(objnames, function(n)
{
x <- get(n, env)
@ -21,13 +31,13 @@ format_public_fields <- function(env, exclude=character(0))
if(nchar(x) > maxwidth - nchar(n) - 10)
x <- paste0(substr(x, 1, maxwidth - nchar(n) - 10), " ...")
x
}
}
else deparse(x)[[1]]
paste0(strwrap(paste0(n, ": ", deparsed), width=maxwidth, indent=2, exdent=4),
collapse="\n")
}, simplify=FALSE)
empty <- sapply(objconts, is.null)
objconts <- objconts[!empty]
@ -39,6 +49,8 @@ format_public_fields <- function(env, exclude=character(0))
}
#' @rdname format
#' @export
format_public_methods <- function(env)
{
objnames <- ls(env)

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

@ -6,8 +6,10 @@
#' @param username If `auth_type == "resource_owner"`, your username.
#' @param certificate If `auth_type == "client_credentials", a certificate to authenticate with. This is a more secure alternative to using an app secret.
#' @param auth_type The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". If `NULL`, this is chosen based on the presence of the `username` and `password` arguments.
#' @param version The Azure Active Directory version to use for authenticating.
#' @param host Your Microsoft Graph host. Defaults to `https://graph.microsoft.com/`. Change this if you are using a government or private cloud.
#' @param aad_host Azure Active Directory host for authentication. Defaults to `https://login.microsoftonline.com/`. Change this if you are using a government or private cloud.
#' @param scopes The Microsoft Graph scopes (permissions) to obtain for this Graph login. Only for `version=2`.
#' @param config_file Optionally, a JSON file containing any of the arguments listed above. Arguments supplied in this file take priority over those supplied on the command line. You can also use the output from the Azure CLI `az ad sp create-for-rbac` command.
#' @param token Optionally, an OAuth 2.0 token, of class [AzureAuth::AzureToken]. This allows you to reuse the authentication details for an existing session. If supplied, all other arguments to `create_graph_login` will be ignored.
#' @param refresh For `get_graph_login`, whether to refresh the authentication token on loading the client.
@ -18,7 +20,7 @@
#' @details
#' `create_graph_login` creates a login client to authenticate with Microsoft Graph, using the supplied arguments. The authentication token is obtained using [get_azure_token], which automatically caches and reuses tokens for subsequent sessions. Note that credentials are only cached if you allowed AzureGraph to create a data directory at package startup.
#'
#' `get_graph_login` returns a login client by retrieving previously saved credentials. It searches for saved credentials according to the supplied tenant; if multiple logins are found, it will prompt for you to choose one.
#' `get_graph_login` returns a previously created login client. If there are multiple existing clients, you can specify which client to return via the `selection`, `app`, `scopes` and `auth_type` arguments. If you don't specify which one to return, it will pop up a menu and ask you to choose one.
#'
#' One difference between `create_graph_login` and `get_graph_login` is the former will delete any previously saved credentials that match the arguments it was given. You can use this to force AzureGraph to remove obsolete tokens that may be lying around.
#'
@ -34,12 +36,12 @@
#' [ms_graph], [AzureAuth::get_azure_token] for more details on authentication methods
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta)
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @examples
#' \dontrun{
#'
#' # without any arguments, this will create a client using your AAD credentials
#' # without any arguments, this will create a client using your AAD organisational account
#' az <- create_graph_login()
#'
#' # retrieve the login in subsequent sessions
@ -47,18 +49,26 @@
#'
#' # this will create an Microsoft Graph client for the tenant 'microsoft.onmicrosoft.com',
#' # using the client_credentials method
#' az <- create_graph_login("microsoft", app="{app_id}", password="{password}")
#' az <- create_graph_login("mytenant", app="{app_id}", password="{password}")
#'
#' # you can also login using credentials in a json file
#' az <- create_graph_login(config_file="~/creds.json")
#'
#' # creating and obtaining a login with specific scopes
#' create_graph_login("mytenant", scopes=c("User.Read", "Files.ReadWrite.All"))
#' get_graph_login("mytenant", scopes=c("User.Read", "Files.ReadWrite.All"))
#'
#' # to use your personal account, set the tenant to one of the following
#' create_graph_login("9188040d-6c67-4c5b-b112-36a304b66dad")
#' create_graph_login("consumers") # requires AzureAuth 1.2.6
#'
#' }
#' @rdname graph_login
#' @export
create_graph_login <- function(tenant="common", app=.az_cli_app_id,
password=NULL, username=NULL, certificate=NULL, auth_type=NULL,
create_graph_login <- function(tenant="common", app=NULL,
password=NULL, username=NULL, certificate=NULL, auth_type=NULL, version=2,
host="https://graph.microsoft.com/", aad_host="https://login.microsoftonline.com/",
config_file=NULL, token=NULL, ...)
scopes=".default", config_file=NULL, token=NULL, ...)
{
if(!is_azure_token(token))
{
@ -66,13 +76,22 @@ create_graph_login <- function(tenant="common", app=.az_cli_app_id,
{
conf <- jsonlite::fromJSON(config_file)
call <- as.list(match.call())[-1]
call$config_file <- NULL
call$config_file <- call$token <- NULL
call <- lapply(modifyList(call, conf), function(x) eval.parent(x))
return(do.call(create_graph_login, call))
}
tenant <- normalize_tenant(tenant)
app <- normalize_guid(app)
app <- if(is.null(app))
{
if(tenant %in% c("consumers", "9188040d-6c67-4c5b-b112-36a304b66dad"))
.azurer_graph_app_id
else .az_cli_app_id
}
else normalize_guid(app)
if(version == 2)
host <- c(paste0(host, scopes), "openid", "offline_access")
token_args <- list(resource=host,
tenant=tenant,
@ -82,6 +101,7 @@ create_graph_login <- function(tenant="common", app=.az_cli_app_id,
certificate=certificate,
auth_type=auth_type,
aad_host=aad_host,
version=version,
...)
hash <- do.call(token_hash, token_args)
@ -110,7 +130,7 @@ create_graph_login <- function(tenant="common", app=.az_cli_app_id,
#' @rdname graph_login
#' @export
get_graph_login <- function(tenant="common", selection=NULL, refresh=TRUE)
get_graph_login <- function(tenant="common", selection=NULL, app=NULL, scopes=NULL, auth_type=NULL, refresh=TRUE)
{
if(!dir.exists(AzureR_dir()))
stop("AzureR data directory does not exist; cannot load saved logins")
@ -126,43 +146,22 @@ get_graph_login <- function(tenant="common", selection=NULL, refresh=TRUE)
stop(msg, call.=FALSE)
}
if(length(this_login) == 1 && is.null(selection))
selection <- 1
else if(is.null(selection))
{
tokens <- lapply(this_login, function(f)
readRDS(file.path(AzureR_dir(), f)))
choices <- sapply(tokens, function(token)
{
app <- token$client$client_id
paste0("App ID: ", app, "\n Authentication method: ", token$auth_type)
})
msg <- paste0("Choose a Microsoft Graph login for ", format_tenant(tenant))
selection <- utils::menu(choices, title=msg)
}
if(selection == 0)
return(NULL)
file <- if(is.numeric(selection))
this_login[selection]
else if(is.character(selection))
this_login[which(this_login == selection)] # force an error if supplied hash doesn't match available logins
file <- file.path(AzureR_dir(), file)
if(is_empty(file) || !file.exists(file))
stop("Azure Active Directory token not found for this login", call.=FALSE)
message("Loading Microsoft Graph login for ", format_tenant(tenant))
token <- readRDS(file)
client <- ms_graph$new(token=token)
# do we need to choose which login client to use?
have_selection <- !is.null(selection)
have_auth_spec <- any(!is.null(app), !is.null(scopes), !is.null(auth_type))
token <- if(length(this_login) > 1 || have_selection || have_auth_spec)
choose_token(this_login, selection, app, scopes, auth_type)
else load_azure_token(this_login)
if(is.null(token))
return(NULL)
client <- ms_graph$new(token=token)
if(refresh)
client$token$refresh()
client
}
@ -243,4 +242,82 @@ format_tenant <- function(tenant)
if(tenant == "common")
"default tenant"
else paste0("tenant '", tenant, "'")
}
}
# algorithm for choosing a token:
# if given a hash, choose it (error if no match)
# otherwise if given any of app|scopes|auth_type, use those (error if no match)
# otherwise if given a number, use it
# otherwise ask
choose_token <- function(hashes, selection, app, scopes, auth_type)
{
if(is.character(selection))
{
if(!(selection %in% hashes))
stop("Token with selected hash not found", call.=FALSE)
return(load_azure_token(selection))
}
if(any(!is.null(app), !is.null(scopes), !is.null(auth_type)))
{
if(!is.null(scopes))
scopes <- tolower(scopes)
# look for matching token
for(hash in hashes)
{
app_match <- scope_match <- auth_match <- TRUE
token <- load_azure_token(hash)
if(!is.null(app) && token$client$client_id != hash)
app_match <- FALSE
if(!is.null(scopes))
{
# is this an AAD v1.0 token?
if(is.null(token$scope))
scope_match <- FALSE
else
{
tok_scopes <- tolower(basename(grep("^.+://", token$scope, value=TRUE)))
if(!setequal(scopes, tok_scopes))
scope_match <- FALSE
}
}
if(!is.null(auth_type) && token$auth_type != auth_type)
auth_match <- FALSE
if(app_match && scope_match && auth_match)
return(token)
}
# if we get here, no tokens match provided criteria
stop("Token with selected authentication parameters not found", call.=FALSE)
}
if(is.numeric(selection))
{
if(selection > length(hashes))
stop("Invalid numeric selection", call.=FALSE)
return(load_azure_token(hashes[selection]))
}
# bring up a menu
tokens <- lapply(hashes, load_azure_token)
tenant <- tokens[[1]]$tenant
choices <- sapply(tokens, function(token)
{
app <- token$client$client_id
scopes <- if(!is.null(token$scope))
paste(tolower(basename(grep("^.+://", token$scope, value=TRUE))), collapse=" ")
else "<NA>"
paste0("App ID: ", app,
"\n Scopes: ", scopes,
"\n Authentication method: ", token$auth_type,
"\n MD5 Hash: ", token$hash())
})
msg <- paste0("Choose a Microsoft Graph login for ", format_tenant(tenant))
selection <- utils::menu(choices, title=msg)
if(selection == 0)
NULL
else tokens[[selection]]
}

19
R/is.R
Просмотреть файл

@ -40,7 +40,24 @@ is_group <- function(object)
#' @rdname info
#' @export
is_directory_object <- function(object)
is_directory_role <- function(object)
{
R6::is.R6(object) && inherits(object, "az_directory_role")
}
#' @rdname info
#' @export
is_aad_object <- function(object)
{
R6::is.R6(object) && inherits(object, "az_object")
}
#' @rdname info
#' @export
is_msgraph_object <- function(object)
{
R6::is.R6(object) && inherits(object, "ms_object")
}

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

@ -29,8 +29,10 @@
#' - `username`: if `auth_type == "resource_owner"`, your username.
#' - `certificate`: If `auth_type == "client_credentials", a certificate to authenticate with. This is a more secure alternative to using an app secret.
#' - `auth_type`: The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". See [get_azure_token] for how the default method is chosen, along with some caveats.
#' - `version`: The Azure Active Directory (AAD) version to use for authenticating.
#' - `host`: your Microsoft Graph host. Defaults to `https://graph.microsoft.com/`.
#' - `aad_host`: Azure Active Directory host for authentication. Defaults to `https://login.microsoftonline.com/`. Change this if you are using a government or private cloud.
#' - `scopes`: The Microsoft Graph scopes (permissions) to obtain for this Graph login. Only for `version=2`.
#' - `token`: Optionally, an OAuth 2.0 token, of class [AzureAuth::AzureToken]. This allows you to reuse the authentication details for an existing session. If supplied, all other arguments will be ignored.
#'
#' @section App creation:
@ -44,7 +46,7 @@
#' [create_graph_login], [get_graph_login]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta)
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @examples
#' \dontrun{
@ -85,9 +87,9 @@ public=list(
# authenticate and get subscriptions
initialize=function(tenant="common", app=.az_cli_app_id,
password=NULL, username=NULL, certificate=NULL, auth_type=NULL,
password=NULL, username=NULL, certificate=NULL, auth_type=NULL, version=2,
host="https://graph.microsoft.com/", aad_host="https://login.microsoftonline.com/",
token=NULL, ...)
scopes=".default", token=NULL, ...)
{
if(is_azure_token(token))
{
@ -101,7 +103,10 @@ public=list(
self$tenant <- normalize_tenant(tenant)
app <- normalize_guid(app)
self$token <- get_azure_token(resource=self$host,
if(version == 2)
host <- c(paste0(host, scopes), "openid", "offline_access")
self$token <- get_azure_token(resource=host,
tenant=self$tenant,
app=app,
password=password,
@ -109,6 +114,7 @@ public=list(
certificate=certificate,
auth_type=auth_type,
aad_host=aad_host,
version=version,
...)
NULL

130
R/ms_object.R Normal file
Просмотреть файл

@ -0,0 +1,130 @@
#' Azure Active Directory object
#'
#' Base class representing a object in Microsoft Graph. All other Graph object classes ultimately inherit from this class.
#'
#' @docType class
#' @section Fields:
#' - `token`: The token used to authenticate with the Graph host.
#' - `tenant`: The Azure Active Directory tenant for this object.
#' - `type`: The type of object, in a human-readable format.
#' - `properties`: The object properties.
#' @section Methods:
#' - `new(...)`: Initialize a new directory object. Do not call this directly; see 'Initialization' below.
#' - `delete(confirm=TRUE)`: Delete an object. By default, ask for confirmation first.
#' - `update(...)`: Update the object information in Azure Active Directory.
#' - `do_operation(...)`: Carry out an arbitrary operation on the object.
#' - `sync_fields()`: Synchronise the R object with the data in Azure Active Directory.
#'
#' @section Initialization:
#' Objects of this class should not be created directly. Instead, create an object of the appropriate subclass.
#'
#' @seealso
#' [ms_graph], [az_object]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
#'
#' @format An R6 object of class `ms_object`.
#' @export
ms_object <- R6::R6Class("ms_object",
public=list(
token=NULL,
tenant=NULL,
# user-readable object type
type=NULL,
# object data from server
properties=NULL,
initialize=function(token, tenant=NULL, properties=NULL)
{
self$token <- token
self$tenant <- tenant
self$properties <- properties
},
update=function(...)
{
self$do_operation(body=list(...), encode="json", http_verb="PATCH")
self$properties <- self$do_operation()
self
},
sync_fields=function()
{
self$properties <- self$do_operation()
invisible(self)
},
delete=function(confirm=TRUE)
{
if(confirm && interactive())
{
name <- self$properties$displayName
if(is.null(name))
name <- self$properties$name
if(is.null(name))
name <- self$properties$id
msg <- sprintf("Do you really want to delete the %s '%s'?",
self$type, name)
if(!get_confirmation(msg, FALSE))
return(invisible(NULL))
}
self$do_operation(http_verb="DELETE")
invisible(NULL)
},
do_operation=function(op="", ...)
{
op <- construct_path(private$api_type, self$properties$id, op)
call_graph_endpoint(self$token, op, ...)
},
print=function(...)
{
cat("<Graph directory object '", self$properties$displayName, "'>\n", sep="")
cat(" directory id:", self$properties$id, "\n")
cat("---\n")
cat(format_public_methods(self))
invisible(self)
}
),
private=list(
# object type as it appears in REST API path
api_type=NULL,
get_paged_list=function(lst, next_link_name="@odata.nextLink", value_name="value", simplify=FALSE)
{
bind_fn <- if(requireNamespace("vctrs"))
vctrs::vec_rbind
else base::rbind
res <- lst[[value_name]]
while(!is_empty(lst[[next_link_name]]))
{
lst <- call_graph_url(self$token, lst[[next_link_name]], simplify=simplify)
res <- if(simplify)
bind_fn(res, lst[[value_name]]) # this assumes all objects have the exact same fields
else c(res, lst[[value_name]])
}
res
},
init_list_objects=function(lst, type_filter=NULL, default_generator=ms_object)
{
lst <- lapply(lst, function(obj)
{
class_gen <- find_class_generator(obj, type_filter, default_generator)
if(is.null(class_gen))
NULL
else class_gen$new(self$token, self$tenant, obj)
})
lst[!sapply(lst, is.null)]
}
))

43
R/utils.R Normal file
Просмотреть файл

@ -0,0 +1,43 @@
#' Miscellaneous utility functions
#'
#' @param lst A named list of objects.
#' @param name_fields The components of the objects in `lst`, to be used as names.
#' @param x For `is_empty`, An R object.
#' @details
#' `named_list` extracts from each object in `lst`, the components named by `name_fields`. It then constructs names for `lst` from these components, separated by a `"/"`.
#'
#' @return
#' For `named_list`, the list that was passed in but with names. An empty input results in a _named list_ output: a list of length 0, with a `names` attribute.
#'
#' For `is_empty`, whether the length of the object is zero (this includes the special case of `NULL`).
#'
#' @rdname utils
#' @export
named_list <- function(lst=NULL, name_fields="name")
{
if(is_empty(lst))
return(structure(list(), names=character(0)))
lst_names <- sapply(name_fields, function(n) sapply(lst, `[[`, n))
if(length(name_fields) > 1)
{
dim(lst_names) <- c(length(lst_names) / length(name_fields), length(name_fields))
lst_names <- apply(lst_names, 1, function(nn) paste(nn, collapse="/"))
}
names(lst) <- lst_names
dups <- duplicated(tolower(names(lst)))
if(any(dups))
{
duped_names <- names(lst)[dups]
warning("Some names are duplicated: ", paste(unique(duped_names), collapse=" "), call.=FALSE)
}
lst
}
#' @rdname utils
#' @export
is_empty <- function(x)
{
length(x) == 0
}

89
R/zzz_class_directory.R Normal file
Просмотреть файл

@ -0,0 +1,89 @@
#' Extensible registry of Microsoft Graph classes that AzureGraph supports
#'
#' @param name The name of the Graph class, eg "user", "servicePrincipal", etc.
#' @param R6_generator An R6 class generator corresponding to this Graph class.
#' @param check_function A boolean function that checks if a list of properties is for an object of this class.
#' @details
#' As written, AzureGraph knows about a subset of all the object classes contained in Microsoft Graph. These are mostly the classes originating from Azure Active Directory: users, groups, app registrations, service principals and registered devices.
#'
#' You can extend AzureGraph by writing your own R6 class that inherits from `ms_object`. If so, you should also _register_ your class by calling `register_graph_class` and providing the generator object, along with a check function. The latter should accept a list of object properties (as obtained from the Graph REST API), and return TRUE/FALSE based on whether the object is of your class.
#'
#' @return
#' An invisible vector of registered class names.
#' @examples
#' \dontrun{
#'
#' # built-in 'az_user' class, for an AAD user object
#' register_graph_class("user", az_user,
#' function(props) !is.null(props$userPrincipalName))
#'
#' }
#' @export
register_graph_class <- function(name, R6_generator, check_function)
{
if(!R6::is.R6Class(R6_generator))
stop("R6_generator should be an R6 class generator object", call.=FALSE)
if(!is.function(check_function))
stop("check_function should be a function")
.graph_classes[[name]] <- list(
generator=R6_generator,
check=check_function
)
invisible(ls(.graph_classes))
}
.graph_classes <- new.env()
# classes supplied by AzureGraph
register_graph_class("user", az_user,
function(props) !is.null(props$userPrincipalName))
register_graph_class("group", az_group,
function(props) !is.null(props$groupTypes))
register_graph_class("application", az_app,
function(props) !is.null(props$appId) && is.null(props$servicePrincipalType))
register_graph_class("servicePrincipal", az_service_principal,
function(props) !is.null(props$appId) && !is.null(props$servicePrincipalType))
register_graph_class("device", az_device,
function(props) !is.null(props$publishingState))
register_graph_class("directoryRole", az_directory_role,
function(props) !is.null(props$roleTemplateId))
find_class_generator <- function(props, type_filter, default_generator)
{
# use ODATA metadata if available
if(!is.null(props$`@odata.type`))
{
type <- sub("^#microsoft.graph.", "", props$`@odata.type`)
if(!(type %in% ls(.graph_classes)))
type <- NA
}
else # check for each known type in turn
{
type <- NA
for(n in ls(.graph_classes))
{
if(.graph_classes[[n]]$check(props))
{
type <- n
break
}
}
}
# here, 'type' will be one of the known types or NA for unknown
# always return the default class if unknown, even if a type filter is provided
if(is.na(type))
return(default_generator)
if(is.null(type_filter) || type %in% type_filter)
.graph_classes[[type]]$generator
else NULL
}

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

@ -4,9 +4,8 @@
\name{az_app}
\alias{az_app}
\title{Registered app in Azure Active Directory}
\format{An R6 object of class \code{az_app}, inheriting from \code{az_object}.}
\usage{
az_app
\format{
An R6 object of class \code{az_app}, inheriting from \code{az_object}.
}
\description{
Base class representing an AAD app.
@ -39,7 +38,7 @@ Base class representing an AAD app.
\item \code{add_password(password_name=NULL, password_duration=NULL)}: Adds a strong password. \code{password_duration} is the length of time in years that the password remains valid, with default duration 2 years. Returns the ID of the generated password.
\item \code{remove_password(password_id, confirm=TRUE)}: Removes the password with the given ID. By default, ask for confirmation first.
\item \code{add_certificate(certificate)}: Adds a certificate for authentication. This can be specified as the name of a .pfx or .pem file, an \code{openssl::cert} object, an \code{AzureKeyVault::stored_cert} object, or a raw or character vector.
\item \code{remove_certificate(certificate_id, confirm=TRUE}): Removes the certificate with the given ID. By default, ask for confirmation first.
\item \verb{remove_certificate(certificate_id, confirm=TRUE}): Removes the certificate with the given ID. By default, ask for confirmation first.
}
}
@ -98,4 +97,3 @@ app$update(displayName="MyRenamedApp")
\seealso{
\link{ms_graph}, \link{az_service_principal}, \link{az_user}, \link{az_group}, \link{az_object}
}
\keyword{datasets}

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

@ -4,12 +4,11 @@
\name{az_device}
\alias{az_device}
\title{Device in Azure Active Directory}
\format{An R6 object of class \code{az_device}, inheriting from \code{az_object}.}
\usage{
az_device
\format{
An R6 object of class \code{az_device}, inheriting from \code{az_object}.
}
\description{
Base class representing a registered device.
Class representing a registered device.
}
\section{Fields}{
@ -43,6 +42,5 @@ Create objects of this class via the \code{list_registered_devices()} and \code{
\link{ms_graph}, \link{az_user}, \link{az_object}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta}{REST API reference}
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}
\keyword{datasets}

44
man/az_directory_role.Rd Normal file
Просмотреть файл

@ -0,0 +1,44 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/az_dir_role.R
\docType{class}
\name{az_directory_role}
\alias{az_directory_role}
\title{Directory role}
\format{
An R6 object of class \code{az_directory_role}, inheriting from \code{az_object}.
}
\description{
Class representing a role in Azure Active Directory.
}
\section{Fields}{
\itemize{
\item \code{token}: The token used to authenticate with the Graph host.
\item \code{tenant}: The Azure Active Directory tenant for this role.
\item \code{type}: always "directory role" for a directory role object.
\item \code{properties}: The item properties.
}
}
\section{Methods}{
\itemize{
\item \code{new(...)}: Initialize a new object. Do not call this directly; see 'Initialization' below.
\item \code{delete(confirm=TRUE)}: Delete this item. By default, ask for confirmation first.
\item \code{update(...)}: Update the item's properties in Microsoft Graph.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the item.
\item \code{sync_fields()}: Synchronise the R object with the item metadata in Microsoft Graph.
}
}
\section{Initialization}{
Currently support for directory roles is limited. Objects of this class should not be initialized directly.
}
\seealso{
\link{ms_graph}, \link{az_user}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}

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

@ -4,12 +4,11 @@
\name{az_group}
\alias{az_group}
\title{Group in Azure Active Directory}
\format{An R6 object of class \code{az_group}, inheriting from \code{az_object}.}
\usage{
az_group
\format{
An R6 object of class \code{az_group}, inheriting from \code{az_object}.
}
\description{
Base class representing an AAD group.
Class representing an AAD group.
}
\section{Fields}{
@ -59,6 +58,5 @@ grp$list_owners()
\link{ms_graph}, \link{az_app}, \link{az_user}, \link{az_object}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta}{REST API reference}
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}
\keyword{datasets}

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

@ -4,12 +4,11 @@
\name{az_object}
\alias{az_object}
\title{Azure Active Directory object}
\format{An R6 object of class \code{az_object}.}
\usage{
az_object
\format{
An R6 object of class \code{az_object}, inheriting from \code{ms_object}.
}
\description{
Base class representing a directory object in Microsoft Graph.
Base class representing an Azure Active Directory object in Microsoft Graph.
}
\section{Fields}{
@ -43,6 +42,5 @@ Objects of this class should not be created directly. Instead, create an object
\link{ms_graph}, \link{az_app}, \link{az_service_principal}, \link{az_user}, \link{az_group}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta}{REST API reference}
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}
\keyword{datasets}

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

@ -4,12 +4,11 @@
\name{az_service_principal}
\alias{az_service_principal}
\title{Service principal in Azure Active Directory}
\format{An R6 object of class \code{az_service_principal}, inheriting from \code{az_object}.}
\usage{
az_service_principal
\format{
An R6 object of class \code{az_service_principal}, inheriting from \code{az_object}.
}
\description{
Base class representing an AAD service principal.
Class representing an AAD service principal.
}
\section{Fields}{
@ -43,6 +42,5 @@ Creating new objects of this class should be done via the \code{create_service_p
\link{ms_graph}, \link{az_app}, \link{az_object}
\href{https://docs.microsoft.com/en-us/graph/overview}{Azure Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta}{REST API reference}
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}
\keyword{datasets}

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

@ -4,12 +4,11 @@
\name{az_user}
\alias{az_user}
\title{User in Azure Active Directory}
\format{An R6 object of class \code{az_user}, inheriting from \code{az_object}.}
\usage{
az_user
\format{
An R6 object of class \code{az_user}, inheriting from \code{az_object}.
}
\description{
Base class representing an AAD user account.
Class representing an AAD user account.
}
\section{Fields}{
@ -31,7 +30,7 @@ Base class representing an AAD user account.
\item \code{sync_fields()}: Synchronise the R object with the app data in Azure Active Directory.
\item \code{list_group_memberships()}: Return the IDs of all groups this user is a member of.
\item \code{list_object_memberships()}: Return the IDs of all groups, administrative units and directory roles this user is a member of.
\item \code{list_direct_memberships(id_only=TRUE)}: List the groups this user is a direct member of. Set \code{id_only=TRUE} to return only a vector of group IDs (the default), or \code{id_only=FALSE} to return a list of group objects.
\item \code{list_direct_memberships(id_only=TRUE)}: List the groups and directory roles this user is a direct member of. Set \code{id_only=TRUE} to return only a vector of IDs (the default), or \code{id_only=FALSE} to return a list of group objects.
\item \code{list_owned_objects(type=c("user", "group", "application", "servicePrincipal"))}: List directory objects (groups/apps/service principals) owned by this user. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_created_objects(type=c("user", "group", "application", "servicePrincipal"))}: List directory objects (groups/apps/service principals) created by this user. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_owned_devices()}: List the devices owned by this user.
@ -71,6 +70,5 @@ usr$list_owned_objects(type=c("application", "servicePrincipal"))
\link{ms_graph}, \link{az_app}, \link{az_group}, \link{az_device}, \link{az_object}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta}{REST API reference}
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}
\keyword{datasets}

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

@ -11,7 +11,7 @@ call_graph_endpoint(token, operation, ..., options = list(),
call_graph_url(token, url, ..., body = NULL, encode = "json",
http_verb = c("GET", "DELETE", "PUT", "POST", "HEAD", "PATCH"),
http_status_handler = c("stop", "warn", "message", "pass"),
auto_refresh = TRUE)
simplify = FALSE, auto_refresh = TRUE)
}
\arguments{
\item{token}{An Azure OAuth token, of class \link{AzureToken}.}
@ -34,6 +34,8 @@ call_graph_url(token, url, ..., body = NULL, encode = "json",
\item{http_status_handler}{How to handle in R the HTTP status code of a response. \code{"stop"}, \code{"warn"} or \code{"message"} will call the appropriate handlers in httr, while \code{"pass"} ignores the status code.}
\item{simplify}{Whether to turn arrays of objects in the JSON response into data frames. Set this to \code{TRUE} if you are expecting the endpoint to return tabular data and you want a tabular result, as opposed to a list of objects.}
\item{auto_refresh}{Whether to refresh/renew the OAuth token if it is no longer valid.}
}
\value{
@ -46,6 +48,8 @@ Call the Microsoft Graph REST API
}
\details{
These functions form the low-level interface between R and Microsoft Graph. \code{call_graph_endpoint} forms a URL from its arguments and passes it to \code{call_graph_url}.
If \code{simplify} is \code{TRUE}, \code{call_graph_url} will exploit the ability of \code{jsonlite::fromJSON} to convert arrays of objects into R data frames. This can be useful for REST calls that return tabular data. However, it can also cause problems for \emph{paged} lists, where each page will be turned into a separate data frame; as the individual objects may not have the same fields, the resulting data frames will also have differing columns. This will cause base R's \code{rbind} to fail when binding the pages together. When processing paged lists, AzureGraph will use \code{vctrs::vec_rbind} instead of \code{rbind} when the vctrs package is available; \code{vec_rbind} does not have this problem. For safety, you should only set \code{simplify=TRUE} when vctrs is installed.
}
\seealso{
\link[httr:GET]{httr::GET}, \link[httr:PUT]{httr::PUT}, \link[httr:POST]{httr::POST}, \link[httr:DELETE]{httr::DELETE}, \link[httr:stop_for_status]{httr::stop_for_status}, \link[httr:content]{httr::content}

22
man/format.Rd Normal file
Просмотреть файл

@ -0,0 +1,22 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/format.R
\name{format_public_fields}
\alias{format_public_fields}
\alias{format_public_methods}
\title{Format a Microsoft Graph or Azure object}
\usage{
format_public_fields(env, exclude = character(0))
format_public_methods(env)
}
\arguments{
\item{env}{An R6 object's environment for printing.}
\item{exclude}{Objects in \code{env} to exclude from the printout.}
}
\description{
Miscellaneous functions for printing Microsoft Graph and Azure R6 objects
}
\details{
These are utilities to aid in printing R6 objects created by this package or its descendants. They are not meant to be called by the user.
}

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

@ -7,13 +7,14 @@
\alias{list_graph_logins}
\title{Login to Azure Active Directory Graph}
\usage{
create_graph_login(tenant = "common", app = .az_cli_app_id,
password = NULL, username = NULL, certificate = NULL,
auth_type = NULL, host = "https://graph.microsoft.com/",
aad_host = "https://login.microsoftonline.com/", config_file = NULL,
token = NULL, ...)
create_graph_login(tenant = "common", app = NULL, password = NULL,
username = NULL, certificate = NULL, auth_type = NULL, version = 2,
host = "https://graph.microsoft.com/",
aad_host = "https://login.microsoftonline.com/", scopes = ".default",
config_file = NULL, token = NULL, ...)
get_graph_login(tenant = "common", selection = NULL, refresh = TRUE)
get_graph_login(tenant = "common", selection = NULL, app = NULL,
scopes = NULL, auth_type = NULL, refresh = TRUE)
delete_graph_login(tenant = "common", confirm = TRUE)
@ -32,11 +33,15 @@ list_graph_logins()
\item{auth_type}{The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". If \code{NULL}, this is chosen based on the presence of the \code{username} and \code{password} arguments.}
\item{host}{Your Microsoft Graph host. Defaults to \code{https://graph.microsoft.com/}. Change this if you are using a government or private cloud.}
\item{version}{The Azure Active Directory version to use for authenticating.}
\item{aad_host}{Azure Active Directory host for authentication. Defaults to \code{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud.}
\item{host}{Your Microsoft Graph host. Defaults to \verb{https://graph.microsoft.com/}. Change this if you are using a government or private cloud.}
\item{config_file}{Optionally, a JSON file containing any of the arguments listed above. Arguments supplied in this file take priority over those supplied on the command line. You can also use the output from the Azure CLI \code{az ad sp create-for-rbac} command.}
\item{aad_host}{Azure Active Directory host for authentication. Defaults to \verb{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud.}
\item{scopes}{The Microsoft Graph scopes (permissions) to obtain for this Graph login. Only for \code{version=2}.}
\item{config_file}{Optionally, a JSON file containing any of the arguments listed above. Arguments supplied in this file take priority over those supplied on the command line. You can also use the output from the Azure CLI \verb{az ad sp create-for-rbac} command.}
\item{token}{Optionally, an OAuth 2.0 token, of class \link[AzureAuth:AzureToken]{AzureAuth::AzureToken}. This allows you to reuse the authentication details for an existing session. If supplied, all other arguments to \code{create_graph_login} will be ignored.}
@ -59,7 +64,7 @@ Login to Azure Active Directory Graph
\details{
\code{create_graph_login} creates a login client to authenticate with Microsoft Graph, using the supplied arguments. The authentication token is obtained using \link{get_azure_token}, which automatically caches and reuses tokens for subsequent sessions. Note that credentials are only cached if you allowed AzureGraph to create a data directory at package startup.
\code{get_graph_login} returns a login client by retrieving previously saved credentials. It searches for saved credentials according to the supplied tenant; if multiple logins are found, it will prompt for you to choose one.
\code{get_graph_login} returns a previously created login client. If there are multiple existing clients, you can specify which client to return via the \code{selection}, \code{app}, \code{scopes} and \code{auth_type} arguments. If you don't specify which one to return, it will pop up a menu and ask you to choose one.
One difference between \code{create_graph_login} and \code{get_graph_login} is the former will delete any previously saved credentials that match the arguments it was given. You can use this to force AzureGraph to remove obsolete tokens that may be lying around.
}
@ -71,7 +76,7 @@ If you are using a Linux \href{https://azure.microsoft.com/en-us/services/virtua
\examples{
\dontrun{
# without any arguments, this will create a client using your AAD credentials
# without any arguments, this will create a client using your AAD organisational account
az <- create_graph_login()
# retrieve the login in subsequent sessions
@ -79,16 +84,24 @@ az <- get_graph_login()
# this will create an Microsoft Graph client for the tenant 'microsoft.onmicrosoft.com',
# using the client_credentials method
az <- create_graph_login("microsoft", app="{app_id}", password="{password}")
az <- create_graph_login("mytenant", app="{app_id}", password="{password}")
# you can also login using credentials in a json file
az <- create_graph_login(config_file="~/creds.json")
# creating and obtaining a login with specific scopes
create_graph_login("mytenant", scopes=c("User.Read", "Files.ReadWrite.All"))
get_graph_login("mytenant", scopes=c("User.Read", "Files.ReadWrite.All"))
# to use your personal account, set the tenant to one of the following
create_graph_login("9188040d-6c67-4c5b-b112-36a304b66dad")
create_graph_login("consumers") # requires AzureAuth 1.2.6
}
}
\seealso{
\link{ms_graph}, \link[AzureAuth:get_azure_token]{AzureAuth::get_azure_token} for more details on authentication methods
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta}{REST API reference}
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}

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

@ -5,7 +5,9 @@
\alias{is_service_principal}
\alias{is_user}
\alias{is_group}
\alias{is_directory_object}
\alias{is_directory_role}
\alias{is_aad_object}
\alias{is_msgraph_object}
\title{Informational functions}
\usage{
is_app(object)
@ -16,7 +18,11 @@ is_user(object)
is_group(object)
is_directory_object(object)
is_directory_role(object)
is_aad_object(object)
is_msgraph_object(object)
}
\arguments{
\item{object}{An R object.}

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

@ -4,9 +4,8 @@
\name{ms_graph}
\alias{ms_graph}
\title{Azure Active Directory Graph}
\format{An R6 object of class \code{ms_graph}.}
\usage{
ms_graph
\format{
An R6 object of class \code{ms_graph}.
}
\description{
Base class for interacting with Microsoft Graph API.
@ -43,8 +42,10 @@ To authenticate with the \code{ms_graph} class directly, provide the following a
\item \code{username}: if \code{auth_type == "resource_owner"}, your username.
\item \code{certificate}: If `auth_type == "client_credentials", a certificate to authenticate with. This is a more secure alternative to using an app secret.
\item \code{auth_type}: The OAuth authentication method to use, one of "client_credentials", "authorization_code", "device_code" or "resource_owner". See \link{get_azure_token} for how the default method is chosen, along with some caveats.
\item \code{host}: your Microsoft Graph host. Defaults to \code{https://graph.microsoft.com/}.
\item \code{aad_host}: Azure Active Directory host for authentication. Defaults to \code{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud.
\item \code{version}: The Azure Active Directory (AAD) version to use for authenticating.
\item \code{host}: your Microsoft Graph host. Defaults to \verb{https://graph.microsoft.com/}.
\item \code{aad_host}: Azure Active Directory host for authentication. Defaults to \verb{https://login.microsoftonline.com/}. Change this if you are using a government or private cloud.
\item \code{scopes}: The Microsoft Graph scopes (permissions) to obtain for this Graph login. Only for \code{version=2}.
\item \code{token}: Optionally, an OAuth 2.0 token, of class \link[AzureAuth:AzureToken]{AzureAuth::AzureToken}. This allows you to reuse the authentication details for an existing session. If supplied, all other arguments will be ignored.
}
}
@ -91,6 +92,5 @@ gr$create_app("mycertapp", password=FALSE, certificate=cert)
\link{create_graph_login}, \link{get_graph_login}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta}{REST API reference}
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}
\keyword{datasets}

44
man/ms_object.Rd Normal file
Просмотреть файл

@ -0,0 +1,44 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ms_object.R
\docType{class}
\name{ms_object}
\alias{ms_object}
\title{Azure Active Directory object}
\format{
An R6 object of class \code{ms_object}.
}
\description{
Base class representing a object in Microsoft Graph. All other Graph object classes ultimately inherit from this class.
}
\section{Fields}{
\itemize{
\item \code{token}: The token used to authenticate with the Graph host.
\item \code{tenant}: The Azure Active Directory tenant for this object.
\item \code{type}: The type of object, in a human-readable format.
\item \code{properties}: The object properties.
}
}
\section{Methods}{
\itemize{
\item \code{new(...)}: Initialize a new directory object. Do not call this directly; see 'Initialization' below.
\item \code{delete(confirm=TRUE)}: Delete an object. By default, ask for confirmation first.
\item \code{update(...)}: Update the object information in Azure Active Directory.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the object.
\item \code{sync_fields()}: Synchronise the R object with the data in Azure Active Directory.
}
}
\section{Initialization}{
Objects of this class should not be created directly. Instead, create an object of the appropriate subclass.
}
\seealso{
\link{ms_graph}, \link{az_object}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}
}

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

@ -0,0 +1,35 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/zzz_class_directory.R
\name{register_graph_class}
\alias{register_graph_class}
\title{Extensible registry of Microsoft Graph classes that AzureGraph supports}
\usage{
register_graph_class(name, R6_generator, check_function)
}
\arguments{
\item{name}{The name of the Graph class, eg "user", "servicePrincipal", etc.}
\item{R6_generator}{An R6 class generator corresponding to this Graph class.}
\item{check_function}{A boolean function that checks if a list of properties is for an object of this class.}
}
\value{
An invisible vector of registered class names.
}
\description{
Extensible registry of Microsoft Graph classes that AzureGraph supports
}
\details{
As written, AzureGraph knows about a subset of all the object classes contained in Microsoft Graph. These are mostly the classes originating from Azure Active Directory: users, groups, app registrations, service principals and registered devices.
You can extend AzureGraph by writing your own R6 class that inherits from \code{ms_object}. If so, you should also \emph{register} your class by calling \code{register_graph_class} and providing the generator object, along with a check function. The latter should accept a list of object properties (as obtained from the Graph REST API), and return TRUE/FALSE based on whether the object is of your class.
}
\examples{
\dontrun{
# built-in 'az_user' class, for an AAD user object
register_graph_class("user", az_user,
function(props) !is.null(props$userPrincipalName))
}
}

29
man/utils.Rd Normal file
Просмотреть файл

@ -0,0 +1,29 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.R
\name{named_list}
\alias{named_list}
\alias{is_empty}
\title{Miscellaneous utility functions}
\usage{
named_list(lst = NULL, name_fields = "name")
is_empty(x)
}
\arguments{
\item{lst}{A named list of objects.}
\item{name_fields}{The components of the objects in \code{lst}, to be used as names.}
\item{x}{For \code{is_empty}, An R object.}
}
\value{
For \code{named_list}, the list that was passed in but with names. An empty input results in a \emph{named list} output: a list of length 0, with a \code{names} attribute.
For \code{is_empty}, whether the length of the object is zero (this includes the special case of \code{NULL}).
}
\description{
Miscellaneous utility functions
}
\details{
\code{named_list} extracts from each object in \code{lst}, the components named by \code{name_fields}. It then constructs names for \code{lst} from these components, separated by a \code{"/"}.
}

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

@ -0,0 +1,10 @@
context("Class registration")
test_that("Class registration works",
{
newclass <- R6::R6Class("newclass", inherit=ms_object)
expect_false("newclass" %in% ls(.graph_classes))
expect_silent(register_graph_class("newclass", newclass, function(x) FALSE))
expect_true("newclass" %in% ls(.graph_classes))
expect_error(register_graph_class("badclass", "badclassname", FALSE))
})

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

@ -9,6 +9,7 @@ if(tenant == "" || app == "")
if(!interactive())
skip("Authentication tests skipped: must be in interactive session")
scopes <- c("https://graph.microsoft.com/.default", "openid", "offline_access")
test_that("Graph authentication works",
{
@ -16,7 +17,7 @@ test_that("Graph authentication works",
expect_is(gr, "ms_graph")
expect_true(is_azure_token(gr$token))
token <- get_azure_token("https://graph.microsoft.com/", tenant, app)
token <- get_azure_token(scopes, tenant, app, version=2)
gr2 <- ms_graph$new(token=token)
expect_is(gr2, "ms_graph")

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

@ -3,7 +3,7 @@ context("App creation/deletion")
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
pemfile <- Sys.getenv("AZ_TEST_CERT_FILE")
if(tenant == "" || cert_thumb == "")
if(tenant == "" || pemfile == "")
skip("App method tests skipped: login credentials not set")
if(!interactive())

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

@ -2,6 +2,7 @@ context("Users/groups")
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
user <- Sys.getenv("AZ_TEST_USERPRINCIPALNAME")
admin_user <- Sys.getenv("AZ_TEST_ADMINUSERPRINCIPALNAME")
if(tenant == "" || user == "")
skip("User method tests skipped: login credentials not set")
@ -15,7 +16,7 @@ gr <- get_graph_login(tenant=tenant)
test_that("User/group read functionality works",
{
me <- gr$get_user()
expect_equal(me$properties$userPrincipalName, user)
expect_equal(me$properties$userPrincipalName, admin_user)
me2 <- gr$get_user(user)
expect_equal(me2$properties$userPrincipalName, user)
@ -30,7 +31,7 @@ test_that("User/group read functionality works",
expect_true(is.character(grps2))
grps3 <- me$list_direct_memberships(id_only=FALSE)
expect_true(all(sapply(grps3, is_group)))
expect_true(all(sapply(grps3, function(x) is_group(x) || is_directory_role(x))))
expect_true(all(sapply(grps3, function(g) !is.null(g$properties$id))))
grp <- gr$get_group(grps1[1])

131
vignettes/extend.Rmd Normal file
Просмотреть файл

@ -0,0 +1,131 @@
---
title: "Extending AzureGraph"
author: Hong Ooi
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Extending}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{utf8}
---
As written, AzureGraph provides support for Microsoft Graph objects derived from Azure Active Directory (AAD): users, groups, app registrations and service principals. This vignette describes how to extend it to support other services.
## Extend the `ms_object` base class
AzureGraph provides the `ms_object` class to represent a generic object in Graph. You can extend this to support specific services by adding custom methods and fields.
For example, the [SharePointR](https://github.com/hongooi73/SharePointR) package extends AzureGraph to support SharePoint Online sites and OneDrive filesystems (both personal and business). This is the `ms_site` class from that package, which represents a SharePoint site. To save space, the actual code in the new methods has been elided.
```r
ms_site <- R6::R6Class("ms_site", inherit=ms_object,
public=list(
initialize=function(token, tenant=NULL, properties=NULL)
{
self$type <- "site"
private$api_type <- "sites"
super$initialize(token, tenant, properties)
},
list_drives=function() {}, # ...
get_drive=function(drive_id=NULL) {}, # ...
list_subsites=function() {}, # ...
get_list=function(list_name=NULL, list_id=NULL) {}, # ...
print=function(...)
{
cat("<Sharepoint site '", self$properties$displayName, "'>\n", sep="")
cat(" directory id:", self$properties$id, "\n")
cat(" web link:", self$properties$webUrl, "\n")
cat(" description:", self$properties$description, "\n")
cat("---\n")
cat(format_public_methods(self))
invisible(self)
}
))
```
Note the following:
- The `initialize()` method of your class should take 3 arguments: the OAuth2 token for authenticating with Graph, the name of the AAD tenant, and the list of properties for this object as obtained from the Graph endpoint. It should set 2 fields: `self$type` contains a human-readable name for this type of object, and `private$api_type` contains the object type as it appears in the URL of a Graph API request. It should then call the superclass method to complete the initialisation. `initialize()` itself should not contact the Graph endpoint; it should merely create and populate the R6 object given the response from a previous request.
- The `print()` method is optional and should display any properties that can help identify this object to a human reader.
You can read the code of the existing classes such as `az_user`, `az_app` etc to see how to call the API. The `do_operation()` method should suffice for any regular communication with the Graph endpoint.
## Register the class with `register_graph_class`
Having defined your new class, call `register_graph_class` so that AzureGraph becomes aware of it and can automatically use it to populate object lists. If you are writing a new package, the `register_graph_class` call should go in your package's `.onLoad` startup function. For example, registering the `ms_site` SharePoint class looks like this.
```r
.onLoad <- function(libname, pkgname)
{
register_graph_class("site", ms_site,
function(props) grepl("sharepoint", props$id, fixed=TRUE))
# ... other startup code ...
}
```
`register_graph_class` takes 3 arguments:
- The name of the object class, as it appears in the [Microsoft Graph online documentation](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0).
- The R6 class generator object, as defined in the previous section.
- A check function which takes a list of properties (as returned by the Graph API) and returns TRUE/FALSE based on whether the properties are for an object of your class. This is necessary as some Graph calls that return lists of objects do not always include explicit metadata indicating the type of each object, hence the type must be inferred from the properties.
## Add getter and setter methods
Finally, so that people can use the same workflow with your class as with AzureGraph-supplied classes, you can add getter and setter methods to `ms_graph` and any other classes for which it's appropriate. Again, if you're writing a package, this should happen in the `.onLoad` function.
In the case of `ms_site`, it's appropriate to add a getter method not just to `ms_graph`, but also the `ms_group` class. This is because SharePoint sites have associated user groups, hence it's useful to be able to retrieve a site given the object for a group. The relevant code in the `.onLoad` function looks like this (slightly simplified):
```r
.onLoad <- function(libname, pkgname)
{
# ...
ms_graph$set("public", "get_sharepoint_site", overwrite=TRUE,
function(site_url=NULL, site_id=NULL)
{
op <- if(is.null(site_url) && !is.null(site_id))
file.path("sites", site_id)
else if(!is.null(site_url) && is.null(site_id))
{
site_url <- httr::parse_url(site_url)
file.path("sites", paste0(site_url$hostname, ":"), site_url$path)
}
else stop("Must supply either site ID or URL")
ms_site$new(self$token, self$tenant, self$call_graph_endpoint(op))
})
az_group$set("public", "get_sharepoint_site", overwrite=TRUE,
function()
{
res <- self$do_operation("sites/root")
ms_site$new(self$token, self$tenant, res)
})
# ...
}
```
Once this is done, the object for a SharePoint site can be instantiated as follows:
```r
library(AzureGraph)
library(SharePointR)
gr <- get_graph_login()
# directly from the Graph client
mysite1 <- gr$get_sharepoint_site("https://mytenant.sharepoint.com/sites/my-site-name")
# or via a group
mygroup <- gr$get_group("my-group-guid")
mysite2 <- mygroup$get_sharepoint_site()
```

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

@ -10,7 +10,7 @@ vignette: >
[Microsoft Graph](https://docs.microsoft.com/en-us/graph/overview) is a comprehensive framework for accessing data in various online Microsoft services, including Azure Active Directory (AAD), Office 365, OneDrive, Teams, and more. AzureGraph is a simple R6-based interface to the Graph REST API, and is the companion package to [AzureRMR](https://github.com/Azure/AzureRMR) and [AzureAuth](https://github.com/Azure/AzureAuth).
Currently, AzureGraph aims to provide an R interface only to the AAD part, with a view to supporting R interoperability with Azure: registered apps and service principals, users and groups. Like AzureRMR, it could potentially be extended to support other services.
Currently, AzureGraph aims to provide an R interface only to the AAD part, with a view to supporting R interoperability with Azure: registered apps and service principals, users and groups. However, it can be extended to support other services; for more information, see the "Extending AzureGraph" vignette.
## Authentication