2 Imports System.Collections.Generic
3 Imports System.Collections.Specialized
6 Imports System.Security
7 Imports System.Diagnostics
10 '''OAuth認証を使用するHTTP通信。HMAC-SHA1固定
13 '''使用前に認証情報を設定する。認証確認を伴う場合はAuthenticate系のメソッドを、認証不要な場合はInitializeを呼ぶこと。
15 Public Class HttpConnectionOAuth
16 Inherits HttpConnection
17 Implements IHttpConnection
20 '''OAuth署名のoauth_timestamp算出用基準日付(1970/1/1 00:00:00)
22 Private Shared ReadOnly UnixEpoch As New DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Unspecified)
25 '''OAuth署名のoauth_nonce算出用乱数クラス
27 Private Shared ReadOnly NonceRandom As New Random
30 '''OAuthのアクセストークン。永続化可能(ユーザー取り消しの可能性はある)。
32 Private token As String = ""
35 '''OAuthの署名作成用秘密アクセストークン。永続化可能(ユーザー取り消しの可能性はある)。
37 Private tokenSecret As String = ""
42 Private consumerKey As String
45 '''OAuthの署名作成用秘密コンシューマーデータ
47 Protected consumerSecret As String
50 '''認証成功時の応答でユーザー情報を取得する場合のキー。設定しない場合は、AuthUsernameもブランクのままとなる
52 Private userIdentKey As String = ""
55 '''認証完了時の応答からuserIdentKey情報に基づいて取得するユーザー情報
57 Private authorizedUsername As String = ""
60 '''認証完了時の応答からuserIdentKey情報に基づいて取得するユーザー情報
62 Private streamReq As HttpWebRequest = Nothing
65 '''OAuth認証で指定のURLとHTTP通信を行い、結果を返す
67 '''<param name="method">HTTP通信メソッド(GET/HEAD/POST/PUT/DELETE)</param>
68 '''<param name="requestUri">通信先URI</param>
69 '''<param name="param">GET時のクエリ、またはPOST時のエンティティボディ</param>
70 '''<param name="content">[OUT]HTTP応答のボディデータ</param>
71 '''<param name="headerInfo">[IN/OUT]HTTP応答のヘッダ情報。必要なヘッダ名を事前に設定しておくこと</param>
72 '''<param name="callback">処理終了直前に呼ばれるコールバック関数のデリゲート 不要な場合はNothingを渡すこと</param>
73 '''<returns>HTTP応答のステータスコード</returns>
74 Public Function GetContent(ByVal method As String, _
75 ByVal requestUri As Uri, _
76 ByVal param As Dictionary(Of String, String), _
77 ByRef content As String, _
78 ByVal headerInfo As Dictionary(Of String, String), _
79 ByVal callback As IHttpConnection.CallbackDelegate) As HttpStatusCode Implements IHttpConnection.GetContent
81 If String.IsNullOrEmpty(token) Then Return HttpStatusCode.Unauthorized
83 Dim webReq As HttpWebRequest = CreateRequest(method, _
88 AppendOAuthInfo(webReq, param, token, tokenSecret)
90 Dim code As HttpStatusCode
91 If content Is Nothing Then
92 code = GetResponse(webReq, headerInfo, False)
94 code = GetResponse(webReq, content, headerInfo, False)
96 If callback IsNot Nothing Then
97 Dim frame As New StackFrame(1)
98 callback(frame.GetMethod.Name, code, content)
106 Public Function GetContent(ByVal method As String, _
107 ByVal requestUri As Uri, _
108 ByVal param As Dictionary(Of String, String), _
109 ByVal binary As List(Of KeyValuePair(Of String, FileInfo)), _
110 ByRef content As String, _
111 ByVal headerInfo As Dictionary(Of String, String), _
112 ByVal callback As IHttpConnection.CallbackDelegate) As HttpStatusCode Implements IHttpConnection.GetContent
114 If String.IsNullOrEmpty(token) Then Return HttpStatusCode.Unauthorized
116 Dim webReq As HttpWebRequest = CreateRequest(method, _
122 AppendOAuthInfo(webReq, Nothing, token, tokenSecret)
124 Dim code As HttpStatusCode
125 If content Is Nothing Then
126 code = GetResponse(webReq, headerInfo, False)
128 code = GetResponse(webReq, content, headerInfo, False)
130 If callback IsNot Nothing Then
131 Dim frame As New StackFrame(1)
132 callback(frame.GetMethod.Name, code, content)
138 '''OAuth認証で指定のURLとHTTP通信を行い、ストリームを返す
140 '''<param name="method">HTTP通信メソッド(GET/HEAD/POST/PUT/DELETE)</param>
141 '''<param name="requestUri">通信先URI</param>
142 '''<param name="param">GET時のクエリ、またはPOST時のエンティティボディ</param>
143 '''<param name="content">[OUT]HTTP応答のボディストリーム</param>
144 '''<returns>HTTP応答のステータスコード</returns>
145 Public Function GetContent(ByVal method As String, _
146 ByVal requestUri As Uri, _
147 ByVal param As Dictionary(Of String, String), _
148 ByRef content As Stream) As HttpStatusCode Implements IHttpConnection.GetContent
150 If String.IsNullOrEmpty(token) Then Return HttpStatusCode.Unauthorized
152 streamReq = CreateRequest(method, requestUri, param, False)
154 AppendOAuthInfo(streamReq, param, token, tokenSecret)
157 Dim webRes As HttpWebResponse = CType(streamReq.GetResponse(), HttpWebResponse)
158 content = webRes.GetResponseStream()
159 Return webRes.StatusCode
160 Catch ex As WebException
161 If ex.Status = WebExceptionStatus.ProtocolError Then
162 Dim res As HttpWebResponse = DirectCast(ex.Response, HttpWebResponse)
163 Return res.StatusCode
170 Public Sub RequestAbort() Implements IHttpConnection.RequestAbort
172 If streamReq IsNot Nothing Then
175 Catch ex As Exception
182 '''OAuth認証の開始要求(リクエストトークン取得)。PIN入力用の前段
185 '''呼び出し元では戻されたurlをブラウザで開き、認証完了後PIN入力を受け付けて、リクエストトークンと共にAuthenticatePinFlowを呼び出す
187 '''<param name="requestTokenUrl">リクエストトークンの取得先URL</param>
188 '''<param name="requestUri">ブラウザで開く認証用URLのベース</param>
189 '''<param name="requestToken">[OUT]認証要求で戻されるリクエストトークン。使い捨て</param>
190 '''<param name="authUri">[OUT]requestUriを元に生成された認証用URL。通常はリクエストトークンをクエリとして付加したUri</param>
191 '''<returns>取得結果真偽値</returns>
192 Public Function AuthenticatePinFlowRequest(ByVal requestTokenUrl As String, _
193 ByVal authorizeUrl As String, _
194 ByRef requestToken As String, _
195 ByRef authUri As Uri) As Boolean
197 authUri = GetAuthenticatePageUri(requestTokenUrl, authorizeUrl, requestToken)
198 If authUri Is Nothing Then Return False
203 '''OAuth認証のアクセストークン取得。PIN入力用の後段
206 '''事前にAuthenticatePinFlowRequestを呼んで、ブラウザで認証後に表示されるPINを入力してもらい、その値とともに呼び出すこと
208 '''<param name="accessTokenUrl">アクセストークンの取得先URL</param>
209 '''<param name="requestUri">AuthenticatePinFlowRequestで取得したリクエストトークン</param>
210 '''<param name="pinCode">Webで認証後に表示されるPINコード</param>
211 '''<returns>取得結果真偽値</returns>
212 Public Function AuthenticatePinFlow(ByVal accessTokenUrl As String, _
213 ByVal requestToken As String, _
214 ByVal pinCode As String) As HttpStatusCode
216 If String.IsNullOrEmpty(requestToken) Then Throw New Exception("Sequence error.(requestToken is blank)")
219 Dim content As String = ""
220 Dim accessTokenData As NameValueCollection
221 Dim httpCode As HttpStatusCode = GetOAuthToken(New Uri(accessTokenUrl), pinCode, requestToken, Nothing, content)
222 If httpCode <> HttpStatusCode.OK Then Return httpCode
223 accessTokenData = ParseQueryString(content)
225 If accessTokenData IsNot Nothing Then
226 token = accessTokenData.Item("oauth_token")
227 tokenSecret = accessTokenData.Item("oauth_token_secret")
229 If Me.userIdentKey <> "" Then
230 authorizedUsername = accessTokenData.Item(Me.userIdentKey)
232 authorizedUsername = ""
234 If token = "" Then Throw New InvalidDataException("Token is null.")
235 Return HttpStatusCode.OK
237 Throw New InvalidDataException("Return value is null.")
242 '''OAuth認証のアクセストークン取得。xAuth方式
244 '''<param name="accessTokenUrl">アクセストークンの取得先URL</param>
245 '''<param name="username">認証用ユーザー名</param>
246 '''<param name="password">認証用パスワード</param>
247 '''<returns>取得結果真偽値</returns>
248 Public Function AuthenticateXAuth(ByVal accessTokenUrl As Uri, ByVal username As String, ByVal password As String, ByRef content As String) As HttpStatusCode Implements IHttpConnection.Authenticate
250 If String.IsNullOrEmpty(username) OrElse String.IsNullOrEmpty(password) Then
251 Throw New Exception("Sequence error.(username or password is blank)")
254 Dim parameter As New Dictionary(Of String, String)
255 parameter.Add("x_auth_mode", "client_auth")
256 parameter.Add("x_auth_username", username)
257 parameter.Add("x_auth_password", password)
260 Dim httpCode As HttpStatusCode = GetOAuthToken(accessTokenUrl, "", "", parameter, content)
261 If httpCode <> HttpStatusCode.OK Then Return httpCode
262 Dim accessTokenData As NameValueCollection = ParseQueryString(content)
264 If accessTokenData IsNot Nothing Then
265 token = accessTokenData.Item("oauth_token")
266 tokenSecret = accessTokenData.Item("oauth_token_secret")
268 If Me.userIdentKey <> "" Then
269 authorizedUsername = accessTokenData.Item(Me.userIdentKey)
271 authorizedUsername = ""
273 If token = "" Then Throw New InvalidDataException("Token is null.")
274 Return HttpStatusCode.OK
276 Throw New InvalidDataException("Return value is null.")
281 '''OAuth認証のリクエストトークン取得。リクエストトークンと組み合わせた認証用のUriも生成する
283 '''<param name="accessTokenUrl">リクエストトークンの取得先URL</param>
284 '''<param name="authorizeUrl">ブラウザで開く認証用URLのベース</param>
285 '''<param name="requestToken">[OUT]取得したリクエストトークン</param>
286 '''<returns>取得結果真偽値</returns>
287 Private Function GetAuthenticatePageUri(ByVal requestTokenUrl As String, _
288 ByVal authorizeUrl As String, _
289 ByRef requestToken As String) As Uri
290 Const tokenKey As String = "oauth_token"
293 Dim content As String = ""
294 Dim reqTokenData As NameValueCollection
295 If GetOAuthToken(New Uri(requestTokenUrl), "", "", Nothing, content) <> HttpStatusCode.OK Then Return Nothing
296 reqTokenData = ParseQueryString(content)
298 If reqTokenData IsNot Nothing Then
299 requestToken = reqTokenData.Item(tokenKey)
301 Dim ub As New UriBuilder(authorizeUrl)
302 ub.Query = String.Format("{0}={1}", tokenKey, requestToken)
310 '''OAuth認証のトークン取得共通処理
312 '''<param name="requestUri">各種トークンの取得先URL</param>
313 '''<param name="pinCode">PINフロー時のアクセストークン取得時に設定。それ以外は空文字列</param>
314 '''<param name="requestToken">PINフロー時のリクエストトークン取得時に設定。それ以外は空文字列</param>
315 '''<param name="parameter">追加パラメータ。xAuthで使用</param>
316 '''<returns>取得結果のデータ。正しく取得出来なかった場合はNothing</returns>
317 Private Function GetOAuthToken(ByVal requestUri As Uri, ByVal pinCode As String, ByVal requestToken As String, ByVal parameter As Dictionary(Of String, String), ByRef content As String) As HttpStatusCode
318 Dim webReq As HttpWebRequest = Nothing
319 'HTTPリクエスト生成。PINコードもパラメータも未指定の場合はGETメソッドで通信。それ以外はPOST
320 If String.IsNullOrEmpty(pinCode) AndAlso parameter Is Nothing Then
321 webReq = CreateRequest("GET", requestUri, Nothing, False)
323 webReq = CreateRequest("POST", requestUri, parameter, False) 'ボディに追加パラメータ書き込み
325 'OAuth関連パラメータ準備。追加パラメータがあれば追加
326 Dim query As New Dictionary(Of String, String)
327 If parameter IsNot Nothing Then
328 For Each kvp As KeyValuePair(Of String, String) In parameter
329 query.Add(kvp.Key, kvp.Value)
332 'PINコードが指定されていればパラメータに追加
333 If Not String.IsNullOrEmpty(pinCode) Then query.Add("oauth_verifier", pinCode)
334 'OAuth関連情報をHTTPリクエストに追加
335 AppendOAuthInfo(webReq, query, requestToken, "")
337 Dim header As New Dictionary(Of String, String) From {{"Date", ""}}
338 Dim responceCode As HttpStatusCode = GetResponse(webReq, content, header, False)
339 If responceCode = HttpStatusCode.OK Then Return responceCode
340 If Not String.IsNullOrEmpty(header("Date")) Then content += Environment.NewLine + "Check the Date & Time of this computer." + Environment.NewLine + "Server:" + CDate(header("Date")).ToString + " PC:" + Now.ToString
345 #Region "OAuth認証用ヘッダ作成・付加処理"
347 '''HTTPリクエストにOAuth関連ヘッダを追加
349 '''<param name="webRequest">追加対象のHTTPリクエスト</param>
350 '''<param name="query">OAuth追加情報+クエリ or POSTデータ</param>
351 '''<param name="token">アクセストークン、もしくはリクエストトークン。未取得なら空文字列</param>
352 '''<param name="tokenSecret">アクセストークンシークレット。認証処理では空文字列</param>
353 Protected Overridable Sub AppendOAuthInfo(ByVal webRequest As HttpWebRequest, _
354 ByVal query As Dictionary(Of String, String), _
355 ByVal token As String, _
356 ByVal tokenSecret As String)
358 Dim parameter As Dictionary(Of String, String) = GetOAuthParameter(token)
359 'OAuth共通情報にquery情報を追加
360 If query IsNot Nothing Then
361 For Each item As KeyValuePair(Of String, String) In query
362 parameter.Add(item.Key, item.Value)
366 parameter.Add("oauth_signature", CreateSignature(tokenSecret, webRequest.Method, webRequest.RequestUri, parameter))
368 Dim sb As New StringBuilder("OAuth ")
369 For Each item As KeyValuePair(Of String, String) In parameter
370 '各種情報のうち、oauth_で始まる情報のみ、ヘッダに追加する。各情報はカンマ区切り、データはダブルクォーテーションで括る
371 If item.Key.StartsWith("oauth_") Then
372 sb.AppendFormat("{0}=""{1}"",", item.Key, UrlEncode(item.Value))
375 webRequest.Headers.Add(HttpRequestHeader.Authorization, sb.ToString)
379 '''OAuthで使用する共通情報を取得する
381 '''<param name="token">アクセストークン、もしくはリクエストトークン。未取得なら空文字列</param>
382 '''<returns>OAuth情報のディクショナリ</returns>
383 Protected Function GetOAuthParameter(ByVal token As String) As Dictionary(Of String, String)
384 Dim parameter As New Dictionary(Of String, String)
385 parameter.Add("oauth_consumer_key", consumerKey)
386 parameter.Add("oauth_signature_method", "HMAC-SHA1")
387 parameter.Add("oauth_timestamp", Convert.ToInt64((DateTime.UtcNow - UnixEpoch).TotalSeconds).ToString()) 'epoch秒
388 parameter.Add("oauth_nonce", NonceRandom.Next(123400, 9999999).ToString())
389 parameter.Add("oauth_version", "1.0")
390 If Not String.IsNullOrEmpty(token) Then parameter.Add("oauth_token", token) 'トークンがあれば追加
397 '''<param name="tokenSecret">アクセストークン秘密鍵</param>
398 '''<param name="method">HTTPメソッド文字列</param>
399 '''<param name="uri">アクセス先Uri</param>
400 '''<param name="parameter">クエリ、もしくはPOSTデータ</param>
401 '''<returns>署名文字列</returns>
402 Protected Overridable Function CreateSignature(ByVal tokenSecret As String, _
403 ByVal method As String, _
405 ByVal parameter As Dictionary(Of String, String) _
407 'パラメタをソート済みディクショナリに詰替(OAuthの仕様)
408 Dim sorted As New SortedDictionary(Of String, String)(parameter)
409 'URLエンコード済みのクエリ形式文字列に変換
410 Dim paramString As String = CreateQueryString(sorted)
412 Dim url As String = String.Format("{0}://{1}{2}", uri.Scheme, uri.Host, uri.AbsolutePath)
413 '署名のベース文字列生成(&区切り)。クエリ形式文字列は再エンコードする
414 Dim signatureBase As String = String.Format("{0}&{1}&{2}", method, UrlEncode(url), UrlEncode(paramString))
415 '署名鍵の文字列をコンシューマー秘密鍵とアクセストークン秘密鍵から生成(&区切り。アクセストークン秘密鍵なくても&残すこと)
416 Dim key As String = UrlEncode(consumerSecret) + "&"
417 If Not String.IsNullOrEmpty(tokenSecret) Then key += UrlEncode(tokenSecret)
419 Dim hmac As New Cryptography.HMACSHA1(Encoding.ASCII.GetBytes(key))
420 Dim hash As Byte() = hmac.ComputeHash(Encoding.ASCII.GetBytes(signatureBase))
421 Return Convert.ToBase64String(hash)
427 '''初期化。各種トークンの設定とユーザー識別情報設定
429 '''<param name="consumerKey">コンシューマー鍵</param>
430 '''<param name="consumerSecret">コンシューマー秘密鍵</param>
431 '''<param name="accessToken">アクセストークン</param>
432 '''<param name="accessTokenSecret">アクセストークン秘密鍵</param>
433 '''<param name="userIdentifier">アクセストークン取得時に得られるユーザー識別情報。不要なら空文字列</param>
434 Public Sub Initialize(ByVal consumerKey As String, _
435 ByVal consumerSecret As String, _
436 ByVal accessToken As String, _
437 ByVal accessTokenSecret As String, _
438 ByVal userIdentifier As String)
439 Me.consumerKey = consumerKey
440 Me.consumerSecret = consumerSecret
441 Me.token = accessToken
442 Me.tokenSecret = accessTokenSecret
443 Me.userIdentKey = userIdentifier
447 '''初期化。各種トークンの設定とユーザー識別情報設定
449 '''<param name="consumerKey">コンシューマー鍵</param>
450 '''<param name="consumerSecret">コンシューマー秘密鍵</param>
451 '''<param name="accessToken">アクセストークン</param>
452 '''<param name="accessTokenSecret">アクセストークン秘密鍵</param>
453 '''<param name="username">認証済みユーザー名</param>
454 '''<param name="userIdentifier">アクセストークン取得時に得られるユーザー識別情報。不要なら空文字列</param>
455 Public Sub Initialize(ByVal consumerKey As String, _
456 ByVal consumerSecret As String, _
457 ByVal accessToken As String, _
458 ByVal accessTokenSecret As String, _
459 ByVal username As String, _
460 ByVal userIdentifier As String)
461 Initialize(consumerKey, consumerSecret, accessToken, accessTokenSecret, userIdentifier)
462 authorizedUsername = username
468 Public ReadOnly Property AccessToken() As String
477 Public ReadOnly Property AccessTokenSecret() As String
486 Public ReadOnly Property AuthUsername() As String Implements IHttpConnection.AuthUsername
488 Return authorizedUsername