dispatch(reboot)でoauthを扱う

rebootと呼ばれる新しいdispatchにはoauth exchangeというoauth 1.0aを簡単に扱うための仕組みがあるのですが、公式ドキュメントに解説が載っていないのでメモ。

メインとなるのはExchangeトレイトで、自分型としてSomeHttpSomeConsumerSomeCallbackSomeEndpointsの各トレイトがミックスインされています。

これらの各トレイトを継承したオブジェクトを作ることでaccess tokenの取得までが簡単に行えます。

oauth exchangeの実装

以下はDropbox Core APIの場合の実装例。

import com.ning.http.client.oauth._
import dispatch._
import dispatch.oauth._

trait DropboxHttp extends SomeHttp {
  def http: HttpExecutor = Http
}

trait DropboxConsumer extends SomeConsumer {
  def consumer: ConsumerKey = new ConsumerKey("ほげ", "ふが")
}

trait DropboxCallback extends SomeCallback {
  def callback: String = "oob"
}

trait DropboxEndpoints extends SomeEndpoints {
  def requestToken: String = "https://api.dropbox.com/1/oauth/request_token"
  def accessToken: String = "https://www.dropbox.com/1/oauth/authorize"
  def authorize: String = "https://api.dropbox.com/1/oauth/access_token"
}

object DropboxExchange extends Exchange
  with DropboxHttp with DropboxConsumer with DropboxCallback with DropboxEndpoints

ここで出てくるConsumerKeyAsync Http Clientのものです。

ちなみにcallbackに指定された指定された値はrequest token取得時に渡される仕様(oauth 1.0a)なので、authorizeへのリクエスト時に渡したい場合(oauth 1.0)はオーバーライドする必要がありそう。とここまで書いて、authorize時にoauth_callbackを指定するdropbox core apiを例に出したのは失敗だなと気づきました。

oauth exchangeの利用

以下がaccess tokenの取得例。

import com.ning.http.client.oauth._
import dispatch._
import dispatch.Defaults._
import scala.concurrent._

val res1 = DropboxExchange.fetchRequestToken
val requestToken = res1 map {
 case Right(t) => t
 case Left(m)  => sys.error(m)
}

val verifier = requestToken map { t =>
  blocking {
    val url = DropboxExchange.signedAuthorize(t)
    println(url)
    println("please press enter after authorize")
    readLine()
  }
}

val res2 = requestToken flatMap { t =>
  verifier flatMap { v =>
    DropboxExchange.fetchAccessToken(t, v)
  }
}

val accessToken = res2 map {
  case Right(token)  => token
  case Left(message) => sys.error(message)
}

access tokenを利用してAPIを叩く例。

val accountInfo = :/("api.dropbox.com").secure / "1" / "account" / "info"
val signedAccountInfo = accountInfo <@ (DropboxExchange.consumer, accessToken())
val res3 = Http(signedAccountInfo OK as.String)
res3 onSuccess {
  case a => println(a) 
}

ちょっとfutureを扱うコードが汚くなっていますが、ちゃんと書けばそれっぽく見えると思います(?)。

oauth 2.0

oauth 2.0を実装したPRが出ているのですが、現時点より6ヶ月前で更新が止まっています。