Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
15
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

@44d

python3-ldapを触ってみた

事の起こり

windows7+python3.4+django1.7でLDAP連携しようとしたら、python-ldapがWindows7+Python3環境で準備できなかった。
(Python3向けはコンパイルが必要なのだが、自分の環境ではコンパイルできず…Windows向けに用意されているインストーラーはPython2までしか無かった)

そこで色々調べてみたら、python3-ldapなるものを発見。Pure Pythonで書かれているPython用のLDAP連携ライブラリみたい。
で、コイツを使って「とりあえず」でAD連携のバックエンドを書いてみる。

python3-ldapをいじる

とりあえずサンプルに従って、ADサーバに接続→認証するところまで書いてみる。

quickstart_python3-ldap.py
from ldap3 import Server, Connection, AUTH_SIMPLE, STRATEGY_SYNC, STRATEGY_ASYNC_THREADED, SEARCH_SCOPE_WHOLE_SUBTREE, GET_ALL_INFO

s = Server('servername', port = 389, get_info = GET_ALL_INFO)  # define an unsecure LDAP server, requesting info on DSE and schema
c = Connection(s, auto_bind = True, client_strategy = STRATEGY_SYNC, user='username', password='password', authentication=AUTH_SIMPLE, check_names=True)

Connection生成するときに、userとpasswordでバインド(認証)を行う。バインドに失敗すると例外が発生

bind_error.py3tb
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "C:\work\venv\lib\site-packages\ldap3\core\connection.py", line 243, in __init__
    raise LDAPBindError(self.last_error)
ldap3.core.exceptions.LDAPBindError: automatic bind not successful - invalidCredentials

バインドに成功した場合は、例外は発生せず、以降はConnectionオブジェクトを使ってldapsearchやら何やらを実行できるようになる(バインドしたユーザーの権限によるけど)。
今回は認証だけできればOKなので、Connection使って何ができるか、という部分はスルー

認証バックエンドを自作する

お次はDjango側のコード。python-ldapを使う場合はdjango-auth-ldapを使えばオシマイなのだが、python3-ldapはまだ新しいライブラリなんでdjango用のライブラリは見つからず。
仕方ないので、自前で認証バックエンドのコードを書いて動かしてみる。

backends.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from ldap3 import Server, Connection, AUTH_SIMPLE, STRATEGY_SYNC, STRATEGY_ASYNC_THREADED, SEARCH_SCOPE_WHOLE_SUBTREE, GET_ALL_INFO

from django.conf import settings
UserModel = get_user_model()

LDAP_SERVER_HOST = 'server_name'
LDAP_SERVER_PORT = 389
ACTIVE_DIRECTORY_DOMAIN = 'domain_name'

def get_server(host='localhost', port=389) :
    return Server(host, port=port, get_info = GET_ALL_INFO)

def get_connection(server, user, password) :
    return Connection(server, auto_bind = True, client_strategy = STRATEGY_SYNC, user=user, password=password, authentication=AUTH_SIMPLE, check_names=True)

class ActiveDirectoryBackend(ModelBackend):
    def authenticate(self, username=None, password=None):
        server = get_server(host=LDAP_SERVER_HOST, port=LDAP_SERVER_PORT)
        if not server.check_availability() :
            return None

        user = '%s@%s'%(username,ACTIVE_DIRECTORY_DOMAIN)
        try :
            conn = get_connection(server, user=user, password=password)
        except :
            return None

        try :
            return UserModel.objects.get(username=username)
        except :
            return None

    def get_user(self, user_id):
        try :
            return UserModel.objects.get(pk=user_id)
        except :
            return None

本当はドメイン名も引数で受け取るようにしといた方がいいんだろうけど、やり方がよく分かってないので固定で。

あとはsettings.pyに自作した認証バックエンドを指定すればOK。
ホストやポート、ドメイン名も、settings.pyに移した方がいいかも。

settings.py
AUTHENTICATION_BACKENDS = (
    'python3ldap.backends.ActiveDirectoryBackend',
)

なお、Django側のDBにも、同じユーザー名でデータ突っ込んでおかないと認証失敗するので、お忘れなく。
(最後の最後、これでハマった)

2014/11/28:
ユーザーモデルをdjango.contrib.auth.get_user_model()で取得するよう修正。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
15
Help us understand the problem. What are the problem?