lib/libgss/action_request.rb in libgss-0.7.6 vs lib/libgss/action_request.rb in libgss-0.8.0

- old
+ new

@@ -9,10 +9,13 @@ class ActionRequest class Error < StandardError end + class SignatureError < Error + end + STATUS_PREPARING = 0 STATUS_SENDING = 1 STATUS_WAITING = 2 STATUS_RECEIVED = 3 STATUS_SUCCESS = 4 @@ -23,10 +26,12 @@ # 読み込みのみ、書き込み不可 attr_reader :network attr_reader :action_url, :req_headers attr_reader :status, :outputs + attr_accessor :response_hook + # コンストラクタ def initialize(network, action_url, req_headers) @network = network @action_url = action_url @req_headers = req_headers @@ -54,31 +59,109 @@ # アクション群を実行するために実際にHTTPリクエストを送信します。 def send_request(&callback) res = Libgss.with_retry("action_request") do network.httpclient_for_action.post(action_url, {"inputs" => @actions.map(&:to_hash)}.to_json, req_headers) end - r = process_response(res, :async_request) + response_hook.call(res) if response_hook # テストでレスポンスを改ざんを再現するために使います + r = process_response(res, :action_request) @outputs = Outputs.new(r["outputs"]) callback.call(@outputs) if callback @outputs end + # レスポンスの処理を行います def process_response(res, req_type) case res.code.to_i when 200..299 then # OK else raise Error, "failed to send #{req_type}: [#{res.code}] #{res.content}" end + verify_signature(res) do |content| + begin + JSON.parse(content) + rescue JSON::ParserError => e + $stderr.puts("\e[31m[#{e.class}] #{e.message}\e[0m\n#{content}") + raise e + end + end + end + private :process_response + + # シグネチャの検証を行います + def verify_signature(res, &block) + case network.api_version + when "1.0.0" then verify_signature_on_headers(res, &block) + when "1.1.0" then verify_signature_included_body(res, &block) + else + raise Error, "Unsupported API version: #{network.api_version}" + end + end + + private + + # ヘッダから検証のための諸情報を取得します。 + # キーの名前がbodyのJSONから取得する場合と微妙に違っているので注意してください。 + def verify_signature_on_headers(res, &block) + content = res.content + attrs = { + content: content, + consumer_key: res.headers["Res-Sign-Consumer-Key"] || "", + nonce: res.headers["Res-Sign-Nonce"], + timestamp: res.headers["Res-Sign-Timestamp"], + } + verify_signature_by_oauth(res.headers["Res-Sign-Signature"], attrs, &block) + end + + # bodyのJSONから検証のための諸情報を取得します。 + # キーの名前がヘッダから取得する場合と微妙に違っているので注意してください。 + def verify_signature_included_body(res, &block) + resp = nil begin - return JSON.parse(res.content) + resp = JSON.parse(res.content) rescue JSON::ParserError => e $stderr.puts("\e[31m[#{e.class}] #{e.message}\e[0m\n#{res.content}") raise e end + content = resp["body"] + attrs = { + content: content, + consumer_key: resp["res_sign_consumer_key"] || "", + nonce: resp["res_sign_nonce"], + timestamp: resp["res_sign_timestamp"], + } + verify_signature_by_oauth(resp["res_sign_signature"], attrs, &block) end - private :process_response + def verify_signature_by_oauth(signature, attrs) + if network.skip_verifying_signature? + return yield(attrs[:content]) if block_given? + end + res_hash = { + "uri" => "", + "method" => "", + "parameters" => { + "body" => attrs[:content], + "oauth_consumer_key" => attrs[:consumer_key], + "oauth_token" => network.auth_token, + "oauth_signature_method" => "HMAC-SHA1", + "oauth_nonce" => attrs[:nonce], + "oauth_timestamp" => attrs[:timestamp] + } + } + s = OAuth::Signature.build(res_hash){ [ network.signature_key, network.consumer_secret] } + # puts "res_hash: " << res_hash.inspect + # puts "signature_key: " << network.signature_key.inspect + # puts "consumer_secret: " << network.consumer_secret.inspect + # puts "signature_base_string: " << s.signature_base_string + unless signature == s.signature + raise SignatureError, "invalid signature or something" + end + return yield(attrs[:content]) if block_given? + end + + public + # 条件に該当するデータを取得 # @param [String] name 対象となるコレクション名 # @param [Hash] conditions 検索条件 # @param [Array<Array<String, Integer>>] order フィールド名と(1 or -1)の組み合わせの配列 # @return [Array<Libgss::JsonObject>]該当したデータを表すJSONオブジェクトの配列 @@ -86,10 +169,11 @@ args = {action: "all", name: name} args[:conditions] = conditions if conditions args[:order] = order if order add_action(args) end + alias_method :all, :find_all # ページネーション付きで条件に該当するデータを取得 # @param [String] name 対象となるコレクション名 # @param [String] page 取得するページ # @param [String] per_page 1ページあたりの件数 @@ -122,10 +206,11 @@ args = {action: "first", name: name} args[:conditions] = conditions if conditions args[:order] = order if order add_action(args) end + alias_method :first, :find_first # 辞書テーブルからinputに対応するoutputの値を返します。 # @param [String] name 対象となる辞書のコレクション名 # @param [String] input 入力オブジェクトの文字列表現 # @param [Hash] conditions 検索条件 @@ -133,10 +218,11 @@ def get_by_dictionary(name, input, conditions = nil) args = {action: "get", name: name, input: input} args[:conditions] = conditions if conditions add_action(args) end + alias_method :get_dictionary, :get_by_dictionary # 期間テーブルからinputに対応するoutputの値を返します。 # @param [String] name 対象となる機関テーブルのコレクション名 # @param [Integer] time 対象となるUNIX時刻 # @param [Hash] conditions 検索条件 @@ -144,10 +230,11 @@ def get_by_schedule(name, time = Time.now.to_i, conditions = nil) args = {action: "get", name: name, time: time} args[:conditions] = conditions if conditions add_action(args) end + alias_method :get_schedule, :get_by_schedule # 整数範囲テーブルからinputに対応するoutputの値を返します。 # @param [String] name 対象となる整数範囲テーブルのコレクション名 # @param [Integer] input 対象となる入力値 # @param [Hash] conditions 検索条件 @@ -155,10 +242,11 @@ def get_by_int_range(name, input, conditions = nil) args = {action: "get", name: name, input: input} args[:conditions] = conditions if conditions add_action(args) end + alias_method :get_int_range, :get_by_int_range # 確率テーブルからinputに対応するoutputの値を返します。 # diceがあるのであまり使われないはず。 # # @param [String] name 対象となる確率テーブルのコレクション名 @@ -168,10 +256,11 @@ def get_by_probability(name, value, conditions = nil) args = {action: "get", name: name, value: value} args[:conditions] = conditions if conditions add_action(args) end + alias_method :get_probability, :get_by_probability # プレイヤーからplayer_idに対応するプレイヤーを返します # # @param [String] name 対象となるコレクション名 # @param [String] player_id 対象となるplayer_id @@ -181,10 +270,11 @@ args = {action: "get"} args[:name] = name if name args[:player_id] = player_id.to_s if player_id add_action(args) end + alias_method :get_player, :get_by_player # ゲームデータからplayer_idに対応するゲームデータを返します # # @param [String] name 対象となるコレクション名 # @param [String] player_id 対象となるplayer_id @@ -194,9 +284,10 @@ args = {action: "get"} args[:name] = name if name args[:player_id] = player_id.to_s if player_id add_action(args) end + alias_method :get_game_data, :get_by_game_data # ログあるいは履歴を登録します。