Cookieに設定値を保存しているWebExtensionsベースのアドオンについて、MCDで設定の初期値を指定する方法 - 2018-01-19 - ククログ

ククログ

株式会社クリアコード > ククログ > Cookieに設定値を保存しているWebExtensionsベースのアドオンについて、MCDで設定の初期値を指定する方法

Cookieに設定値を保存しているWebExtensionsベースのアドオンについて、MCDで設定の初期値を指定する方法

アドオンの設定の集中管理

Firefox 56およびそれ以前のバージョンで使用可能だった従来型アドオンと、Firefox 57以降のバージョンで使用可能なWebExtensionsベースのアドオンでは、法人での利用においても様々な点で違いがあります。その1つが、管理者による設定の集中管理の方法です。

現在のFirefox ESR52およびThunderbird 52で従来型のアドオンの設定を集中管理するには、Firefox/Thunderbird自体の設定を集中管理するための仕組みであるMCDを使用します。

その一方で、WebExtensionsベースのアドオン用には標準的な設定の集中管理のための仕組みが用意されていません。 Firefox ESR60以降のバージョン(正確には、Firefox 58以降)ではManaged StorageというAPIが実装されているため、将来的にはこれが主流となっていくと思われます(実際、当社で開発しているIE View WEをはじめとしてManaged Storageに対応しているアドオンは既に存在しています)。 しかしながら、この機能を使うためにはアドオン自体がそれ前提の設計となっている必要があり、Managed Storageでは設定を管理できないというアドオンの設定の集中管理の方法は依然として必要となります。

この記事ではその1つとして、設定をCookieで管理している種類のアドオンについての対応をご紹介します。

アドオンでのCookieの保存と読み取り

WebExtensionsベースのアドオンは、一般的なWebページに組み込むJavaScriptのコードと同じ要領で記述します。 Cookieを使って設定を保存したい場合も、以下のようにごく一般的な書き方で実現できます。

function setCookie(key, value) {
  // 10年間だけ設定を保持する場合。無期限であれば expires=... の部分自体を省略する。
  const TEN_YEARS_IN_MSEC = 1000 * 60 * 60 * 24 * 356 * 10;
  const expireDate = new Date(Date.now() + TEN_YEARS_IN_MSEC);
  document.cookie = `${key}=${value}; expires=${expireDate.toGMTString()}; path=/`;
}
// setCookie('Enabled', 'true') のように使う

function getCookie(key) {
  var parts = document.cookie.split(`${key}=`);
  if (parts.length > 1)
    return parts[1].split(';')[0];
  else
    return '';
}
// getCookie('Enabled') で 'true' のような文字列値が返る

ところで、一般的にCookieはホスト名とパスに紐付ける形で保存されます。 Webページであればホスト名は明白ですが、アドオンから保存したCookieの場合、ホスト名はどうなるのでしょうか?

WebExtensionsベースのアドオンの内部UUID

アドオンは通常、他のアドオンとの識別のためのIDを持っています。これは多くの場合メールアドレスのような形式で表記され、addon-name@clear-code.comのような文字列です。 ということは、このIDがCookie保存時のホスト名の代わりに使われるのでしょうか。

実はそうではありません。実際にはアドオンのIDではなく、自動生成された内部的なUUIDが使われます。 これには、いわゆるフィンガープリンティングを防ぐためという意味があります。

仮にアドオンの内部リソースに対して、moz-extension://addon-name.at.clear-code.com/main.js のようにアドオンのIDに基づいたURLが割り当てられていたとすると、Webページ上のコンテンツから容易に読み込みを試行できてしまい、訪問者の使用アドオンを特定されたり、アドオンの使用状況から個人を特定されてしまう可能性があります。

このようなフィンガープリンティングを無効化する目的で、Firefoxはアドオンがインストールされるごとに新しいUUID(ユニークな識別子となる文字列)を自動生成し、それを使ってアドオンの内部リソースを moz-extension://a93cdbe2-b549-401c-ad50-56572822cee3/main.js のようなURLで表すようになっています。 この例でホスト名に相当する部分に現れているa93cdbe2-b549-401c-ad50-56572822cee3がそのアドオンに割り当てられたUUIDで、環境ごとに異なる物が生成され、それどころか同じ環境であっても、そのアドオンをアンインストールした後で再インストールすると新たに別のUUIDが生成されます。

アドオンで保存したCookieは、この自動生成されたUUIDをホスト名代わりにして管理されています。

MCD用設定ファイルを用いてCookieの初期値を設定する

MCD用設定ファイルは一般的にlockPref()などのディレクティブを使用して設定を定義します。 しかし実際の所は、MCD用設定ファイルはJavaScriptとして解釈されており、ifによる条件分岐やforなどでのループ処理の他、XPConnect経由で各種XPCOMコンポーネントの機能を呼び出すことすらもできます。 応用すれば、MCD用設定ファイルでアドオン用のCookieを設定するという事もできます。

Cookieの値の保存(上書き)

MCD用設定ファイルの中でCookieの値を設定する際は、上記のような一般的なWeb上のJavaScriptの作法とは事なり、Cookieを管理するためのXPCOMコンポーネントを直接呼び出す方法を取ります。以下はFirefox ESR52で動作する実装例です。

function setCookie(params) {
  const ONE_DAY_IN_MSEC = 1000 * 60 * 60 * 24;
  const CM = Components.classes['@mozilla.org/cookiemanager;1']
               .getService(Components.interfaces.nsICookieManager2);
  const expiry = params.expireDays ?
                   Date.now() + (params.expireDays * ONE_DAY_IN_MSEC) :
                   null;
  try {
    CM.remove(params.host,
              params.key,
              params.path || '/',
              false,
              '');
  } catch(e) {}
  CM.add(params.host,
         params.path || '/',
         params.key,
         params.value,
         false,
         false,
         false,
         false,
         expiry,
         {});
}

setCookie({
  host:  'a93cdbe2-b549-401c-ad50-56572822cee3',
  key:   'Enabled',
  value: 'true'
});

例の最後に示している通り、Cookieを保存するホスト名としてアドオンの内部UUIDを明示的に指定する必要があります。 そうなると問題になるのが、その内部UUIDはどこで調べられるのか?という事です。

アドオンの内部UUIDを取得する

WebExtensionsベースのアドオンをインストールすると、そのアドオンに関する情報がextensions.webextensions.uuidsという名前の文字列型の設定に保存されます。この値はJSON形式なので、JSON.parse()で簡単に解析できます。 以下はFirefox ESR52で動作する、必要な情報が既に存在している場合とまだ存在していない場合の両方に対応した、アドオンの内部UUIDを取得する実装の例です。

const { Services } = Components.utils.import('resource://gre/modules/Services.jsm', {});

function tryInitializeCookie() {
  try {
    // アドオンのIDからUUIDを解決する
    let uuids = Services.prefs.getCharPref('extensions.webextensions.uuids');
    if (uuids) {
      let json = JSON.parse(uuids);
      let host = json['addon-name@clear-code.com']; // 'a93cdbe2-b549-401c-ad50-56572822cee3'
      if (host) {
        setCookie({
          host,
          key:   'Enabled',
          value: 'true'
        });
        return true;
      }
    }
  } catch(e) {}
  return false;
}

// アドオンの情報がすでに存在していれば、即座にCookieを上書きする
if (!tryInitializeCookie()) {
  // そうでない(初回起動時で、アドオンの情報がまだ存在していない)場合、
  // 設定値の変化を監視して、アドオンの情報が保存された時点で
  // Cookieを上書きする
  Services.prefs.addObserver('extensions.webextensions.uuids', (subject, topic, data) => {
    if (data == 'extensions.webextensions.uuids' &&
        topic == 'nsPref:changed')
      tryInitializeCookie();
  }, false);
}

このようなコードをMCD用設定ファイルに記載しておくことで、Cookieで設定を管理する種類のアドオンであっても管理者側で設定を集中管理できます。

まとめ

WebExtensionsベースのアドオンのうち、Cookieで設定を管理するように設計されたアドオンについて、MCD用設定ファイルで設定を集中管理する方法を解説しました。

現時点ではこの記事で解説したような方法でCookieを編集できますが、この方法は今後のFirefoxの更新で使えなくなっていく可能性があります。 特にFirefox 57でレガシーアドオンのサポートが打ち切られて以降は、Firefoxの内部的なAPIの互換性を維持する必然性がなくなったということで、その変化が一層加速しています。 また、設定の保存方法にはこれ以外にも、Web APIとして一般的なlocalStorageや、WebExtensions APIのLocal Storageなど色々な方法があり、それらの対応にも気を配っていく必要があります。

当社では、FirefoxやThunderbirdの使用時に起こる様々なトラブルに対する技術的サポートを有償にて行っております。 管理者側でアドオンの設定を管理したいものの、方法が不明でお困りという場合には、ぜひ当社までご相談下さい