Use Microsoft Graph en el marco de trabajo de SharePoint
Usar una API de REST protegida con Azure Active Directory (Azure AD) y Open Authorization (OAuth 2.0) desde un elemento web del lado cliente o una extensión de SharePoint Framework es un escenario de negocio común a nivel empresarial.
Puede usar SharePoint Framework (presentado en la versión 1.4.1), para consumir las API de REST de Microsoft Graph o cualquier otra API de REST que esté registrada en Azure AD.
En este artículo, descubrirá cómo crear una solución de SharePoint Framework que use la API de Microsoft Graph con un conjunto personalizado de permisos. Para obtener información general sobre los conceptos de esta tecnología, vea Conectarse a API protegidas por Azure AD en soluciones de SharePoint Framework.
Importante
Puede usar microsoft Graph API con versiones de SharePoint Framework anteriores a la versión 1.4.1, ya sea a través de GraphHttpClient nativo o directamente mediante el uso directo del flujo de OAuth implícito de las bibliotecas de autenticación platfomr de identidad de Microsoft. Pero el primer método está relacionado con un conjunto predefinido de permisos, que presenta algunas limitaciones, y el último es complejo desde una perspectiva de desarrollo. Para obtener información sobre cómo implementar un flujo implícito de OAuth, vea Conectarse a API protegidas con Azure Active Directory.
Información general sobre la solución
Los pasos de este artículo muestran cómo crear un elemento web del lado cliente que permita buscar usuarios en el espacio empresarial actual, tal como se muestra en la siguiente captura de pantalla. La búsqueda se basa en Microsoft Graph y se necesita, como mínimo, el permiso User.ReadBasic.All.
El elemento web del lado cliente permite la búsqueda de usuarios en función de su nombre y proporciona todos los usuarios que coincidan con un componente DetailsList de Office UI Fabric. El elemento web tiene una opción en el panel de propiedades para seleccionar cómo obtener acceso a Microsoft Graph. A partir de la versión 1.4.1 de SharePoint Framework, puede obtener acceso a Microsoft Graph con el cliente nativo de Graph (MSGraphClient), o bien el tipo de nivel bajo usado para obtener acceso a cualquier API de REST protegida por Azure AD (AadHttpClient).
Nota:
Para obtener el código fuente de esta solución, vea el repositorio de GitHub api-scopes.
Si ya está familiarizado con el procedimiento para crear soluciones de SharePoint Framework, puede pasar a Configurar las solicitudes de permisos de API.
Crear la solución inicial
Si tiene una versión anterior del generador de SharePoint Framework, necesita actualizarlo a la versión 1.4.1 o posterior. Para ello, ejecute el siguiente comando para instalar la última versión del paquete de forma global.
npm install -g @microsoft/generator-sharepoint
A continuación, cree una solución nueva de SharePoint Framework:
Cree una carpeta en el sistema de archivos. Se almacenará el código fuente de la solución y se moverá la ruta de acceso actual a esta carpeta.
Ejecute el generador de Yeoman para aplicar scaffolding en una nueva solución.
yo @microsoft/sharepoint
En el momento en que se le solicite, introduzca los siguientes valores (seleccione la opción predeterminada para todas las solicitudes que se omiten a continuación):
- ¿Cómo se llama su solución?: spfx-api-scopes-tutorial
- ¿Qué paquetes de línea de base desea que se destinen al componente o los componentes? Solo SharePoint Online (versiones más recientes)
- ¿Cuál es el tipo de componente del lado cliente que se va a crear? Elemento web
- ¿Cómo se llama su elemento web? GraphConsumer
- ¿Qué marco desea utilizar? React
Inicie Visual Studio Code (o su editor de código favorito) en el contexto de la carpeta actual.
code .
Configurar los elementos web básicos
A continuación, configure los elementos iniciales en el elemento web del lado cliente.
Configurar las propiedades personalizadas
Cree un nuevo archivo de código fuente en la carpeta ./src/webparts/graphConsumer/components de la solución.
Asigne el nombre ClientMode.ts al nuevo archivo y úselo para declarar un elemento
enum
de TypeScript con las opciones disponibles para la propiedadClientMode
del elemento web.export enum ClientMode { aad, graph, }
Después, abra el archivo GraphConsumerWebPart.ts en la carpeta ./src/webparts/graphConsumer de la solución.
Cambie la definición de la interfaz de
IGraphConsumerWebPartProps
para aceptar un valor de tipo ClientMode.export interface IGraphConsumerWebPartProps { clientMode: ClientMode; }
Después, actualice el método
getPropertyPaneConfiguration()
del elemento web del lado cliente para que admita la selección de opciones en el panel de propiedades. En el siguiente ejemplo, se muestra la nueva implementación del método.protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { return { pages: [ { header: { description: strings.PropertyPaneDescription }, groups: [ { groupName: strings.BasicGroupName, groupFields: [ PropertyPaneChoiceGroup('clientMode', { label: strings.ClientModeLabel, options: [ { key: ClientMode.aad, text: "AadHttpClient"}, { key: ClientMode.graph, text: "MSGraphClient"}, ] }), ] } ] } ] }; }
Necesita actualizar el método
render()
del elemento web del lado cliente para crear una instancia configurada correctamente del componente de React para su representación. En el código siguiente, se muestra la definición del método de actualización.public render(): void { const element: React.ReactElement<IGraphConsumerProps > = React.createElement( GraphConsumer, { clientMode: this.properties.clientMode, context: this.context, } ); ReactDom.render(element, this.domElement); }
Para que este código funcione, deberá agregar algunas instrucciones de importación al principio del archivo GraphConsumerWebPart.ts, tal como se muestra en el ejemplo siguiente. Observe la importación del control
PropertyPaneChoiceGroup
, así como la importación de la enumeraciónClientMode
.import * as React from "react"; import * as ReactDom from "react-dom"; import { Version } from "@microsoft/sp-core-library"; import { BaseClientSideWebPart, IPropertyPaneConfiguration, PropertyPaneChoiceGroup, } from "@microsoft/sp-webpart-base"; import * as strings from "GraphConsumerWebPartStrings"; import GraphConsumer from "./components/GraphConsumer"; import { IGraphConsumerProps } from "./components/IGraphConsumerProps"; import { ClientMode } from "./components/ClientMode";
Actualizar las cadenas de recursos
Para compilar la solución, necesita actualizar el archivo mystrings.d.ts en la carpeta ./src/webparts/graphConsumer/loc de la solución.
Vuelva a escribir la interfaz que define las cadenas de recursos con el código siguiente.
declare interface IGraphConsumerWebPartStrings { PropertyPaneDescription: string; BasicGroupName: string; ClientModeLabel: string; SearchFor: string; SearchForValidationErrorMessage: string; }
Después, configure los valores correctos de las nuevas cadenas de recursos; para hacerlo, actualice el archivo en-us.js que verá en la misma carpeta.
define([], function () { return { PropertyPaneDescription: "Description", BasicGroupName: "Group Name", ClientModeLabel: "Client Mode", SearchFor: "Search for", SearchForValidationErrorMessage: "Invalid value for 'Search for' field", }; });
Actualizar el estilo del elemento web del lado cliente
También necesita actualizar el archivo de estilos SCSS.
Abra el archivo GraphConsumer.module.scss en la carpeta ./src/webparts/graphConsumer/components de la solución. Agregue las siguientes clases de estilo, justo después de la clase .title
:
.form {
@include ms-font-l;
@include ms-fontColor-white;
}
label {
@include ms-fontColor-white;
}
Actualizar el componente de React que representa el elemento web
Ahora, puede actualizar el componente GraphConsumer de React en la carpeta ./src/webparts/graphConsumer/components de la solución.
Primero, actualice el archivo IGraphConsumerProps.ts para aceptar las propiedades personalizadas que necesita la implementación de elementos web. En el ejemplo siguiente, se muestra el contenido actualizado del archivo IGraphConsumerProps.ts. Observe la importación de la definición de enumeración
ClientMode
, así como la importación del tipoWebPartContext
. Usará esto más adelante.import { WebPartContext } from "@microsoft/sp-webpart-base"; import { ClientMode } from "./ClientMode"; export interface IGraphConsumerProps { clientMode: ClientMode; context: WebPartContext; }
Cree una nueva interfaz para que contenga el estado del componente de React. Cree un nuevo archivo en la carpeta ./src/webparts/graphConsumer/components y asígnele el nombre IGraphConsumerState.ts. La siguiente es la definición de la interfaz.
import { IUserItem } from "./IUserItem"; export interface IGraphConsumerState { users: Array<IUserItem>; searchFor: string; }
Defina la interfaz
IUserItem
(en un archivo llamado IUserItem.ts que se almacena en la carpeta ./src/webparts/graphConsumer/components). Esta interfaz se importará en el archivo de estado. La interfaz se usará para definir el esquema de los usuarios recuperados del espacio empresarial actual y que se enlazaron alDetailsList
en la interfaz de usuario.export interface IUserItem { displayName: string; mail: string; userPrincipalName: string; }
Después, actualice el archivo GraphConsumer.tsx. Primero, agregue instrucciones import para importar los tipos que definió anteriormente. Observe la importación de
IGraphConsumerProps
,IGraphConsumerState
,ClientMode
yIUserItem
. También hay otras importaciones de los componentes de Office UI Fabric usados para representar la interfaz de usuario del componente de React.import * as strings from "GraphConsumerWebPartStrings"; import { BaseButton, Button, CheckboxVisibility, DetailsList, DetailsListLayoutMode, PrimaryButton, SelectionMode, TextField, } from "office-ui-fabric-react"; import * as React from "react"; import { AadHttpClient, MSGraphClient } from "@microsoft/sp-http"; import { escape } from "@microsoft/sp-lodash-subset"; import { ClientMode } from "./ClientMode"; import styles from "./GraphConsumer.module.scss"; import { IGraphConsumerProps } from "./IGraphConsumerProps"; import { IGraphConsumerState } from "./IGraphConsumerState"; import { IUserItem } from "./IUserItem";
Después de las importaciones, defina el esquema de las columnas del componente
DetailsList
de Office UI Fabric.// Configure the columns for the DetailsList component let _usersListColumns = [ { key: "displayName", name: "Display name", fieldName: "displayName", minWidth: 50, maxWidth: 100, isResizable: true, }, { key: "mail", name: "Mail", fieldName: "mail", minWidth: 50, maxWidth: 100, isResizable: true, }, { key: "userPrincipalName", name: "User Principal Name", fieldName: "userPrincipalName", minWidth: 100, maxWidth: 200, isResizable: true, }, ];
Esta matriz se usará en la configuración del componente
DetailsList
, como puede ver en el métodorender()
del componente de React.Cambie este componente por el siguiente código.
public render(): React.ReactElement<IGraphConsumerProps> { return ( <div className={ styles.graphConsumer }> <div className={ styles.container }> <div className={ styles.row }> <div className={ styles.column }> <span className={ styles.title }>Search for a user!</span> <p className={ styles.form }> <TextField label={ strings.SearchFor } required={ true } onChange={ this._onSearchForChanged } onGetErrorMessage={ this._getSearchForErrorMessage } value={ this.state.searchFor } /> </p> <p className={ styles.form }> <PrimaryButton text='Search' title='Search' onClick={ this._search } /> </p> { (this.state.users != null && this.state.users.length > 0) ? <p className={ styles.form }> <DetailsList items={ this.state.users } columns={ _usersListColumns } setKey='set' checkboxVisibility={ CheckboxVisibility.hidden } selectionMode={ SelectionMode.none } layoutMode={ DetailsListLayoutMode.fixedColumns } compact={ true } /> </p> : null } </div> </div> </div> </div> ); }
Actualice la declaración de tipos de componente de React y agregue un constructor, tal como se muestra en el siguiente ejemplo:
export default class GraphConsumer extends React.Component<IGraphConsumerProps, IGraphConsumerState> { constructor(props: IGraphConsumerProps, state: IGraphConsumerState) { super(props); // Initialize the state of the component this.state = { users: [], searchFor: "" }; }
Hay algunas reglas de validación y eventos de control para que el componente
TextField
pueda recopilar los criterios de búsqueda. Las siguientes son las implementaciones del método.Agregue estos dos métodos al final de la clase
GraphConsumer
:private _onSearchForChanged = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string): void => { // Update the component state accordingly to the current user's input this.setState({ searchFor: newValue, }); } private _getSearchForErrorMessage = (value: string): string => { // The search for text cannot contain spaces return (value == null || value.length == 0 || value.indexOf(" ") < 0) ? '' : `${strings.SearchForValidationErrorMessage}`; }
El elemento
PrimaryButton
desencadena una función\_search()
, que determina la tecnología cliente que se usará con Microsoft Graph. Agregue este método al final de la claseGraphConsumer
:private _search = (event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button, MouseEvent>) : void => { console.log(this.props.clientMode); // Based on the clientMode value search users switch (this.props.clientMode) { case ClientMode.aad: this._searchWithAad(); break; case ClientMode.graph: this._searchWithGraph(); break; } }
La instancia del componente DetailsList
se representa en el método render()
, en caso de que haya elementos en la propiedad users
del estado del componente.
Configurar las solicitudes de permisos de la API
Para usar Microsoft Graph o cualquier otra API de REST de terceros, necesita declarar de forma explícita los requisitos de permisos desde una perspectiva de OAuth en el manifiesto de la solución.
En SharePoint Framework versión 1.4.1 o posteriores, puede hacer esto si configura la propiedad webApiPermissionRequests
del archivo package-solution.json en la carpeta config de la solución. En el siguiente ejemplo se muestra un extracto de ese archivo para la solución actual.
Copie la declaración de la propiedad webApiPermissionRequests
.
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "spfx-api-scopes-tutorial-client-side-solution",
"id": "841cd609-d821-468d-a6e4-2d207b966cd8",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"skipFeatureDeployment": true,
"webApiPermissionRequests": [
{
"resource": "Microsoft Graph",
"scope": "User.ReadBasic.All"
}
]
},
"paths": {
"zippedPackage": "solution/spfx-api-scopes-tutorial.sppkg"
}
}
Observe el elemento webApiPermissionRequests
, que es una matriz de elementos de webApiPermissionRequest
. Cada elemento define el resource
y el scope
de la solicitud de permiso.
El resource
puede ser el nombre o el valor de ObjectId (en Azure AD) del recurso para el que quiera configurar la solicitud de permiso. Para Microsoft Graph, el nombre es Microsoft Graph. El valor de ObjectId no es único y varía según el espacio empresarial.
El scope
puede ser el nombre del permiso, o bien el Id. único de ese permiso. Puede obtener el nombre del permiso de la documentación de la API. Puede obtener el identificador del permiso del archivo de manifiesto de la API.
Nota:
Para obtener una lista de los permisos que están disponibles en Microsoft Graph, consulte Referencia de permisos de Microsoft Graph.
De forma predeterminada, la entidad de servicio no tiene permisos explícitos concedidos para tener acceso a Microsoft Graph. Sin embargo, si solicita un token de acceso de Microsoft Graph, obtendrá un token con el permiso user_impersonation
que puede usar para leer la información sobre los usuarios (User.Read.All). Puede solicitar a los administradores de espacios empresariales que le concedan permisos adicionales. Para obtener más información, vea Conectarse a API protegidas por Azure AD en soluciones de SharePoint Framework.
El permiso User.ReadBasic.All es suficiente para buscar usuarios y obtener sus valores displayName
,mail
y userPrincipalName
.
Al empaquetar e implementar una solución, usted (o un administrador) tendrá que conceder los permisos solicitados para la solución. Para obtener información, vea Implementar la solución y conceder permisos.
Usar Microsoft Graph
Puede implementar ahora los métodos para usar Microsoft Graph. Tiene dos opciones:
- Usar el objeto de cliente AadHttpClient
- Usar el objeto de cliente MSGraphClient
El objeto de cliente AadHttpClient resulta útil para el uso de las API de REST. Puede usarlo para usar Microsoft Graph o cualquier otra API de REST de terceros (o propia).
El objeto de cliente MSGraphClient solo puede usarse con Microsoft Graph. De forma interna, usa el objeto de cliente AadHttpClient y admite la sintaxis fluida del SDK de Microsoft Graph.
Usar AadHttpClient
Para usar cualquier API de REST con el objeto de cliente AadHttpClient, cree una instancia del tipo AadHttpClient
llamando al método context.aadHttpClientFactory.getClient()
y especificando el URI del servicio de destino.
El objeto creado le proporcionará los métodos para realizar las siguientes solicitudes:
get()
: realiza una solicitud GET HTTPpost()
: realiza una solicitud HTTP POSTfetch()
: realiza cualquier otro tipo de solicitud HTTP según los argumentosHttpClientConfiguration
yIHttpClientOptions
proporcionados
Dado que todos estos métodos admiten el modelo de desarrollo asincrónico de JavaScript o TypeScript, puede procesar su resultado con promesas.
En el siguiente ejemplo se muestra el método \_searchWithAad()
de la solución de ejemplo.
private _searchWithAad = (): void => {
// Log the current operation
console.log("Using _searchWithAad() method");
// Using Graph here, but any 1st or 3rd party REST API that requires Azure AD auth can be used here.
this.props.context.aadHttpClientFactory
.getClient("https://graph.microsoft.com")
.then((client: AadHttpClient) => {
// Search for the users with givenName, surname, or displayName equal to the searchFor value
return client
.get(
`https://graph.microsoft.com/v1.0/users?$select=displayName,mail,userPrincipalName&$filter=(givenName%20eq%20'${escape(this.state.searchFor)}')%20or%20(surname%20eq%20'${escape(this.state.searchFor)}')%20or%20(displayName%20eq%20'${escape(this.state.searchFor)}')`,
AadHttpClient.configurations.v1
);
})
.then(response => {
return response.json();
})
.then(json => {
// Prepare the output array
var users: Array<IUserItem> = new Array<IUserItem>();
// Log the result in the console for testing purposes
console.log(json);
// Map the JSON response to the output array
json.value.map((item: any) => {
users.push( {
displayName: item.displayName,
mail: item.mail,
userPrincipalName: item.userPrincipalName,
});
});
// Update the component state accordingly to the result
this.setState(
{
users: users,
}
);
})
.catch(error => {
console.error(error);
});
}
El método get()
obtiene la dirección URL de la solicitud de OData como argumento de entrada. Una solicitud correcta devuelve un objeto JSON con la respuesta.
Usar MSGraphClient
Si usa Microsoft Graph como destino, puede usar el objeto de cliente MSGraphClient, que ofrece una sintaxis más fluida.
En el siguiente ejemplo se muestra la implementación del método _searchWithGraph()
de la solución de ejemplo.
private _searchWithGraph = () : void => {
// Log the current operation
console.log("Using _searchWithGraph() method");
this.props.context.msGraphClientFactory
.getClient()
.then((client: MSGraphClient) => {
// From https://github.com/microsoftgraph/msgraph-sdk-javascript sample
client
.api("users")
.version("v1.0")
.select("displayName,mail,userPrincipalName")
.filter(`(givenName eq '${escape(this.state.searchFor)}') or (surname eq '${escape(this.state.searchFor)}') or (displayName eq '${escape(this.state.searchFor)}')`)
.get((err, res) => {
if (err) {
console.error(err);
return;
}
// Prepare the output array
var users: Array<IUserItem> = new Array<IUserItem>();
// Map the JSON response to the output array
res.value.map((item: any) => {
users.push( {
displayName: item.displayName,
mail: item.mail,
userPrincipalName: item.userPrincipalName,
});
});
// Update the component state accordingly to the result
this.setState(
{
users: users,
}
);
});
});
}
Obtendrá una instancia del tipo MSGraphClient
asignando el nombre al método context.msGraphClientFactory.getClient()
.
Después, use la API fluida del SDK de Microsoft Graph para definir la consulta de OData que se ejecutará en el punto de conexión de destino de Microsoft Graph.
El resultado será una respuesta JSON que tendrá que descodificar y asignar al resultado especificado.
Nota:
Puede usar un método escrito por completo si usa los tipos de TypeScript de Microsoft Graph.
Implementar la solución y conceder permisos
Ahora ya está preparado para crear una solución, agruparla, crear un paquete e implementarla.
Ejecute los comandos de Gulp para comprobar que la solución se genera correctamente.
gulp build
Use el comando siguiente para agrupar y crear el paquete de la solución.
gulp bundle gulp package-solution
Después, explore el Catálogo de aplicaciones del espacio empresarial de destino y cargue el paquete de solución. Encontrará el paquete de solución en la carpeta sharepoint/solution de la solución. Es el archivo .sppkg. Después de cargar el paquete de solución, en el Catálogo de aplicaciones se mostrará un cuadro de diálogo similar al que se muestra en la captura de pantalla siguiente.
Un mensaje en la parte inferior de la pantalla indica que el paquete de solución necesita una aprobación de permisos. Esto es debido a la propiedad
webApiPermissionRequests
del archivo package-solution.json.En el Centro de administración moderno de SharePoint Online, en el menú de inicio rápido en la parte izquierda, en Avanzadas seleccione el elemento de menú Acceso de API. Verá una página similar a la siguiente.
Desde esta página, puede aprobar o denegar cualquier solicitud de permiso pendiente (así como cualquier otro administrador del espacio empresarial de SharePoint Online). No se muestra el paquete de solución que solicita cada permiso, porque los permisos se definen en el nivel del espacio empresarial y para una única aplicación.
Nota:
Para más información sobre cómo se definen internamente los ámbitos de permiso de nivel de espacio empresarial, lea los artículos en la sección Vea también.
Seleccione el permiso solicitado en el archivo package-solution.json de la solución, elija Aprobar o rechazar acceso y después, Aprobar. En la captura de pantalla siguiente, se muestra el panel en la interfaz de usuario de administración.
Advertencia
Si obtiene una excepción inesperada al intentar aprobar el permiso ([HTTP]:400 - [CorrelationId]
), actualice el atributo resource
de su package-solution.json para que use el valor “Microsoft.Azure.AgregatorService
” en lugar de “Microsoft Graph
”, como se indicaba anteriormente en este tutorial. Rechace la solicitud existente y actualice el paquete de solución en el Catálogo de aplicaciones con el valor de actualización.
Probar la solución
Ejecute la solución con el siguiente comando de Gulp.
gulp serve --nobrowser
Abra el explorador y vaya a la siguiente dirección URL para ir a la página del área de trabajo de SharePoint Framework:
https://<your-tenant>.sharepoint.com/_layouts/15/Workbench.aspx
Agregue el elemento web del lado cliente GraphConsumer, configure el modo de cliente y busque usuarios.
Al realizar la primera solicitud, se mostrará una ventana emergente durante solo un instante. Es la ventana de inicio de sesión usada por ADAL JS, que usa SharePoint Framework internamente para recuperar el token de acceso de Azure AD con el flujo implícito de OAuth.
Y ya está. Ahora puede crear soluciones empresariales que usen las API de REST protegidas por Azure AD.