2017年1月10日火曜日

Office365 API を Ruby で実行してみた

概要

Rails5 を使って Office365 の API をコールしてみました
コールしたのは Outlook の API になります

環境

  • Windows7 64bit
  • Ruby 2.2.4p230
  • gem 2.4.8
  • Rails 5.0.0
  • Office365 API v2.0 (2016/08/04 時点)

MS アプリケーションポータルでアプリの作成

Office365 API をコールするためにアプリを作成します
このアプリは Facebook アプリや Twitter アプリの概念と同じものになります

まず https://apps.dev.microsoft.com/ にアクセスします
自分の MS アカウントでログインしましょう
するとアプリケーションポータルという画面に移動するのでアプリを追加します
主にやることは以下の通り

  • アプリの追加 -> test_app -> アプリケーション ID が表示されていることを確認
  • アプリケーションシークレット -> 新しいパスワードを生成 -> ランダムな文字列のパスワードを取得
  • プラットフォーム -> プラットフォームの追加 -> リダイレクト URI -> http://localhost:3000/authorize を設定
  • 最後に「保存」

でアプリを作成することができます
アプリ名は自身の好きな名前を指定すれば OK です

最終的には以下のように「アプリケーション ID」と「アプリケーションシークレット」の 2 つが取得できれば OK です
try_office365_api_creating_app.png

Rails アプリの作成

  • rails new o365-tutorial
  • cd o365-tutorial
  • vim Gemfile
gem 'json'
gem 'oauth2'
gem 'faraday'

を追記して

  • bundle install

コーディングしていく

一気にコーディングしていきます

トップ画面の作成

  • cd o365-tutorial
  • vim app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include AuthHelper

  def home
    login_url = get_login_url
    render html: "<a href=\"#{login_url}\">Log in and view my email</a>".html_safe
  end
end
  • vim config/routes.rb
Rails.application.routes.draw do
  root 'application#home'
  get 'authorize' => 'auth#gettoken'
  get 'mail/index'
end

認証部分の作成

  • cd o365-tutorial
  • rails generate controller Auth
  • vim app/helpers/auth_helper.rb
module AuthHelper
  # App's client ID. Register the app in Application Registration Portal to get this value.
  CLIENT_ID = 'be0c3ce7-1379-45b9-830a-9960c8656b30'
  # App's client secret. Register the app in Application Registration Portal to get this value.
  CLIENT_SECRET = 'Tkp******************************'

  REDIRECT_URI = 'http://localhost:3000/authorize' # Temporary!

  # Scopes required by the app
  SCOPES = [ 'openid',
             'https://outlook.office.com/mail.read' ]

  # Generates the login URL for the app.
  def get_login_url
    client = OAuth2::Client.new(CLIENT_ID,
                                CLIENT_SECRET,
                                :site => 'https://login.microsoftonline.com',
                                :authorize_url => '/common/oauth2/v2.0/authorize',
                                :token_url => '/common/oauth2/v2.0/token')

    login_url = client.auth_code.authorize_url(:redirect_uri => authorize_url, :scope => SCOPES.join(' '))
  end

  # Exchanges an authorization code for a token
  def get_token_from_code(auth_code)
    client = OAuth2::Client.new(CLIENT_ID,
                                CLIENT_SECRET,
                                :site => 'https://login.microsoftonline.com',
                                :authorize_url => '/common/oauth2/v2.0/authorize',
                                :token_url => '/common/oauth2/v2.0/token')

    token = client.auth_code.get_token(auth_code,
                                       :redirect_uri => authorize_url,
                                       :scope => SCOPES.join(' '))
  end

  # Parses an ID token and returns the user's email
  def get_email_from_id_token(id_token)

    # JWT is in three parts, separated by a '.'
    token_parts = id_token.split('.')
    # Token content is in the second part
    encoded_token = token_parts[1]

    # It's base64, but may not be padded
    # Fix padding so Base64 module can decode
    leftovers = token_parts[1].length.modulo(4)
    if leftovers == 2
      encoded_token += '=='
    elsif leftovers == 3
      encoded_token += '='
    end

    # Base64 decode (urlsafe version)
    decoded_token = Base64.urlsafe_decode64(encoded_token)

    # Load into a JSON object
    jwt = JSON.parse(decoded_token)

    # Email is in the 'preferred_username' field
    email = jwt['preferred_username']
  end
end

CLIENT_ID, CLIENT_SECRET の部分はアプリポータルで取得したアプリケーション ID とアプリケーションシークレットを入力してください

  • vim app/controllers/auth_controller.rb
class AuthController < ApplicationController
  def gettoken
    token = get_token_from_code params[:code]
    session[:azure_access_token] = token.token
    session[:user_email] = get_email_from_id_token token.params['id_token']
    redirect_to mail_index_url
  end
end

メールの一覧を表示する部分の作成

  • cd o365-tutorial
  • rails generate controller Mail index
  • vim app/controllers/mail_controller.rb
class MailController < ApplicationController
  def index
    # session.each {|k, v|
    #   puts "key -> #{k}, v -> #{v}"
    # }
    token = session[:azure_access_token]
    email = session[:session_id]
    if token
      # If a token is present in the session, get messages from the inbox
      conn = Faraday.new(:url => 'https://outlook.office.com') do |faraday|
        # Outputs to the console
        faraday.response :logger
        # Uses the default Net::HTTP adapter
        faraday.adapter  Faraday.default_adapter  
      end
      response = conn.get do |request|
        # Get messages from the inbox
        # Sort by ReceivedDateTime in descending orderby
        # Get the first 20 results
        request.url '/api/v2.0/Me/Messages?$orderby=ReceivedDateTime desc&$select=ReceivedDateTime,Subject,From&$top=20'
        request.headers['Authorization'] = "Bearer #{token}"
        request.headers['Accept'] = "application/json"
        # request.headers['X-AnchorMailbox'] = email
      end
      # Assign the resulting value to the @messages
      # variable to make it available to the view template.
      @messages = JSON.parse(response.body)['value']
    else
      # If no token, redirect to the root url so user
      # can sign in.
      redirect_to root_url
    end
  end
end

ポイントがあり session[:session_id] を取得するように変更しています
サンプルだと session[:user_email] というようになっていたのですが、session オブジェクトをデバッグしてみたらそんなシンボルはなく変更しました
が、結局 request.headers['X-AnchorMailbox'] = email の部分をコメントアウトしています
これがあるとエラーとなりうまく取得できませんでした

  • vim app/views/mail/index.html.erb
<h1>My messages</h1>
<table>
  <tr>
    <th>From</th>
    <th>Subject</th>
    <th>Received</th>
  </tr>
  <% @messages.each do |message| %>
    <tr>
      <% if !message['From'].nil? %>
      <td><%= message['From']['EmailAddress']['Name'] %></td>
      <% else %>
      <td>x</td>
      <% end %>
      <td><%= message['Subject'] %></td>
      <td><%= message['ReceivedDateTime'] %></td>
    </tr>
  <% end %>
</table>

動作確認

  • cd o365-tutorial
  • rails server --bind=0.0.0.0

http://localhost:3000/ にブラウザでアクセスします
ログインリンクがあるのでログインすると最新の受信メールの一覧が表示されるはずです

最後に

Rails を使って Office365 API をコールする方法を紹介しました
ログインしてコールバック URL にセッション情報が戻ってくるので GUI が必要なのがちょっと残念な点でした
認証は OAuth2.0 なので、GUI はなくてもたぶんできるとは思います

また、検証中に自分が遭遇したエラーだと

  • コールバック URL がちゃんと設定されていなく Office365 API から認証情報が取得できなかった
  • 認証後 Outlook API を叩く際に X-AnchorMailbox の値がおかしく Json のパースの部分でエラーになっていた
  • session[:session_id] がなく nil を X-AnchorMailbox に設定しているせいで null 参照が発生しエラーになったいた
  • メールを表示する erb で From がなぜか存在しない場合があり nil チェックを入れた

あたりが遭遇したエラーです
特に X-AnchorMailbox はよくわからなく、結局ヘッダに設定しないようにしたら成功しました

参考サイト

0 件のコメント:

コメントを投稿