devise_ldap_authenticatable でグループ制限したい人生だった (Rails 4)

とあるアプリ (でLDAP認証を実現するために、 cschiewek/devise_ldap_authenticatable · GitHub を使っていて幸せに暮らしていたけど、ある日突然○○グループのみ認証を通したくなったので、設定ファイルをいじったら簡単にできるやろって思ったらできなかったので、ちょこっと頑張ったお話。

devise_ldap_authenticatable でグループ認証したい人向けのお話です

先に結論から

  • config/initializers/devise_ldap_authenticatable_customizer.rb を作成
  • config/ldap.yml を編集

で完了

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認証の流れ

  1. 個人アカウントでLDAP認証してオブジェクト取得
  2. そのオブジェクトを使ってグループ認証

ざっくりこんな感じ

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か所

  1. Devise::LDAP::Connection の initialize で ldap.yml の設定を読んでいたので、check_group_membership_without_admin を読ませる

  2. グループに属しているかチェックしている in_group? メソッドで、uid で認証して欲しかったので、正規表現で切り取って認証

手を加えたのは Devise::LDAP::Connection の中の2行だけ

これでグループ認証があなたのもとへ、やったね!

カニでもわかるWindows 7新規インストール

windows機のHDDが突然お亡くなりになったので、新規HDDにwindows DSP版を入れて若干四苦八苦したところをメモる。

環境

マシン

ThinkPad Edge E420

HDD

壊れたHDD

ST9750420AS

http://www.tigerdirect.com/applications/SearchTools/item-details.asp?EdpNo=6324326

調べたら非AFTらしい。

AFTってなんじゃい

このへんを見ました。

http://www.pc-master.jp/jisaku/aft-hdd.html

なる、ほ、ど。。。

わからんが、AFTのものを買った方が良さそう。それに合わせてwindows7 SP1 適応済みにしたほうがよさげ

新しく買ったHDD

WD7500BPVX

http://shop.tsukumo.co.jp/goods/4515479660908/

これはちょっと調べるのに苦労したけど、AFTっぽい

OS

Microsoft Windows7 Home Premium 64bit Service Pack 1 日本語 DSP版 DVD LCP 【紙パッケージ版】

http://www.amazon.co.jp/gp/product/B00HSC9E78/ref=oh_details_o00_s00_i00?ie=UTF8&psc=1

なんかよくわかんないけど、一番安そうなやつ買いました

インストール

インストール自体は何の問題もなく、ディスクを入れて電源を入れたら勝手にインストール画面になったので、キーボードとか色々設定をポチポチしていくだけ

ここからが本当の地獄だ...

さてとりあえず chrome 入れるかと思って IE 立ち上げたらネットつながんねーわ

ディスプレイも外部ディスプレイ検出しねーわ

Bluetooth ヘッドセットも接続できねーわ etc...

なんか色々できない!

デバイスドライバを見ると、とりあえずネットワークアダプターねーじゃんよ

まずはインターネットから

ぐぐってたらレノボのサポートサイトから必要なデバイスドライバーを入れてみることに

http://support.lenovo.com/ja_JP/research/hints-or-tips/detail.page?DocID=HT037710

幸いメインPCはMacさんなのでそっちからネットワークドライバーさんを落として、USBメモリからwin機に入れたらインターネットできました、素敵!!

ディスプレイ

ディスプレイもドライバー入れたら検出してくれました

最後の難関 Bluetooth ヘッドセット

これはほぼ自分用

MW600 を使ってるんですが、PCと接続しようとするとデバイス認証するところまではできた。

でも接続はできない

なぜだ

ぐぐったり、製品のQ&Aを見に行ったりして悶絶してたら、電源ボタン+電話マークのボタン (何て言うんだこれ) 同時長押しで何かがリセットされたっぽくて、接続できるようになりました。

というわけで、ちょっと悶絶しましたが無事 windows 使えるようになりました

カニでもわかるRails 3 (nginx + unicorn) 開発環境構築

今まで Rails 1系だけ触った事があったのですが、Rails 3系をやる機会があったので、開発環境構築手順をまとめました。

手順として、

  • rvm インストール
  • Rails インストール
  • unironのインストールと設定 (nginxとの連携)
  • nginx インストールと設定 (unicornとの連携)

という感じでいきます。

rvm インストール

ちなみにサーバーは Debian (squeeze) です。
今回は開発環境なので、自分だけ使う前提で自分のアカウントでrvmをインストールします。

curl -L get.rvm.io | bash -s stable
# パスを通す
source ~/.rvm/scripts/rvm
# ruby インストール
rvm install 1.9.3
# gemset 作成 
rvm gemset create kanipan_rails
# kanipan_rails gemset を使う
rvm gemset use kanipan_rails

gemset use してgem installすると、

/home/kanipan/.rvm/gems/ruby-(version)@(gemset名)/gems/

以下にgemが入ります。

Rails インストール

今回は最新版 (3.2) をインストールします。

gem install rails
#バージョン確認
rails -v
Rails 3.2.11
# rails プロジェクト作成 databaseをmysqlに指定
rails new kanipan_project --database=mysql

unicornのインストールと設定

vim Gemfile

Gemfileに以下を追加

gem 'unicorn'

Gemfile保存後、

bundle install

# unicornの設定ファイル作成
vim config/unicorn.rb

サンプルとして

listen "/tmp/unicorn.sock"
pid "/tmp/unicorn.pid"

worker_processes 1
preload_app true

if ENV['RAILS_ENV'] == 'production'
  shared_path = "/var/www/shared"
  stderr_path = "#{shared_path}/log/unicorn.stderr.log"
  stdout_path = "#{shared_path}/log/unicorn.stdout.log"
end

# ログ
stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

preload_app true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{ server.config[:pid] }.oldbin"
  unless old_pid == server.pid
    begin
      Process.kill :QUIT, File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

unicornを起動します。

# -D : デーモンとして動かす, -E : Railsの環境, -c : unicorn設定ファイルのパス
unicorn_rails -D -E development -c config/unicorn.rb

unicornはmasterプロセスと複数のwokerプロセスから成り立っています。 unicornを停止するにはmasterのプロセスを止めます。

pidはunicorn.rbにパスを指定しているので、

kill -9 `cat /tmp/unicorn.pid`

nginx インストールと設定

sudo apt-get install nginx
sudo vim /etc/nginx/ngin
# kanipan_project用の設定ファイル
sudo vim /etc/nginx/sites-available/kanipan_project

以下サンプル

# statements for each of your virtual hosts
upstream kanipan_project{
  server unix:/tmp/unicorn.sock;
}

server {
  listen 80;
  server_name localhost;

  access_log /var/log/nginx/kanipan_project/access.log combined2;
  error_log /var/log/nginx/kanipan_project/error.log warn;

  root /var/www/kanipan_project/current/public;

  #rails params用
  proxy_buffer_size          128k;
  proxy_buffers            4 256k;
  proxy_busy_buffers_size    256k;
  proxy_temp_file_write_size 256k;

  location / {

     proxy_pass http://kanipan_project;

}

kanipan_project用設定へのシンボリックリンクを sites-enabled以下に置きます。

sudo ln -s /etc/nginx/sites-available/kanipan_project /etc/nginx/sites-enabled/kanipan_project

ドキュメントルートにkanipan_projectのpublicフォルダへのシンボリックリンクを置きます。

# /var/wwwにシンボリックリンクをはる
sudo ln -s kanipan_project/public /var/www/current/kanipan_project

これで準備完了!