Djangoのメール周りメモ

追記: 翔泳社さんでDjangoの書籍を出版するのでぜひ読んでみてください。


はじめに

この記事は 2015 tech-yuruyuru アドベントカレンダー - connpass の18日目です

メール周りの機能は基本的に↓のEmailのトピックページにまとまってはいるのですが、他のページに書いてある内容が必要になったりするのでその辺りのことも一緒にまとめてます。

開発中の設定

開発中は実際にメールを送信してほしくないので、その設定をします。 ドキュメントを見た感じ2つぐらい方法があるみたいです。

Dummy Backend

settings.pyで以下のように記述すると、メールを実際には送信せず送信内容等をコンソールに表示してくれます。

EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

SMTPサーバをたてる

以下のコマンドを打つと、localhostのリスニングポート1025でSMTPサーバがたちあがります。

python -m smtpd -n -c DebuggingServer localhost:1025

EMAIL_HOSTEMAIL_PORT をこれに合わせて設定しておくと、SMTPサーバは受け取った内容を端末に表示します。もちろんこの時送信はしません。 基本的にDummy Backendを使っておけばいい気がします。

メールの送信

準備

settings.pyに下記の設定を追記します。 EMAIL_PORTやEMAIL_HOSTは先程建てたSMTPサーバに合わせます。

# settings.py
EMAIL_HOST = 'localhost'
EMAIL_PORT = 1025
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
DEFAULT_FROM_EMAIL = 'system@example.com'

メールの送信 (send_mail)

「件名」「本文」「送信元メールアドレス」「送信先メールアドレス」の順で指定します。

# send_mail.py
from django.core.mail import send_mail
from <project_name> import settings


def send_mail_for_inquiring(to_email):
    send_mail('問い合わせについて,
              'お問い合わせありがとうございました。',
              settings.DEFAULT_FROM_EMAIL,
              [to_email], fail_silently=False)

なお送信先はリストで指定します(複数指定可)。

email_userメソッド

https://docs.djangoproject.com/en/1.9/ref/contrib/auth/#django.contrib.auth.models.User.email_user

django.contrib.auth.modelsのUserクラスにもemail_userメソッドというのが用意されています。 中ではsend_mail呼んでいるだけですが、宛先が1人だけならこちらを使えばよさそうです。

user.email_user('メールの件名', 'メールの本文')

メール送信周りのテスト

https://docs.djangoproject.com/en/1.7/topics/testing/tools/#email-services

django.core.mail.outbox が送信されるメールを保持しますが、manage.pyのtestコマンド実行中は実際には送信されません。 またoutboxは各テストケースの開始時に初期化されます。

>>> from django.core import mail
>>> mail.send_mail('件名', 'メッセージ', 'from@example.com', ['to@example.com'], fail_silently=False)
>>> mail.outbox[0].subject
件名
>>> mail.outbox[0].message
メッセージ

メールを使ったアカウント登録処理

ユーザが登録時に入力したメールアドレスにメールを送信し、そこに記載されているリンクを踏むとアカウントを有効化する。というサービスを結構みかけます。 1から実装するのは少し大変そうですが、django-registration-reduxというライブラリを使えば簡単に実装できます。

ドキュメントも丁寧に書かれてあるので英語が大丈夫な人はそちらで十分ですが、以前 id:ottati 先生がまとめてくださってたのでそちらを参考にすると良さそうです。

http://ottati.hatenablog.com/entry/2015/04/14/184637

以上です

DockerでHerokuの環境を再現する

はじめに

2015 tech-yuruyuru アドベントカレンダー - connpass 13日目です。

PythonのWebアプリを開発するとき、Mac上でそのまま開発することが多いんですがHerokuに近い環境で動作確認もしたい。 Heroku-toolbeltの中にDockerfileを生成してくれるコマンドがあるみたいなので試してみる。

ここをザッと読む限り app.json ファイルにプロジェクトの構成を記述すればHeroku-Toolbeltから簡単にDockerfileが生成出来るみたいなので試してみる。

Dockerfileの作成

heroku-toolbeltに heroku-docker プラグインをインストール。

$ heroku plugins:install heroku-docker

python版のdocker-imageは↓にあるみたい。

app.jsonの用意

{
  "name": "python/flask docker environment",
  "description": "Docker configulation for run python/flask application.",
  "image": "heroku/python",
  "addons": [ "heroku-postgresql" ]
}

Procfileと他の設定ファイルも用意

Procfile

# Flask
web: gunicorn manage:app -w 1 --log-file -

runtime.txt: デフォルトでは2系なので指定。もう3.5が使える。

python-3.5.0

requirements.txt

$ pip freeze > requirements.txt

Dockerfile生成

Dockerfileとdocker-compose.ymlを生成

$ heroku docker:init

実行

docker-machineを起動してIPアドレスをdocker-daemonに伝える(手順は省略)。

$ docker-compose up web
$ open "http://$(docker-machine ip default):8080"

docker-compose up web が何度も失敗して不思議だったけどdocker-machineをrestartしたら上手くいった。 ただしブラウザでアクセスしてもつながらない

python2系だと上手くいく。

その他

  • shell
$ docker-compose run shell
  • コンテナのrebuild
$ docker-compose build
  • デプロイ

heroku docker:release が使える。それ以外は今まで通り

$ heroku create          # 登録
$ heroku docker:release  # リリース
$ heroku open            # ブラウザで開く
$ heroku logs            # ログの確認
$ heroku run python      # pythonの実行

おわりに

ギリギリ13日目...

今のところ、変更があるたびにdocker buildし直すのは面倒そうなので、開発は基本的にMac上で直接行うつもり。

Pythonにおけるハッシュ計算

エキスパートPythonプログラミング改訂2版

エキスパートPythonプログラミング改訂2版

はじめに

ADVENTARのPythonアドベントカレンダー 4日目 です。 今作っているもの で後々必要になるかなと思い調べていたハッシュ関数について書きます。


ハッシュ関数について

ハッシュ関数は、任意長のデータ x を与えると固定長のビット列 y を返す関数です。 イメージとして↓のような感じになると思います。

H(x) -> y  # Hはハッシュ関数、xは任意長のデータ、yは固定長のビット列

この時の特性として y から x を特性するのが困難な一方向性関数である必要があり、データの正当性を検証する際などに使われます。

代表的なハッシュ関数アルゴリズムとして、 crc32md5sha-1sha-256 などがありますが、これらは目的に応じて使い分けます。

例えば crc32チェックサムのために設計されたもので、高速に動作し大きなファイルに有効です。そのためファイルが破損してないかどうかの確認によく利用されます。 ただし衝突が多くセキュリティの面では望ましくないようです。

一方、SHA-256は衝突が少なく非常に安全ですが動作は比較的遅く、速さと安全性はトレードオフとなっています。


Pythonのhashlibを触ってみる

Pythonの標準モジュールに含まれているhashlibの中にはMD5SHA-1、SHA-256などのハッシュ関数が入っています。

>>> import hashlib
>>> hashlib.md5(b'hello hello').hexdigest()
'f52d885484f1215ea500a805a86ff443'

>>> hashlib.md5(b'hello hellO').hexdigest()
'263ac69e8be54cdc5baeb3638a63709e'

文字列を少し変更するだけで全く違うハッシュ値が返ってきました。この特徴は y から x を特定されないために重要な特徴です。 ちなみにSHA-256を使いたければ hashlib.sha256 を使えば同じことが簡単にできます(sha-256なので256ビットのビット列が表示されるはずです)。

Cookieのハッシング

もう少し具体的な例を考えてみます。ここではHTTPのリクエストヘッダに↓を加えWebサイトに訪れた回数をクッキーに格納する例を考えます。

set-cookie: visits = s, [hash]

この時、ハッシュ関数s を与えた時の値が hash と一致すれば値は改ざんされていないとみなします。 コードにすると↓のようになります。

import hashlib

def hash_str(s):
    return hashlib.md5(s).hexdigest()

def make_secure_value(s):
    return "%s,%s" % (s, hash_str(s))

def check_secure_value(h):
    value = h.split(',')[0]
    if h == make_secure_value(value):
        return value

しかしこの例では、使っているアルゴリズムを推定して簡単に偽造が出来てしまうという大きな問題があります。

hmacを使ってみる

ハッシュ関数への入力 x に文字列を加えると当然ですがハッシュ値は大きく変化します。さっきの方法ではアルゴリズムがバレれば容易に偽造できますが、入力 x に文字列を足す場合、アルゴリズムが推測されてもこの文字列がばれない限り改ざんが出来ません。

さっきはhashlibを使ってましたが、pythonには標準でhmac(Hash-based Message Authentication Code)モジュールがあるので、そちらを利用します。

>>> import hmac
>>> hmac.new(b'secret', b'shibata').hexdigest()
'5ad21c9df4140462850c3c8cdbf37358'

hmacを使って書き換えると↓のようになります。

import hmac

SECRET = 'secretstring'

def hash_str(s):
    return hmac.new(SECRET, s).hexdigest()

def make_secure_value(s):
    return "%s|%s" % (s, hash_str(s))

def check_secure_value(h):
    value = h.split('|')[0]
    if h == make_secure_value(value):
        return value

これで、SECRETの文字列とアルゴリズムがバレないかぎり、偽造出来ないようになりました。

もう少し考えるとすればSECRETを工夫する必要がありそうです。 具体的にはユーザIDなどから、攻撃者に推測されないユーザ固有の文字列を生成すると良さそうです。 これについて興味のある方は「Salt 作り方」とかで調べれば出てくると思います。 今度それについても書きたい

おわりに

この記事が今年初のAdvent Calender。ためてる内容がいくつかあるのでこの調子で減らしていきたい。