よく知らないままに ldapjs を使ってみる

# 概要
この記事はエムスリーアドベントカレンダーの6日目の記事です。普段の仕事ではコードらしいコードを書かないで仕事をしているのですが、アドベントカレンダーを覗いてみたら絶妙に空いているところがあったのでエントリーしてみた次第です。
Node.js は初めて触れているので試しながら行きたいと思います。

# アジェンダ
- ldapjs の環境を構築する
- ldapjs を軽く使ってみる
- こんな使い方が出来るんじゃないかというポエムコーナー

# 環境構築
AmazonのVPSサービス[Amazon Lightsail](https://amazonlightsail.com/,Amazon Lightsail)が発表されたのでさくっと環境構築します。

instance image Node.js を選択して、
<img width="727" alt="スクリーンショット 2016-12-05 21.35.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/103627/b437de74-4c7d-cac0-2839-97267c38cc4e.png">
instance planを選択して create するだけでもう出来ています。
<img width="816" alt="スクリーンショット 2016-12-05 21.35.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/103627/4a815417-f74a-88e4-528b-22c9b9fe3c56.png">

事前にkey pairをアップロードしておくと仮想マシンへのSSH接続に利用可能です。

次にldapjsをインストールします。

```
$ sudo npm install -g ldapjs
```

#　ldapjs を試してみる
## サーバを立ち上げる
```js:
var ldap = require('ldapjs');
var server = ldap.createServer();
server.listen(1389, function() {
  console.log(server.url);
});
```
```
$ node server.js
ldap://0.0.0.0:1389
```
この状態だと検索したりすることはできないのですが。LDAPサーバを立ち上げるだけならこれだけです。

## バインド
LDAPを操作する上でDN(識別名)を認証する操作をバインドと呼ぶようです。

```js
server.bind('cn=root', function(req, res, next) {
  if (req.dn.toString() !== 'cn=root' || req.credentials !== 'secret')
    return next(new ldap.InvalidCredentialsError());

  res.end();
  return next();
});
```

サンプルのままですが、cn=rootもしくはパスワードがsecretでないときはエラーとなるような定義です。

## 認可(権限設定)

```js
function authorize(req, res, next) {
  if (!req.connection.ldap.bindDN.equals('cn=root'))
    return next(new ldap.InsufficientAccessRightsError());

  return next();
}
```
authorizeのための関数を定義します。cn=rootでないとエラーになるような定義です。

##　検索用関数の定義
検索用の関数を定義します。サーバ上の/etc/passwd の中身をattributeとして定義しています。

```js

function loadPasswdFile(req, res, next) {
  fs.readFile('/etc/passwd', 'utf8', function(err, data) {
    if (err)
      return next(new ldap.OperationsError(err.message));

    req.users = {};

    var lines = data.split('\n');
    for (var i = 0; i < lines.length; i++) {
      if (!lines[i] || /^#/.test(lines[i]))
        continue;

      var record = lines[i].split(':');
      if (!record || !record.length)
        continue;

      req.users[record[0]] = {
        dn: 'cn=' + record[0] + ', ou=users, o=myhost',
        attributes: {
          cn: record[0],
          uid: record[2],
          gid: record[3],
          description: record[4],
          homedirectory: record[5],
          shell: record[6] || '',
          objectclass: 'unixUser'
        }
      };
    }

    return next();
  });
}
```

## 検索
最後にauthorizeと検索用の関数を紐付けてサーバに検索機能を実装します。

```js
var pre = [authorize, loadPasswdFile];

server.search('o=myhost', pre, function(req, res, next) {
  Object.keys(req.users).forEach(function(k) {
    if (req.filter.matches(req.users[k].attributes))
      res.send(req.users[k]);
  });

  res.end();
  return next();
});
```
実際に検索してみた結果はいかのようになります。

```
entry: {"dn":"cn=root, ou=users, o=myhost","controls":[],"cn":"root"}
entry: {"dn":"cn=bin, ou=users, o=myhost","controls":[],"cn":"bin"}
entry: {"dn":"cn=daemon, ou=users, o=myhost","controls":[],"cn":"daemon"}
entry: {"dn":"cn=adm, ou=users, o=myhost","controls":[],"cn":"adm"}
entry: {"dn":"cn=lp, ou=users, o=myhost","controls":[],"cn":"lp"}
```

## クライアント
クライアントも同じように実装可能です。

```js
var ldap = require('ldapjs');
var assert = require('assert');
var client = ldap.createClient({
  url: 'ldap://localhost:1389'
});

client.bind('cn=root', 'secret', function(err) {
  assert.ifError(err);
});

var opts = {
  filter: '(objectclass=*)',
  scope: 'sub',
  attributes: ['dn', 'sn', 'cn']
};

client.search('o=myhost', opts, function(err, res) {
  assert.ifError(err);

  res.on('searchEntry', function(entry) {
    console.log('entry: ' + JSON.stringify(entry.object));
  });
  res.on('searchReference', function(referral) {
    console.log('referral: ' + referral.uris.join());
  });
  res.on('error', function(err) {
    console.error('error: ' + err.message);
  });
  res.on('end', function(result) {
    console.log('status: ' + result.status);
  });
});
```


# まとめ
- ldapjsを使うことでLDAPサーバ/クライアントを簡単に実装できる
- DBサーバなどをバックエンドにしたLDAPサーバを実装してみると便利そうですね。
- 普段やってないことに突然挑戦してみても出来ることがなさ過ぎて悲しい結果になります。

それでも調べてみたことで疑問や興味がわいてきたし良かったんじゃないかと思います(ポエム)。