名もなき未知

エンジニアリングとか、日常とかそういうのをまとめる場所。

はてなAPIを叩くためにoauth_signatureを作成する(Python3で)

urllib.request で十分に通信ができてしまうので、requestsわざわざ使う必要もないかなーと思って(実際Lambdaにあげようとしているのでライブラリは少ないほうが助かる)、oauth_signatureを自作していたのですが、まあ結構辛かったので、メモです。

コード辺

本当に大変だった。概念的には下記みたいな感じ。リクエスト部分はまた今度。

ACCESS_POINT = (
    "https://blog.hatena.ne.jp/MireiMixin/namonakimichi.hatenablog.com/atom/entry"
)

class MethodType(Flag):
    GET = auto()
    POST = auto()
    PUT = auto()
    DELETE = auto()
    WITH_BODY = POST | PUT | DELETE


METHOD_TYPE_STRING = {
    MethodType.GET: "GET",
    MethodType.POST: "POST",
    MethodType.PUT: "PUT",
    MethodType.DELETE: "DELETE",
}

RequestParams = NewType("RequestParams", Union[str, int])
RequestDict = NewType("RequestDict", Dict[str, RequestParams])

def load_access_keys() -> Dict[str, str]:
    keys = ("consumer_key", "consumer_secret_key", "token_key", "token_secret_key")
    return {key: os.getenv(key.upper()) for key in keys}


def create_auth_signature(url, method, baseparam, consumer_keys):
    signature = dict(baseparam)
    signature = "&".join(
        "{0}={1}".format(quote(key, "~"), quote(signature[key], "~"))
        for key in sorted(signature)
    )
    signature = (
        "{0}&{1}".format(
            quote(consumer_keys["consumer_secret_key"], "~"),
            quote(consumer_keys["token_secret_key"], "~"),
        ),
        "{0}&{1}&{2}".format(
            quote(METHOD_TYPE_STRING[method].upper()),
            quote(url, "~"),
            quote(signature, "~"),
        ),
    )
    key_utf, text_utf = [s.encode("utf-8") for s in signature]
    signature = hmac.new(key_utf, text_utf, hashlib.sha1)
    signature = binascii.b2a_base64(signature.digest())[:-1].decode("utf-8")
    return signature


def create_oauth_header(url, method):
    consumer_keys = load_access_keys()
    baseparam = {
        "oauth_token": consumer_keys["token_key"],
        "oauth_consumer_key": consumer_keys["consumer_key"],
        "oauth_signature_method": "HMAC-SHA1",
        "oauth_timestamp": str(int(time.time())),
        "oauth_nonce": str(random.getrandbits(64)),
        "oauth_version": "1.0",
    }
    oauth_signature = create_auth_signature(url, method, baseparam, consumer_keys)
    header = dict(baseparam)
    header.update({"oauth_signature": oauth_signature})
    header = ",".join(
        "{0}={1}".format(quote(k, "~"), quote(header[k], "~")) for k in sorted(header)
    )
    return {
        "Authorization": "OAuth {0}".format(header),
        "Content-Type": "application/xml; charset=utf-8",
    }


def auth_request(url, method, body):
    header = create_oauth_header(url, method)
    return request(
        url=url,
        headers=RequestDict(header),
        with_encode=True,
        body=RequestDict(body),
    )

参考にしたページ

まとめ

リクエスト部分はまた今度で。。。はてなブログAPIを叩くためのAWS LambdaをServerless Frameworkで建てる、みたいな記事を書く予定。

二回もやりたくないかなこれは・・・ 最終的にはRequestsの中身をかなり読んで、それを参考に実装しました。Python3の実装がその辺に転がっていなかったのも結構辛かったです。

とりあえずもう少し上は整理される予定ですが、一旦記事にしとかないとどういうロジックだったか忘れるのでブログにメモだけ残しておきます。

はてなAPI VS わし

N時間かかりました。

最終的にできたもの

なんかもうOAuthとか何もわからないよ… の気持ちになってこれができました。

import urllib.parse
import requests
from requests_oauthlib import OAuth1
# build parameters to post
consumer_key = "Authきぃ"
consumer_secret = "Authしいくれっと"
token_key = "APIきぃ"
token_secret = "APIしいくれっと"
message = """<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:app="http://www.w3.org/2007/app">
  <title>いきしちに</title>
  <content type="text/x-markdown">## ひみいりに

**ぽ!!!!!!!!111**

無事投稿できましたかな?やりましたかな?
 </content>
  <updated>2020-01-12T00:00:00</updated>
  <category/>
  <app:control>
    <app:draft>yes</app:draft>
  </app:control>
</entry>
"""
message = message.encode()
post_url = "https://blog.hatena.ne.jp/MireiMixin/namonakimichi.hatenablog.com/atom/entry"

auth = OAuth1(
    consumer_key,
    consumer_secret,
    token_key,
    token_secret,
)

headers = {'Content-Type': 'application/xml; utf-8'}

print(requests.post(post_url, headers=headers, auth=auth, data=message).text)

最終的にやったこと

みたところ

反省

  • どうもGoの理解が薄めだがGoで頑張りすぎた
    • 良い勉強にはなったが動かなさすぎ + デバッグのしづらさでストレス
    • OAuthクライアント周りの話をあまり知らないのにわからない言語でやる、は事故でしかなかった
    • どうもXMLデバッグ方法がよくわからないとなっていた。ラップされているところを一部崩して自分でOAuthのヘッダー作ってリクエストするとかしたほうが早かったかも
  • OAuth1.0あたりを本当に理解してなかった感
    • 普段出来上がってるものの保守が多いため、あんまり認証・認可を考えて来なかったツケが回ってきたなという感じ
    • あとWSSEやBASIC認証する手もあったけど、まあこれらは・・・ うん・・・ なんか個人的にやったら負け感があったのでOAuthにこだわった
    • 最初Auth用のキーとCustomerキーの違いがいまいちわかってなかったので一度発行しないといけないのがわかってなかった感
      • ついでにいうとCUIだけで完結する方法もいまいちわかってない
  • 結局ドキュメント読むのがやや辛かった
    • あれを読んでピンとこなかった感パないので地力が足らない
    • 実装サンプルもPerlRubyと自分にとって馴染みがなく、イマイチ何をしているのかわかってなかった
  • Pythonにして1時間以内に動くところまで行ったので、やっぱり言語的にわからないもの+仕組み的にわからないものを同時学習するコスト高いなと思った
    • 今作ってるのはさっさと形にしたいのでPythonでかく
    • 余裕があったらGoで書き直そう
    • あと単純にはてなブログAPIをGoで叩いてるケースとか、運用してるケースが見受けられなかった感じがするので、わかったことがあればちゃんと書かないとなと思った

まとめ

しんどいので今日はねます。三連休はコーディングがはかどって本当にいいと思う。

serverlessでとりあえずHelloWorldくらいのAPIを立ててみる

IAMとか理解してないので引っかかりまくったゾ。。。

やったこと

この記事を参考にした。

Serverless Frameworkの使い方まとめ - Qiita

ただ、最終的にデプロイしたいのはGoのコードだったので、Goの方はこれを参考に、 aws-go-mod テンプレートを使用した。

ServerlessFrameworkにてaws-go-modテンプレートのサービスをデプロイしてみる - Qiita

事前にnpm系統の何かをアップデートしていた。(この手順については割愛)

AWS周りの設定

デプロイ用のユーザーが必要なのでIAMを作成した。初めてでよくわからなかったのだが、頑張ってこれを見ながらすすめる…。

Serverless Dashboard - Access Roles

やったこととしては、こんな感じ。ちなみにOrganization的なやつでやってる場合はよくわからないです。僕は個人で遊んでいるだけなので。。。。

  • AWSのIAMを作る画面に移動する
  • ユーザーあたりをクリックして、ユーザーを追加
  • プログラムの実行ユーザーとかで選ぶ
  • 操作権限は雑にAdministratorAccessとかにした(真面目に管理する場合はもう少しちゃんとした権限にする)
  • あとはポチポチしてたのであんまり記憶にない、最後にアクセスキーとかが表示されるのでコピるなりCSVをダウンロードするなりしておく

で、awsのconfigにつっこんどく。きーとしーくれっとはいい感じに置換する。

serverless config credentials --provider aws --key きー --secret しーくれっと

(ここでローカルの ~/.aws/credential みたいなファイルを見たら、デフォルトユーザーになっていたので、後でしれっとserverless_deployみたいなユーザー名にした)

ここまででやっとAWSにつなぐための設定ができた。

Serverlessでデプロイしてみる

npm のグローバルインストールで serverless を入れておく。(あんまりグローバルには入れたくないのだが、まあこれは使い回すかなあと思って入れておく)

npm install -g serverless

そのあと雑なフォルダで下記を実行した。

sls create --template aws-go-mod --path test-func

ビルドしてデプロイ。それなりに時間がかかった。

make build
serverless deploy -v --aws-profile serverless_deploy

その後Curlで表示されてるAPI叩いたら動いた。

f:id:MireiMixin:20200112144951p:plain

まとめ

取りあえず動くところまで行けたので良かったです。あとserverless.yml をもう少し設定変えてあげれば色々できるのかなーと思いつつ、色々知らないので調べないと・・・ という気持ちになりました。いちいち発行されるドメイン覚えておくのもあれだし、ドメインなんか取ろうかな…。

手軽に小さいAPIがデプロイできるようになったので、もう少し勉強して活用できるようになれば、と思います。