Backlogのフリープランでスペースを作る

Backlogは、見た目が可愛いくて多機能な課題管理ツールです。

backlog.com

Backlogにまつわるいろんな実験をする時に使うスペースが欲しいと思いました。そこで、Backlogのフリープランを使ってみようと思います。

フリープランでできることをおしえてください – Backlog ヘルプセンター を見ると作れるプロジェクトが1つだったり、親子課題を設定できなかったりと制限はあるものの無料でスペースを使わせてもらえるのはありがたい!

フリープランでアカウント作成します。

  1. https://backlog.com/ja/ を表示します
  2. f:id:ponsuke_tarou:20210803184135p:plain
    [プランと料金]を選択します。
  3. f:id:ponsuke_tarou:20210803184349p:plain
    ページの中程にある[フリープランはこちら]リンクで申し込み画面を表示します。
  4. f:id:ponsuke_tarou:20210803190306p:plain
  5. 既に他のBacklogで使っているメールアドレスでアカウントを作成したい場合は、最初に[メールアドレス]を入力します。
    1. f:id:ponsuke_tarou:20210803190541p:plain
      メッセージとリンクが表示されます。
    2. リンクからログインして、申し込み画面を再度表示すると[スペースID][組織名]の入力だけで済みます。
  6. 全ての項目を入力して[無料で試してみる]ボタンで進むとBacklogのスペースができます。
    • 組織名 : ここの入力値はスペース名になります
  7. f:id:ponsuke_tarou:20210803191625p:plain

f:id:ponsuke_tarou:20210803201920j:plain
ザ マンダリン オリエンタル グルメ ショップで食べたケーキ

使い方はヘルプサイトがわかりやすいです。

たくさんの画像付きでわかりやすいので使い方を調べるときはヘルプサイトがおすすめです。 support-ja.backlog.com

APIを使います。

developer.nulab.com

せっかくスペースを作ったので記念にスペース情報の取得 | Backlog Developer API | Nulabをします。 APIの認証で使うAPIキーの取得方法は、APIの設定 – Backlog ヘルプセンタが参考になります。 コマンドでAPIを呼び出すときは、合わせてjqコマンドを使うとレスポンスのJSONが整形されてみやすくなります。

$ curl -X GET https://{スペースID}.backlog.com/api/v2/space?apiKey={APIキー} | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   228  100   228    0     0   1868      0 --:--:-- --:--:-- --:--:--  1868
{
  "spaceKey": "{スペースID}",
  "name": "ぽんすぺーす",
  "ownerId": 5...,
  "lang": "ja",
  "timezone": "Asia/Tokyo",
  "reportSendTime": "00:00:00",
  "textFormattingRule": "markdown",
  "created": "2021-08-03T10:08:17Z",
  "updated": "2021-08-03T10:59:06Z"
}

f:id:ponsuke_tarou:20210803202030j:plain
ザ マンダリン オリエンタル グルメ ショップで食べた流行りもの

外の世界から守ってくれるプロキシ

プロキシは、インターネットとの接続を中継してくれるサーバです。

プロキシは、単に「プロキシ」と言ったり「プロキシサーバ」と言ったりします。

Webアクセスで利用されるプロキシサーバの機能として,適切なものはどれか。

エ. 内部ネットワークのクライアントが外部サーバと通信する場合,中継役となりクライアントの代わりに外部サーバに接続する。

平成17年秋期問56 プロキシサーバの機能|基本情報技術者試験.com

社内ネットワークなど内部とインターネットなど外部の接続を中継してくれます。英語で書くと「proxy」で日本語に訳すと「代理人」になります。

TCP/IPネットワークのフォワードプロキシに関する説明のうち,最も適切なものはどれか。

ウ. Webブラウザの代理として,Webサーバに対するリクエストを送信する。

令和元年秋期問35 フォワードプロキシはどれか|応用情報技術者試験.com

インターネットは楽しいですが、怖い人々がたくさん待ち構えています。 そんな、怖い人々と直接やりとりするのは嫌なのでプロキシさんに代理人としてやりとりしてもらうのです(これはイメージです)。

「接続元」「接続先」によってフォワードプロキシとリバースプロキシがあります。

接続するパソコンが、内部ネットワークにあってインターネットに接続する時に、代理でインターネットに接続してくれるのがフォワードプロキシです。 英語で書くと「forward proxy」で「前方の + 代理人」になります。

Webサーバを使ったシステムにおいて,インターネット経由でアクセスしてくるクライアントから受け取ったリクエストをWebサーバに中継する仕組みはどれか。

エ. リバースプロキシ

https://www.ap-siken.com/kakomon/31_haru/img/35.gif

平成31年春期問35 Webサーバに中継する仕組みはどれか|応用情報技術者試験.com

Webサーバを作る人々にとっては、インターネットから接続し来る人々はどんな怖い人がいるか分からないので怖いものです。 だから、インターネットからWebサーバへの接続の時に代理人を立てます。これがリバースプロキシです。 英語で書くと「reverse proxy」で「反対の + 代理人」になります。

種類 接続元 接続先
フォワードプロキシ 内部ネットワークのクライアント インターネット上のサーバ
リバースプロキシ インターネット上のクライアント 内部ネットワークのサーバ

裁判をする時に訴える人も訴えられる人も弁護士さんを立てます。 訴える人と訴えられる人が、接続元のパソコンや接続先サーバで、弁護士さんがプロキシサーバ、みたいなイメージです(本当にイメージです)。

フォワードプロキシにはいろんな利点があります。

接続元PCの情報を外部に曝さずにインターネットを使えます。

プロキシサーバが接続元PCの代理としてインターネットに接続してくれるのでPC固有のIPアドレスなどの情報を晒さずに済みます。

https://www.infraexpert.com/studygif/security36.5.gif

プロキシサーバとは - infraexpert.com

ユーザー認証機能をくっつければ、認証した人だけがインターネットに接続できます。

プロキシサーバにユーザー認証を実装することで、登録されて認証できた人だけがインターネットを使えるようにすることができます。

キャッシュでよくみるページへのアクセスが速くなります。

プロキシサーバには、表示したページ情報をキャッシュしておく機能があり、同じページを再度表示する場合はキャッシュを使用することで素早くページを表示できます。

https://xtech.nikkei.com/it/article/COLUMN/20071102/286318/zu04_01.jpg

Lesson4:HTTPのやりとりを仲介するプロキシを使った流れを知ろう | 日経クロステック(xTECH)

フィルタリングで、怪しいサイトへの接続を禁止できます。

社内の決まり(ポリシー)に沿って、怪しいサイトへの接続を禁止することでPCへのマルウェアの侵入対策になります。

https://image.itmedia.co.jp/ait/articles/1608/31/sectecmap1.gif

プロキシサーバ/コンテンツフィルタ――ポリシーに合致しないアクセスの防止技術:セキュリティ・テクノロジー・マップ(6) - @IT

情報を一元管理できます。

内部ネットワークにあるPCは、みんなプロキシサーバを経由することになるので各自のインターネット接続情報が一元管理されることになります。 それにより、何か事故や問題が起こった時にプロキシサーバの記録をたどることで調査が可能になります。

リバースプロキシにも利点があります。

リバースプロキシでは、負荷分散を行なったり、プロキシで暗号化・復号を行うことによるSSL通信の高速化を図ることができます。

https://image.itmedia.co.jp/ait/articles/1608/25/wi-fig02.png

リバースプロキシ(Reverse Proxy):90秒の動画で学ぶITキーワード - @IT

f:id:ponsuke_tarou:20210802221445j:plain
板橋区志村の熊野湯

使っているPCに設定されたプロキシサーバを確認してみます。

Macの場合

[システム環境設定] > [ネットワーク] > [Wifi]などのインターフェースを選択 > [詳細]ボタン > [プロキシ]タブ、とたどっていくとみられます。 f:id:ponsuke_tarou:20210802222601p:plain

Windowsの場合

コマンドで確認したい場合は、コマンドプロンプトなどのターミナルを起動してnetsh winhttp show proxと打てばOKです。

# プロキシサーバーを使っていないとこんな感じ
$ netsh winhttp show prox

現在の WinHTTP プロキシ設定:

    直接アクセス (プロキシ サーバーなし)。

画面で見たい場合は、Win + X > [設定] > [Windowsの設定]ダイアログ表示 > [ネットワークとインターネット] > [プロキシ]とたどっていくとみられます。 f:id:ponsuke_tarou:20210803093434p:plain

Kintoneでフィールドが非表示になる時と「アクセス権がありません。」と表示される時の違い

レコードの詳細画面でフィールが非表示になる時と「アクセス権がありません。」と表示される時があります。

f:id:ponsuke_tarou:20210630192402p:plain
こういうフィールドが、フィールドの権限設定によって
f:id:ponsuke_tarou:20210630192310p:plain
こんな感じで表示に違いがあります。

違いは、アクセス権の設定に「フォームのフィールド」を使っているかどうかです。

懇切丁寧に質問サイトに説明が書いてあります。

「フィールドのアクセス権」では、アクセス権を適用するユーザーの設定方法によって、該当ユーザーがアクセスした時の表示が変わります。

よくあるご質問 | フィールドのアクセス権を設定している場合、レコード詳細画面に「アクセス権がありません。」と表示される場合と、フィールド自体が非表示になる場合があります。

実際にやってみました。

理解力が低くてちょっとわからなかったのでやってみました。

使ったアプリは、社員名簿 - kintone(キントーン)- すぐに使えるサンプルアプリ | サイボウズの業務改善プラットフォームをちょっぴりいじったものです。

フィールドのアクセス権がこんな感じの場合

f:id:ponsuke_tarou:20210630192858p:plain

[閲覧]権限があるユーザーの表示

f:id:ponsuke_tarou:20210630191720p:plain

[閲覧]権限がないユーザーの表示

f:id:ponsuke_tarou:20210630193230p:plain

フィールドのアクセス権に「フォームのフィールド」だけ設定しても「アクセス権がありません。」と表示されました。

「フォームのフィールドを追加」から一つでもフィールドを追加して設定した場合

(省略)※ 該当フィールドに対して閲覧権限がないユーザーは、「アクセス権がありません。」と表示されます。

よくあるご質問 | フィールドのアクセス権を設定している場合、レコード詳細画面に「アクセス権がありません。」と表示される場合と、フィールド自体が非表示になる場合があります。

「ユーザー/組織/グループとアクセス権」を設定せず、「フォームのフィールド」だけ設定したらどうなるのだろう?とふと思ったのでやってみました。

f:id:ponsuke_tarou:20210630221512p:plain
フィールドのアクセス権がこんな感じの場合
f:id:ponsuke_tarou:20210630221606p:plain
[閲覧]権限があるユーザーの表示

やはり「アクセス権がありません。」と表示されました。

f:id:ponsuke_tarou:20210630221652p:plain
[閲覧]権限がないユーザーの表示
「フォームのフィールドを追加」から一つでもフィールドを追加して設定した場合は、「アクセス権がありません。」と表示されます。

ポイントは、「フォームのフィールド」

f:id:ponsuke_tarou:20210630193800p:plain f:id:ponsuke_tarou:20210630194047p:plain

f:id:ponsuke_tarou:20210630194320j:plain
港区にあるふれあいの湯

Kintoneで他のアプリからユーザー選択の値を取得する

やりたいこと

とあるアプリで、他のアプリから値を取得して任意のフィールドに設定したいのです。

  1. [社員名簿]アプリでは、「社員名」と「承認者」を紐づけている。
  2. [作業依頼申請]アプリでは、「申請者」とその「承認者」を入れる必要がある。

上記のような状態のアプリがあったとします。 [作業依頼申請]アプリで「承認者」を入力するのはとても面倒くさいので、[社員名簿]アプリから「承認者」を取得して設定したいです。 f:id:ponsuke_tarou:20210622213458j:plain

[社員名簿]アプリ

社員名簿 - kintone(キントーン)- すぐに使えるサンプルアプリ | サイボウズの業務改善プラットフォームを使っています。

f:id:ponsuke_tarou:20210622211206p:plain
[社員名]フィールドというユーザー選択フィールドを追加しています。
今回使うフィールドは以下です。

フィールド名 フィールドコード フィールドの種類
社員名 社員名 ユーザー選択
承認者 承認者 ユーザー選択

[作業依頼申請]アプリ

作業依頼申請 - kintone(キントーン)- すぐに使えるサンプルアプリ | サイボウズの業務改善プラットフォームを使っています。 f:id:ponsuke_tarou:20210622212125p:plain 今回使うフィールドは以下です。

フィールド名 フィールドコード フィールドの種類
申請者 applicant 作成者
承認者 authorizer ユーザー選択

JavaScriptを実装します。

レコード編集を始めるタイミングで処理を起動します。

kintone.events.on([
    'app.record.index.edit.show',
    'app.record.create.show',
    'app.record.edit.show'
], function(event) {

レコード編集を始めるタイミングで処理が動くようにイベントハンドラーを登録します。

イベントハンドラーを登録する

kintone.events.on(type, handler)

イベント処理の記述方法 – cybozu developer network

引数のtypeには以下のタイミングを指定します。

イベントが発生するタイミング イベントタイプ Promise対応
レコード一覧画面 - インライン編集開始 app.record.index.edit.show o
レコード追加画面 - 表示後 app.record.create.show o
レコード編集画面 - 表示後 app.record.edit.show o

表の参考元 : kintone JavaScript API(イベント)一覧 – cybozu developer network

APIの呼び出しを行うので、kintone.Promiseに対応したイベントを使います。

※ この kintone.Promise オブジェクトを return することで、kintone.api() の実行を待ってから次の処理を実行できるイベントがあります。 対応しているイベントはこちらをご参照ください。

kintone REST API リクエスト – cybozu developer network

他のアプリでの検索条件に使う値を取得する。

[社員名簿]アプリから情報を取得する検索条件に使う値を取得します。 処理を「新規作成」「新規作成以外(既に登録されているレコードの編集)」に分岐させています。

if (event.type === 'app.record.create.show') {
    // レコード追加時には、[申請者]フィールドにログインユーザーが設定されていないので、ログインユーザー情報を直接取得する
    user = kintone.getLoginUser().code;
} else {
    // [申請者]フィールドからユーザー情報を取得する
    user = event.record['applicant']['value']['code'];
}

検索条件に使用する[社員名簿]アプリの[申請者]フィールドには[作成者]を使用しています。

https://jp.cybozu.help/k/img-ja/created_by_01.png

レコードを保存したユーザーが自動で設定されるフィールドです。

作成者 - kintone ヘルプ

この[作成者]は、新規作成して初回保存時に設定されるようです。 ということで、「新規作成」の時はログインユーザーから情報を取得するようにしています。

Kintone REST APIで他のアプリからユーザー選択の値を取得する

APIを使って[社員名簿]アプリの[承認者]フィールドを取得します。検索条件は「[社員名簿]アプリの[社員名]フィールドが、[作業依頼申請]アプリの[申請者]フィールドと同じ」です。

let params = {
    app: 11,
    fields: ['承認者'],
    query: '社員名 in ("' + user + '")'
};

表の出典 : レコードの一括取得(クエリで条件を指定) – cybozu developer network

パラメータ名 指定する値 必須 説明
fields 文字列の配列 省略可 レスポンスに含めるフィールドコードを指定します。
省略した場合は、閲覧権限を持つすべてのフィールドの値が返されます。
app 数値又は文字列 必須 アプリのID を指定します。
query 文字列 省略可 レスポンスに含めるレコードの条件を指定するクエリ文字列です。 クエリ文字列内では、後述の演算子とオプションが使用できます。
省略した場合は、閲覧権限を持つすべてのレコードが返されます。

Kintone REST APIの呼び出しで、コールバック関数を指定する方法と省略する方法があります。 ここでは、コールバック関数を省略してkintone.Promise オブジェクトが返却されるようにします。

Promiseを使う利点

レコード作成時などに、処理を待ってからレコードを保存することができる(同期的処理、と呼びます) 「あるアプリAのレコードを保存時、アプリBのレコードを取得し、その値を利用」というようにレコードの保存時などにkintone APIを使って他のデータを取得したり変更したり、同期的に処理することができるようになります。

kintoneにおけるPromiseの書き方の基本 – cybozu developer network

そうすることで、APIを使って[社員名簿]アプリから取得したデータを画面のフィールドに反映させることができます。

return kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', params).then( function(resp) {
    // API の呼び出しが成功したら実行される処理...
    return event;
}).catch(function(error) {
    // API の呼び出しが失敗したら実行される処理...
    return event;
});

f:id:ponsuke_tarou:20210622214925j:plain

ソースファイルを登録すれば出来上がりです。

  1. [作業依頼申請]アプリの設定画面を開く
    • f:id:ponsuke_tarou:20210630184357p:plain
  2. f:id:ponsuke_tarou:20210630184514p:plain
    [設定]タブ > [JavaScript / CSSでカスタマイズ]リンクからJavaScriptを登録する画面を表示
  3. [適用範囲]=「すべてのユーザーに適用」を選択
  4. [PC用のJavaScriptファイル]の[アップロードして追加]ボタンで実装したソースファイルをアップロード
  5. f:id:ponsuke_tarou:20210630184802p:plain
    [保存]ボタンで保存
  6. f:id:ponsuke_tarou:20210630184935p:plain
    [アプリを更新]ボタンで適用する

できた!

f:id:ponsuke_tarou:20210630185439p:plain
[社員名簿]アプリにこんなレコードがあると・・・
こんな感じで、[作業依頼申請]アプリでレコードを追加したり編集すると[社員名簿]アプリから「承認者」を取得して自動で設定されるようになりました。 f:id:ponsuke_tarou:20210630185649g:plain

うまくいかなかったこと

APIで取得した結果を設定したのに反映されない!

原因 : ハンドラーが return してないから

return kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', params).then(function(resp) {
    // ...省略...
    event.record['authorizer']['value'] = userlist;
}).catch(function(error) {
    console.log(error.message);
});

フィールドの値を書き換える

ハンドラーが record オブジェクトのフィールドの値を書き換えて event オブジェクトを return した場合、その値でフィールドの値を更新します。

  • 最後のハンドラーが return しない場合、フィールドの値を更新しません。

レコード編集イベント – cybozu developer network

原因 : Promiseオブジェクトを使う方法じゃないから

return kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', params, function(resp) {
    // API の呼び出しが成功したら実行されるコールバック処理
    return event;
}, function(error) {
    // API の呼び出しが失敗したら実行されるコールバック処理
    return event;
});

コールバック関数を指定しており、kintone.Promise オブジェクトは返却されません。 というわけでなのでAPIを呼び出す処理が終わるのを待たずに画面表示処理が進んでしまいました。

このように kintone.api() はコールバック関数を省略するとPromiseオブジェクトが返り値になります。

kintone.api(kintone.api.url('/k/v1/record', true), 'GET', {app: 1, id: 1}); // これでPromiseオブジェクトが生成される

それを return してあげることによってkintone側で app.record.create.submit 時など、処理を待ってくれる仕組みをkintoneは持っています。Promiseオブジェクトを return しないと処理をまってくれないので注意しましょう。(逆に言えば、レコード詳細ページなど、処理を待たせる必要がなければPromiseオブジェクトの return は必須ではありません。)

kintoneにおけるPromiseの書き方の基本 – cybozu developer network

Failed to load resource: the server responded with a status of 400 (Bad Request) - 入力内容が正しくありません。

f:id:ponsuke_tarou:20210622185513p:plain
ブラウザのコンソールにエラーが出力された

  • 事象 : APIの呼び出しでエラー
  • 原因 : APIでの検索条件で文字列が"に囲まれていないから
  • 対応 : "で囲む
/** @type {object} API に渡すパラメータオブジェクト. */
let params = {
    app: 11,
    fields: ['承認者'],
    query: '社員名 in (' + user + ')' // 正解>>> '社員名 in ("' + user + '")'
};

520 (520) - 社員名フィールドのフィールドタイプには演算子=を使用できません。

  • 事象 : APIの呼び出しでエラー
  • 原因 : APIでの検索条件でユーザー選択フィールドに=演算子を使ったから
  • 対応 : ユーザー選択には、innot inを使う
/** @type {object} API に渡すパラメータオブジェクト. */
let params = {
    app: 11,
    fields: ['承認者'],
    query: '社員名 = "' + user + '"' // 正解>>> '社員名 in ("' + user + '")'
};
フィールド又はシステム識別子 利用可能な演算子 利用可能な関数
ユーザー選択 in not in LOGINUSER()

表の出典元 : フィールド、システム識別子ごとの利用可能な演算子と関数一覧 – cybozu developer network

f:id:ponsuke_tarou:20210622214554j:plain
PARADIS小石川本店のケーキ

Kintoneの基本機能だけで通知メールをちょっぴりカスタマイズする方法

Kintoneはなかなか便利なサービスです。

Kintoneはプログラミングの知識がなくても業務アプリを作れる便利なサービスです。

交通費申請や経費申請などの業務フローも[プロセス管理]の設定で細かく設定できます。

参考 : プロセス管理でできること - kintone ヘルプ

[プロセス管理]のなかで[ステータス]が更新されるとメール通知が送信されます。

ただ、メール通知は基本的に件名と本文が決まっています。

Q メール通知の本文を変更したいです。レコードのすべての情報を表示したいです。

A メール通知の本文を変更する機能は未搭載です。(省略)

よくあるご質問 | メール通知の本文を変更したいです。レコードのすべての情報を表示したいです。 - aq.cybozu.info

メール通知の件名は、メール通知の件名 - kintone ヘルプで確認できます。

メール通知に埋め込まれる[レコードのタイトル]だけは設定できます。

メール通知の件名と本文には、[レコードのタイトル]というものが埋め込まれます。 この[レコードのタイトル]に設定でるものにはいくつかあります。

レコードタイトルには、次の種類のフィールドを指定できます。

  • レコード番号
  • 文字列(1行)
  • 文字列(複数行)
  • リッチエディター
  • 数値
  • 計算
  • ルックアップ

レコードタイトルを設定する - kintone ヘルプ

[レコードのタイトル]に設定できる フィールドは1つ なのですが、とても便利なのでこの[レコードのタイトル]を利用してメール通知をちょっぴりカスタマイズしたいと思います。

通知メールに複数のフィールド情報を設定します。

Kintoneテンプレートにある[交通費申請]アプリをちょっぴりいじってやってみました。

f:id:ponsuke_tarou:20210618211104p:plain
例えばこんな風に入力した場合のお話です。

「タイトル」が設定されている場合の通知メール

テンプレートにある[交通費申請]アプリでは、[レコードのタイトル]に「タイトル」が設定されていました。

f:id:ponsuke_tarou:20210618223123p:plain
こんな感じにメール通知の件名と本文に「タイトル」が設定されます。

設定方法

今回は、メールに「社員番号」と「申請者」両方の情報があったらいいのに・・・という要望があったという想定です。

f:id:ponsuke_tarou:20210618223612p:plain
今回は、「社員番号」と「申請者」の情報をメール通知に設定します。

1. [レコードのタイトル]に設定するフィールドを作成します。

メール通知の件名に埋め込まれる文字になるので[文字列(1行)]フィールドでシンプルに作成します。 あまり長い文字列だとメールの件名が見にくくなっちゃいますからね。

参考 : フォームを設定する - kintone ヘルプ

ポイントは追加するフィールドで「メール通知に入れたい情報を文字列連携させる」ということです(というよりこれが全てです)。 文字列の連結方法は、[&演算子]文字や数値の結合 - kintone ヘルプに説明があるので参考にします。

f:id:ponsuke_tarou:20210618212721p:plain
こんなフィールドをアプリの設定で追加します。

ちなみに「社員番号」と「申請者」の設定は以下のようにしていました。

フィールドの種類 フィールド名 フィールドコード
数値 社員番号 fc_社員番号
作成者 申請者 fc_申請者

2. レコードのタイトルに作ったフィールドを設定します。

参考 : レコードタイトルを設定する - kintone ヘルプ f:id:ponsuke_tarou:20210618213104p:plain

3. 作ったフィールドを見えないように権限設定します。

作ったフィールドは、メール通知で使いたいだけなので入力する時に見る必要はないので、見えないように設定します。

参考 : フィールドにアクセス権を設定する - kintone ヘルプ

f:id:ponsuke_tarou:20210618214325p:plain
誰にも見えないように設定します([アプリの設定]では見えます)。

4. アプリを更新します。

f:id:ponsuke_tarou:20210618214513p:plain
このボタンを押下し忘れると変更が適用されません。

アプリを更新する前に設定した一部の内容は[アプリの動作テスト]から動かして確認することができます。 しかし、メール通知は送信されないので注意してください。

動作テスト環境でできないこと

テスト環境では、次の設定や操作はできません。(省略)

・通知は送信されません。

アプリの動作テストをする - kintone ヘルプ

このやり方は完璧ではありません。

今回ご紹介した方法では、メールの件名と本文を完全に任意の内容に変えられるわけではありません。 また、フォームに設定されたフィールドを使用するので通知を行う各アプリでそれぞれ設定しなければなりません

そして、Kintoneのメールをカスタマイズする方法はいくつかあります。

  1. 有料のプラグインやサービスを使う
  2. 自分で他のサービスと連携させる

しかし、メール通知を受信する人がメール通知を受信しないように設定できます。

https://jp.cybozu.help/k/img-ja/enable_mail_notification05.png

自分のメール通知の設定(ユーザー) - kintone ヘルプ

どんなにお金と時間と労力をかけても「メール通知を受信しないように設定」してたら・・・。

たとえ、外部サービスなどを利用してメール通知の設定に関わらずメールを送信できたとしても「受信しないように設定したのにメール送らないでよ!」と思われたら悲しいですよね。

そんな時は、今回ご紹介した完璧ではないけど簡単にできるやり方もカスタマイズの候補として考えてみてもいいかもしれません。

f:id:ponsuke_tarou:20210618234150j:plain
板橋区栄町の一の湯

web.xmlメモ用紙

内容を覚えられない・・・本に書いてある通りに書きたくてもクラス名が長くて書けない・・・ググるのが面倒くさい・・・だからメモしちゃう。 書き溜めておけばいつか役に立つって期待してる。

f:id:ponsuke_tarou:20210607223529j:plain
文京区本郷にあるほていやのお蕎麦は超ボリューミー

web-app

<?xml version="1.0" encoding="UTF-8"?>
<web-app {ここの書き方のメモ}>
</web-app>

Servlet 3.0

<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://java.sun.com/xml/ns/javaee"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
  version="3.0">

Servlet 4.0

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">

f:id:ponsuke_tarou:20210607223215j:plain
台東13の富久の湯(ふくのゆ)

servlet

<web-app ...>
  <servlet>
    {ここの書き方のメモ}
  </servlet>
  <servlet-mapping>
    {ここの書き方のメモ}
  </servlet-mapping>
</web-app>

Spring MVCのフロントコントローラを利用するための設定

  <!-- DispatcherServletクラスをサービレットコンテナに登録する -->
  <servlet>
    <servlet-name>api</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!-- サーブレットのcontextClassパラメータにAnnotationConfigWebApplicationContextクラスを指定する -->
      <param-name>contextClass</param-name>
      <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </init-param>
    <init-param>
      <!-- サーブレットのcontextConfigLocationパラメータに作成した設定クラスを指定する -->
      <param-name>contextConfigLocation</param-name>
      <!-- AppConfigの内容は下記参照 -->
      <param-value>example.config.AppConfig</param-value>
    </init-param>
  </servlet>
  <!-- 定義したDispatcherServletを使用してリクエストをハンドリングするURLのパターンを指定する -->
  <servlet-mapping>
    <servlet-name>api</servlet-name>
    <!-- 全てのリクエストを定義したDispatcherServletを使用してハンドリングする -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>
package example.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
@ComponentScan("example")
public class AppConfig implements WebMvcConfigurer {
}

f:id:ponsuke_tarou:20210607221859j:plain
荒川2の千代の湯

filter

<?xml version="1.0" encoding="UTF-8"?>
<web-app ...>
  <filter>
    {ここの書き方のメモ}
  </filter>
  <filter-mapping>
    {ここの書き方のメモ}
  </filter-mapping>
</web-app>

入力値の日本語が文字化けしないようにするための設定

  <!-- CharacterEncodingFilterクラスをサーブレットコンテナに登録する(入力値の日本語の文字化け対策用) -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <!-- サーブレットのencodingパラメータにリクエストパラメータの文字エンコーディング(UTF-8)を指定する -->
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <!-- サーブレットのforceEncodingパラメータにリクエストおよびレスポンスのエンコーディング上書きするかを指定する -->
      <param-name>forceEncoding</param-name>
      <!-- true:encodingへ強制的に上書きされる -->
      <param-value>true</param-value>
    </init-param>
  </filter>
  <!-- CharacterEncodingFilterを適用するリクエストのURLパターンを指定する -->
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <!-- 全てのリクエストを適用対象にする -->
    <url-pattern>/*</url-pattern>
  </filter-mapping>

ISO 8859-1(ISO Latin 1)以外の文字を扱う必要がある場合は、CharacterEncodingFilterを使用して適切な文字エンコーディングの指定が必要です。 また、サーブレットフィルタを複数登録する場合は、リクエストパラメータから値を取得するサーブレットフィルタより前にフィルタ処理が適用されるように登録してください。 順番が逆転すると文字化けしてしまいます。

第4章Spring MVC - Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発

HiddenHttpMethodFilter

HiddenHttpMethodFilterを使用すると、クライアントとの物理的な通信はPOSTメソッドを使用しますが、サーブレットコンテナ内ではリクエストパラメータで送られてきた値に置き換えて処理を行うことができます。

第6章RESTful Webサービスの開発 - Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発

  <!-- HiddenHttpMethodFilterクラスをサーブレットコンテナに登録する -->
  <filter>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
  </filter>
  <!-- HiddenHttpMethodFilterを適用するとサーブレットコンテナ内でリクエストパラメータで送られてきた値に置き換えて処理を行うことができる -->
  <filter-mapping>
    <filter-name>HiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

f:id:ponsuke_tarou:20210607222706j:plain
板橋区板橋にある大むさしの卵とじそば

GitHubとSlackをSlackのアプリで連携させる方法

Slackのアプリを使ってGitHubを連携します。

slack.com

1. GitHubにSlackアプリをインストールします。

参考 : 個人アカウントでアプリケーションをインストールする - GitHub Docs

  1. 連携するGitHubアカウントにログインします。
  2. [Marketplace] > 「slack」でアプリを検索 > 「Slack + GitHub」を選択してページを表示します。
    • f:id:ponsuke_tarou:20210601211921p:plain
  3. [Install it for free]ボタンで次のページを表示します。
  4. [Complete order and begin installation]ボタンでインストールします。

f:id:ponsuke_tarou:20210601214036p:plain
アプリがインストールされているかは[Account settings]画面で確認できます

2. SlackにGitHubアプリをインストールします。

  1. Slack + GitHub のページを開いて、[Add to Slack]ボタンを押下します。
    • f:id:ponsuke_tarou:20210601211319p:plain
  2. 設定したいSlackのワークスペースになっていることを確認して[Allow]ボタンで進みます。
  3. Slackのメッセージが届いてワークスペースGitHubアプリが追加されます。
    • f:id:ponsuke_tarou:20210601211154p:plain

3. SlackでGitHubアカウントへの連携設定をします。

  1. 使いたいチャネルで /invite @GitHub を送信してGitHubアプリをチャネルに追加します。
  2. /github subscribe ユーザ名/リポジトリ名 を送信して、表示された[Connect GitHub account]ボタンでWeb画面を表示します。
  3. [Authorize Slack]ボタンを押下すると認証コードが画面に表示されます。
    • f:id:ponsuke_tarou:20210601214216p:plain
  4. Slackに表示された[Enter code]ボタンで認証コードを入力します。
    • f:id:ponsuke_tarou:20210601211715p:plain

うまくいかなかったこと

Either the app isn't installed on your repository or the repository does not exist. Install it to proceed.

f:id:ponsuke_tarou:20210601212409p:plain
事象 : GitHubリポジトリを連携したらメッセージが表示された

  • 原因 : GitHub 側で Slack アプリがインストールされていないから
  • 対応
    1. メッセージに表示された[Install GitHub App]ボタンでWeb画面を開きます。
    2. [Only select repositories]で通知したいリポジトリを選択します(複数選択可)。
      • f:id:ponsuke_tarou:20210601213543p:plain
        リポジトリにインストールしたい場合は、[All repositories]を選択します。
    3. [Install]ボタンでインストールします。

Workspaces on free subscriptions can only install 10 apps and your workspace has reached the limit.

2020年3月頃にGitHubとSlackを連携させました。 ある日、GitHubと連携するアプリを使っているチャネルにこんな通知が来ました。 f:id:ponsuke_tarou:20210506104208p:plain

Action required - upgrade app for {ワークスペース名}.
GitHub app is built on Slack's workspace apps which are now deprecated. The legacy GitHub app will stop working on July 15, 2021.
Don't worry, we have built a new version of GitHub-Slack integration. You can just upgrade the app and get back to your work.
Learn more about this upgrade here.
(なんとなくの訳)
GitHubアプリは非推奨になって2021-07-15に使えなくなるよ。
でも新しいアプリがあるからアップグレードすればOKよ。

というわけで対応しようと、通知にある[Upgrade App]ボタンから進んでいくと・・・。 f:id:ponsuke_tarou:20210506195913p:plain

アプリ

インストールできるサードパーティ製アプリやカスタムアプリは最大 10 個です。

Slack フリープランでのメッセージ、ファイルやアプリの制限 | Slack

有料プランから無料プランへプラン変更したワークスペースのためか、インストールされている「アプリ」と「インテグレーション」の合計数が10個をがっつり超えていました。

SlackのGitHubアプリをアップグレードしよう(2021年7月15日まで) | DevelopersIOを参考にすると通知にある[Upgrade App]ボタンからぽちぽちすればアップグレードできるようですが、今回はワークスペースにインストールするアプリの上限に達しているため一旦古いGitHubアプリを削除して再度新しいGitHubアプリで設定することにしました。

  1. Get Started | Slackをブラウザで表示 > [Manage]
  2. [GitHub (Legacy)]をアンインストールする
    1. [Installed apps]タブ > 一覧から[GitHub (Legacy)]を選択して画面を表示
    2. [Settings] > [Warkspace access] > [Uninstall app]でポップアップを表示
    3. 「アプリが使えなくなっちゃうって理解しているよ」チェックボックス2つにチェックを入れる
      • チェックすると[Uninstall app]ボタンが活性化する
    4. [Uninstall app]ボタンでアプリをアンインストールする
  3. 他の不要なアプリやをインテグレーションをアンインストール

PostgreSQLのpsqlをインストールする

PostgreSQLの本体じゃなくてコマンドだけが欲しい時のお話です。

以前、MacPostgreSQLをインストールして使ったことがありました。 ponsuke-tarou.hatenablog.com 今回は、自分のパソコンじゃなくてサーバにあるPostgreSQLにコマンドで接続したいです。 なので、PostgreSQL本体が欲しいのではなくコマンドだけが欲しいのです。

Windows

インストールする

  1. Download PostgreSQL Database for Windows, Linux and MacOS & 32-bit or 64-bit Versions | EDBをブラウザで表示する
  2. 任意のバージョンのWindowsのところにある[Download]ボタンからexeファイルをダウンロードする
  3. ダウンロードしたexeファイルを起動する
  4. f:id:ponsuke_tarou:20210427112448p:plain
    しばし待つ
  5. f:id:ponsuke_tarou:20210427112600p:plain
    [Next]ボタンで進む
  6. f:id:ponsuke_tarou:20210427112949p:plain
    インストール先を指定する
  7. f:id:ponsuke_tarou:20210427113018p:plain
    [Command Line Tools]だけにチェックを入れる
  8. f:id:ponsuke_tarou:20210427113049p:plain
    [Next]ボタンで進む
  9. f:id:ponsuke_tarou:20210427113108p:plain
    [Next]ボタンで進む
  10. f:id:ponsuke_tarou:20210427113120p:plain
    しばし待つ
  11. f:id:ponsuke_tarou:20210427113142p:plain
    [Finish]ボタンでダイアログを閉じる

パスを通す

パスは、使っているシェルや環境によって設定がちょっぴり違います。 今回は、コマンドプロンプトpsqlコマンドを使うので、システムのプロパティから環境変数PATHに設定します。

  1. Win + R > sysdm.cplを入力 > Enterで[システムのプロパティ]ダイアログを開く
  2. [詳細設定]タブ > [環境変数...] > [ユーザー環境変数]か[システム環境変数]の[Path]を選択 > [編集]ボタンでダイアログを開く
  3. [新規]ボタン > {インストール先フォルダ}\binを入力 > [OK]ボタンでダイアログを閉じる
# 環境変数PATHに設定されました
>echo %path%
...省略...C:\apps\PostgreSQL\10\bin;...省略...

# psqlコマンドが使えるようになりました
>psql -V
psql (PostgreSQL) 10.16

# データベースへはこんな感じで接続できます(切断はCtrl+C)
# psql -h {ホスト} -p {ポート} -U {ユーザー} -d {データベース}
>psql -h example.com -p 5432 -U ponsuke -d my_db
psql (10.16, server 9.3.10)
Type "help" for help.

my_db=#
psqlのオプション 意味
-h {ホスト}
--host={ホスト}
サーバを実行しているマシンのホスト名を指定
-p {ポート}
--port={ポート}
サーバが接続監視を行っているTCPポートもしくはローカルUnixドメインソケットファイルの拡張子
環境変数PGPORTの値、環境変数が設定されていない場合はコンパイル時に指定した値(通常は5432)がデフォルト値
-U {ユーザー}
--username={ユーザー}
接続するユーザーを指定
-d {データベース}
--dbname={データベース}
接続するデータベースの名前
-c {コマンド}
--command={コマンド}
実行するコマンド
-cまたは-fが指定されると、psqlは標準入力からコマンドを読み取りません。
その代わりに、すべての-cオプションおよび-fオプションを順に処理した後、終了します。
-f {ファイル}
--file={ファイル}
コマンドを読み取るファイルを指定

上記表の出典 : psql - PostgreSQL 12.4文書

f:id:ponsuke_tarou:20210429105512j:plain
板橋区幸町の第二富士見湯

Windows以外

いつか・・・やった時に書きます。

AESは電子政府推奨暗号リストに載ってる共通鍵暗号方式

前回の勉強内容

ponsuke-tarou.hatenablog.com

AESは、暗号化の規格です。

  • 正式名称 : Advanced(高度な) Encryption(暗号化) Standard(標準)

アメリカで標準規格として採用されていたDESにとって変わって登場した暗号化方式です。

暗号化と復号で同じ鍵を使う共通鍵暗号方式です。

暗号方式に関する記述のうち,適切なものはどれか。

  1. AESは公開鍵暗号方式RSA共通鍵暗号方式の一種である。
  2. 共通鍵暗号方式では,暗号化及び復号に同一の鍵を使用する。
  3. 公開鍵暗号方式を通信内容の秘匿に使用する場合は,暗号化に使用する鍵を秘密にして,復号に使用する鍵を公開する。
  4. ディジタル署名公開鍵暗号方式が使用されることはなく,共通鍵暗号方式が使用される。

平成29年秋期問41 暗号方式に関する記述|応用情報技術者試験.com

使う鍵の組み合わせはこんな感じです。

使う鍵 共通鍵暗号方式 公開鍵暗号方式
暗号化 共通鍵 公開鍵
復号 共通鍵 秘密鍵

f:id:ponsuke_tarou:20210413225125j:plain

暗号方式のうち,共通鍵暗号方式はどれか。

  1. AES
  2. ElGamal暗号
  3. RSA
  4. 楕円曲線暗号

平成28年春期問37 共通鍵暗号方式はどれか|応用情報技術者試験.com

データベースで管理されるデータの暗号化に用いることができ,かつ,暗号化と復号とで同じ鍵を使用する暗号化方式はどれか。

  1. AES
  2. PKI
  3. RSA
  4. SHA-256

平成27年春期問39 同じ鍵を使用する暗号化方式|基本情報技術者試験.com

上記過去問の選択肢を分類するとこんな感じです。

問題の選択肢 共通鍵
暗号方式
公開鍵
暗号方式
暗号方式じゃない
AES o
ElGamal暗号 o
RSA o
楕円曲線暗号 o
PKI PKIは、暗号化技術と電子署名で世の中の安全を守る仕組みです。
SHA-256 ハッシュ関数です。

データを決まった長さに区切って暗号化するブロック暗号です。

ブロック暗号は、元のデータを決まった長さに区切って、その区切った単位ごとに暗号化します。
この区切ったデータをブロックと言います。
AESでは、このブロックの長さ(ブロック長)を128bitで分けています。
f:id:ponsuke_tarou:20210413225305j:plain
もう一つストリーム暗号という方式があります。
ストリーム暗号は、元のデータを端から1bitまたは1byte単位で次々に暗号化します。
端からバンバン暗号化するのでデータを全部受信していなくてもさくさく暗号化できちゃいます。
だから、早い!

が、ブロック暗号の方が研究が進んでいて安全だそうです。
研究が進んでいるおかげかAESは、早いそうです。

ブロック暗号 ストリーム暗号
暗号化の単位 ブロック 1bitまたは1byte
逐次生 o
安全性 o
暗号方式 DES, AES RC4

f:id:ponsuke_tarou:20210415202903j:plain
荒川区の藤の湯

電子政府推奨暗号リストは、安全性及び実装性能が確認された暗号方式を載せたリストです。

暗号化技術の安全性を評価したりしている、CRYPTRECというプロジェクトがあります。

  • 正式名称 : Cryptography(暗号技術) Research(調査) and Evaluation(評価) Committees(委員会)

CRYPTREC とはCryptography Research and Evaluation Committees の略であり、電子政府推奨暗号の安全性を評価・監視し、暗号技術の適切な実装法・運用法を調査・検討するプロジェクトである。
CRYPTREC | CRYPTRECとは

このCRYPTRECから「電子政府推奨暗号リスト」というものが出されています。

電子政府推奨暗号リストとは,CRYPTRECによって安全性及び実装性能が確認された暗号技術のうち,市場における利用実績が十分であるか今後の普及が見込まれると判断され,当該技術の利用を推奨するもののリストである。
平成27年春期問8 CRYPTREC 暗号リスト|情報処理安全確保支援士.com

AESは、安全性及び実装性能が確認された暗号方式です。

CRYPTREC | CRYPTREC暗号リスト(電子政府推奨暗号リスト)にあるPDFで「電子政府推奨暗号リスト」を確認すると(2021-04-13時点)
共通鍵暗号」の「128ビットブロック暗号」のところにAESが載っています。

ということで、「CRYPTRECにより安全性及び実装性能が確認された暗号方式」ということですね。

AESは、鍵長が長いほど暗号文は解読されにくくなります。

鍵長は、そのままの意味で鍵の長さ(単位はbit)です。
AESでは、暗号化で使う共通鍵の鍵長が「128bit」「192bit」「256bit」から選べます。
悪い人が共通鍵を総当たりで割り出そうとした時に「0」「1」の組み合わせといえど長くなれば長くなるほど大変になるわけですね。

AES-256で暗号化されていることが分かっている暗号文が与えられているとき,ブルートフォース攻撃で鍵と解読した平文を得るまでに必要な試行回数の最大値はどれか。

  1. 256
  2. 2^{128}
  3. 2^{255}
  4. 2^{256}

平成30年秋期問37 ブルートフォース攻撃の試行回数|基本情報技術者試験.com

ブルートフォース攻撃は、根性で不正ログインを頑張る総当たり攻撃です。
ブルートフォース攻撃をする悪い人は、復号する共通鍵をどんどん変えて復号を頑張ります。
鍵長が「256bit」の「0」「1」の組み合わせパターンを頑張って鍵を見つけるために最大2^{256}回鍵を変えて復号を試し続けることになるのです。
頑張りますね。

AESの特徴は、「鍵長によって段数が決まる」です。

AESの特徴はどれか。

  1. 鍵長によって,段数が決まる。
  2. 段数は,6回以内の範囲で選択できる。
  3. データの暗号化,復号,暗号化の順に3回繰り返す。
  4. 同一の公開鍵を用いて暗号化を3回繰り返す。

平成29年春期問1 AESの特徴はどれか|情報処理安全確保支援士.com

暗号化では、ラウンド関数という関数を繰り返し処理します。
その関数を処理する回数を段数とかラウンド数といい、この数が多いほど強度の高い暗号文になります。

鍵長と段数の組み合わせ

鍵長 段数
128bit 10
192bit 12
256bit 14

というわけで、「使う共通鍵の鍵長が大きい」->「暗号化の段数が多い」->「解読されにくい暗号文ができる」ということになります。

f:id:ponsuke_tarou:20210415203026j:plain
豊島区の前田湯はお花屋さんと一緒になっている

最後にAESで暗号化と復号をしてみます。

参考 : コマンドラインで簡単にAES暗号化、または Java での AES 暗号化 - 理系学生日記

# Macに入っているopensslコマンドを使います。
$ openssl version
LibreSSL 2.8.3

# 使えるAESの暗号方式をみてみます。
$ openssl list-cipher-commands | grep aes
aes-128-cbc
aes-128-ecb
aes-192-cbc
aes-192-ecb
aes-256-cbc
aes-256-ecb

# 鍵長256bitの共通鍵を使ったAESで暗号化します。
$ echo "ponsuke" | openssl aes-256-cbc -e -base64 -p -pass pass:password
salt=1CD83F097F07009C
key=8E71364EB09F97B6BACAC68B1EB944FC416CE8EBBF81E8B2681667AEE7991D63  # << 共通鍵
iv =68F150FA9F95D8590F8FE50002A1571E
U2FsdGVkX18c2D8JfwcAnEg69cNx1g30btqEdInz7TQ=   # << 暗号化された暗号文

# 暗号文を復号します。
$ echo "U2FsdGVkX18c2D8JfwcAnEg69cNx1g30btqEdInz7TQ=" | openssl aes-256-cbc -d -base64 -pass pass:password
ponsuke

f:id:ponsuke_tarou:20210415203128j:plain
板橋区大和町の愛染湯

次回の勉強内容

勉強中・・・

いろんなMacがある

すぐ忘れちゃう。 同じようなアルファベットの並びがあるとなんだかわからなくなる。 フルスペルなんて1mmも覚えられない。そんな自分への記録です。

MacintoshMac

Appleが作ってるりんごマークのついたパソコンです。

f:id:ponsuke_tarou:20210404220246j:plain
私はステッカーを貼るセンスと器用さがありません。が貼ります。

Media Access ControlのMACアドレス

ネットワーク上でパソコンとかスマホとか機械を識別するための番号です。

f:id:ponsuke_tarou:20210404222428p:plain
MacMACアドレスを確認しました。
機械の個人番号のようなもので、基本的には世界で1つの番号です。

# コマンドだとこんな感じで確認できます。
$ ifconfig en0 ether
en0: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1500
        options=400<CHANNEL_IO>
        ether 12:34:56:ab:cd:ef #<<< ここがMACアドレス

このMACアドレスがあるので広い広いネットワークの中でもどの器械(パソコンとか)にどの器械から通信しているかわかるようになります。

ponsuke-tarou.hatenablog.com

f:id:ponsuke_tarou:20210409212912j:plain
台東区の日の出湯

Message Authentication CodeのMAC

メッセージ認証をする時にデータと一緒に送られてくる検証用のデータです。

ponsuke-tarou.hatenablog.com

悪い人が送ったデータじゃないことを確かめるために使うデータです。

例えば、GitHubのWebhookを使うと送られてくる情報にこんな感じでくっついてきます。

{
    ...省略...
    "headers": {
        ...省略...
        "x-hub-signature": "sha1=sha1のHMAC値",
        "x-hub-signature-256": "sha256=sha256のHMAC値"
    },
    ...省略...
    "body": "データの本体",
    ...省略...
}

f:id:ponsuke_tarou:20210409213312j:plain
荒川区の藤の湯

LambdaのPythonでGitHubのWebhookから送られてくるHAMC値をチェックする

LambdaのPythonGitHubのWebhookから送られてくるHAMC値をチェックするコード

# -*- coding: utf-8 -*-
from __future__ import print_function
import json, hmac, hashlib, base64, urllib

def checkHmac(event) -> bool:
    """GitHubのWebhookから送られてくるHAMC値をチェックする"""
    # 送信者と受信者で秘密情報であるSecretの値
    secret = 'GitHubのWebhookで設定したSecretの値(直書きしないで環境変数とかSecrets Managerから取得すべし)'
    # GitHubから送られてきた情報にあったHMAC値
    sent_hmac = event['headers']['x-hub-signature-256']
    print('SHA-256ハッシュ関数用のHMAC値:' + sent_hmac)

    # Secretの値をbytes型に変換する
    secret_bytes = bytes(secret, 'utf-8')
    if (event['isBase64Encoded']):
        # bodyをbase64でデコードしてbytes型にする
        body_bytes = base64.b64decode(event['body'])
    else:
        # bodyをbytes型に変換する
        body_bytes = bytes(event['body'], 'utf-8')

    # 「Secret + メッセージ + ハッシュ関数」でHMAC値を作る
    created_hmac = 'sha256=' + hmac.new(secret_bytes, body_bytes, hashlib.sha256).hexdigest()
    print('自分で作ったHMAC値:' + created_hmac)

    # 2つのHMAC値が同じであったら改竄されていないと判断する
    return hmac.compare_digest(created_hmac, sent_hmac)

def lambda_handler(event, context):
    if checkHmac(event):
        print('HMAC値で認証できた!')
        # base64デコードした上で文字列にする
        b64decd = base64.b64decode(event['body']).decode()
        # URLデコードして「payload=」を削除する
        urldecd = urllib.parse.unquote(b64decd).lstrip('payload=')
        # 辞書型にする
        body = json.loads(urldecd)
        # そして処理で使う
        print(body)
    else:
        print('へんなHMAC値がGitHubから送られてきた!')

f:id:ponsuke_tarou:20210407234526j:plain
東京都あきる野市今熊山のすみれ

ことの発端は試験勉強

  1. じみに続けている情報処理試験の勉強でメッセージ認証を勉強していた。
  2. そういえば以前Backlogの課題にGitHubのコミットを連携するをやった時にGitHubのWebhookでHMAC値を使ったのを思い出して、今一度やってみようと思った。
  3. 手頃にGitHubでWebhookを設定
  4. AWSAPI GatewayとLambdaをノリで作ってみた。
  5. WebhookからきたHMAC値と自作したHMAC値が一致しない!

f:id:ponsuke_tarou:20210407234800j:plain
金剛の滝

GitHubのWebhookが送ってくるbodyがへんだ

print(event)してCloudWatch Logsをみたら・・・bodyが・・・肉眼で読めない・・・。

{
   "version": "2.0",
...
  "body": "cGF5bG9hZD0lN0IlMjJ...=",
  "isBase64Encoded": true
}

アルファベットと数字と最後にある=からBase64エンコードされてるっぽい・・・前にやった時は普通に読めたのに・・・・。

ペイロードがBase 64でエンコードされている

じっと見つめると "isBase64Encoded": true と最後に書いてある・・・なにこれ?

Lambda プロキシ統合の場合、API Gateway は、次のようにクライアントリクエスト全体をバックエンド Lambda 関数の入力 event パラメータにマッピングします。

...省略...

"isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encoded"

API Gateway で Lambda プロキシ統合を設定する - Amazon API Gateway

訳すと「適用可能なリクエスト・ペイロードがBase 64でエンコードされているかどうかを示す真偽フラグ」。

えっ?Base64エンコードやっぱされている

f:id:ponsuke_tarou:20210407234849j:plain
今熊神社の三つ葉つつじ

HMAC値を作るにはbodyをBase64デコードします。

# 「cGF5bG9hZD0lN0IlMjJ...=」なbodyをBase64デコードしてbytes型にしてあげる
body_bytes = base64.b64decode(event['body'])
# bytes型にしたbodyを使ってHMAC値を作ろう
created_hmac = 'sha256=' + hmac.new(secret_bytes, b64decd, hashlib.sha256).hexdigest()

WebhookからきたHMAC値は X-Hub-SignatureではなくX-Hub-Signature-256 を使うのがお勧めです。

{
    ...省略...
    "headers": {
        ...省略...
        "x-hub-signature": "sha1=sha1のHMAC値",
        "x-hub-signature-256": "sha256=sha256のHMAC値"
    },
    ...省略...
}
ヘッダ 説明
X-Hub-Signature このヘッダは、webhook が secret で設定されている場合に送信されます。 これはリクエスト本文の HMAC hex digest であり、SHA-1 ハッシュ関数と HMAC keyとしての secret を使用して生成されます。 X-Hub-Signature は、既存の統合との互換性のために提供されているため、より安全な X-Hub-Signature-256 を代わりに使用することをお勧めします。
X-Hub-Signature-256 ​このヘッダは、webhook が secret で設定されている場合に送信されます。 これはリクエスト本文の HMAC hex digest であり、SHA-256 ハッシュ関数と HMAC key としての secret を使用して生成されます。

表の出典 : webhook イベントとペイロード - GitHub Docs

isBase64EncodedがFalseの時も考えておきます。

isBase64EncodedFalseだとBase64エンコードはされなさそうなので処理の分岐を作っておきます。

    if (event['isBase64Encoded']):
        # bodyをbase64でデコードしてbytes型にする
        body_bytes = base64.b64decode(event['body'])
    else:
        # bodyをbytes型に変換する
        body_bytes = bytes(event['body'], 'utf-8')

処理でBodyの内容を使う時はさらに加工して使うといいでしょう。

        # base64デコードした上で文字列にする
        b64decd = base64.b64decode(event['body']).decode()
        # こんな感じになる >>> payload=%7B%22ref%22%3A%22refs%2Fheads%2Fmaster%22%2C%22before...
        print(b64decd)
        # URLデコードして「payload=」を削除する
        urldecd = urllib.parse.unquote(b64decd).lstrip('payload=')
        # こんな感じになる >>> {'ref': 'refs/heads/master', 'before...
        print(urldecd)
        # 辞書型にすると使いやすい(と思う)
        body = json.loads(urldecd)

f:id:ponsuke_tarou:20210407234635j:plain
今熊山の名前のわからない花

付加データを付き合わせてなりすましを見破るHMAC

前回の勉強内容

ponsuke-tarou.hatenablog.com

勉強のきっかけになった問題

令和2年度秋(2020-10)の情報処理安全確保支援士試験午後1の問題にHMACについての問題が出題されました。

そして、大敗を喫しました・・・ので勉強します。

なりすまし対策としてデータが改竄されていないかを検証します。

「勉強のきっかけになった問題」では、「会員番号をバーコードで表示するアプリのなりすまし対策」にメッセージ認証を用いることになっています。

「他人のバーコードを会員番号から推測して表示」「他人の会員番号を盗んでバーコードを生成」しちゃってなりすしできちゃうので対策をするわけです。

メッセージ認証は、検証用のデータをメッセージに添付する方法です。

ネットワークを伝わっている間にデータが改竄されても検出できるように以下の流れで検証を行います。

  1. メッセージをやりとりする送信者と受信者で秘密情報(共有鍵など)を共有する
  2. 送信者がメッセージ本文から秘密情報などをごにょごにょ使って検証用のデータを作る
  3. 送信者は、メッセージ本文と検証用のデータを受信者に送る
  4. 受信者がメッセージ本文から秘密情報などをごにょごにょ使って検証用のデータを作る
  5. 受信者は、送られてきた検証用のデータと自分で作った検証用のデータを比較する
  6. 2つの検証用のデータが同じであったら改竄されていないと判断する

https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/MAC.svg/1322px-MAC.svg.png

メッセージ認証符号 - Wikipedia

検証用のデータのことをMACといいます。

  • 英語 : Message Authentication Code
  • 日本語 : メッセージ認証符号

送信者Aが,受信者Bと共有している鍵を用いて,メッセージからメッセージ認証符号を生成し,そのメッセージ認証符号とメッセージを受信者Bに送信する。このとき,メッセージとメッセージ認証符号を用いて,受信者Bができることはどれか。

答. メッセージの改ざんがないことを判定できる。

平成27年秋期問16 メッセージ認証符号|ネットワークスペシャリスト.com

f:id:ponsuke_tarou:20210401213939j:plain
北区志茂にあるHOTランドみどり湯

HMACは、ハッシュ関数を使って作られたMACのことです。

  • 英語 : Hash-based Message Authentication Code
メッセージ本文 + 共有鍵 = MAC
メッセージ本文 + 共有鍵 + ハッシュ関数 = HMAC

みたいな感じです。

MAC(Message Authentication Code)は、通信内容の改ざんの有無を検証し、完全性を保証するために通信データから生成される固定長のビット列です。 MACの生成には、共通鍵暗号を用いたもの(DES-MACやAES-MAC)とハッシュ関数を用いたもの(HMAC)がありますが、設問ではブロック暗号を用いてMACを生成しているので共通鍵暗号を用いた方式であることがわかります。

平成18年春期問74 メッセージ認証で使用される鍵|応用情報技術者試験.com

GitHubのWebhookでもHMACを使います。

以前、Backlogの課題にGitHubのコミットを連携する方法 - ponsuke_tarou’s blogをやった時にHMACを使いました。

  1. GitHubでWebhookを登録する時に合わせて登録するSecretが「メッセージをやりとりする送信者と受信者で秘密情報」になります。
    • f:id:ponsuke_tarou:20210405172807p:plain
  2. GitHubは、情報と一緒に検証用のデータを送ってくれます。
    • (下記のjson参照)GitHubから送られてきたデータのheadersにHMAC値が設定されています。
  3. BODY部分のメッセージ本文とSecret(秘密情報)とハッシュ関数でHMAC値を作ります。
    • (下記Pythonのコード参照)Pythonでやってみました。
    • hmac.new(secret_bytes, body_bytes, hashlib.sha256).hexdigest()で「Secret + メッセージ + ハッシュ関数」からHMAC値を作っています。
  4. 2つのHMAC値が同じであったら改竄されていないと判断する(下記Pythonのコード参照)
{
    ...省略...
    "headers": {
        ...省略...
        "x-hub-signature": "sha1=検証データ",
        "x-hub-signature-256": "sha256=検証データ"
    },
    ...省略...
    "body": "BODYに送ってくれた情報がある"
    ...省略...
}
# -*- coding: utf-8 -*-
from __future__ import print_function
import json, hmac, hashlib

def lambda_handler(event, context):
    # 送信者と受信者で秘密情報であるSecretの値
    secret = '3m@6vC5Y_mNwhhA'
    # GitHubから送られてきた情報にあったHMAC値
    sent_hmac = event['headers']['x-hub-signature-256']
    print('SHA-256ハッシュ関数用のHMAC値:' + sent_hmac)

    # Secretの値をbytes型に変換する
    secret_bytes = bytes(secret, 'utf-8')
    if (event['isBase64Encoded']):
        # bodyをbase64でデコードしてbytes型にする
        body_bytes = base64.b64decode(event['body'])
    else:
        # bodyをbytes型に変換する
        body_bytes = bytes(event['body'], 'utf-8')

    # 「Secret + メッセージ + ハッシュ関数」でHMAC値を作る
    created_hmac = 'sha256=' + hmac.new(secret_bytes, body_bytes, hashlib.sha256).hexdigest()
    print('自分で作ったHMAC値:' + created_hmac)

    # 2つのHMAC値が同じであったら改竄されていないと判断する
    if created_hmac == sent_hmac:
        print('HMAC値で認証できた!')
    else:
        print('認証できないHMAC値がGitHubから送られてきた!')

上記は以下の投稿で紹介しているコードを基にしていますので、ご興味のある方はご参照ください。 ponsuke-tarou.hatenablog.com

f:id:ponsuke_tarou:20210404142105j:plain
みどり湯の前にある公楽の酢豚は絶品

次回の勉強内容

ponsuke-tarou.hatenablog.com

はじめてのGoogle Analytics Reporting APIをNode.jsでちょっとだけ使ってみる

以前の投稿でGoogle AnalyticsGoogle Cloud Platformを設定してGoogle Analytics Reporting APIを使えるようにしました。

ponsuke-tarou.hatenablog.com

今回は、自分のQiitaのページにアナリティクスを設定、Qiitaページ用のサービスアカウントを作ったので、その情報をNode.jsを使ってAPIで取得してみたいと思います。 ちなみに、Node.jsは未経験です、ずぶのど素人です。

準備する

環境 : Windows10 Pro バージョン1909

  1. Node.jsをインストールする
  2. yarnをインストールする
  3. 作業用のディレクトリを作成する(ここ以降は下記のコードを参照)
  4. yarnの初期化する
  5. GoogleAPIのNode.js用ライブラリgoogle-api-nodejs-clientをインストールする
  6. Google Cloud Platformで作成したサービスアカウントの鍵ファイル(json)を格納するためのディレクトリを作成する
  7. 鍵ファイル(json)を格納する
# 作業用のディレクトリを作成する
$ mkdir google-analytics-api
$ cd google-analytics-api/

# yarnの初期化する
$ yarn init
...省略...
Done in 93.28s.

# GoogleAPIのNode.js用ライブラリgoogle-api-nodejs-clientをインストールする
$ yarn add googleapis
yarn add v1.22.10
info No lockfile found.
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 31 new dependencies.
info Direct dependencies
└─ googleapis@68.0.0
info All dependencies
├─ abort-controller@3.0.0
...省略...
Done in 7.96s.

# Google Cloud Platformで作成したサービスアカウントの鍵ファイル(json)を格納するためのディレクトリを作成する
$ mkdir config

# 鍵ファイル(json)を格納する
$ ls config/
client_secrets.json

コードを書く

Google APIでの認証については、Analytics Reporting API - 承認  |  アナリティクス Reporting API v4が参考になります。

analytics.jsというファイルを作って以下のコードを書きました。

リクエストの内容についてはメソッド: reports.batchGet  |  アナリティクス Reporting API v4  |  Google Developersを見ながらしてする値を決めました。

const { google } = require('googleapis')

const KEY_FILE = './config/client_secrets.json' // 鍵ファイルのパスを指定する
const VIEW_ID = 'GoogleアナリティクスのViewID'

/**
 * Auth2.0認証をしてクライアントを取得する
 * @returns クライアント
 */
async function getAnalyticsreportingClient() {
  const client = await google.auth.getClient({
    keyFile: KEY_FILE,
    scopes: 'https://www.googleapis.com/auth/analytics.readonly'
  })
  const analyticsreporting = google.analyticsreporting({
    version: 'v4',
    auth: client
  })
  return analyticsreporting
}

/** Google Analyticsから情報を取得する */
async function getReporting() {
  const client = await getAnalyticsreportingClient()
  // 取得期間をyyyy-MM-dd形式で指定
  let dateRanges = [{startDate: '2021-03-19', endDate: '2021-03-30'}]
  const res = await client.reports.batchGet({
    requestBody: {
      reportRequests: [
        // 指定期間でのPV数TOP3のページを取得するリクエスト
        {
          viewId: VIEW_ID,
          dateRanges: dateRanges,
          dimensions: [{name: 'ga:pagePath'}], // ページ単位の情報を取得する
          metrics: [{expression: 'ga:pageviews'}, {expression: 'ga:avgTimeOnPage'}], // PV数と平均ページ滞在時間を取得する
          orderBys: {fieldName: 'ga:pageviews', sortOrder: 'DESCENDING'}, // PV数の降順でソートする
          pageSize: 3 // 3件分取得する
        },
        // 直近1週間での平均ページ滞在時間TOP3のページを取得するリクエスト
        {
          viewId: VIEW_ID,
          dateRanges: dateRanges,
          dimensions: [{name: 'ga:sourceMedium'}], // ページ単位の情報を取得する
          metrics: [{expression: 'ga:bounceRate'}, {expression: 'ga:avgTimeOnPage'}], // 直帰率と平均ページ滞在時間を取得する
          orderBys: {fieldName: 'ga:avgTimeOnPage', sortOrder: 'DESCENDING'}, // 平均ページ滞在時間の降順でソートする
          pageSize: 3 // 3件分取得する
        }
      ]
    }
  })
  // コンソールに取得内容を表示
  console.log(JSON.stringify(res.data))
}

getReporting()

Reporting APIを呼び出す

早速、ターミナルから実行してみます。本当はレスポンスが1行なのですが、見にくいので改行などを入れています。

$ node analytics.js
{"reports":[
  {
    "columnHeader":{"dimensions":["ga:pagePath"],"metricHeader":{"metricHeaderEntries":[{"name":"ga:pageviews","type":"INTEGER"},{"name":"ga:avgTimeOnPage","type":"TIME"}]}},
    "data":{"rows":[
      {"dimensions":["/ponsuke0531/items/4629626a3e84bcd9398f"],"metrics":[{"values":["1631","387.46666666666664"]}]},
      {"dimensions":["/ponsuke0531/items/df51a784b5ff48c97ac7"],"metrics":[{"values":["1160","601.5056179775281"]}]},
      {"dimensions":["/ponsuke0531/items/edf2eee638202aa7f61f"],"metrics":[{"values":["999","401.8253968253968"]}]}
    ],
    "totals":[{"values":["35429","382.3464974141984"]}],
    "rowCount":499,
    "minimums":[{"values":["1","0.0"]}],
    "maximums":[{"values":["1631","1679.0"]}]},
    "nextPageToken":"3"
  },
  {
    "columnHeader":{"dimensions":["ga:sourceMedium"],"metricHeader":{"metricHeaderEntries":[{"name":"ga:bounceRate","type":"PERCENT"},{"name":"ga:avgTimeOnPage","type":"TIME"}]}},
    "data":{"rows":[
      {"dimensions":["27.94.140.223:8080 / referral"],"metrics":[{"values":["0.0","1335.0"]}]},
      {"dimensions":["nekorokkekun.hatenablog.com / referral"],"metrics":[{"values":["0.0","996.0"]}]},
      {"dimensions":["zenn.dev / referral"],"metrics":[{"values":["0.0","909.0"]}]}
    ],
    "totals":[{"values":["86.22115384615384","382.3464974141984"]}],
    "rowCount":69,
    "minimums":[{"values":["0.0","0.0"]}],
    "maximums":[{"values":["100.0","1335.0"]}]},
    "nextPageToken":"3"
  }
]}

はじめてのGoogle Analytics Reporting APIをPythonとCloud9でちょっとだけ使ってみる

以前の投稿でGoogle AnalyticsGoogle Cloud Platformを設定してGoogle Analytics Reporting APIを使えるようにしました。

ponsuke-tarou.hatenablog.com

そのAPIを簡単に呼び出してみたいと思います。

今回は、どんなものが取得できるかをみてみたいだけなので簡易にReporting APIのドキュメントにあるクイックスタートのコードとAWSのCloud9を使います。

Pythonをそれほど知りません、が、大丈夫。なぜなら「実装」と呼べるほどのことはしません。ちょっと設定するだけです。

準備する

  1. はじめてのアナリティクス Reporting API v4: サービス アカウント向け Python クイックスタートからPythonのコード(HelloAnalytics.py)をダウンロードする
    • f:id:ponsuke_tarou:20210319103855p:plain
      ページに表示されているコードとダウンロードされるコードはちょっと違うことがあるのでダウンロードする方をお勧めします。
  2. Google AnalyticsでView IDを確認する
    1. Google Analyticsへログインする
    2. 管理 > 対象のアカウント選択 > 対象のプロパティ選択 > [ビューの設定]で画面を開く
      • f:id:ponsuke_tarou:20210319113817p:plain
    3. [基本設定] > [ビュー ID]に表示されたView IDをメモする
  3. AWSでCloud9の環境を作成する

f:id:ponsuke_tarou:20210401203012j:plain
文京区の大黒湯

Cloud9でコードを設定する

  1. Cloud9の環境を起動して表示する
  2. [File] > [Upload Local Files...]で以下のファイルをアップロードする
    1. サービスアカウントの鍵ファイル(json)
    2. ダウンロードしたクイックスタートのコードファイル(HelloAnalytics.py)
    3. f:id:ponsuke_tarou:20210319112312p:plain
  3. HelloAnalytics.pyを開く
  4. 上のほうにある以下の値を書き換える
    • <REPLACE_WITH_JSON_FILE> : サービスアカウントの鍵ファイル名(HelloAnalytics.pyと違う場所に配置した場合はそのパス)
    • <REPLACE_WITH_VIEW_ID> : Google AnalyticsのView ID

ライブラリをインストールする

GoogleAPIを呼び出すために必要なライブラリをインストールします。

Cloud9を使っている場合は必ず--userオプションを使用して自分の使うPythonに対してインストールします。 忘れるとModuleNotFoundError: No module named 'apiclient'となることがあるので注意してください。

# 認証に使うライブラリをインストールする
$ python -m pip install --user --upgrade oauth2client

# Google APIを使うためのライブラリをインストールする
$ python -m pip install --user --upgrade google-api-python-client

Reporting APIを呼び出す

あとはコードを実行するだけです。

HelloAnalytics.pyを表示した状態で画面上部の[Run]ボタンを押下すればGoogle AnalyticsからAPIで取得した情報が表示されます。

これを基にメソッド: reports.batchGetを見ながらコードのパラメータを変えてどんな情報が取得できるかを試していきたいと思います。 f:id:ponsuke_tarou:20210319115410p:plain

f:id:ponsuke_tarou:20210401203159j:plain
文京区の白山浴場

Google Analytics Reporting APIでの発生したリクエスト数を確認する

Google APIには、リクエスト数の制限があります。

Google アナリティクスは数多くのサイトで使用されています。Google では、処理能力を超える負荷がシステムにかからないようにすること、システム リソースが均等に配分されるようにすることを目的に、API リクエストに制限と割り当てを設けています。この制限と割り当ては変更されることがあります。

API リクエストの制限と割り当て - Google Developers

Analytics Reporting APIもタダで使えますが、リクエスト数に制限があるので制限を超えるとエラーコードが返却されるようになります。

Google Cloud Platformのプロジェクトでは「割り当て(1 日あたりの API リクエスト数など)」というものが設定できます。 割り当ての詳細は、割り当ての操作  |  ドキュメント  |  Google Cloudを参照してください。

f:id:ponsuke_tarou:20210325143452p:plain
プロジェクト全体の割り当ては[IAMと管理]画面で確認できます
f:id:ponsuke_tarou:20210325143653p:plain
APIの割り当ては[APIとサービス]画面で確認できます

Analytics Reporting APIのリクエスト数制限

API リクエストの制限と割り当て - Google Developersと上記割り当て(割り当ては初期値のまま)ではリクエスト数の単位がバラバラしていてわかりにくかったのでそれぞれの情報を表にして集めてみました。

単位 リクエスト数上限 参照元
プロジェクト 50,000件/日 [APIとサービス]の割り当て & API リクエストの制限と割り当て
プロジェクト 1,200件/分 [APIとサービス]の割り当て
プロジェクト 2,000件/100秒 API リクエストの制限と割り当て
ユーザー 1 人につきプロジェクトごとで 100件/100秒(1,000 件まで引き上げ可能)
10件/秒
API リクエストの制限と割り当て
ユーザー 600件/分 [APIとサービス]の割り当て
ビュー 10,000件/日(引き下げ不可) API リクエストの制限と割り当て
プロジェクトごとのリクエストの失敗(プロファイルあたり) 10件/時 かつ 50件/日 Reporting API のリクエスト エラーについて - API リクエストの制限と割り当て

表を見ていて気が付いたのは「1日単位の制限」を守っても「1秒や100秒単位の制限」を守らないとエラーになってしまうということでした。特定の時間にどっとリクエストがくるとエラーになる可能性があるのですね。

Google Analytics Reporting APIでの発生したリクエスト数を確認します。

リクエスト数に制限があるので、APIを使うアプリケーションを作る場合にどのくらいリクエスト数が発生するかを確認したいと思いました。

Google APIのリクエスト数は[APIとサービス]の[ダッシュボード]で確認できます。

API ダッシュボードを使用する

API 指標を表示する最も簡単な方法は、Google Cloud Console の API ダッシュボードを使用することです。すべての API の使用状況の概要を表示することも、特定の API の使用状況を詳細に調べることもできます。

API 使用状況のモニタリング - Google Cloud

  1. Google Cloud Platformにログインする
  2. 左上メニュー > [APIとサービス] > [ダッシュボード]で画面遷移する
    • f:id:ponsuke_tarou:20210325091840p:plain
  3. f:id:ponsuke_tarou:20210325092540p:plain
    表示したい期間を選択すると各Google APIでのリクエスト数が表示される

APIのリクエスト数を[割り当て]で詳しく確認します。

[ダッシュボード]ではAPI毎の「任意期間での合計リクエスト数」を確認できましたが、使っている特定APIのリクエスト数をもっと詳しく確認したい、そのんな時のことです。 例えば、今回はGoogle Analytics Reporting APIについて詳しく見てみます。

  1. [ダッシュボード]画面一覧表から[Analytics Reporting API]リンクで画面遷移する
    • f:id:ponsuke_tarou:20210325093715p:plain
  2. 左メニュー > [割り当て]で画面遷移する
    • f:id:ponsuke_tarou:20210325093911p:plain
  3. [Requests]の右にある「v」で詳細を表示 > プルダウンで[Requests1分あたり]を選択 > 表示期間を選択
    • f:id:ponsuke_tarou:20210325094402p:plain
  4. 表示される「平均」の期間は「表示期間」によって変わるようなので細かく見る場合は注意する
    • f:id:ponsuke_tarou:20210325100705p:plain
      「表示期間」が[1時間]の場合は「10秒での平均」
    • f:id:ponsuke_tarou:20210325100917p:plain
      「表示期間」が[30日]の場合は「3時間での平均」

各ユーザーのリクエスト数を[指標]で確認します。

[割り当て]画面の下には制限数の一覧表があります。 f:id:ponsuke_tarou:20210325095635p:plain

「Requests 1 分あたり /ユーザー」という行がとても気になります。

f:id:ponsuke_tarou:20210325103604p:plain
ユーザーごとのリクエスト数をみたいところですが[割り当て]では見られないようです

そこで探し回っていると、[割り当て]と同じくAnalytics Reporting APIの[指標]をから各ユーザーのリクエスト数が確認できました。

  1. [ダッシュボード]画面一覧表から[Analytics Reporting API]リンクで画面遷移する
  2. 左メニュー > [指標]で画面遷移する
  3. [Credentials]プルダウンで見たいユーザーを選択 > [OK]ボタン > 表示期間を選択
    • f:id:ponsuke_tarou:20210325121854p:plain
  4. f:id:ponsuke_tarou:20210325122418p:plain
    グラフの下にある[メソッド]の一覧にリクエスト数が表示される

1回のAPI呼出しにリクエストを複数指定してもリクエストは1回?

Analytics Reporting APIでは、1回のAPI呼出しにリクエストを5つまで含めることができます。

リクエストは最大で 5 つ送信できます。すべてのリクエストには、同じ dateRanges、viewId、segments、samplingLevel、および cohortGroup が含まれている必要があります。

メソッド: reports.batchGet  |  アナリティクス Reporting API v4  |  Google Developers

1回のAPI呼出しにリクエストを複数指定するとカウントアップされるリクエスト回数は何回なのでしょうか?

試しに1回のAPI呼出しに5リクエストを指定してみました。

f:id:ponsuke_tarou:20210330165458p:plain
実行前の[指標]ページ

  let dateRanges = [{startDate: '2021-03-29', endDate: '2021-03-30'}]
  const res = await client.reports.batchGet({
    requestBody: {
      reportRequests: [
        {
          viewId: VIEW_ID,
          dateRanges: dateRanges,
          dimensions: [{name: 'ga:pagePath'}],
          metrics: [{expression: 'ga:pageviews'}, {expression: 'ga:avgTimeOnPage'}],
        },
        {
          viewId: VIEW_ID,
          dateRanges: dateRanges,
          dimensions: [{name: 'ga:sourceMedium'}],
          metrics: [{expression: 'ga:bounceRate'}, {expression: 'ga:avgTimeOnPage'}],
        },
        {
          viewId: VIEW_ID,
          dateRanges: dateRanges,
          dimensions: [{name: 'ga:source'}],
          metrics: [{expression: 'ga:pageviews'}],
        },
        {
          viewId: VIEW_ID,
          dateRanges: dateRanges,
          dimensions: [{name: 'ga:browser'}],
          metrics: [{expression: 'ga:pageviews'}],
        },
        {
          viewId: VIEW_ID,
          dateRanges: dateRanges,
          dimensions: [{name: 'ga:country'}],
          metrics: [{expression: 'ga:pageviews'}],
        }
      ]
    }
  })

なんと1件しかカウントアップされませんでした。同じ期間などを条件にするのであればまとめてAPI呼出ししたほうがお得なのかもしれません。

f:id:ponsuke_tarou:20210330170215p:plain
実行後の[指標]ページ

とはいえGoogleのドキュメントにある以下の記載は気になるところです。

★注: バッチ処理で複数の Reporting API リクエストを 1 回のリクエストにまとめても、定められた割り当て量を超えるリクエストを実行することはできません。

Reporting API | API リクエストの制限と割り当て - Google Developers

バッチ処理」のリンク先がGoogle アナリティクス Management APIのGoogle アナリティクス API リクエストのバッチ処理になっているのでManagement APIを使った時のことなのかもしれませんが、実装の時には気をつけるポイントになりそうです。(Google アナリティクス Management APIを知らないのですが・・・)