Pluginはすでにある?

Activedirectory(以下AD)へのアクセス方法はLDAPだということがわかったので,LDAPCatalystを使ってADのデータを検索するサイトを作ってみました.ここではCatalystがすでにインストールしてあるものとして話を進めます.インストール方法や,アプリケーションの作り方に関しては,sekimuraさんのqootas.org/blogのエントリを参考にしてください.

作ろうとしているAD検索アプリケーションのアルゴリズムはこんな感じになります.見てもらうとわかりますが,単なるcgiとして作りました.Catalystを使うほどでもないかもしれないですが,まぁ,何事も勉強ですから.ハイ.

  1. http://SERVERNAME/cgi-bin/search にアクセスすると検索画面を表示
  2. http://SERVERNAME/cgi-bin/search にsnという名前で苗字をPOSTするとADにLDAPでアクセスし,氏名,電話番号,メールアドレス,所属をテーブル形式で表示

まず,必要なファイルの雛形を作ります.作り方は以下のようになります.

% catalyst.pl Staff::Search
% cd Staff-Search
% script/create.pl view TT TT
% script/create.pl model Search

次にlib/Staff/Search.pmにある,デフォルトコントローラを実装します.Search.pm中の'!default'アクションを以下のように編集します.

Staff::Search->action(

	'!default' => sub {
		my ($self, $c) = @_;
		my (@lists, $sn);
		$sn = $c->req->params->{sn};
		$sn = sanitize($sn);
		if($sn){
			@lists = Staff::Search::M::Search->engage($sn);
			$c->stash->{lists} = \@lists;
			$c->stash->{sn}    = $sn;
		}
		$c->stash->{template} = 'Search.tt';
		$c->forward('Staff::Search::V::TT');
	},
);

この中で使っている入力データをサニタイズする関数は以下のように作ってみました.ざっと作ったので適当です.もう少し考えた方がよかったかも.

sub sanitize
{
	my $arg = shift;
	$arg =~ s/&/&/g;
	$arg =~ s/</&lt;/g;
	$arg =~ s/>/&gt;/g;
	$arg =~ s/"/&quot;/g;
	$arg =~ s/[\\\/\^\$\*\+\?\{\|\}\[\]\.\(\)]//g;
	return $arg;
}

Search.ttは以下のようにTemplate-Toolkitの記述方法を使って記述します.デザインセンスがないですが,笑ってすましてください.画面を表示した際にはフォームの部分にフォーカスがあたるようにjavascriptを組み込んでいます.

<?xml version="1.0" envoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="content-script-type" content="text/javascript" />
<title>AD検索</title>
</head>

<body onLoad="document.form1.sn.focus()">
[% IF lists %]
"[% sn %]"さんの検索結果</p>
<table border="4" align="center">
<tr bgcolor="#cccccc">
<th>氏名</th>
<th>電話番号</th>
<th>email</th>
<th>所属</th>
</tr>
[% FOREACH user IN lists -%]
<tr align="center">
<td>[% user.displayName %]</td>
<td>[% user.telephoneNumber %]</td>
<td><a href="mailto:[% user.mail %]">[% user.mail %]</a></td>
<td>[% user.department %]</td>
</tr>
[% END %]
</table>
<p/>
[% END %]

<form name="form1" action="" method="post">
苗字を入力してください:
<input name="sn" size="30" type="text">
<input name="submit" value="AD検索" type="submit">
</form>
</body>
</html>

さて,ここからがLDAPへのアクセスを実行する部分です.'Staff-Search/lib/Staff/Search/M/Search.pm'を以下のようの作りました.

package Staff::Search::M::Search;

use base 'Catalyst::Base';
use Net::LDAP;
use strict;

sub engage{
	my ($self, $sn) = @_;
	my ($server, $uname, $pass);
	my ($ldap, $mesg, $entry);
	my (@attr, @ret);

	$server = "SERVER";
	$uname  = "foo\@hoge.co.jp";
	$pass   = "PASSWORD";
	@attr   = ('displayName', 'telephoneNumber', 'mail', 'department');
	$ldap = Net::LDAP->new($server);
	$ldap->bind($uname, password => $pass);
	$mesg = $ldap->search(
		base => 'cn=users,dc=hoge,dc=co,dc=jp',
		scope => 'sub',
		filter => "(&(objectclass=user)(!(objectclass=computer))(sn=$sn))",
		attrs  => \@attr
	);
	for (my $i = 0; $i < $mesg->count; $i++){
		my %data;
		$entry = $mesg->entry($i);
		for my $attr (@attr){
			my $str = $entry->get_value($attr);
			$data{$attr} = $str;
		}
		push(@ret, \%data);
	}
	$ldap->unbind;
	return @ret;
}

いくつかポイントをしぼって解説します.まず,

	$uname  = "foo\@hoge.co.jp";

LDAPにアクセスする際のユーザ名はこの形で指定してください.DN形式ではADの認証が通りません.

	@attr   = ('displayName', 'telephoneNumber', 'mail', 'department');

ADでは,それぞれ「表示名」「電話番号」「電子メール」「部署」としてデータを使用します.

		base => 'cn=users,dc=hoge,dc=co,dc=jp',
		scope => 'sub',

searchファンクションでは,'cn=users,dc=hoge,dc=co,dc=jp'配下を指定しています.

		filter => "(&(objectclass=user)(!(objectclass=computer))(sn=$sn))",

検索対象として,ユーザであって,コンピュータでないものを指定しています.さらにそのなかで,snという属性名を持つ「姓」が指定したものかどうかをチェックしています.

		attrs  => \@attr

searchメソッド中の'attrs => \@attr'は,検索結果として返すエントリが含む属性の指定です.これ以外の属性は流れないので,検索を効率的に行なうことができます.

これでAD検索ができるようになります.調子にのってエントリの登録もwebからできるようにしようかと一瞬思ったのですが,次の瞬間にはめんどくささが山のように押し寄せてきたのでやめました.とりあえず動いたので,ここまでのまとめとしてメモします.