GaroonポータルでOutlookの未読メール数をチェックしよう

目次

caution
警告

2020年8月改訂のセキュアコーディングガイドライン に抵触する内容が含まれています。認証情報が漏洩した場合の影響を考慮して慎重に検討してください。
該当箇所:外部ライブラリ(MSAL.js)内のアクセストークン保存部分。

はじめに

この記事では、Garoonのポータル機能を利用して、Office 365 Outlookで受信したメールの最新情報を表示するGaroonカスタマイズポートレットを紹介します。
スケジュールや掲示板はGaroonを使っているけれど、メーラーはOffice 365 Outlookを利用している。そんな場合でも、Garoonをチェックするだけで未読メールを確認できます!

必要なもの

  • クラウド版Garoon
  • Microsoftアカウント

完成イメージ

Garoonのポータルに、Office 365 Outlookの受信トレイにある未読メール件数を表示します。

Outlookサインイン

未読確認

システム構成図

このカスタマイズの構成としくみは次のとおりです。

  1. GaroonからOAuth認証を用いてEntra ID V2 Endpointへサインインします。
    Microsoft Authentication Library for JavaScript (MSAL.js) (External link) を利用することで、OAuthを使った認可フローを実現します。
  2. Azureからアクセストークンを取得します。
  3. アクセストークンを使って、Microsoft Graph APIを実行します。
    Microsoft Graph API(以降、Graph API)は、Microsoft 365サービスの情報を利用するAPIです。
  4. Outlookメール情報を取得します。
  5. HTMLポートレット側で、未読メール件数をチェックして表示します。

Microsoft Entra IDの設定

Garoonから Graph API (External link) を利用するため、Microsoft Entra IDにアプリケーションを登録します。この設定は、管理者のみ行う作業です。利用者が個々に設定する必要はありません。

アプリケーションの作成

  1. Azureポータル (External link) にアクセスします。

  2. 左サイドメニューから[Microsoft Entra ID]を選択します。

  3. Microsoft Entra IDのメニューから[App registrations]([アプリの登録])を選択します。

  4. [New registration]([アプリケーションの登録])をクリックします。

  5. 作成するアプリケーション情報を次のように入力します。
    入力後、[Register]([登録])ボタンをクリックし、アプリケーションを登録します。

    項目 備考
    Name
    (名称)
    任意の値を入力してください。
    この記事では「garoon-outlook-mail」とします。
    作成するアプリケーション名です。
    Supported account types
    (サポートされているアカウントの種類)
    「Account in any organization directory(Any Entra ID directory - Multitenant)」(「任意の組織のディレクトリー内のアカウント」)
    を選択
  6. アプリケーション作成後、作成したアプリケーションの詳細画面が表示されます。
    「Application(client)ID」(「アプリケーション(クライアント)ID」)の値をメモしておいてください。
    Garoonカスタマイズファイルで利用します。


    詳細画面は、Azureポータル >「Microsoft Entra ID」>「App registrations」の手順でも開くことができます。

アプリケーションの設定

作成したアプリケーションに対し、次の設定をします。

  1. リダイレクトURLの設定
  2. OAuth認証スコープの設定
手順1. リダイレクトURLの設定
  1. 作成したアプリケーションの詳細画面の左サイドメニューから、[Authentication]([認証])を選択します。

  2. 次の内容を入力します。入力後、[Save]([保存])ボタンをクリックし、保存します。

    項目 備考
    Redirect URIs
    (リダイレクトURL)
    次の3つのURLを入力します。
    Type(種類)は、すべて「Web」を選択します。
    • https://{subdomain}.cybozu.com/g/portal/index.csp
    • https://{subdomain}.cybozu.com/g/
    • https://{subdomain}.cybozu.com/g/index.csp
    {subdomain} の部分は、利用環境に合わせてください
    Implicit grant
    (暗黙の付与)
    以下の項目を選択します。
    • Access tokens
    • ID tokens
手順2. OAuth認証スコープの設定
  1. 作成したアプリケーションの詳細画面の左サイドメニューから、[API permissions]([APIのアクセス許可])を選択します。

  2. [Add a permission]([アクセス許可の追加])ボタンをクリックします。

  3. [Microsoft Graph]を選択します。

  4. [Delegated permissions]([委任されたアクセス許可])を選択します。
    「Mail」欄の次の権限を選択し、[Add permission]([追加])ボタンをクリックして追加します。

    • Mail.Read

  5. 「Mail.Read」「User.Read」(デフォルトで設定されている権限)が一覧に表示されていればOKです。

Garoonの設定

Garoonでは、以下の設定をします。

  1. Graph APIを利用するためのプロキシAPIを設定
  2. ポートレットに表示する画像ファイルを、画像アセットへ追加
  3. HTMLポートレットの作成・カスタマイズの適用
  4. ポータルへの配置

なお、このカスタマイズでは、次の外部ライブラリを利用しています。

  • Microsoft Authentication Library for JavaScript(MSAL)v0.1.2
  • jQuery v3.3.1(Cybozu CDNを利用)
    • https://js.cybozu.com/jquery/3.3.1/jquery.min.js
  • SweetAlert2 v8.2.1(Cybozu CDNを利用)
    • https://js.cybozu.com/sweetalert2/v8.2.1/sweetalert2.min.js
    • https://js.cybozu.com/sweetalert2/v8.2.1/sweetalert2.min.css

手順1. プロキシAPI設定

手順の詳細は、 プロキシAPIの設定 (External link) を参照してください。

  1. 「Garoonシステム管理」画面を開きます。
  2. 「基本システムの管理」タブをクリックし、[API]を選択します。
  3. [プロキシAPIの設定]をクリックします。
  4. [追加する]をクリックします。
  5. 以下の内容を入力します。入力後、[追加する]ボタンをクリックして、設定を追加します。

    項目
    ステータス 「有効」を選択します。
    プロキシコード GET_OUTLOCK_MAILBOX
    メソッド 「GET」を選択します。
    URL https://graph.microsoft.com/

手順2. 画像ファイルを画像アセットへ追加

ポートレットに表示するメールアイコンの画像ファイルを、画像アセットに保存します。
手順の詳細は、 画像アセットの追加 (External link) を参照してください。

  1. 「Garoonシステム管理」画面を開きます。
  2. 「各アプリケーションの管理」タブをクリックし、[画像アセット]を選択します。
  3. [画像アセット一覧]をクリックします。
  4. [画像アセットを追加する]をクリックします。
  5. 以下の内容を入力します。入力後、[追加する]ボタンをクリックして画像を追加します。

    項目
    ファイル こちら からダウンロードしたファイルを追加します。
    ファイルキー OUTLOOK_UNREAD_MAIL

手順3. HTMLポートレットの作成・カスタマイズの適用

カスタマイズポートレットを作成します。
手順の詳細は、 HTMLポートレットを追加する (External link) を参照してください。

  1. 「Garoonシステム管理」画面を開きます。

  2. 「各アプリケーションの管理」タブをクリックし、[ポータル]をクリックします。

  3. [HTMLポートレット]をクリックします。

  4. [HTMLポートレットを追加する]をクリックします。

  5. 以下の内容を入力します。入力後、[追加する]をクリックします。

    項目
    ポートレット名 任意の値を入力してください。
    この記事では、「Outlookメール未読件数」とします。
    ポートレットの内容
  6. 追加した「HTMLポートレットの詳細」画面で、[JavaScript / CSSによるカスタマイズ]をクリックします。

  7. 次のように入力します。入力後、[設定する]ボタンをクリックして設定します。

    項目
    カスタマイズ 「適用する」を選択します。
    JavaScriptカスタマイズ 次の順で指定します。
    • Microsoft Authentication Library for JavaScript(msal.min.js)
    • https://js.cybozu.com/jquery/3.3.1/jquery.min.js
    • https://js.cybozu.com/sweetalert2/v8.2.1/sweetalert2.min.js
    • カスタマイズファイル(outlook_mail.js)
      詳細は、後述の「 サンプルコード(outlook_mail.js) 」を参照してください
    CSSカスタマイズ 次の順で指定します。
Microsoft Authentication Library for JavaScript(msal.min.js)の入手方法
  1. GitHubのリポジトリ (External link) にアクセスします。
  2. [Source code(zip)]からzipファイルをダウンロードします(バージョンは0.1.2を利用します)
  3. zipファイルを解凍します。
  4. 解凍したフォルダーの「out」フォルダー以下の「msal.min.js」を利用します。

手順4. ポータルへの配置

手順の詳細は、 ポートレットの配置 (External link) を参照してください。

  1. 「Garoonシステム管理」画面を開きます。
  2. 「各アプリケーションの管理」タブをクリックし、[ポータル]をクリックします。
  3. [ポータルの一覧]をクリックします。
  4. 作成したポートレットを配置したいポータル名をクリックします。
    ポータルを新規に作る場合は、 ポータルの追加 (External link) を参考に作成してください。
  5. 左メニューのポートレット一覧から、作成したポートレット(Outlookメール未読件数)を、右側のレイアウトにドラッグして配置します。
  6. 配置したポートレット右上の「未公開」ボタンをクリックして「公開中」に変更します。

動作確認

  1. Garoonで、カスタマイズしたHTMLポートレットを配置したポータルを開きます。
  2. サインインを求めるダイアログが表示されます。
    Office 365 Outlookを利用しているアカウントでサインインします。
    「ポップアップウィンドウをブロックする」設定がされている場合があります。その場合は、ブロック設定を解除してください。
    その後、ポートレットの「ログインする」リンクをクリックすると、サインインのダイアログを再表示できます。

  3. Garoonのポータルに未読メール数が表示されます。

サンプルコード

ポートレットHTML

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!--
* Garoon Outlook Unread Portal sample program
* Copyright (c) 2019 Cybozu
*
* Licensed under the MIT License
-->

<div class="portlet_base_grn">
  <table class="top_title">
    <tbody>
      <tr>
        <td>
          <span id="outlooksample-portal-title" class="portlet_title_grn">
            <img id="outlooksample-portal-img">
            <a href="https://outlook.office.com/owa/?path=/mail/inbox" target="_blank">Outlook未読メール情報</a>
          </span>
        </td>
      </tr>
    </tbody>
  </table>
  <div class="portal_frame portal_frame_new_mail_grn">
    <div>
      <table id="outlooksample-mailnotification" class="list_column" width="100%">
        <colgroup>
          <col width="100%">
        </colgroup>
        <tbody id="outlooksample-mailnotification-tbody">
          <tr valign="top">
            <th id="outlooksample-mailnotification-header">
            </th>
          </tr>
          <tr valign="top" id="outlooksample-mailnotification-row-notice">
            <td id="outlooksample-mailnotification-notice"></td>
          </tr>
        </tbody>
      </table>
      <input type="hidden" name="csrf_ticket" value="">
    </div>
  </div>
</div>
outlook_mail.js

12行目のCLIENT_IDを、「 Microsoft Entra IDの設定:アプリケーションの作成 」でメモした「アプリケーション(クライアント)ID」に変更してください。

次の値は、必要に応じて変更してください。

  • 15行目TIME_INTERVAL_SEC:未読件数を取得する間隔(秒)
  • 16行目UNREAD_CHECK_BUTTON_TEXT:未読件数を再取得するボタンの文字
  • 17行目GAROON_PROXY_CODE:Garoonの「プロキシ設定」で設定したプロキシコード
  • 18行目IMAGE_ASSETS_KEY:Garoonの「画像アセット」で設定したファイルキー
  • 19行目MAIL_FOLDER_NAME:未読件数を取得する対象のメールフォルダー名
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
 * Garoon Outlook Unread Portal sample program
 * Copyright (c) 2019 Cybozu
 *
 * Licensed under the MIT License
*/

(function() {
  'use strict';
  const myJQuery = jQuery.noConflict(true);
  (function($) {
    const CLIENT_ID = '';
    const API_SCOPES = ['User.Read', 'Mail.Read'];

    const TIME_INTERVAL_SEC = 60;
    const UNREAD_CHECK_BUTTON_TEXT = '未読確認';
    const GAROON_PROXY_CODE = 'GET_OUTLOCK_MAILBOX';
    const IMAGE_ASSETS_KEY = 'OUTLOOK_UNREAD_MAIL';
    const MAIL_FOLDER_NAME = '受信トレイ';

    const sweetAlert = {
      sa: Swal,
      showProcessing: function() {
        this.sa.fire({
          title: '処理中です...',
          type: 'info',
          allowOutsideClick: false,
          allowEscapeKey: false,
          showConfirmButton: false
        });
      },
      showError: function(message) {
        this.sa.fire({
          type: 'error',
          title: 'エラーが発生しました',
          html: message || ''
        });
      },
      close: function() {
        this.sa.close();
      }
    };

    const MailNotification = function() {
      if (!(this instanceof MailNotification)) {
        return new MailNotification();
      }

      this.CLIENT_ID = CLIENT_ID;
      this.API_SCOPES = API_SCOPES;
      this.userAgentApplication = new Msal.UserAgentApplication(
        this.CLIENT_ID,
        null,
        null
      );
      this.isLoggingIn = false;
      this.unreadItemCount = 0;

      this.setLoginStatus = function(status) {
        this.isLoggingIn = status;
      };

      this.login = function() {
        sweetAlert.showProcessing();
        const user = this.userAgentApplication.getUser();
        const hash = location.hash;
        const self = this;
        const LOCAL_STORAGE_KEY = 'outlook-sample-setInterval';
        return new garoon.Promise((resolve, reject) => {
          // ログイン中であるかを判定
          if (user || hash.indexOf('id_token') >= 0) {
            const isSetInterval = !localStorage.getItem(LOCAL_STORAGE_KEY);
            localStorage.removeItem(LOCAL_STORAGE_KEY);
            return resolve(isSetInterval);
          } else if (hash.indexOf('error') === -1) {
            // エラーリダイレクトでないかを判定
            return self.userAgentApplication.loginPopup(
              self.API_SCOPES
            ).then(() => {
              resolve(true);
              localStorage.setItem(LOCAL_STORAGE_KEY, 'true');
            }).catch((e) => {
              reject(e);
            });
          }
          // エラー処理
          console.error('ログインエラー');
          return reject();

        });
      };

      this.fetchUnreadItemsFromMailBox = function() {
        const self = this;
        this.userAgentApplication.acquireTokenSilent([
          'https://graph.microsoft.com/mail.read'
        ]).then((token) => {
          const header = {
            Authorization: 'Bearer ' + token,
            'Content-Type': 'application/json'
          };
          const query = '?$filter=' + encodeURIComponent('displayName eq \'' + MAIL_FOLDER_NAME + '\'');
          const url = 'https://graph.microsoft.com/v1.0/me/mailFolders' + query;
          garoon.base.proxy.send(
            GAROON_PROXY_CODE,
            url,
            'GET',
            header,
            {}
          ).then((response) => {
            const responseJson = window.JSON.parse(
              !response ? '{}' : response[0]
            );
            if (!!responseJson.value && responseJson.value.length > 0) {
              self.unreadItemCount = responseJson.value[0].unreadItemCount;
              $('#outlooksample-unreadnumber').text(self.unreadItemCount).ready(() => {
                sweetAlert.close();
              });
            } else {
              throw new Error('指定したフォルダ名" ' + MAIL_FOLDER_NAME + ' "からメールフォルダー情報を取得できませんでした。<br />' +
                    'フォルダ名の指定を確認してください。'
              );
            }
          }).catch((e) => {
            sweetAlert.close();
            console.error(e);
            sweetAlert.showError(e.message);
          });
        }).catch((e) => {
          sweetAlert.close();
          console.error(e);
          sweetAlert.showError(e);
        });
      };

      this.setContents = function() {
        const self = this;
        const $mailNotificationHeader = $('#outlooksample-mailnotification-header');
        if (this.isLoggingIn) {
          const user = this.userAgentApplication.getUser();
          const $userName = $('<span></span>', {
            id: 'outlooksample-mailnotification-username',
            text: '<' + user.displayableId + '>'
          });
          const $linkOutlook = $('<a></a>', {
            id: 'sample-link',
            href: 'https://outlook.office.com/owa/?path=/mail/inbox',
            target: '_blank',
            html: '未読メールが' +
                '<wbr>&nbsp;' +
                '<span id="outlooksample-unreadnumber">' + this.unreadItemCount + '</span>' +
                '&nbsp;件'
          });
          $('#outlooksample-mailnotification-notice').append($linkOutlook);
          $linkOutlook.before('&nbsp;' + MAIL_FOLDER_NAME + 'に<wbr>&nbsp;').after('<wbr>&nbsp;あります。');
          $mailNotificationHeader.empty().append($userName).append(
            $('<button></button>', {
              text: UNREAD_CHECK_BUTTON_TEXT,
              id: 'outlooksample-update-btn',
              on: {
                click: function() {
                  sweetAlert.showProcessing();
                  self.fetchUnreadItemsFromMailBox();
                }
              }
            })
          );
        } else {
          const id = 'outlooksample-mailnotification-login';
          if ($('#' + id).length === 0) {
            $mailNotificationHeader.append(
              $('<span></span>', {
                id: id,
                text: 'ログインする',
                on: {
                  click: function() {
                    self.login().then((isLoginProcess) => {
                      self.setLoginStatus(true);
                      self.setContents();
                      self.fetchUnreadItemsFromMailBox();
                      self.setInterval(isLoginProcess);
                      sweetAlert.close();
                    }).catch((error) => {
                      console.error(error);
                      self.setContents();
                      sweetAlert.close();
                    });
                  }
                }
              })
            );
          }
        }
      };

      this.setInterval = function(isLoginProcess) {
        const self = this;
        if (isLoginProcess) {
          setInterval(() => {
            self.fetchUnreadItemsFromMailBox();
          }, TIME_INTERVAL_SEC * 1000);
        }
      };

      this.setMailImageInTitle = function() {
        const $mailImg = $('#outlooksample-portal-img');
        const imgUrl = garoon.assets.images.getUrl(IMAGE_ASSETS_KEY);
        $mailImg.attr('src', imgUrl);
      };
    };

    const main = function() {
      const mailNotification = new MailNotification();
      mailNotification.setMailImageInTitle();
      mailNotification.login().then((isLoginProcess) => {
        mailNotification.setLoginStatus(true);
        mailNotification.setContents();
        mailNotification.fetchUnreadItemsFromMailBox();
        mailNotification.setInterval(isLoginProcess);
        sweetAlert.close();
      }).catch((error) => {
        console.error(error);
        mailNotification.setContents();
        sweetAlert.close();
      });
    };

    main();
  })(myJQuery);
})();
outlook_mail.css
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
 * Garoon Outlook Unread Portal sample program
 * Copyright (c) 2019 Cybozu
 *
 * Licensed under the MIT License
*/

#outlooksample-update-btn {
  cursor: pointer;
  margin-left: 20px;
}

#outlooksample-mailnotification-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

#outlooksample-mailnotification-tbody {
  display: hidden;
}

#outlooksample-mailnotification-login {
  cursor: pointer;
}

#outlooksample-mailnotification-login:hover {
  color: #248dd7;
}

#outlooksample-portal-title {
  display: flex;
  align-items: center;
}

#outlooksample-portal-img {
  width: 1.5em;
  height: auto;
  margin-right: 2px;
}

/* sweet alertのボタンデザイン修正 */
.swal2-confirm.swal2-styled,
.swal2-cancel.swal2-styled {
  padding: 0;
  width: 90px;
  height: 35px;
}

おわりに

この記事では、Office 365 Outlookの未読件数をGaroonポータルでチェックできるカスタマイズを紹介しました。
他にもcybozu developer networkでは、さまざまな Garoonポータルカスタマイズ を紹介しています。ぜひ参照してください。

このカスタマイズでは次のAPIを利用しています。

このカスタマイズは、サイボウズ オフィシャルSIパートナー クロス・ヘッド株式会社による有償サポートの対象カスタマイズです。
詳細は こちら (External link) を参照してください。