SearchLogic と Rails 2.3.10 を利用する際はご用心
先日のyouRoomで発生した間違いメール送信の不具合について調査しましたので、公開いたします。
発端は、Rails2.3.9のセキュリティーアップデートでした。youRoomでは、Rails2.3.9を利用していたためRails2.3.10へのアップデートを行ないました。基本的な動作確認はとれたのでリリースしたのですが、上記のような不具合が残ってしまいました。
原因は、SearchLogicの実装とRails2.3.9からRails2.3.10までのあるコミット(セキュリティー対応以外)にありました。
http://github.com/rails/rails/commit/9b78af95be01534b3f1a4187024d803313c0ee1f
実際には、このような現象が発生しました。
# Group -has_many-> Participation # Group.joined ==> defined named_scope # Group.user_id_not_null ==> genarated named_scope by searchlogic g = Group.find(1) # SQL) SELECT * FROM `groups` WHERE (`groups`.`id` = 1) g.participations # SQL) SELECT * FROM `participations` WHERE (`participations`.group_id = 1) g.participations.joined.user_id_not_null # Acctual SQL) SELECT * FROM `participations` WHERE ((participations.user_id IS NOT NULL) AND (participations.status = 'ACTIVE')) # Collect SQL) SELECT * FROM `participations` WHERE ((participations.user_id IS NOT NULL) AND ((participations.status = 'ACTIVE') AND (`participations`.group_id = 1))) g.participations.joined.user_id_not_null # Redo # SQL) SELECT * FROM `participations` WHERE (`participations`.group_id = 1) AND ((participations.user_id IS NOT NULL) AND (participations.status = 'ACTIVE'))
本来は、Acctual SQLの部分でCollect SQLのようにgroup_idで絞ったSQLが発行されるはずですが、実際にはSQLが抜けた状態になりgroupで絞ることができませんでした。このため、正しくない送信先が選ばれることになり、昨日の間違いメールが発生してしまいました。
このような実装で発行されるSQLが上記の様になってしまうのは、Railsを利用している人にとっては非常に恐ろしいところです。さらに、2度目に実行すると正しい結果になるので、非常に見つけにくい現象であると思います。
なぜ、このような現象が発生したのかソースを追って解説します。
g.participations.joined.user_id_not_null ^^^^^^^^^^^^^^^^^
この部分で、本来定義されていないメソッドなので、SearchLogicのmethod_missingに処理が渡ります。
proxy_reflection.klass.send(method, *args)
を解釈すると
Participaion.send('user_id_not_null')
となります。このようにnamed_scopeのChainから一旦抜けたような感じになります。
ここから次にまだメソッドは存在しないので
http://github.com/binarylogic/searchlogic/blob/master/lib/searchlogic/named_scopes/conditions.rb
この辺りで、named_scopeが定義されます。そして、そのメソッドが send で呼び出されます。Line 75 ですね。この時、以下のような呼び出しになります。
Participation.user_id_not_null
ここで、Rails2.3.10の差分が効いてきます。
http://github.com/rails/rails/commit/9b78af95be01534b3f1a4187024d803313c0ee1f
http://github.com/rails/rails/blob/9b78af95be01534b3f1a4187024d803313c0ee1f/activerecord/lib/active_record/named_scope.rb
Rails2.3.10: unless (Scope === proxy_scope || ActiveRecord::Associations::AssociationCollection === proxy_scope) Rails2.3.9: unless Scope === proxy_scope
これの差分は、一般的に proxy_scope が AssociationCollection の場合は、同じ Where 区が登録されるのを防ぐために変更されました。
しかし、SearchLogicで動的に生成した場合も上記の様に呼び出した時 proxy_scope が AssociationCollection となります。これまで、group.id の Where区の制限が、with_scope で引き継がれていましたが、引き継がれなくなってしまいます。
もう少し具体的に書くと、正しい場合はnamed_scope.rbのL182あたりで以下の様に呼ばれるのに対して、正しくない場合は一つ上のif文でfalseとなりwith_scopeで引き継がれずに実行されてしまいます。
if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) with_scope current_scoped_methods_when_defined do # ここのwith_scopeで group_id の制限がかかる {:conditions => {:group_id => 1} } という感じ proxy_scope.send(method, *args, &block) # Participation.find ... end else proxy_scope.send(method, *args, &block) end
そのような流れで、以上なデータが取得され今回の現象が発生してしまったようです。最後の方は、非常に複雑で文章で表現するのは難しく、理解しがたいかと思いますが、現象としては理解いただけるかと思います。
youRoom側の対応としては、Rails2.3.9のセキュリティーの問題も今回のSearchLogicの問題も致命的ですので、一旦、Rails2.3.10の必要なパッチをRails2.3.9に当てる形で対応しています。
http://gist.github.com/632068
この問題は、SearchLogicとRails2.3.10を併用したときのみ発生するようですが、非常に重大ですのでSearchLogicへも連絡しようと思っています。