HKENTO's Blog

日々の記録をつけます セキュリティとかその他のこと

Python2.7からGmailを送信する

研究室で毎週特定の曜日(水曜日)の特定の時間にお知らせメールを送信する仕事を任されていて、いままでは前日の毎週火曜日にRight Inboxを使って送信設定していたが面倒になった + Right Inboxの無料枠を使い潰したくないので、Gmailを送信するPythonスクリプトを作ってそれをcronから叩くようにした。備忘録として手順を残しておきます。

Google API Client Libraryを使って、PythonからGmailを送信

今回は、Google API Client Libraryという各言語から各種APIにアクセスする用のライブラリを使った方法を書いていきます。

Gmailを送信するまでの手順

GmailPythonから送信するまでには、OAuthの認証周りを含む以下のような手順が必要です。

  1. Google Developer Consoleから新しいプロジェクトを作成し、OAuthクライアントIDを取得する
  2. authorization codeを取得する
  3. authorization codeをアクセストークンとリフレッシュトークンと交換する
  4. メールを送信する

新しいプロジェクトを作成し、OAuthクライアントIDを取得する

Google Developer Consoleから新しいプロジェクトを作ったら、API Managerの認証情報 -> 認証情報を作成 -> OAuthクライアントID を選択し、クライアントIDとクライアントシークレットを作成します。

f:id:thunders1028:20170605195438p:plain

次に出てくる「アプリケーションの種類」の部分では、今回はPythonスクリプトからAPIを叩くので「その他」を選択してください。

これで、クライアントIDとクライアントシークレットを取得することができました。

次に、Pythonからプロジェクトの情報を利用するための設定ファイル client_secrets.json を作成します。

{
  "web": {
    "client_id": "クライアントID",
    "client_secret": "クライアントシークレット",
    "redirect_uris": [],
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://accounts.google.com/o/oauth2/token"
  }
}

使い捨てみたいなスクリプトをつくるんならわざわざファイルを分けずにハードコードしてもいいんじゃない?って思うかもしれないけれど、OAuth認証をするライブラリ oauth2client がファイルから情報を読むようになっているので、ファイルに書いておいたほうが無難です。(あとでコードを公開することになったときにヘタをこくのも防止できる)

authorization codeを取得する

ここからは、Python上の作業になります(すこしブラウザ操作も入りますが…)。

authorization codeとは、アクセストークンとリフレッシュトークン(後述)を取得するために一度だけ使うトークンのようなものです。一度アクセストークンを取得するために使い終わると、そのauthorization codeはもう使うことができません。

def retrieve_authorize_code(flow):
    auth_url = flow.step1_get_authorize_url()
    webbrowser.open(auth_url) # ブラウザで認証画面を表示
    exit(0)

~~~

"""Main"""
flow = client.flow_from_clientsecrets(
    "client_secrets.json",
    scope = "https://www.googleapis.com/auth/gmail.send", # メール送信の権限を取得する
    redirect_uri = "urn:ietf:wg:oauth:2.0:oob") # authorization codeをブラウザ上に表示

retrieve_authorize_code(flow)

flowとは、OAuthの処理の流れを制御するオブジェクト…のようなものです。OAuthとflowについては、Google Client LibraryのOAuthのガイドページにあるスライドがとても簡潔にまとめられていてわかりやすいです。

さて、retrieve_authorize_code(flow) を実行すると、ブラウザが立ち上がり認証画面が表示されます。

f:id:thunders1028:20170605201537p:plain

ここで「許可」をクリックすると、画面上にauthorization codeが表示されます。

f:id:thunders1028:20170605201738p:plain

このcodeをコピーしてとっておきましょう。

実行したコードは、webbrowser.open(auth_url) のところで止まっています。Ctrl-cで一度中断してください。codeを取得してコード上に埋め込んだら(次のステップでやります)、retrieve_authorize_code(flow) はもう不要です。コメントアウトするなりしましょう。

authorization codeをアクセストークンとリフレッシュトークンと交換する

取得したauthorization codeを使って、アクセストークンとリフレッシュトークンを交換します。リフレッシュトークンとは、アクセストークンの期限が切れた際に、新しいアクセストークンを取得するのに使われるトークンです。

def retrieve_credential(flow):
    auth_code = "auth_code" 
    credential = flow.step2_exchange(auth_code)

    #with open("credential.pickle", "w") as fpickle:
    #    pickle.dump(credential, fpickle)

    #with open("credential.pickle", "r") as fpickle:
    #   credential = pickle.load(fpickle)

    return credential

auth_code のところにauthorization codeを書いてください。

flow.step2_exchange(auth_code) にauthorization codeを渡すだけでアクセストークンとリフレッシュトークンが入ったcredentialが取得できます。このcredentialはメール送信をする際に使いまわすので、DBなどに保存してください。今回は簡単にpickleで保存しました。(oauth2clientのStorage.locked_put() というのを使うのがスジらしいけどなぜか自分の環境ではうまく動かなかったのでこの方法になりました。)

メールを送信する

やっとメール送信です。

def create_service(credential):
    http_auth = credential.authorize(httplib2.Http())
    return build("gmail", "v1", http=http_auth)

def create_mail(message_text, subject, to, from_):
    message = MIMEText(message_text)
    message["subject"] = subject
    message["to"] = to
    message["from"] = from_

    return {"raw": base64.urlsafe_b64encode(message.as_string())}

def send_mail(service, raw):
    try:
        message = service.users().messages().send(userId="me", body=raw).execute()
        print "A message sent. Id: {}".format(message["id"])

    except errors.HttpError, error:
        print "An error occurred: {}".format(error)

~~~

flow = client.flow_from_clientsecrets(
    "client_secrets.json",
    scope = "https://www.googleapis.com/auth/gmail.send",
    redirect_uri = "urn:ietf:wg:oauth:2.0:oob")

credential = retrieve_credential(flow)

service = create_service(credential)

message_text = "hello, world!"
subject = "test subject"
to = "example@gmail.com"
from_ = "example@gmail.com"

mail = create_mail(message_text, subject, to, from_)

send_mail(service, mail)

疲れたので続きはあとで追記します…

Coursera MLコースを修了した

研究で機械学習を使っていることもあり、一度ちゃんと勉強したいという想いから定評のあるAndrew Ng先生のCourseraの機械学習コースを4月終わりに受講し始め、本日修了しました。

f:id:thunders1028:20170529011015p:plain

線形回帰から始まり、ロジスティック回帰、ニューラルネットワーク、応用編にはOCRアプリケーションの構築の仕方など幅広くとてもわかりやすい講義で、機械学習分野を広く一望することができたように思う。まさに自分の学びたかったことなので楽しく受講することができた。

これからは、ゼロからつくるディープラーニングというこれまた定番の本を(研究費で)Getしたので、やっていきます。

ビットコインを買ってみた

TL;DR

ビットコイン買ってみたら増えた


特に理由はないんだけど、なんか目新しいことをしたくなってビットコインを買ってみた。ブロックチェーンとか仮想通貨とかそういう周辺のことにちょっと興味があったというのもある。

使ったサイトはコインチェックというサイト。ここは決済にクレカが使えるとかいろいろ利点があった気がする(よく覚えてない)。

まったく知らなかったんだけど、暗号通貨にはビットコイン以外にも何種類もあるらしく、コインチェックではそれらも扱っているらしい。知らない世界だ。ビットコイン以外だとモナーコインくらいしか知らなかった。

いきなり増えた

数日前ここでビットコイン15000円分買ってそのまま放置してみたところ、1000円増えて16000円になった。7%くらい増えた計算。なにやらちょうど暗号通貨に関する大きいカンファレンスがあるらしく、各種仮想通貨の価格が荒れているらしい?

長期保有も手では?

暗号通貨って、ネット通販とか送金とか、これからいろんなところで使われるようになると思う。実際いろんな銀行とか小売企業がビットコインについて言及したり、実際に使えるようになったりしている。すると、信用が増して全体的に値段があがるのではないか…と思う。短期的には急騰、急落はあるだろうけど、ベースラインになる値段は右肩上がりになるはず。とすると、今のうちに買っておいて年単位で持っておけばいい感じに利益がでるのでは?

って思うけど、経済も暗号通貨もド素人の意見なのでなんともいえない。

とりあえず最初に入れた15000円分はそのまま持っといてどうなるのか観察したいと思う。上がればラッキー、下がれば勉強代ということで。

ラズパイはSELinuxが(デフォルトでは)使えない

TL;DR

ラズパイのカーネルSELinuxの設定をせずにコンパイルされている

SELinuxを使いたければ自力でコンパイルしましょう

環境

カーネル

$ uname -a
Linux centos-rpi3 4.4.15-v7+ #897 SMP Tue Jul 12 18:42:55 BST 2016 armv7l armv7l armv7l GNU/Linux

概要

外部公開を予定しているラズパイのSELinuxの設定をしようと何気なくgetenforceしたところ、

$ getenforce                    
Disabled

デフォルトでDisabledだと…!

いやな予感がする。

とりあえずpermissiveにしようと/etc/selinux/configを編集

# This file controls the state of SELinux on the system.
# 1= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=permissive #→ disabledになっていたので変更

で、再起動してみたものの…

$ getenforce                    
Disabled

なぜ???

disabledからpermissiveに変更したときに実行されるはずのラベリングも走っている様子がない。。

/.autorelabelというファイルを作っておけば再起動時にラベリングしてくれるらしいので試してみるが…やはり応答なし。

setenforceはdisabledのときは(たしか)使えないため

$ setenforce 0
setenforce: SELinux is disabled

うーん…

SELinuxを触るのは初めてなのでなにか見落としが…?と思ったがとくにそういう気配もなく。

調べていると、こんな記事を発見

raspberrypi.stackexchange.com

SELinux requires kernel support, and the default Raspberry Pi kernel does not include it.

カーネルがサポートしてない(╯•﹏•╰)

SELinuxを有効にしたければ自分でカーネルコンパイルして入れなければならないとのこと。

[Guide] SELinux {Get/Install/Setup} - Raspberry Pi Forums

ここに詳しい設定・コンパイルの仕方が書いてあるので、まぁヒマができたらやってみようかなぁ…とりあえず後回しで。