はじめに
pythonでLDAPを操作する方法を3回に分けてまとめてきました。複数人で開発する場合は全員がldap3のライブラリやLDAPのことについて理解する必要がありますが、それには時間と労力がかかります。そこでLDAPはツリー構造のデータであるということだけを知っていれば誰でも使えるようにクラスを作成しました。
環境
- python:3.6.5
- ldap3:2.7
- イメージ:osixia/openldap
メリット
- LDAPのことを意識せずに使える
- ルールを加えられる(dc=xxxの後はouにするなど)
- ぱっと見で分かりやすい
今回のクラスを使用した例
まずは、今回のクラスを使用した場合にどのようにLDAPを操作するのかを記載します。使用するときはクラス名を変えるとより分かりやすくなるため、この後の説明で出てくるDomainクラスをAddress、OrganizationをOwner、CommonをPetに変更することでAddress -> Owner -> Petの階層であることが意識できます。
from ldap_obj.Address import Address
# 場所の追加
address = Address('localhost', 'LdapPass', 'admin', 'sample-ldap')
address.set_item('tokyo')
address.save()
# 人の追加
owner = address.create_owner()
owner.set_item('sato')
owner.save()
# ペット(たま)の追加
pet_tama = owner.create_pet()
pet_tama.set_item('tama')
pet_tama.save({'sn': 'sato', 'st': 'tama', 'title': 'cat'})
# ペット(ぽち)の追加
pet_pocho = owner.create_pet()
pet_pocho.set_item('pochi')
pet_pocho.save({'sn': 'sato', 'st': 'pochi', 'title': 'dog'})
# titleを検索条件にしてdomain層からcnの値を取得する
address.set_filter('title')
print(address.get_common())
print('***********************')
# Addressを生成
address_get = Address('localhost', 'LdapPass', 'admin', 'sample-ldap')
address_get.set_item('tokyo')
# Address -> Ownerの生成
owner_get = address_get.create_owner()
owner_get.set_item('sato')
# Address -> Owner -> Petの生成
pet_get = owner_get.create_pet()
pet_get.set_item('tama')
print(pet_get.get_common())
LDAP用クラス
LDAP用クラスは各階層ごとにクラスを作成します。
ディレクトリ構成
\--
|--ldap_obj\
| |--BaseClass.py (基準のクラス)
| |--CommonClass.py (cnのクラス)
| |--DomainClass.py (dcのクラス)
| |--OrganizationClass.py (ouのクラス)
|
|--main.py
基準のクラス
基準のクラスは基本的にldap3のラッパーになります。特に複雑なことはしていません。コントラクタでは接続に必要なクラスの生成をして、get_xxx()では_read_ldap()にオブジェクトクラスを渡してLDAPから情報を取得しています。オブジェクトクラスが増えた場合はこの関数が増えます。
from ldap3 import Server, Connection, ObjectDef, Reader
class BaseClass(object):
def __init__(self, host, passwd, user, top_domain, dn=None):
self.passwd = passwd
self.user = user
self.host = host
self.top_domain = top_domain
self.filter = None
if (dn):
self.dn = dn
else:
self.dn = 'dc=' + top_domain
self.server = Server(self.host)
self.conn = Connection(self.host, 'cn={},dc={}'.format(user, top_domain), password=passwd)
def set_filter(self, filter):
self.filter = filter
def get_domain(self):
return self._read_ldap('domain', self.filter)
def get_organizational(self):
return self._read_ldap('organizationalUnit', self.filter)
def get_common(self):
return self._read_ldap('inetOrgPerson', self.filter)
def get_domain_dict(self):
return self._read_ldap_dict('domain', self.filter)
def get_organizational_dict(self):
return self._read_ldap_dict('organizationalUnit', self.filter)
def get_common_dict(self):
return self._read_ldap_dict('inetOrgPerson', self.filter)
def _read_ldap(self, object_type, search_attr = None):
data_list = []
self.conn.bind()
obj_dn = ObjectDef(object_type, self.conn)
data_reader = Reader(self.conn, obj_dn, self.dn)
data_reader.search(search_attr)
for data in data_reader:
data_list.append(data)
data_reader.reset()
self.conn.unbind()
return data_list
def _read_ldap_dict(self, object_type, search_attr = None):
data_list = []
self.conn.bind()
obj_dn = ObjectDef(object_type, self.conn)
data_reader = Reader(self.conn, obj_dn, self.dn)
data_reader.search(search_attr)
for data in data_reader:
data_list.append(data.entry_attributes_as_dict)
data_reader.reset()
self.conn.unbind()
return data_list
拡張クラス
上の基準のクラスを継承したクラスをそれぞれの階層ごとに作成していきます。
Domainクラス
dcの値が必要なためset_item()
でdnの文字列にdcの値を追加しています。このself.dnを使用して基準のクラスのget_xxx()
で情報を取得したり、save()
で追加します。create_organization()
はou用のクラスを生成して返却しています。今回の例は、dc=xxx,ou=yyyとしてほしいのでou用の生成関数しかないですがdc=xxx,dc=yyyとしたいときはdc用の生成関数を同じように作成すればできます。
from ldap_obj.BaseClass import BaseClass
from ldap_obj.OrganizationClass import OrganizationClass
class DomainClass(BaseClass):
def set_item(self, item):
self.dn = 'dc=' + item + ',' + self.dn
def create_organization(self):
return OrganizationClass(self.host, self.passwd, self.user, self.top_domain, self.dn)
def save(self):
self.conn.bind()
result = self.conn.add(self.dn, 'domain')
self.conn.unbind()
return result
Organizationクラス
Domainクラスから生成したOrganizationクラスにはself.dnにcnまでのパスが入っているのでset_item()
でouの値を追加しています。このself.dnを使用して基準のクラスのget_xxx()
で情報を取得したり、save()
で追加します。create_common()
はcn用のクラスを生成して返却しています。今回の例は、dc=xxx,ou=yyy,cn=zzzzとしてほしいのでcn用の生成関数しかないですが他の構成にしたいときはそれ用の生成関数を作成します。
from ldap_obj.BaseClass import BaseClass
from ldap_obj.CommonClass import CommonClass
class OrganizationClass(BaseClass):
def set_item(self, item):
self.dn = 'ou=' + item + ',' + self.dn
def create_common(self):
return CommonClass(self.host, self.passwd, self.user, self.top_domain, self.dn)
def save(self):
self.conn.bind()
result = self.conn.add(self.dn, 'organizationalUnit')
self.conn.unbind()
return result
Commonクラス
Organizationクラスから生成したCommonクラスにはself.dnにouまでのパスが入っているのでset_item()
でcnの値を追加しています。このself.dnを使用して基準のクラスのget_xxx()
で情報を取得したり、save()
で追加します。今回の例は、このcnで最後なので生成関数はありませんがさらに階層が深い場合はそれ用の生成関数を作成します。
from ldap_obj.BaseClass import BaseClass
class CommonClass(BaseClass):
def set_item(self, item):
self.dn = 'cn=' + item + ',' + self.dn
def save(self, attr_dict):
self.conn.bind()
result = self.conn.add(self.dn, 'inetOrgPerson', attr_dict)
self.conn.unbind()
return result
呼び出し元メイン関数
これらのクラスの使い方は、最初にDomainClassを生成してからdcの値を入れます、次に生成したDomainClassのcreate_organization()
を使用してOrganizationクラスを生成してouの値をいれます。commonの生成は生成したOrganizationクラスのcreate_common()
を使用してcnの値をいれます。それぞれの生成クラスで取得関数や追加関数を使用してLDAPの操作をします。
from ldap_obj.DomainClass import DomainClass
domain = DomainClass('localhost', 'LdapPass', 'admin', 'sample-ldap')
domain.set_item('sample-component')
domain_item_list = domain.get_domain()
for domain_item in domain_item_list:
print(domain_item)
print("=====================")
domain.set_filter('st')
domain_item_list = domain.get_common()
for domain_item in domain_item_list:
print(domain_item)
print("=====================")
organization = domain.create_organization()
organization.set_item('sample-unit')
organization_list = organization.get_organizational()
for organization_item in organization_list:
print(organization_item)
print("=====================")
common = organization.create_common()
common.set_item('sample-name')
common_list = common.get_common()
for common_item in common_list:
print(common_item)
print("***********************************")
new_domain = DomainClass('localhost', 'LdapPass', 'admin', 'sample-ldap')
new_domain.set_item('new-component')
print(new_domain.save())
print("=====================")
new_organization = new_domain.create_organization()
new_organization.set_item('new-organization')
print(new_organization.save())
print("=====================")
new_common = new_organization.create_common()
new_common.set_item('new-common')
print(new_common.save({'st':'new-st', 'sn': 'new-sn'}))
print("=====================")
new_common_list = new_common.get_common()
for common_item in new_common_list:
print(common_item)
おわりに
極力LDAPの要素を排して操作できるようなクラスを作ってみました。ldap3のライブラリを知っているとクラス経由の操作は面倒に思えますが実際にソースを見てみるとぱっと見で何をしているのかがわかりやすくなりました。今回は取得と追加の機能を持たせましたが、削除と移動と更新は基準のクラスにldap3を使用する関数を追加すれば良いと思います。ここまで分かりやすくすればデータの格納の候補として使用にも躊躇がなくなるのではないかと思います。