pythonでLDAPにデータの追加取得をする(WriterとReader編)

# はじめに
[前回](https://qiita.com/mink0212/items/03c653dad49d05811a2e)はLDAPの追加と取得を行いました。今回は、WriterとReaderを使用した追加と取得の方法をまとめます。

# 環境
 - python:3.6.5
 - ldap3:2.7
 - イメージ:osixia/openldap 

# LDAP操作
## Readerを使用したLDAPの読み込み
ldap3には色々な機能を持ったReaderクラスがあります。それを使用してLDAPの情報を取得します。Readerにはコネクションとオブジェクトとcn(検索パス)が必要になります。例は基本的に上から順に続きものとして記載しています。

### オブジェクトの生成
オブジェクトはObjectDefクラスに対象のオブジェクト名とコネクションを渡して生成します。今回は、cnの値を取りたいため```inetOrgPerson```を指定します。ouを取得したいときは```organizationalUnit```など取得したい対象を指定します。

``` main.py

from ldap3 import Server, Connection, ObjectDef, Reader

server = Server('localhost')
conn = Connection(server, 'cn=admin,dc=sample-ldap',  password='LdapPass')
result = conn.bind()

# inetOrgPersonのオブジェクト生成
obj_cn_name = ObjectDef('inetOrgPerson', conn)
```

### Readerの生成
先ほど生成したオブジェクトとコネクション、検索パスを与えてReaderを生成します。この時に与える検索パスにより、どの階層から情報を取得できるかを指定できます。ここではReaderの生成だけで検索していないので値は入っていません。

``` main.py

# リーダーの生成
data_reader = Reader(conn, obj_cn_name, 'ou=sample-unit,dc=sample-component,dc=sample-ldap')
```

### LDAPの値の取得
#### LDAPの値の配下をリストで取得
リーダーの```search()```を使用することでLDAP値のリストを取得することができます。

``` main.py

# ここで検索をする
data = data_reader.search()

# 全アイテムが取れる
print(data)
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-27T20:50:15.470086
    cn: sample-name
    objectClass: inetOrgPerson
    sn: sample1
        sample2
    st: test2
, DN: cn=sample-name1,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-27T20:50:15.478084
    cn: sample-name1
    objectClass: inetOrgPerson
    sn: sample
]
```

結果を見るとReaderに指定した```ou=sample-unit,dc=sample-component,dc=sample-ldap```以下のcnが全て取得できていることがわかります。さらにそれぞれの属性値であるsnとstが取得できています。

#### 属性による検索データの取得
```search()```に属性の文字列を入れて属性値のあるデータを取得します。例ではstを与えており、LDAPの情報は先ほどと同じなのでcn: sample-nameが取れるはずです。

```  main.py

# 検索条件を指定する
data = data_reader.search('st')
print(data)
```

結果

```

DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-27T20:50:15.470086
    cn: sample-name
    objectClass: inetOrgPerson
    sn: sample1
        sample2
    st: test2
```

結果を見ると予想した通りsample-nameのcnが取れました。

#### json形式で取得する
リーダーの```entry_to_json()```を使用することでLDAPの値をjson形式の文字列に変換して取得できる機能があります。

``` main.py

# json形式で取得できる
json_str = data[0].entry_to_json()
print(json_str)
print(type(json_str))
```

結果

```

{
    "attributes": {
        "st": [
            "test2"
        ]
    },
    "dn": "cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap"
}
<class 'str'>
```

#### Dict形式で取得する
リーダーの```entry_attributes_as_dict```を使用することでLDAPの値をDict形式に変換して取得できる機能があります。

``` main.py

ldap_dict = data[0].entry_attributes_as_dict

print(ldap_dict)
print(type(ldap_dict))
```

結果

```

{'st': ['test2']}
<class 'dict'>
```


#### cnをパスにしてcnを1つ取得する
cnを指定することでその1つのcnのみ情報を取得することができます。

``` main.py

# cnをパスにすれば一個だけとれる
data_reader = Reader(conn, obj_cn_name, 'cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap')
data = data_reader.search()
print(data)
```

結果

```

[DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-27T21:16:17.284094
    cn: sample-name
    objectClass: inetOrgPerson
    sn: sample1
        sample2
    st: test2
]
```

## Writerを使用したLDAPの書き込み
ldap3には色々な機能を持ったWriterクラスを使用してLDAPの情報を書き込むことができます。WriterにはReaderを利用して生成することができます。


### Writerの生成・書き込み
Writerの```from_cursor()```にLDAPの値を取得したReaderを与えてWriterを生成します。
生成したWriterの変数に値を入れて```commit()```することで値を書き込みます。

``` main.py

from ldap3 import Server, Connection, ObjectDef, Reader, Writer

server = Server('localhost')

conn = Connection(server, 'cn=admin,dc=sample-ldap',  password='LdapPass')
result = conn.bind()

# readerを使った取得
obj_cn_name = ObjectDef('inetOrgPerson', conn)
data_reader = Reader(conn, obj_cn_name, 'cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap')

data = data_reader.search()

# 更新前に表示する
print(data[0])

# writerに読み込ませる
data_writer = Writer.from_cursor(data_reader)

# writer経由で値を入れる
data_writer[0].sn = 'sample10'
data_writer[0].sn += 'sample20'
data_writer[0].st = 'test10'

# 変更結果の反映
data_writer.commit()

# 更新後に表示する
data_reader2 = Reader(conn, obj_cn_name, 'cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap')
data2 = data_reader2.search()
print(data2[0])
```

結果

```

DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-27T21:36:03.493031
    cn: sample-name
    objectClass: inetOrgPerson
    sn: sample1
        sample2
    st: test1

DN: cn=sample-name,ou=sample-unit,dc=sample-component,dc=sample-ldap - STATUS: Read - READ TIME: 2020-03-27T21:36:03.686030
    cn: sample-name
    objectClass: inetOrgPerson
    sn: sample10
        sample20
    st: test10

```

指定したように値が変わっていることがわかります。


# おわりに
LDAPのReaderとWriterを使用したLDAPの取得・変更・追加についてまとめました。ReaderとWriterを使用することでLDAPの扱い方が少し容易になった気がします。そのおかげでRDBの代わりにLDAPを使用するということが現実的になってきました。今回は極力簡単にするために構成を考えずにべたでソースを書いていきましたがもう少し工夫することでさらにソースが便利になるのではないかと思っています。