Ruby で扱う LDAP のススメ

Preview:

DESCRIPTION

RubyKaigi2010 企画 ”Ruby で扱う LDAP のススメ”資料です。 数例ある事例紹介の内、谷口さんの事例紹介のスライドは以下にあります http://www.slideshare.net/tasheeen/active-ldap

Citation preview

Ruby で扱う LDAP のススメ- Ruby meets LDAP -

その選択肢と事例

- Choices and cases –

高瀬一彰 / @tasheeen

Who?

高瀬一彰 (Kazuaki Takase)

エイケア・システムズ株式会社

Lang: perl→php→ruby+javascript

Rails で LDAP 管理アプリ

Why?

Ruby with LDAP に関する情報の不足

LDAP 自体がマイナー

Ruby コミュニティに、LDAPを扱うための入り口となる情報を提供したい

LDAPって?

どんなライブラリがあるの?

どれを使えばいいの?

What?

LDAP の概略

LDAP ライブラリの紹介

事例紹介

コード例

参考資料一覧

LDAPの概略

LDAPの概略(1)

Keywords

LDAP

Entry

ObjectClass

Attribute

DIT

Distinguished Name

LDAPの概略(2)

Lightweight Directory Access Protocol

RFC4510などで定義

元々は DAP。その軽量版(=Lightweight)

Directory = 台帳・名簿

何でも登録可能

台帳として最適化したデータ構造を持つ

LDAPの概略(3)

EntryLDAPにおけるデータの基本単位

=台帳の登録単位。名簿なら「人」

≒RDBにおける行に近い

現実世界の「物(=オブジェクト)」を表す

エントリは属性を持つ

「人」に対する「名前」「電話番号」etc.

RDB と違う点

エントリがスキーマを持つ

LDAPの概略(4)

Object Classエントリが「何」か

OOP の「クラス」と同義

オブジェクトクラスによって持てる属性が変わる

Unix アカウントのエントリなら posixAccount

継承・抽象型・構造型・補助型などの概念とルール

posixAccountは補助型person 等と組み合わせる

LDAPの概略(5)

Attributes

一つの属性に複数の値を持っている場合が多い

台帳・名簿としての考え方

LDAPの概略(6)

Directory Information Tree (DIT)

ディレクトリデータベースを DIB

ツリー構造していると DIT

階層的に管理するのがコンセプト

エントリがツリーとして連なる

LDAPの概略(7)

Distinguished Name(DN)

Rlative Distinguished Name(RDN)

その階層における一意の名前

cn=Ruby Taro

エントリが持つ任意の属性がRDNになる

RDNを下から一番上まで繋げるとツリーで一意に特定される名前になる(DN)

cn=Ruby Taro,o=RubyKaigi2010,c=jp

Ruby with LDAP

Ruby with LDAP - libs

Obsolete !

Obsolete !

Ruby with LDAP - libs

character

install

for use

RFC 1823の実装拡張ライブラリ

Pure Ruby. ActiveRecordのLDAP版Rails と相性がいい抽象度が高い

gem install ruby-ldap gem install net-ldap gen install activeldap

require “rubygems”require “active_ldap”

require “rubygems”require “net-ldap”

require “rubygems”require “ldap”

advantage 速い! ポータビリティが高い 高度な抽象化と豊富な機能

weak point ドキュメントが少ない ruby-ldap より10倍程度遅い

net-ldap よりも3倍程度遅い

Ruby with LDAP – ruby-ldap

ruby-ldapRFC1823 (The LDAP Application Programming Interface)のRuby版実装

使い方は同梱のテストが参考になる

速い!

search

bind

add

modify

etc…

C Extension

Ruby with LDAP – net-ldap

net-ldapPure Ruby な LDAP API 実装

ポータビリティに富む

RDoc のドキュメントが結構しっかりしている

search

bind

add

modify

etc…

Pure Ruby

Ruby with LDAP - activeldap

activeldapActiveRecord を参考に作られた、LDAP API

ruby-ldap や net-ldap, JNDI を内部的に利用

クラスはツリーを抽象、インスタンスはエントリを抽象

ActiveLdap::Base

User(Subclass)

find

destroy

cn=

save

new

Cases

Case 1

事例提供者:カピバラさんhttp://sites.google.com/site/capibaraproject/home

Case カピバラさん

背景

社内の各システム毎にID情報が分散

既存のLDAPサーバがあった

既存の構造的には認証統一には難しかった

新規にLDAP作成

管理アプリ作成

管理ユーザ一覧/検索/新規作成/所属変更

パスワード初期化 等

ユーザパスワード変更

Case カピバラさん

パスワード初期化

ユーザ管理一覧検索新規作成所属変更

パスワード変更

Case カピバラさん

ruby-ldap

ActiveLdap は使いづらかったそうで回避(泣

確かに ruby-ldap の方がプリミティブな実装

ruby-ldapに限らず、LDAPについても情報が不足して困った

速度面・利便性について特に不満なし

Case 2

事例提供者:岡澤さんhttp://d.hatena.ne.jp/yujiorama/

Case 岡澤さん

ruby-ldap を利用

分散した社員マスタ/認証を統合

request

参照できる

パスワード変更などアカウント作成・編集・削除

ML

依頼があり次第作成・変更

管理作業

Case 岡澤さん

ruby-ldap

元々作った人が「Rails 関連は面倒」(笑

openldap のコマンドとほぼ同じような作りで、コマンドの使い方が判ると直観的に使えた

同様の理由で、デバッグ等もやりやすかった

Case 3

事例提供者:谷口さん

Case 4

事例提供者:高瀬

Case 高瀬

request

batch(script/runner)

ユーザ管理有効化/編集/権限付与退職処理パスワード初期化など

共有フォルダ管理サブシステム権限管理etc

共有フォルダ作成権限編集

サブシステム権限編集パスワード変更

sync

PasswordWarning(mail)

batch

(Create folder

& ACL settings)

activeldap

アカウント/権限管理

Case 高瀬

activeldapRails の機能を活用したかった

script/runner でライブラリ使えるの便利

validation や association 使えるの便利validationは作りによってはとっても遅くなる

uid の uinique チェックのため検索するなどのvalidation を多数含めたりすると

検索時に取得する属性の数を少なくして速くする等の工夫をすると吉

テストも Rails に統合VM でdevelopment, test 用の LDAP を作成

環境(development, test)によって接続先 LDAP 切替

Rspec でふつうにテスト

Code Examples

Code Examples – ruby-ldap (1)

Connection and Bind

LDAP::Conn.new / LDAP::Conn#bind

@conn = LDAP::Conn.new('localhost', 389)@conn.bind("cn=Manager,o=RubyKaigi2010,c=jp", pass)# => #<LDAP::Conn:0xb7f0d400>

LDAP::Conn.new で接続

SSL接続には LDAP::SSLCon.new を利用

LDAP::Conn#bind でバインドする

第三引数の定数で認証方式の指定

Default: LDAP_AUTH_SIMPLE

grep LDAP_AUTH ldap.c

SSL 接続には LDAP::SSLConn

Code Examples – ruby-ldap (2)

search

LDAP::Conn#search

検索結果を LDAP ::Entry で返す

ブロック必須

他にも結果を Hash で返す search2 があるSearch2 はブロック無しで検索可能

@conn.search("o=RubyKaigi2010,c=jp", LDAP::LDAP_SCOPE_SUBTREE,"(objectclass=*)") do |entry|entry # => #<LDAP::Entry:…>, #<LDAP::Entry:…>, …

end

Code Examples – ruby-ldap (3)

Add

LDAP::Conn#add

entry = {"objectclass" => ["top", "person"],"cn" => ["Ruby Taro"],"sn" => ["Ruby", "Taro"]}

# 追加先のDN を指定し、属性の内容を渡す@conn.add("cn=#{entry['cn'][0]},o=RubyKaigi2010,c=jp", entry)# => #<LDAP::Conn:0xXXXXXXXX>

HashやLDAP::MOD_ADD でエントリ追加

上記の例は Hash の場合

Code Examples – ruby-ldap (4)

mod_hash = {"sn" => ["hoge", "taro"]}

@conn.modify("cn=Ruby Taro,o=RubyKaigi2010,c=jp", mod_hash) # => #<LDAP::Conn:0xb7f0bbc8>

Modify

LDAP::Conn#modify

HashやLDAP::MOD_MOD でエントリ更新

上記の例は Hash の場合

Code Examples – ruby-ldap (5)

Gathering Error Information

LDAP::Error

begin@conn.delete("cn=Ruby Taro,o=RubyKaigi2010,c=jp")

rescue => ee # => #<LDAP::ResultError: No such object>e.message # => "No such object“

@conn.err # => 32@conn.err2string(@conn.err) # => "No such object"

end

原則として LDAP::Error が投げられる様子

Human Readable なメッセージは LDAP::Error 自身が持っている

エラーコードが欲しい場合に #<LDAP::Conn> に問い合わせる

Code Examples – net-ldap(1)

Connection and BIND

Net::LDAP.new

@ldap = Net::LDAP.new :host => 'localhost',:port => 389,:base => 'o=RubyKaigi2010,c=jp',:auth => {:method => :simple,:username => 'cn=Manager,o=RubyKaigi2010,c=jp',:password => password }

@ldap # => #<Net::LDAP:0xb7e086f4 @base="o=RubyKaigi2010,c=jp",…>

接続と bind を一緒に行うのが通例

Code Examples – net-ldap(2)

search

Net::LDAP#search

デフォルトでは “(objectClass=*)” で検索

フィルタを利用する場合は Net::LDAP::Filter を利用してフィルタを構成する

ブロックを渡してイテレーションさせることも可

@ldap.search # => [#<Net::LDAP::Entry: ...>, #<Net::LDAP::Entry ...>]

@ldap.search(:filter => Net::LDAP::Filter.eq('uid', 'matz')) # => [#<Net::LDAP::Entry: … :uid=>["matz"], …>]

Code Examples – net-ldap(3)

Add

Net::LDAP#add

attrs = {:objectclass => ["top", "inetOrgPerson", "posixAccount"],:cn => "ruby taro",:gidNumber => "1999",:homeDirectory => "/home/ruby_taro",:sn => ["ruby", "taro"],:uid => "ruby_taro",:uidNumber => "1999"

}dn = "uid=#{attrs[:uid]},ou=Users,o=RubyKaigi2010,c=jp"

@ldap.add(:dn => dn, :attributes => attrs) # => false@ldap.get_operation_result # => #<OpenStruct … "Success",…, code=0>

※何故か false を返してますが成功しています(僕の環境のせい?)

Code Examples – net-ldap(4)

Modify

Net::LDAP#modify

dn = "uid=ruby_taro,ou=Users,o=RubyKaigi2010,c=jp"

opts = [[:add, :mail, "hoge@example.com"],[:replace, :sn, ["hoge", "fuga"]],[:delete, :telephoneNumber]

]

@ldap.modify(:dn => dn, :operations => opts) # => false

※これも何故か false を返してますが成功しています(僕の環境のry)

Code Examples – net-ldap(5)

Gathering Error Information

Net::LDAP#get_operation_result

@ldap.add(:dn => dn, :attr => attr) # => false

@ldap.get_operation_result.code # => 2@ldap.get_operation_result.message # => "Protocol Error"@ldap.get_operation_result.error_message # => "no attributes provided"@ldap.get_operation_result.matched_dn # => ""@ldap.get_operation_result # => #<OpenStruct …>

OpenStruct でエラー情報を返す

上記は objectClass の MUST の属性を設定していない場合の例

Code Examples – activeldap(1)

Connection and Bind

ActiveLdap::Base.setup_connection

ActiveLdap::Base.setup_connection :base => "o=RubyKaigi2010,c=jp",:bind_dn => "cn=Manager,o=RubyKaigi2010,c=jp",:password_block => lambda{pass},:logger => logger

この時点では接続が確立されず、何か操作した時に接続

サブクラス毎に接続先を変えること等も可能

Code Examples – activeldap(2)

class User < ActiveLdap::Baseldap_mapping :prefix => "ou=Users",

:classes => %w(person),:scope => :sub,:dn_attribute => "uid"

end

クラスを任意のツリーにマッピング

このクラスを利用して指定したツリー以下の検索等を行う

インスタンスを作成するとエントリにマッピングされる

belongs_to, has_many 等も利用できる

全体的に ActiveRecord と類似したインターフェース

Mapping

define subclass

Code Examples – activeldap(3)

User.find(:all) # => [#<User ...>, #<User ...>, ...]User.find(:first, :filter => “(cn=Ruby Taro)”) #=> #<User ...>

Search

ActiveLdap::Base.find

ActiveRecord と同様の検索方法

検索条件は :filter オプション

エントリとマッピング済みのインスタンスを返す

Code Examples – activeldap(4)

Add/Modify

ActiveLdap::Base#save , save!

user = User.new :sn => 'Ruby', :cn => "Ruby Taro"user.save # => true

user.sn = “Hoge”user.save # => true

インスタンスの状態に合わせて add/modify

新規オブジェクトなら add

既存オブジェクトなら modify

属性に複数の値を入れたい場合は配列で

Code Examples – activeldap(5)

Gathering Error Information

ActiveRecord::Validations#errors

user = User.new :sn => 'Ruby', :cn => "Ruby Taro"user.save # => false

user.errors # => #<ActiveRecord::Errors ...>user.errors.full_messages# => ["distinguishedName is duplicated: cn=Ruby Taro,ou=Users,o=RubyKaigi2010,c=jp"]

errors メソッドが ActiveRecord::Errors を返す

Rails で Webインターフェース等作る際は比較的楽

参考資料

参考資料

LDAP RFC 4510http://datatracker.ietf.org/doc/rfc4510/

Rubyist Magazine - ActiveLdap を使ってみよう(前編)- LDAPとはhttp://jp.rubyist.net/magazine/?0027-ActiveLdap#l4

ruby-ldap Ruby/LDAP (Homepage)http://ruby-ldap.sourceforge.net/

RDochttp://ruby-ldap.sourceforge.net/rdoc/

ソースコード。特に test ディレクトリ以下

net-ldap Net-ldap-0.1.1 Documentation (Homepage)http://net-ldap.rubyforge.org/

activeldap ActiveLdap を使ってみよう(前編)http://jp.rubyist.net/magazine/?0027-ActiveLdap

ActiveLdap を使ってみよう(後編)http://jp.rubyist.net/magazine/?0029-ActiveLdap

Rails で作る ActiveDirectory と連携した社内システムhttp://www.clear-code.com/archives/rails-seminar-technical-night/

ActiveLdap 日本語チュートリアルhttp://code.google.com/p/ruby-activeldap/wiki/TutorialJa

ありがとうございましたThank you.