Consumir o Microsoft Graph na Estrutura do SharePoint

O consumo de APIs REST protegidas pelo Azure Active Directory (Azure AD) e o Open Authorization (OAuth 2.0) de dentro de uma web part ou extensão do lado do cliente do Estrutura do SharePoint é um cenário de negócios de nível corporativo comum.

Introduzido na v1.4.1, você pode usar a Estrutura do SharePoint para consumir as APIs REST do Microsoft Graph, ou qualquer outra API REST que esteja registrada no Azure AD.

Neste artigo, você aprenderá a criar uma solução de Estrutura do SharePoint que usa a API do Microsoft Graph com um conjunto de permissões personalizado. Confira uma visão geral conceitual dessa tecnologia em Conectar-se às APIs protegidas pelo Azure AD em soluções da Estrutura do SharePoint.

Importante

Você pode consumir o Microsoft API do Graph com versões de Estrutura do SharePoint anteriores à v1.4.1, por meio do GraphHttpClient nativo ou usando diretamente bibliotecas de autenticação platfomr de identidade da Microsoft implícitas fluxo OAuth. No entanto, a abordagem anterior está associada a um conjunto predefinido de permissões que apresenta algumas limitações, que são complexas de uma perspectiva de desenvolvimento. Confira os detalhes sobre como implementar um fluxo de OAuth implícito em Conectar-se a APIs protegidas pelo Azure Active Directory.

Visão geral da solução

As etapas neste artigo mostram como criar uma web part do lado do cliente que habilita a pesquisa por usuários no locatário atual, conforme mostrado na captura de tela a seguir. A busca é baseada no Microsoft Graph e requer pelo menos a permissão User.ReadBasic.All.

Uma web part do lado do cliente que tem um botão de pesquisa e uma caixa de texto para pesquisa por usuários em um locatário

A web part do lado do cliente habilita a pesquisa por usuários com base no nome e fornece todos os usuários correspondentes por meio de um componente DetailsList da Office UI Fabric. A Web part tem uma opção no painel de propriedades para selecionar como acessar o Microsoft Graph. Nas versões da Estrutura do SharePoint a partir da v.1.4.1, é possível acessar o Microsoft Graph usando o cliente gráfico nativo (MSGraphClient) ou o tipo de nível baixo usado para acessar qualquer API REST protegida pelo Azure AD (AadHttpClient).

Observação

Para obter o código-fonte para essa solução, confira o repositório do GitHub api-scopes.

Se você já estiver familiarizado com a criação de soluções da Estrutura do SharePoint, poderá continuar para Configurar as solicitações de permissão da API.

Criar a solução inicial

Se você tiver uma versão antiga do gerador da Estrutura do SharePoint, será necessário atualizar para a versão 1.4.1 ou posterior. Para fazer isso, execute o seguinte comando para instalar a versão mais recente do pacote globalmente.

npm install -g @microsoft/generator-sharepoint

Em seguida, crie uma nova solução da Estrutura do SharePoint:

  1. Crie uma pasta no sistema de arquivos. Você vai armazenar o código fonte da solução e mover o caminho atual para esta pasta.

  2. Execute o gerador Yeoman para estruturar uma nova solução.

    yo @microsoft/sharepoint
    
  3. Quando solicitado, insira os seguintes valores (selecione a opção padrão para todos os avisos omitidos abaixo):

    • Qual é o nome da sua solução? spfx-api-scopes-tutorial
    • Quais pacotes de linha de base deseja direcionar para seus componentes? Somente SharePoint Online (mais recente)
    • Que tipo de componente do cliente será criado? Web Part
    • Qual é o nome da Web Part? GraphConsumer
    • Qual estrutura você deseja usar? React
  4. Inicie o Código do Visual Studio (ou o editor de códigos de sua preferência) no contexto da pasta atual.

    code .
    

Configurar os elementos básicos da web part

Em seguida, configure os elementos iniciais da web part do lado do cliente.

Configurar as propriedades personalizadas

  1. Crie um novo arquivo de código-fonte na pasta src/webparts/graphConsumer/components da solução.

    Nomeie o novo arquivo ClientMode.ts e use-o para declarar um TypeScript enum com as opções disponíveis para a propriedade ClientMode da Web part.

    export enum ClientMode {
      aad,
      graph,
    }
    
  2. Abra o arquivo GraphConsumerWebPart.ts na pasta ./src/webparts/graphConsumer da solução.

    Altere a definição da IGraphConsumerWebPartProps interface para aceitar um valor do tipo ClientMode.

    export interface IGraphConsumerWebPartProps {
      clientMode: ClientMode;
    }
    
  3. Atualize o método getPropertyPaneConfiguration() da Web part do lado do cliente para suportar a seleção de escolha no painel de propriedades. O exemplo a seguir mostra a nova implementação do 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"},
                    ]
                  }),
                ]
              }
            ]
          }
        ]
      };
    }
    
  4. É necessário atualizar o método render() da Web part do lado do cliente para criar uma instância devidamente configurada do componente React para renderização. O código a seguir mostra a definição do método atualizado.

    public render(): void {
      const element: React.ReactElement<IGraphConsumerProps > = React.createElement(
        GraphConsumer,
        {
          clientMode: this.properties.clientMode,
          context: this.context,
        }
      );
    
      ReactDom.render(element, this.domElement);
    }
    
  5. Para que esse código funcione, é necessário adicionar algumas instruções de importação no início do arquivo GraphConsumerWebPart.ts, conforme mostrado no exemplo a seguir. Observe a importação para o controle PropertyPaneChoiceGroup e a importação da enumeração ClientMode.

    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";
    

Atualizar as cadeias de caracteres de recursos

Para compilar a solução, você precisa atualizar o arquivo mystrings.d.ts na pasta ./src/webparts/graphConsumer/loc da solução.

  1. Reescreva a interface que define as cadeias de caracteres de recurso com o código a seguir.

    declare interface IGraphConsumerWebPartStrings {
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ClientModeLabel: string;
      SearchFor: string;
      SearchForValidationErrorMessage: string;
    }
    
  2. Configurar os valores adequados para as cadeias de caracteres de recursos recém-criadas atualizando o arquivo en-us.js na mesma pasta.

    define([], function () {
      return {
        PropertyPaneDescription: "Description",
        BasicGroupName: "Group Name",
        ClientModeLabel: "Client Mode",
        SearchFor: "Search for",
        SearchForValidationErrorMessage: "Invalid value for 'Search for' field",
      };
    });
    

Atualizar o estilo da web part do lado do cliente

Também é necessário atualizar o arquivo de estilo SCSS.

Abra a pasta GraphConsumer.module.scss na pasta ./src/webparts/graphConsumer/components da solução. Adicione as seguintes classes de estilo, logo após a classe .title:

.form {
  @include ms-font-l;
  @include ms-fontColor-white;
}

label {
  @include ms-fontColor-white;
}

Atualizar o componente React renderizando a Web part

Agora, você pode atualizar o componente React GraphConsumer na pasta src/webparts/graphConsumer/components da solução.

  1. Atualize o arquivo IGraphConsumerProps.ts para aceitar as propriedades personalizadas necessárias para a implementação da web part. O exemplo a seguir mostra o conteúdo atualizado do arquivo IGraphConsumerProps.ts. Observe a importação da definição da enumeração ClientMode, bem como a importação do tipo WebPartContext. Você usará isso mais tarde.

    import { WebPartContext } from "@microsoft/sp-webpart-base";
    import { ClientMode } from "./ClientMode";
    
    export interface IGraphConsumerProps {
      clientMode: ClientMode;
      context: WebPartContext;
    }
    
  2. Crie uma nova interface para armazenar o estado do componente React. Crie um novo arquivo na pasta ./src/webparts/graphConsumer/components, e o nomeie como IGraphConsumerState.ts. A seguir apresentamos a definição da interface.

    import { IUserItem } from "./IUserItem";
    
    export interface IGraphConsumerState {
      users: Array<IUserItem>;
      searchFor: string;
    }
    
  3. Defina a IUserItem interface (dentro de um arquivo chamado IUserItem.ts armazenado na pasta src/webparts/graphConsumer/components). Essa interface é importada no arquivo de estado. A interface é usada para definir a estrutura dos usuários recuperados do locatário atual e vinculados ao DetailsList na interface do usuário.

    export interface IUserItem {
      displayName: string;
      mail: string;
      userPrincipalName: string;
    }
    
  4. Atualize o arquivo GraphConsumer.tsx. Primeiro, adicione algumas instruções de importação para importar os tipos definidos anteriormente. Observe a importação para IGraphConsumerProps, IGraphConsumerState, ClientMode, e IUserItem. Há também algumas importações dos componentes do Office UI Fabric usados para renderizar a interface do usuário do componente 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";
    
  5. Após as importações, defina a estrutura de tópicos das colunas para o componente DetailsList do 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 é usada nas configurações do componente DetailsList, como você pode ver no método render() do componente React.

  6. Substitua esse componente pelo código a seguir.

    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>
      );
    }
    
  7. Atualize a declaração do tipo de componente React e acrescente um construtor, como mostrado no exemplo a seguir:

    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: ""
        };
      }
    

    Existem algumas regras de validação e tratamento de eventos para o componente TextField coletar os critérios de busca. Apresentamos a seguir as implementações do método.

    Adicione estes dois métodos ao final da classe 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}`;
    }
    

    O PrimaryButton dispara uma função \_search(), que determina qual tecnologia cliente usar para consumir o Microsoft Graph. Adicione este método ao final da classe GraphConsumer:

    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;
      }
    }
    

A instância DetailsList do componente é apresentada no método render(), caso haja itens na propriedade users do estado do componente.

Configurar as solicitações de permissões da API

Para consumir o Microsoft Graph ou qualquer API REST de terceiros, será necessário declarar explicitamente os requisitos de permissão do ponto de vista do OAuth no manifesto da sua solução.

Na Estrutura do SharePoint v1.4.1 ou posterior, você pode fazer isso configurando a propriedade webApiPermissionRequests na pasta package-solution.json na pasta configuração da solução. O exemplo a seguir mostra um trecho desse arquivo para a solução atual.

Copie a declaração da propriedade 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 o webApiPermissionRequests, que é um conjunto de webApiPermissionRequest itens. Cada item define um resource e o scope da solicitação de permissão.

O resource pode ser o nome ou o ObjectId (no Azure Active Directory) do recurso para o qual você deseja configurar o pedido de permissão. Para o Microsoft Graph, o nome é Microsoft Graph. O ObjectId não é único e varia de acordo com o locatário.

O scope pode ser o nome da permissão, ou a identificação única dessa permissão. Você pode obter o nome da permissão na documentação da API. Você pode obter o ID da permissão no arquivo de manifesto da API.

Observação

Confira a lista de permissões que estão disponíveis no Microsoft Graph em Referência de permissões do Microsoft Graph.

Por padrão, a entidade de serviço não tem permissões explícitas concedidas para acessar o Microsoft Graph. No entanto, se você solicitar um token de acesso do Microsoft Graph, obterá um token com a permissão user_impersonation que pode ser usada para ler informações sobre os usuários (User.Read.All). Você pode solicitar permissões adicionais a serem concedidas por administradores de locatário. Saiba mais em Conectar-se às APIs protegidas pelo Azure AD em soluções da Estrutura do SharePoint.

A permissão User.ReadBasic.All é suficiente para procurar usuários e obter seus displayName, mail, e userPrincipalName.

Quando empacotar e implantar sua solução, você (ou um administrador) precisará conceder as permissões solicitadas à sua solução. Para saber mais, confira Implantar a solução e conceder permissões.

Consumir o Microsoft Graph

Agora você pode implementar os métodos para consumo do Microsoft Graph. Você tem duas opções:

  • Usar o objeto de cliente AadHttpClient
  • Usar o objeto de cliente MSGraphClient

O objeto de cliente AadHttpClient é útil para o consumo de qualquer API REST. Você pode usá-lo para consumir o Microsoft Graph ou qualquer outra API REST de terceiros (ou própria).

O objeto de cliente MSGraphClient pode consumir somente o Microsoft Graph. Internamente, ele usa o objeto de cliente AadHttpClient e é compatível com a sintaxe do Microsoft Graph SDK.

Usar o AadHttpClient

Para consumir qualquer API REST usando o objeto de cliente AadHttpClient, crie uma nova instância do tipo AadHttpClient chamando o método context.aadHttpClientFactory.getClient() e fornecendo o URI do serviço de destino.

O objeto criado fornece os métodos para fazer as seguintes solicitações:

  • get(): faz uma solicitação HTTP GET
  • post(): faz uma solicitação HTTP POST
  • fetch(): faz qualquer outro tipo de solicitação HTTP, com base nos argumentos HttpClientConfiguration e IHttpClientOptions fornecidos.

Como todos esses métodos dão suporte ao modelo de desenvolvimento assíncrono do JavaScript/TypeScript, é possível processar o resultado com promessas.

O exemplo a seguir mostra o método \_searchWithAad() do exemplo da solução.

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);
    });
}

O método get() obtém o URL do pedido de OData como argumento de entrada. Uma solicitação bem-sucedida retorna um objeto JSON com a resposta.

Usando o MSGraphClient

Se você estiver visando o Microsoft Graph, você pode usar o objeto cliente MSGraphClient, que fornece uma sintaxe mais fluente.

O exemplo a seguir mostra a implementação do método _searchWithGraph() do exemplo da solução.

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,
            }
          );
        });
    });
}

Você obtém uma instância do tipo MSGraphClient, chamando o método context.msGraphClientFactory.getClient().

Use a API Fluent do Microsoft Graph SDK para definir a consulta de OData a executar no ponto de extremidade do Microsoft Graph de destino.

O resultado é uma resposta JSON que você tem que decodificar e mapear para o resultado digitado.

Observação

É possível usar uma abordagem totalmente digitada usando os tipos de TypeScript do Microsoft Graph.

Implantar a solução e conceder permissões

Agora, você está pronto para compilar, agrupar, empacotar e implantar a solução.

  1. Execute os comandos gulp para confirmar que a solução é compilada corretamente.

    gulp build
    
  2. Use o comando a seguir para agrupar e empacotar a solução.

    gulp bundle
    gulp package-solution
    
  3. Navegue até o catálogo de aplicativos do seu locatário de destino e carregue o pacote da solução. É possível localizar o pacote da solução na pasta sharepoint/solution da sua solução. É o arquivo .sppkg. Depois de carregar o pacote da solução, o catálogo de aplicativos mostra uma caixa de diálogo, semelhante à mostrada na captura de tela a seguir.

    Captura de tela da interface de usuário do catálogo de aplicativos durante o upload do pacote da solução

    Existe uma mensagem na área inferior da tela que informa que o pacote da solução requer a aprovação de permissões. Isto é por causa da propriedade webApiPermissionRequests no arquivo package-solution.json.

  4. No Centro de Administração moderno do Microsoft Office SharePoint Online, no menu de início rápido à esquerda, em Avançado selecione o item de menu Acesso de API. Você verá uma página semelhante à seguinte.

    Captura de tela da página Gerenciamento de WebApiPermission

    Usando essa página, você (ou qualquer outro administrador do locatário do SharePoint Online) pode aprovar ou negar qualquer solicitação de permissão pendente. Você não vê qual pacote da solução está solicitando a permissão porque as permissões são definidas no nível do locatário e para uma aplicativo único.

    Observação

    Para saber mais sobre como os escopos de permissão de nível de locatário funcionam internamente, leia os artigos na seção Confira também.

  5. Escolha a permissão que você solicitou no arquivo package-solution.json da sua solução, selecione Aprovar ou rejeitar acesso, e então selecione Aprovar. A captura de tela a seguir mostra o painel na interface de administração.

    Captura de tela da página de gerenciamento de WebApiPermission durante o processo de aprovação

Aviso

Se você estiver recebendo uma exceção inesperada ao tentar aprovar a permissão ([HTTP]:400 - [CorrelationId]), atualize o atributo resource no seu package-solution.json para usar o valor Microsoft.Azure.AgregatorService em vez do Microsoft Graph, o que foi indicado anteriormente neste tutorial. Rejeite a solicitação existente e atualize o pacote da solução no catálogo de aplicativos com o valor de atualização.

Testar a solução

  1. Execute a solução usando o comando gulp a seguir.

    gulp serve --nobrowser
    
  2. Abra o navegador e acesse o URL a seguir para acessar a página do Workbench da Estrutura do SharePoint:

    https://<your-tenant>.sharepoint.com/_layouts/15/Workbench.aspx
    
  3. Adicione a web part do lado do cliente GraphConsumer, configure o Modo do Cliente e pesquise por usuários.

    Quando você fizer seu primeiro pedido, verá uma janela pop-up aparecer e desaparecer. Essa é a janela de login usada pelo ADAL JS, que é usado internamente pela Estrutura do SharePoint para obter o token de acesso do Azure AD usando um fluxo implícito do OAuth.

    Captura de tela da interface do usuário do exemplo de aplicativo

Pronto! Agora você pode criar soluções de nível corporativo com APIs REST protegidas pelo Azure AD.

Confira também