devise_ldap_authenticatable でグループ制限したい人生だった (Rails 4)
とあるアプリ (でLDAP認証を実現するために、 cschiewek/devise_ldap_authenticatable · GitHub を使っていて幸せに暮らしていたけど、ある日突然○○グループのみ認証を通したくなったので、設定ファイルをいじったら簡単にできるやろって思ったらできなかったので、ちょこっと頑張ったお話。
devise_ldap_authenticatable でグループ認証したい人向けのお話です
先に結論から
で完了
gemのバージョンたち
net-ldap (0.5.1)
devise (3.1.1)
devise_ldap_authenticatable (0.8.1) # 現時点で rubygems 的に最新のgemのバージョン
モンキーパッチを当てる
config/initializers/devise_ldap_authenticatable_customizer.rb
module DeviseLdapAuthenticatableCustomizer
begin
class Railtie < ::Rails::Railtie
config.after_initialize do
Devise::LDAP::Connection.class_eval do
def initialize(params = {})
ldap_config = YAML.load(ERB.new(File.read(::Devise.ldap_config || "#{Rails.root}/config/ldap.yml")).result)[Rails.env]
ldap_options = params
ldap_config["ssl"] = :simple_tls if ldap_config["ssl"] === true
ldap_options[:encryption] = ldap_config["ssl"].to_sym if ldap_config["ssl"]
@ldap = Net::LDAP.new(ldap_options)
@ldap.host = ldap_config["host"]
@ldap.port = ldap_config["port"]
@ldap.base = ldap_config["base"]
@attribute = ldap_config["attribute"]
@ldap_auth_username_builder = params[:ldap_auth_username_builder]
@group_base = ldap_config["group_base"]
@check_group_membership = ldap_config.has_key?("check_group_membership") ? ldap_config["check_group_membership"] : ::Devise.ldap_check_group_membership
# この行を追加
@check_group_membership_without_admin = ldap_config.has_key?("check_group_membership_without_admin") ? ldap_config["check_group_membership_without_admin"] : ::Devise.ldap_check_group_membership_without_admin
@required_groups = ldap_config["required_groups"]
@required_attributes = ldap_config["require_attribute"]
@ldap.auth ldap_config["admin_user"], ldap_config["admin_password"] if params[:admin]
@login = params[:login]
@password = params[:password]
@new_password = params[:new_password]
end
def in_group?(group_name, group_attribute = LDAP::DEFAULT_GROUP_UNIQUE_MEMBER_LIST_KEY)
in_group = false
if @check_group_membership_without_admin
group_checking_ldap = @ldap
else
group_checking_ldap = Connection.admin
end
unless ::Devise.ldap_ad_group_check
group_checking_ldap.search(:base => group_name, :scope => Net::LDAP::SearchScope_BaseObject) do |entry|
#元のコード
#if entry[group_attribute].include? dn
if entry[group_attribute].include? dn.gsub(/uid=([^,]+),.*$/,'\1')
in_group = true
DeviseLdapAuthenticatable::Logger.send("User #{dn} IS included in group: #{group_name}")
end
end
else
# AD optimization - extension will recursively check sub-groups with one query
# "(memberof:1.2.840.113556.1.4.1941:=group_name)"
search_result = group_checking_ldap.search(:base => dn,
:filter => Net::LDAP::Filter.ex("memberof:1.2.840.113556.1.4.1941", group_name),
:scope => Net::LDAP::SearchScope_BaseObject)
# Will return the user entry if belongs to group otherwise nothing
if search_result.length == 1 && search_result[0].dn.eql?(dn)
in_group = true
DeviseLdapAuthenticatable::Logger.send("User #{dn} IS included in group: #{group_name}")
end
end
unless in_group
DeviseLdapAuthenticatable::Logger.send("User #{dn} is not in group: #{group_name}")
end
return in_group
end
end
end
end
rescue Exception(err_msg)
puts (" --- error => #{err_msg} --- ")
end
end
LDAP設定ファイル
config/ldap.yml グループとかLDAPのホストとかは適宜変えてください
## Authorizations
# Uncomment out the merging for each environment that you'd like to include.
# You can also just copy and paste the tree (do not include the "authorizations") to each
# environment if you need something different per enviornment.
authorizations: &AUTHORIZATIONS
group_base: ou=groups,dc=test,dc=com
## Requires config.ldap_check_group_membership in devise.rb be true
# Can have multiple values, must match all to be authorized
required_groups:
# If only a group name is given, membership will be checked against "uniqueMember"
- cn=admins,ou=groups,dc=test,dc=com
- cn=users,ou=groups,dc=test,dc=com
# If an array is given, the first element will be the attribute to check against, the second the group name
- ["moreMembers", "cn=users,ou=groups,dc=test,dc=com"]
## Requires config.ldap_check_attributes in devise.rb to be true
## Can have multiple attributes and values, must match all to be authorized
require_attribute:
objectClass: inetOrgPerson
authorizationRole: postsAdmin
## Environment
development:
host: ldap.network.kani.dc
port: 389
attribute: uid
base: ou=users,dc=kani,dc=co,dc=jp
ssl: false
group_base: ou=groups,dc=kani,dc=co,dc=jp
check_group_membership: true
check_group_membership_without_admin: true
required_groups:
- ["memberuid", "cn=hogegroup,ou=groups,dc=kani,dc=co,dc=jp"]
test:
host: ldap.network.kani.dc
port: 389
attribute: uid
base: ou=users,dc=kani,dc=co,dc=jp
ssl: false
production:
host: ldap.network.kani.dc
port: 389
attribute: uid
base: ou=users,dc=kani,dc=co,dc=jp
ssl: false
group_base: ou=groups,dc=kani,dc=co,dc=jp
check_group_membership: true
check_group_membership_without_admin: true
required_groups:
- ["memberuid", "cn=hogegroup,ou=groups,dc=kani,dc=co,dc=jp"]
解説
LDAP認証の流れ
- 個人アカウントでLDAP認証してオブジェクト取得
- そのオブジェクトを使ってグループ認証
ざっくりこんな感じ
2 を行う際、ldap.yml に check_group_membership_without_admin がないと、グループ認証する際に、入力されたユーザーではなく admin のアカウントとパスワードでバインドしてしまうので、invalid bind みたいな怒られ方をします。
そんな設定なかった
で、config に書くんですが、rubygemsから落とせる最新版 (0.8.1) にはその設定がなく、githubのmasterブランチ (0.8.3) にはありました。
ただ、認証系のgemをmasterから取ってくるのはイヤだったので、0.8.1にモンキーパッチを当てることにしました。
ポイントは2か所
Devise::LDAP::Connection の initialize で ldap.yml の設定を読んでいたので、check_group_membership_without_admin を読ませる
グループに属しているかチェックしている in_group? メソッドで、uid で認証して欲しかったので、正規表現で切り取って認証
手を加えたのは Devise::LDAP::Connection の中の2行だけ
これでグループ認証があなたのもとへ、やったね!