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) を参照してください。