Ruby + ActiveLdapでActiveDirectoryにLDAP接続し、ユーザーの所属グループを取得することがあったので、その時のメモを残します。
環境
- ドメインコントローラー - Windows Server 2008R2
- ドメイン名 - example.local
- 実行端末 - Windows7 (EXAMPLE.localに所属)
- Ruby 2.0.0p481
- gem
- activeldap 4.0.3
- net-ldap 0.6.1
また、ActiveLdapの書き方については公式チュートリアルがとても参考になりました。
TutorialJa - ruby-activeldap
ActiveDirectoryの構成
ドメインの直下にユーザーhogehoge
およびOUhoge
を置いて、OUhoge
の中にユーザーfuga_logon
を入れました*1。
example.local | | | `--hoge (OU) | `--fuga_logon (User) `--fugafuga (User)
ユーザー情報については以下の通りです。
ログオンアカウント名 | フルネーム(CN) | 所属するグループ |
---|---|---|
fuga_logon | fuga_cn | hogehogeとpiyopiyo |
fugafuga | ふがふが | hogehoge |
また、以下を参考にLDAPインタフェースへのアクセスをログに残すようにしました。
Gemfile
activeldap
だけでは動作しないため、LDAPクライアントとしてnet-ldap
を使いました。
source 'https://rubygems.org' gem 'activeldap' gem 'net-ldap'
ActiveDirectoryへの接続設定
デフォルトではActiveDirectoryに対する匿名接続は許可されていないため、ActiveDirectoryのLDAPにBindするアカウント名をfugafuga
として接続するようにします。
なお、これ以降の例でも、ActiveLdap::Base.setup_connection
は同じ内容です。
require 'active_ldap' HOST_IP_V4 = '192.168.0.1' BIND_USER_CN = 'ふがふが' BIND_USER_OU = '' BIND_USER_PASSWORD = 'fugafuga' def create_ou_string BIND_USER_OU.empty? ? '' : "ou=#{BIND_USER_OU}," end # ENV['USERDNSDOMAIN']には、example.localという形で入っている domain = ENV['USERDNSDOMAIN'].split('.') base = "dc=#{domain[0]},dc=#{domain[1]}" bind_user = "cn=#{BIND_USER_CN},#{create_ou_string}#{base}" # 今回の範囲内では、Bindするユーザーは「Domain users」グループでも構わない ActiveLdap::Base.setup_connection host: HOST_IP_V4, port: 636, method: 'ssl', base: base, bind_dn: bind_user, password: BIND_USER_PASSWORD
ENV['USERDNSDOMAIN']
でログインしている端末のドメインが取得できるので、それを元にbaseで使う文字列を作成します。なお、Windowsで設定されているENVの一覧は、以下のコードで調べました。
ENV.each { |env| p env}
ldapsを利用するため、ポートを636
、methodを ssl
として設定します。
Sinatra + ActiveLDAPで簡易LDAP管理インターフェースを作った - ~nabeken/diary/
bind_dnに指定する「接続時にBindするユーザー情報」は、ドメインコントローラーなどでldp.exe
を利用して確認します。
第3回 LDAPを使ってActive Directoryを制御しよう[その1:ldpとcsvde] - 知られざるActive Directory技術の「舞台裏」
ldap_mapping
ActiveLdap::Base
を継承したクラスにて、 ldap_mapping
メソッドを使ってマッピングをします。
class User < ActiveLdap::Base ldap_mapping dn_attribute: "cn", prefix: '' end
dn_attributeとprefixの関係については、Rubyist Magazine27号の記事中の図が参考になりました。
ActiveLdap を使ってみよう(前編) クラス定義と ldap_mapping - Rubyist Magazine27号
また、prefixが不要な場合であっても、prefixパラメータには空文字を渡します。 Re: ActiveLdap questions - Forum: Ruby
ActiveDirectoryからユーザー情報を抽出
example.local
ドメイン内のユーザー情報を抽出
find()メソッドの引数として、ActiveDirectoryユーザーのフルネーム(CN)を指定します*2。
class User < ActiveLdap::Base ldap_mapping dn_attribute: "cn", prefix: '' end p User.find('fuga').dn p User.find('ふがふが').dn
とすると、以下のような結果が得られます。
#<ActiveLdap::DistinguishedName:0x27b28c0 @rdns=[{"cn"=>"fuga"}, {"OU"=>"hoge"}, {"dc"=>"EXAMPLE"}, {"dc"=>"local"}]>
#<ActiveLdap::DistinguishedName:0x27730b0 @rdns=[{"cn"=>"\u3075\u304C\u3075\u304C"}, {"dc"=>"EXAMPLE"}, {"dc"=>"local"}]>
なお、ユーザーを作成時にフルネーム欄へ指定した値がCNと表示名(displayName)として設定されるため、何もしなければCNと表示名は同じ値になっているかと思います。このあたりの詳細は以下のサイトに記載がありました。
- How Can I Work with a CN that Has a Comma in It? - Hey, Scripting Guy! Blog - TechNet Blogs
- Name Attributes
特定のOU以下のユーザー情報を抽出 (例:OUhoge
以下)
class OuUser < ActiveLdap::Base ldap_mapping dn_attribute: "cn", prefix: "ou=hoge" end p OuUser.find('fuga').dn p OuUser.find('ふがふが').dn
とすると、
#<ActiveLdap::DistinguishedName:0x43342b0 @rdns=[{"cn"=>"fuga"}, {"ou"=>"hoge"}, {"dc"=>"EXAMPLE"}, {"dc"=>"local"}]>
path/to/vendor/bundle/ruby/2.0.0/gems/activeldap-4.0.3/lib/active_ldap/operations.rb:347:in `find_one': Couldn't find OuUser: DN: 縺オ縺後・縺・ filter: ["cn", "\u3075\u304C\u3075\u304C"] (ActiveLdap::EntryNotFound)
のように、OUhoge
以下に存在しないユーザーについては例外が発生します。
ActiveDirectoryユーザーのユーザーログオン名
で検索する場合
filterオプションにsAMAccountName
を使用した条件を渡します。
なお、同一ドメイン内ではユーザーログオン名は重複しないと考え、:first
を指定しています。
class LogonUser < ActiveLdap::Base ldap_mapping dn_attribute: "cn", prefix: '' end logon_user = 'fuga' p LogonUser.find(:first, filter: "(sAMAccountName=#{logon_user})").dn
とすると、
#<ActiveLdap::DistinguishedName:0x2761728 @rdns=[{"cn"=>"fuga"}, {"OU"=>"hoge"}, {"dc"=>"EXAMPLE"}, {"dc"=>"local"}]>
と、結果が返ってきます。
sAMAccountName
以外にfilterオプションとして使えそうなものは、ldp.exeを使ったり、以下の記事の「オブジェクトを一括登録する場合のCSVファイルの属性」を指定してみたりします。
第8回 Active Directoryの導入後の作業 (2/3) - @IT 管理者のためのActive Directory入門
また、ActiveLdapでのfilterオプションの書き方は、以下が参考になりました。
ActiveLdap::Base#find の :filter オプション - tashenの日記
ユーザーの所属するグループを取得する
ユーザーの所属するグループは、find()メソッドの戻り値のmemberOf
以下に入っていますので、次のコードで確認できます。
class User < ActiveLdap::Base ldap_mapping dn_attribute: "cn", prefix: '' end user = User.find('fuga') p user.memberOf p user.memberOf[0].rdns[0]['CN']
結果は、
[#<ActiveLdap::DistinguishedName:0x4428e28 @rdns=[{"CN"=>"hogehoge"}, {"OU"=>"hoge"}, {"DC"=>"EXAMPLE"}, {"DC"=>"local"}]>, #<ActiveLdap::DistinguishedName:0x4428690 @rdns=[{"CN"=>"piyo"}, {"OU"=>"hoge"}, {"DC"=>"EXAMPLE"}, {"DC"=>"local"}]>]
"hogehoge"
となります。
そのため、「ユーザーがあるグループに所属しているかどうか」は次のコードで調べられます*3。
class LdapUser < ActiveLdap::Base # OU'hoge'内に制限するなら、prefixに 'hoge' を渡す ldap_mapping dn_attribute: 'cn', prefix: '' def member?(group) # 見つからない場合、Array#indexはnilを返すのを利用 result = self.memberOf.index do |m| m.rdns.index { |r| r.has_value?(group) } end !result.nil? end end # ドメイン内ではユーザーログオン名は一意になるため、 :firstを指定しておく # CNで探す場合 user1 = LdapUser.find(:first, 'fuga_cn') p user1.memberOf if user1.member?('hogehoge') puts 'CNで検索し、所属を確認しました' else puts 'CNで検索し、所属を確認できませんでした' end # ログオン名で探す場合 user2 = LdapUser.find(:first, filter: '(sAMAccountName=fuga_logon)') p user2.memberOf if user2.member?('hogehoge') puts 'ユーザーログオン名で検索し、所属を確認しました' else puts 'ユーザーログオン名で検索し、所属を確認できませんでした' end
結果は、
[#<ActiveLdap::DistinguishedName:0x4351110 @rdns=[{"CN"=>"piyopiyo"}, {"OU"=>"hoge"}, {"DC"=>"EXAMPLE"}, {"DC"=>"local"}]>, #<ActiveLdap::DistinguishedName:0x4350978 @rdns=[{"CN"=>"hogehoge"}, {"OU"=>"hoge"}, {"DC"=>"EXAMPLE"}, {"DC"=>"local"}]>]
CNで検索し、所属を確認しました
[#<ActiveLdap::DistinguishedName:0x43783b0 @rdns=[{"CN"=>"piyopiyo"}, {"OU"=>"hoge"}, {"DC"=>"EXAMPLE"}, {"DC"=>"local"}]>, #<ActiveLdap::DistinguishedName:0x4383bd0 @rdns=[{"CN"=>"hogehoge"}, {"OU"=>"hoge"}, {"DC"=>"EXAMPLE"}, {"DC"=>"local"}]>]
ユーザーログオン名で検索し、所属を確認しました
となり、所属を確認することができました。
ソースコード
所属を確認する部分とGemfileはGistに上げました。
ActriveLdapのサンプルコード
参考資料
ActiveLdap関連
- ActiveLdap を使ってみよう(前編) - Rubyist Magazine27号
- ActiveLdap を使ってみよう(前編) - Rubyist Magazine29号
- Ruby on Rails Technical Night: Railsで作るActive Directoryと連携した社内システム - ククログ
- Rubyで扱うLDAPのススメ
- ActiveDirectoryサーバーを使ったLDAP認証(Rubyから) - 与太郎プログラマの日記
ActiveDirectory / LDAP関連
- 第2回 誰も教えてくれないActive DirectoryとLDAPの「本当の関係」[後編] - 知られざるActive Directory技術の「舞台裏」 gihyo.jp
- OpenLDAPサーバを介してActiveDirectoryに接続する際の注意点ふたつメモ - tagomorisのメモ置き場
- LDAP Attributes. Properties Active Directory Users Computers Distinguished name
- Active Directoryオブジェクトの識別名(DN)とは - @IT
- bibolog: Active DirectoryオブジェクトのCNを変更する
- Active Directory に同姓同名のユーザーを登録しようとしたときの注意点 | SE の雑記