[メモ] RunscopeのURI encoding周りの実装が辛いのでPre-request Scriptsで頑張る


Runscope 上で OAuth 1.0a な API をモニタリングしたい人以外にはほぼ意味がない記事です。

tl;dr

Runscope で OAuth 1.0a を使う API エンドポイントのモニタリングをセットアップしていたら盛大にハマった。

どうやら Runscope はリクエスト送信時に URL parameter に対して RFC 2396 に *ほぼ準拠した方式 で URL encoding を行うのに対し、自身の OAuth ライブラリは RFC 3986 方式で encoding を行い signature の生成を行うため、
RFC 3986 では encoding が必要だが RFC 2396 では必要のない記号文字 (; , / ? : @ & = + $ - _ . ! ~ * ' ( ) # 参照: encodeURI) が URI query parameter に含まれているともれなく OAuth エラー、またはリクエスト時に signature のミスマッチが起きて歯が立たない。
ので、リクエスト前に JavaScript で任意の処理を実行できる Pre-request Scripts の機能を使って自前で OAuth signature の生成、URL query parameter の強制 encoding を実施する必要があった。

RFC 2396 と RFC 3986

これについては下記の内容などが参考になるかと思います。

具体的な例

今回問題になったのは、とある API (GET) が key=http://exmaple.com のように URL 文字列をクエリパラメーターとして渡す必要が合ったケースです。このとき、value に含まれている // が厄介者となります。
まずこの文字が含まれていると何故か Runscope の OAuth ライブラリがエラーを吐いてリクエストを送信することすら出来ません。

さて困りました。
http%3A%2F%2exmaple.com のように予めエンコードした値をクエリパラメーター変数 (parameter) に設定するとこのエラーは消えますが、
そうなると今度は Runscope の OAuth ライブラリがこのエンコードした値をさらに2重エンコードして signature を生成するので、結局 signature のミスマッチが起きて OAuth が期待通りに通りません。

※ちなみに Runscope のサポートによると内部的には oauthlib (Python) を使っているとのこと。

OAuth 1.0a の signature を自分で生成する

こうなると残された道はこれくらいしかなく、冒頭に話した通り Pre-request Scripts を使って OAuth 1.0a の signature をちまちま自前で生成していくことにします。

ということで、出来上がった珍百景はこんな感じ。
https://gist.github.com/smaeda-ks/5ec7a4577fcf0e197922688c9bab24e0

これをそのまま Pre-request Scripts にぶちこんで使います。
注意点として、この方法を取るには Runscope の OAuth ライブラリを無効にする必要があるので、Shared Environment で OAuth 1.0a を有効にしているような場合、残念ながらこの Shared Environment は使えません。コピーして新しい Shared Environment (OAuth 無効) を作り、そちらを使うなどしてください。

URL encoding し直す

そして恐らくこの snippet で1番肝心なのは、末尾のこの部分です:

/*
Escape URI parameter by encodeURIComponent (RFC2396 vs RFC3986)
Runscope does encodeURI for URI parameters by default
*/
if(request.params.length !== 0) {
    // parse request.params
    const request_parameter_array = [];
    for (var i = 0; i < request.params.length; i++) {
        request_parameter_array[i] = request.params[i].name + '=' + encodeURIComponent(request.params[i].value);
    }
    const request_parameter_string = request_parameter_array.join('&');

    request.url = host + request.path + '?' + request_parameter_string;
}

見て分かるように、request.url を本来求めている encoding (encodeURIComponent) で上書きしています。
これでようやく全ての元凶である encoding 問題をものすごく遠回りで修正することができました。

一応…

この結果から分かることとして、Runscope の OAuth ライブラリは Pre-request Scripts が実行される前に既に signature を生成し終わっています。そのため、このように snippet で上書きすることができています。
余談としては、OAuth が有効な Shared Environment を適用してているけど、テストシナリオの途中で一旦認証のいらないエンドポイントにアクセスする必要があるような場合、逆にこの Pre-request Scripts で Authorization ヘッダを必要に応じて削除したりすることも可能です。

その他

今回はなんだか物凄く頭の悪い方法で回避しましたが、一応 Runscope には課金プランに応じて Custom Script Libraries という feature が使えるようで、サードパーティのライブラリを import できるようなので、それを使うと signature の生成は多少楽になるかも?

一応 Runscope のサポートにもこの問題は報告済みで、そのうち何らかの対応をしてくれますように。