読者です 読者をやめる 読者になる 読者になる

【Ruby】RubyでもGoogle APIを使うためにOauth2.0認証する

C#でGoogle APIを使う時にも説明しましたが、各言語のライブラリが公開されています。当然Rubyにもあります。

そんなわけでRubyでもGoogle APIが使えるようにしておきましょう。前回の使いまわしなのでInstalled applicationです。Web applicationとかは(省略)

下準備

API Consoleにプロジェクトを登録する方法については前回の記事で説明したので割愛します。

このページを参考にとりあえずGemだけ貰ってきましょう。

gem install google-api-client

InstalledAppFlowを作る

Ruby用のOauth2.0の解説ページはサンプルも結構あるので意外とさくさく作れそうな気がしますが、トラップも結構あります。

ま、とりあえずざっくりコードを引用してみましょう。

require 'rubygems'
require 'google/api_client'
require 'google/api_client/client_secrets'
require 'google/api_client/auth/file_storage'
require 'google/api_client/auth/installed_app'
require 'logger'
require 'json'

CREDENTIAL_STORE_FILE = "#{$0}-oauth2.json"

client = Google::APIClient.new(:application_name => 'Ruby AdSense sample',
  :application_version => '1.0.0')

file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)

if file_storage.authorization.nil?
  client_secrets = Google::APIClient::ClientSecrets.load
  flow = Google::APIClient::InstalledAppFlow.new(
    :client_id => client_secrets.client_id,
    :client_secret => client_secrets.client_secret,
    :scope => ['https://www.googleapis.com/auth/adsense.readonly']
  )
  
  client.authorization = flow.authorize(file_storage)
else
  client.authorization = file_storage.authorization
end

サンプルではGoogle Adsenseを例にしているみたいですね。

まずはここ。

CREDENTIAL_STORE_FILE = "#{$0}-oauth2.json"

file_storage = Google::APIClient::FileStorage.new(CREDENTIAL_STORE_FILE)

何度もOauth認証しなくとも済むようAccessTokenなどをキャッシュしてくれます。それだけです。別にどんな名前にしてもいいです。

次にここ。

client_secrets = Google::APIClient::ClientSecrets.load

また出ました。client_secretsです。これも前回説明したんで割愛します。

ただ、サンプルだとloadメソッドに何の引数も渡していないんですが、ちゃんとファイルパスを渡さないと「どのファイルだかわかんないんだけど!」とブチ切れられます。ふざけてますね。

勿論用意しなくてもいいです。お任せします。

そしてここ。

flow = Google::APIClient::InstalledAppFlow.new(
  :client_id => client_secrets.client_id,
  :client_secret => client_secrets.client_secret,
  :scope => ['https://www.googleapis.com/auth/adsense.readonly']
)

Google::APIClient::InstalledAppFlowと言うインスタンスを取得しています。client_idとclient_secretはclient_secrets.jsonから取得していますが、勿論直に文字列を渡してしまっても普通に通ります。

scopeも前回説明しましたね。Oauth認証でもらえる許可を指定します。

どのサービスにどんなscopeがあるかはOAuth 2.0 Playgroundで見れます。enumがあればこんなもの必要なかったんですけどね。

最後に

client.authorization = flow.authorize(file_storage)

flow.authorizeを呼び出せば自動でブラウザが立ち上がって認証画面が表示されます。引数に先ほど作ったfile_storageを渡すだけでキャッシュしてくれます。

SSL証明書をインストールする

実際にやってみると、認証後にブラウザが閉じずこんなエラーメッセージが出てくることがあります。

SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed.

読んで字の如く、certificateのverifyにfailedしちゃってますね。

この辺は環境によって対処法が違うと思うんですが、Windowsだとhttp://curl.haxx.se/ca/cacert.pemを適当に落としてきて、SSL_CERT_FILEと言う環境変数に落としたファイルのパスを指定すればOKっぽいです。(参考

その他の環境については興味ありません。各々でぐぐってください。

まとめ

これでまぁ、認証ぐらいなら通ると思います。

折角なので自分でテストしておいたコードも載せておきます。Google Analyticsのやつですが。

ポイントは「kconvか何かでtoutf8しないとマルチバイト文字のパースで死ぬ」ってところです。

# coding:utf-8

require "google/api_client"
require "google/api_client/client_secrets"
require "google/api_client/auth/file_storage"
require "google/api_client/auth/installed_app"
require "json"
require "kconv"

# application_nameもapplication_versitonも何でもいい
# 渡す必要があるのかどうかすら不明
client = Google::APIClient.new(:application_name => "Test",
 :application_versiton => "0.0.1")

file_storage = Google::APIClient::FileStorage.new("#{$0}-oauth2.json")

if file_storage.authorization.nil?
 # 面倒だからclient_secrets関連は全部直に書いてる
 flow = Google::APIClient::InstalledAppFlow.new(
  :client_id => "",
  :client_secret => "",
  :scope => ["https://www.googleapis.com/auth/analytics"]
 )
 
 client.authorization = flow.authorize(file_storage)
else
 client.authorization = file_storage.authorization
end

# このclient.discovered_apiで渡す引数の一覧はどこにあるんだろう…
analytics = client.discovered_api("analytics", "v3")

# api_methodに使いたいメソッド、
# parametersにURIのクエリのハッシュを渡す
# 結構面倒臭い
result = client.execute(
 :api_method => analytics.data.ga.get,
 :parameters => {
  # idsは自分のをいれてね
  "ids" => "",
  "start-date" => DateTime.now.prev_month.strftime("%Y-%m-%d"),
  "end-date" => DateTime.now.strftime("%Y-%m-%d"),
  "metrics" => "ga:visitors",
  "dimensions" => "ga:pageTitle",
 }
)

# result.response.bodyにjsonがそのまま入っている
# 折角のライブラリなんだから最初からパースしたハッシュで渡せよハゲ、と思ってはいけない
# ちなみにstring.encodeだとどう頑張ってもマルチバイト文字が引っかかってjsonがパースできなかった
body = JSON.parse(result.response.body.toutf8)

body["rows"].each{|row|
 puts row.tosjis
}

参考