Python タグの一覧です(49 件)

Flaskアプリケーションに二段階認証機能を実装(通算2回目)

Flask製ブログツールをローカルではなく、パブリックなVM(Digitalocean)に立ててブログを書くことにしたので、念の為、二段階認証機能を実装しました。
実は、ブログに組み入れるのは2回目で、ちょうど2年前の11月に一度実装しています(その後面倒くさくなってやめた)。その時の学んだことは Shizuoka.py#3 で発表させて頂きました(なつかしい)。


当時は二段階認証の名前は聞いたことがあるけど実際にやったことがある人が少なく(Shizuoka.py#3の参加者でも使っている人が10人中1人(私)だった)、発表聴いてくれている方々が理解してくれているかどうか不安でした。それから2年経ち、多くのサービスで利用されるようになってきたので、今現在は実際に使うようになっている方も多いのではないかと思います。

pyotpを使う

pythonでtotpを扱うモジュールはいくつかありますが、私はpyotpを使用しています。シンプルなので気に入っています。 基本動作をIPyhtonで確認してみます

In [1]: import pyotp

In [2]: import time

In [3]: totp = pyotp.TOTP('JBSWY3DPEHPK3PXP')

In [4]: totp.now()
Out[4]: '211775'

In [5]: totp.verify(211775)
Out[5]: True

In [6]: time.sleep(30)

In [7]: totp.verify(211775)
Out[7]: False

因みに上記で記述されているシークレットキー(JBSWY3DPEHPK3PXP)はgoogle authenticatorのサンプルコードでも記述されていて、totpのサンプルで使われるのを時々見かけますが、これはBASE32文字列におけるHexspeak(deadbeef)になっています。その為、サンプルで使われているのかと思慮。

In [1]: import base64

In [2]: import binascii

In [3]: binascii.b2a_hex(base64.b32decode('JBSWY3DPEHPK3PXP'))
Out[3]: b'48656c6c6f21deadbeef'

Flaskアプリへ組み込む

Flaskアプリへ組み込む場合、べた書きしてもよいのですが、再利用できるように、pyotpを使用して受け取ったワンタイムパスワードが正しいかどうか判定するfunctionを作成します。こんな感じです。

from pyotp import TOTP
from datetime import datetime, timedelta

def tfa_auth(secret, otp):
    totp = TOTP(secret)
    now = datetime.now()
    past = now + timedelta(seconds=-30)
    try:
        return otp == totp.now() or otp == totp.at(past)
    except:
        return False

タイムギャップ処理(-30)は必ずつけるのですが、+30をどうするのか悩みます(最近のスマートフォンは必ず時刻同期されているだろうから必要ない?)。
そしてviews側のfunctionはこんなかんじです。この処理の前にユーザー認証処理があって、認証されたらこのfunctonに飛びます。

@app.route('/verify_tfa/', methods=['GET','POST'])
def verify_tfa():
    error = None
    if request.method == 'POST':
        if tfa_auth(app.config['TFA_SECRET'], str(request.form['token'])):
            session['logged_in'] = True
            flash(u'You were logged in', 'alert-success')
            return redirect(url_for('show_entries'))
        else:
            error = 'Invalid verification code'
    return render_template('verify_tfa.html', error = error)

二段階認証機能の実装は、QRコード生成等のステップが無ければ、結構単純なので一度やってみるのもいいと思います。


[Python]剰余算の真理値判定とエラトステネスの篩

例えばこのような奇数を求めるコードがあって

In [1]: list(filter(lambda x: x % 2, range(10)))
Out[1]: [1, 3, 5, 7, 9]

このなかの x % 2 は真理値判定となっていて、余りがあればTrueであり、なければFalseである。ところが(俺だけかもしれないが)これがどうもしっくりこない(笑)% は余りを求めるのだから、余りがあればTrueなんだということは頭では理解しているつもりなのだけど、余りがゼロの場合に割り切れたんだからTrueだ、とふと思ってしまう。なんでだろうとずっと考えていたのだけれど、どうやら俺は奇数より偶数のほうが好きだからかもしれない。。

# -*- coding: utf-8 -*-

def sieve(num):
    primes = list(range(3, num, 2))
    for i in range(len(primes)):
        if primes[i]**2 > primes[-1]: break
        primes[i+1:] = filter(lambda p: p % primes[i], primes[i+1:])
    print(primes)

if __name__ == '__main__':
    import sys
    sieve(int(sys.argv[1]))

上記コードにも偶数をフィルタリングして除外する箇所があって、そこでも剰余算の真理値判定をしている。これはエラトステネスの篩という有名なアルゴリズムらしい。

良いもの。悪いもの。: アルゴリズムの素晴らしさに気付かせてくれたのはエラトステネスの篩だった
こちらのサイトのコードを Python3で書き直させてもらった。


[Python]requestsが正しくエンコード情報を返してくれない場合は apparent_encoding を使うとよいかもしれない

Requestsを使ってサイトにアクセスした際、サイトによってはエンコード情報を適切に判定してくれない場合があるようです。例えば下記のような場合、 SHIFT_JIS が欲しいところで ISO-8859-1 が返ってきます。

In [1]: import requests

In [2]: r = requests.get("http://www.atmarkit.co.jp/")

In [3]: r.encoding
Out[3]: 'ISO-8859-1'

このブログにアクセスした場合はこのようになります。これは正しく返してくれます。

In [4]: r = requests.get("http://blog.aoshiman.org")

In [5]: r.encoding
Out[5]: 'utf-8'

ちゃんと調べてはいないのですが、レスポンスヘッダに文字コード情報が入っていない場合に ISO-8859-1 を返すのかもしれません。とりあえず、正確な文字情報が欲しい場合は apparent_encoding を使うのがいいようです。

In [6]: r = requests.get("http://www.atmarkit.co.jp/")

In [7]: r.encoding
Out[7]: 'ISO-8859-1'

In [8]: r.encoding = r.apparent_encoding

In [9]: r.encoding
Out[9]: 'SHIFT_JIS'

ドキュメントには詳しく書いてないのですが、ソースコードを見てみると、 apparent_encoding はchardetを呼び出しているようです。
requests/models.py at bd3cf95e34aa49c8d764c899672048df107e0d70 · kennethreitz/requests

便利なのですが、やはり処理に時間がかかりますね。決まったところに何度もアクセスするのであれば、最初に調べてあとは決め打ちしたほうが良さそうです。

参考
Python3でrequests使っても日本語文字化けしないようにするには - TPDN launch pad


[Python]ハイフン(-)が含まれるスクリプトファイルをインポートする

Pythonでハイフン(-)が含まれるスクリプトファイルはimport文でインポート出来ないっぽい。例えばこのようなスクリプトファイルがあって

Python 3.4.2 (default, Oct  8 2014, 19:29:32)
Type "copyright", "credits" or "license" for more information.

IPython 2.3.1 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: cat test-dayo.py
# -*- coding: utf-8 -*-

def import_module():
    print(__name__)

if __name__ == '__main__':
    import_module()

importしようとすると、IPythonの場合、ハイフンが含まれたスクリプトファイルが補完されない(IPython賢い)

n [2]: import te
telnetlib  tempfile   termios    test       tests      textwrap

In [2]: import test
test   tests

無理やりimportしようとすると怒られる(四則演算と誤認識されるのだろうか?)

In [2]: import test-dayo
  File "<ipython-input-2-77e219196389>", line 1
    import test-dayo
               ^
SyntaxError: invalid syntax

なので __import__ を使う

In [3]: module = __import__('test-dayo')

In [4]: module.import_module()
test-dayo

ハイフンは含めない方が無難でしょうか

参照
Pythonでスクリプトを動的にimportする - Qiita


[Python][Twitter] twitterbot をようやくPython3対応させた

やるやる詐欺だった twitterbot 3アカウントの Python3 対応ですが、ようやく完了しました。

ネックになっていたのは、Python3 未対応なモジュール( tweepy と pit )の代替をどうするか。結局 tweepy は twython へ、pit に関しては configparser をベースにして似たようなものを自作しました。

これらのbotは大体2008年夏から2009年初頭くらいに作ったもので、(OAuth対応などちょくちょく修正はあったものの)6年ぶりに大幅にリファクタリングしましたw

あと、これらの twitterbot は Bitbucket にプライベートリポジトリで管理しているのですが、そのうちのひとつスクレイピングじゃないやつは GitHub の公開リポジトリへ引越ししました。

aoshiman/keibanews-bot

これには大した理由はないのですが、いつも Bitbucket ばかり使っていて、GitHub は殆ど dotfiles の管理だけに使っていたので 常々リポジトリが寂しいなあと思っていたところなのです。 botとして使うには(twitterでアプリケーション登録やtokenを発行しないといけないので)敷居が高いですが、feedの更新情報の保持の仕方など、botスクリプトの参考になれば幸いです。


PAGE TOP