Использование Microsoft Graph в SharePoint Framework

Использование REST API, защищенных с помощью Azure Active Directory (Azure AD) и Open Authorization (OAuth 2.0), из клиентской веб-части или расширения SharePoint Framework — распространенный бизнес-сценарий корпоративного уровня.

Вы можете использовать SharePoint Framework версии 1.4.1 для вызова REST API Microsoft Graph или любого другого REST API, зарегистрированного в Azure AD.

Из этой статьи вы узнаете, как создать решение SharePoint Framework, которое использует API Microsoft Graph с особым набором разрешений. Общий обзор этой технологии см. в статье Подключение к API, защищенным службой Azure AD, в решениях SharePoint Framework.

Важно!

Вы можете использовать microsoft API Graph с версиями SharePoint Framework более ранних версий, чем версия 1.4.1, либо с помощью собственного GraphHttpClient, либо с помощью неявного потока OAuth проверки подлинности для удостоверений Майкрософт. Однако первый подход связан с предопределенным набором разрешений, который имеет некоторые ограничения, а второй сложен с точки зрения разработки. Инструкции по реализации неявного потока OAuth см. в статье Подключение к API, защищенным с помощью Azure Active Directory.

Обзор решения

В этой статье описано, как создать клиентскую веб-часть, которая позволяет искать пользователей в текущем клиенте, как показано на следующем снимке экрана. Поиск основан на Microsoft Graph, и для него требуется разрешение не ниже User.ReadBasic.All.

Клиентская веб-часть с текстовым полем и кнопкой для поиска пользователей в клиенте

Клиентская веб-часть позволяет искать пользователей по имени и показывает всех подходящих пользователей через компонент DetailsList из Office UI Fabric. В области свойств веб-части есть параметр для выбора способа доступа к Microsoft Graph. Начиная с SharePoint Framework версии 1.4.1, для доступа к Microsoft Graph можно использовать собственный клиент Graph (MSGraphClient) или низкоуровневый тип, используемый для доступа к любому REST API, защищенному с помощью Azure AD (AadHttpClient).

Примечание.

Исходный код этого решения представлен в репозитории GitHub api-scopes.

Если вы уже знаете, как создавать решения SharePoint Framework, можете перейти к разделу Настройка запросов на получение разрешений API.

Создание исходного решения

Если у вас установлена старая версия генератора SharePoint Framework, необходимо обновить ее до версии 1.4.1 или более поздней. Для этого выполните следующую команду, чтобы глобально установить последнюю версию пакета.

npm install -g @microsoft/generator-sharepoint

После этого создайте новое решение SharePoint Framework:

  1. Создайте папку в своей файловой системе. В ней будет храниться исходный код решения. Измените текущий путь, указав эту папку.

  2. Запустите генератор Yeoman, чтобы сформировать шаблоны нового решения.

    yo @microsoft/sharepoint
    
  3. При появлении запроса введите следующие значения (выберите вариант по умолчанию для всех запросов, не перечисленных ниже).

    • Как называется ваше решение? spfx-api-scopes-tutorial
    • Какие базовые пакеты нужно выбрать для ваших компонентов? Только SharePoint Online (последняя версия)
    • Какой тип клиентского компонента нужно создать? Веб-часть
    • Как называется ваша веб-часть? GraphConsumer
    • Какую платформу нужно использовать? React
  4. Запустите Visual Studio Code (или другой редактор кода) в контексте текущей папки.

    code .
    

Настройка базовых элементов веб-части

После этого настройте исходные элементы клиентской веб-части.

Настройка особых свойств

  1. Создайте файл исходного кода в папке src/webparts/graphConsumer/components решения

    Назовите новый файл ClientMode.ts и объявите с его помощью объект TypeScript enum с доступными значениями свойства ClientMode веб-части.

    export enum ClientMode {
      aad,
      graph,
    }
    
  2. Откройте файл GraphConsumerWebPart.ts в папке src/webparts/graphConsumer решения.

    Измените определение интерфейса IGraphConsumerWebPartProps, чтобы он принимал значение типа ClientMode.

    export interface IGraphConsumerWebPartProps {
      clientMode: ClientMode;
    }
    
  3. Обновите метод getPropertyPaneConfiguration() клиентской веб-части, чтобы он поддерживал выбор вариантов в области свойств. В примере ниже показана новая реализация этого метода.

    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. Необходимо обновить метод render() клиентской веб-части, чтобы создавать должным образом настроенный экземпляр компонента React для отрисовки. Ниже показано обновленное определение метода.

    public render(): void {
      const element: React.ReactElement<IGraphConsumerProps > = React.createElement(
        GraphConsumer,
        {
          clientMode: this.properties.clientMode,
          context: this.context,
        }
      );
    
      ReactDom.render(element, this.domElement);
    }
    
  5. Чтобы этот код работал, нужно добавить операторы import в начале файла GraphConsumerWebPart.ts, как показано ниже. Обратите внимание на импорт элемента управления PropertyPaneChoiceGroup и импорт перечисления 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";
    

Обновление строк ресурсов

Чтобы скомпилировать решение, необходимо обновить файл mystrings.d.ts в папке src/webparts/graphConsumer/loc этого решения.

  1. Замените код интерфейса, определяющий строку ресурсов, на приведенный ниже код.

    declare interface IGraphConsumerWebPartStrings {
      PropertyPaneDescription: string;
      BasicGroupName: string;
      ClientModeLabel: string;
      SearchFor: string;
      SearchForValidationErrorMessage: string;
    }
    
  2. Задайте нужные значения для новых строк ресурсов, обновив файл en-us.js в той же папке.

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

Обновление стиля клиентской веб-части

Вам также нужно обновить файл стилей SCSS.

Откройте файл GraphConsumer.module.scss в папке src/webparts/graphConsumer/components решения. Добавьте следующие классы стилей сразу после класса .title:

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

label {
  @include ms-fontColor-white;
}

Обновление компонента React, отображающего веб-часть

Теперь вы можете обновить компонент React GraphConsumer в папке src/webparts/graphConsumer/components решения.

  1. Обновите файл IGraphConsumerProps.ts, чтобы принять особые свойства, необходимые для реализации веб-части. Ниже показано обновленное содержимое файла IGraphConsumerProps.ts. Обратите внимание на импорт определения перечисления ClientMode и импорт типа WebPartContext. Они понадобятся вам позже.

    import { WebPartContext } from "@microsoft/sp-webpart-base";
    import { ClientMode } from "./ClientMode";
    
    export interface IGraphConsumerProps {
      clientMode: ClientMode;
      context: WebPartContext;
    }
    
  2. Создайте интерфейс для хранения состояния компонента React. Создайте файл в папке src/webparts/graphConsumer/components и назовите его IGraphConsumerState.ts. Ниже показано определение интерфейса.

    import { IUserItem } from "./IUserItem";
    
    export interface IGraphConsumerState {
      users: Array<IUserItem>;
      searchFor: string;
    }
    
  3. Определите интерфейс IUserItem (в файле IUserItem.ts, хранящемся в папке src/webparts/graphConsumer/components). Этот интерфейс импортируется в файл состояния. Он используется для определения структуры пользователей, полученной из текущего клиента и привязанной к компоненту DetailsList в пользовательском интерфейсе.

    export interface IUserItem {
      displayName: string;
      mail: string;
      userPrincipalName: string;
    }
    
  4. Обновите файл GraphConsumer.tsx. Сначала добавьте операторы для импорта определенных ранее типов. Обратите внимание на импорт компонентов IGraphConsumerProps, IGraphConsumerState, ClientMode и IUserItem. Также импортируются некоторые компоненты Office UI Fabric, используемые для отрисовки пользовательского интерфейса компонента 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. После операций импорта определите структуру столбцов для компонента DetailsList 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,
      },
    ];
    

    Этот массив используется в параметрах компонента DetailsList, как видно по методу render() компонента React.

  6. Замените этот компонент на приведенный ниже код.

    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. Обновите объявление типа компонента React и добавьте конструктор, как показано ниже.

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

    Для сбора критериев поиска компоненту TextField требуются некоторые правила проверки и обработчики событий. Ниже показаны реализации метода.

    Добавьте эти два метода в конец класса 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}`;
    }
    

    Компонент PrimaryButton вызывает функцию \_search(), которая определяет, какую клиентскую технологию использовать для вызова Microsoft Graph. Добавьте этот метод в конец класса 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;
      }
    }
    

Экземпляр компонента DetailsList отрисовывается в методе render() на тот случай, если в свойстве users состояния компонента имеются элементы.

Настройка запросов разрешений API

Чтобы использовать Microsoft Graph или другой сторонний REST API, в манифесте решения необходимо явно объявить требуемые разрешения с точки зрения OAuth.

В SharePoint Framework 1.4.1 или более новой версии для этого можно настроить свойство webApiPermissionRequests в файле package-solution.json из папки config решения. Ниже показан фрагмент этого файла для текущего решения.

Скопируйте объявление свойства 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"
  }
}

Обратите внимание на объект webApiPermissionRequests, представляющий собой массив элементов webApiPermissionRequest. Каждый элемент определяет resource и scope для запроса на получение разрешений.

Значением resource может быть имя или ObjectId (в Azure AD) ресурса, для которого требуется настроить запрос на получение разрешений. Для Microsoft Graph используется имя Microsoft Graph. Значение ObjectId не уникально и зависит от клиента.

Значением scope может быть имя разрешения или уникальный идентификатор этого разрешения. Имя разрешения можно получить из документации по API. Идентификатор разрешения можно получить из файла манифеста API.

Примечание.

Все доступные разрешения перечислены в справочнике по разрешениям Microsoft Graph.

По умолчанию у субъекта-службы нет явных разрешений на доступ к Microsoft Graph. Однако, запросив маркер доступа для Microsoft Graph, вы получите маркер с разрешением user_impersonation, с помощью которого можно считывать сведения о пользователях (User.Read.All). Вы можете запрашивать дополнительные разрешения, а администраторы клиентов — предоставлять их. Дополнительные сведения см. в статье Подключение к API, защищенным службой Azure AD, в решениях SharePoint Framework.

Для поиска пользователей и получения их displayName, mail и userPrincipalName достаточно разрешения User.ReadBasic.All.

Когда вы упакуете и развернете решение, вам (или администратору) нужно будет предоставить ему запрашиваемые разрешения. Дополнительные сведения см. в разделе Развертывание решения и предоставление разрешений.

Использование Microsoft Graph

Теперь вы можете реализовать методы для использования Microsoft Graph. У вас есть два варианта:

  • использовать клиентский объект AadHttpClient;
  • использовать клиентский объект MSGraphClient.

С помощью клиентского объекта AadHttpClient можно использовать Microsoft Graph или любой другой сторонний (или встроенный) REST API.

Клиентской объект MSGraphClient подходит только для использования Microsoft Graph. Он использует клиентский объект AadHttpClient и поддерживает текучий синтаксис пакета SDK Microsoft Graph.

Использование AadHttpClient

Чтобы использовать любой REST API с помощью клиентского объекта AadHttpClient, создайте экземпляр типа AadHttpClient, вызвав метод context.aadHttpClientFactory.getClient() и указав URI целевой службы.

Созданный объект предоставляет методы для выполнения следующих запросов:

  • get() (отправляет HTTP-запрос GET);
  • post() (отправляет HTTP-запрос POST);
  • fetch() (отправляет любой HTTP-запрос в зависимости от указанных аргументов HttpClientConfiguration и IHttpClientOptions).

Так как все эти методы поддерживают асинхронную модель разработки с использованием JavaScript и TypeScript, вы можете обрабатывать их результаты с помощью обещаний.

Ниже показан метод \_searchWithAad() из демо-решения.

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

Метод get() получает URL-адрес запроса OData в качестве входного аргумента. В случае успешного выполнения запроса возвращается объект JSON с ответом.

Использование MSGraphClient

Если вы планируете использовать Microsoft Graph, можно воспользоваться клиентским объектом MSGraphClient, обеспечивающий более динамичный синтаксис.

В примере ниже показана реализация метода _searchWithGraph() из демо-решения.

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

Вы получаете экземпляр типа MSGraphClient с помощью вызова метода context.msGraphClientFactory.getClient().

Затем используйте текучий API из пакета SDK Microsoft Graph, чтобы определить запрос OData, который выполняется для целевой конечной точки Microsoft Graph.

В результате вы получите ответ JSON, который потребуется раскодировать и сопоставить с типизированным результатом.

Примечание.

Вы можете использовать полностью типизированный подход, используя типы TypeScript для Microsoft Graph.

Развертывание решения и предоставление разрешений

Теперь все готово к сборке, компоновке, упаковыванию и развертыванию решения.

  1. Выполните команды gulp для правильной сборки решения.

    gulp build
    
  2. Используйте приведенную ниже команду, чтобы объединить решение в пакет и упаковать его.

    gulp bundle
    gulp package-solution
    
  3. Перейдите в каталог приложений целевого клиента и отправьте пакет решения. Пакет решения находится в папке sharepoint/solution решения. Это SPPKG-файл. После отправки пакета решения в каталоге приложений откроется примерно такое диалоговое окно:

    Снимок экрана: пользовательский интерфейс каталога приложений во время отправки пакета решения

    В нижней части экрана отображается сообщение о том, что для пакета решения необходимо утвердить разрешения. Это связано со свойством webApiPermissionRequests в файле package-solution.json.

  4. В разделе Дополнительно в меню быстрого запуска в левой части экрана в современном центре администрирования SharePoint Online выберите пункт Доступ к API. Ответ будет выглядеть приблизительно так:

    Снимок экрана: страница управления WebApiPermission

    На этой странице вы (или любой другой администратор вашего клиента SharePoint Online) можете утвердить или отклонить любой запрос на получение разрешений, ожидающий проверки. Вам не видно, какое разрешение запрашивает тот или иной пакет решения, так как разрешения определяются на уровне клиента и для уникального приложения.

    Примечание.

    Дополнительные сведения о принципе работы разрешений на уровне клиента см. в статьях, указанных в разделе См. также.

  5. Выберите разрешение, запрашиваемое в файле решения package-solution.json, нажмите Разрешить или запретить доступ, а затем — Утвердить. Ниже показана панель Центра администрирования.

    Снимок экрана: страница управления WebApiPermission во время утверждения

Предупреждение

В случае непредвиденного исключения при попытке утвердить разрешение ([HTTP]:400 - [CorrelationId]) обновите атрибут resource в файле package-solution.json так, чтобы использовалось значение Microsoft.Azure.AgregatorService, а не Microsoft Graph (как упоминалось ранее в этом руководстве). Отклоните имеющийся запрос и обновите пакет решения в каталоге приложений, указав обновленное значение.

Тестирование решения

  1. Запустите решение с помощью приведенной ниже команды gulp.

    gulp serve --nobrowser
    
  2. Откройте браузер и перейдите на страницу SharePoint Framework Workbench:

    https://<your-tenant>.sharepoint.com/_layouts/15/Workbench.aspx
    
  3. Добавьте клиентскую веб-часть GraphConsumer, выберите Режим клиента и попробуйте найти пользователей.

    При первом запросе появится и исчезнет всплывающее окно. Это окно входа, используемое библиотекой ADAL JS, с помощью которой SharePoint Framework получает маркер доступа из Azure AD, используя неявный поток OAuth.

    Снимок экрана: пользовательский интерфейс примера приложения

Вот и все! Теперь вы можете создавать корпоративные решения, использующие REST API, защищенные с помощью Azure AD.

См. также