Ruby + ActiveLdapでActiveDirectoryにLDAP接続し、ユーザーの所属グループを取得することがあったので、その時のメモを残します。
環境
- ドメインコントローラー - Windows Server 2008R2
- ドメイン名 - example.local
- 実行端末 - Windows7 (EXAMPLE.localに所属)
- Ruby 2.0.0p481
- gem
また、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
domain = ENV['USERDNSDOMAIN'].split('.')
base = "dc=#{domain[0]},dc=#{domain[1]}"
bind_user = "cn=#{BIND_USER_CN},#{create_ou_string}#{base}"
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技術の「舞台裏」
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と表示名は同じ値になっているかと思います。このあたりの詳細は以下のサイトに記載がありました。
特定の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
ldap_mapping dn_attribute: 'cn', prefix: ''
def member?(group)
result = self.memberOf.index do |m|
m.rdns.index { |r| r.has_value?(group) }
end
!result.nil?
end
end
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のサンプルコード
参考資料
ActiveDirectory / LDAP関連