#5 ✓resolved
Andy Koch

n-way join results in wrong id

Reported by Andy Koch | December 8th, 2008 @ 08:40 PM

When using an n-way (has_many :through) join, the wrong model id is selected when evaluating the join relationship.

Consider the following models. User, Group, Customer represent a standard hmt relationship. User, Group, Build (UGB) is an n-way style join - where the relationship between Users and Builds is possible since Builds belong to Customers who are joined to Users. Note the additional attributes to the hm in the Build model (ie, :foreign_key & :primary_key).


class User < ActiveRecord::Base
  has_many :role_objs, :class_name => 'Role', :dependent => :destroy
  def roles
    (role_objs || []).map {|r| r.title.to_sym}
  end

  has_many :groups
  has_many :customers, :through => :groups
end

class Group < ActiveRecord::Base
  belongs_to :user
  belongs_to :customer
end

class Customer < ActiveRecord::Base
  has_many :builds, :dependent => :destroy

  has_many :groups
  has_many :members, :through => :groups, :source => :user
end

class Build < ActiveRecord::Base
  belongs_to :customer

  has_many :groups, :foreign_key => :customer_id, :primary_key => :customer_id
  has_many :accessors, :through => :groups, :source => :user

  using_access_control :include_read => true
end

Now, to control the access to Builds based on the UGB relationship.


authorization do
  role :customer_account do
    has_permission_on :builds do
      to :read
      if_attribute :accessors => contains{user}
    end
  end
end

The problem is that the @build.accessors results in an empty [] - because the SQL is using the @build.id rather than @build.customer_id. See related SQL listings...


User Load (2.6ms)   SELECT * FROM "users" WHERE ("users"."login" = E'hp_person') LIMIT 1
Customer Load (2.5ms)   SELECT "customers".* FROM "customers" INNER JOIN groups ON customers.id = groups.customer_id WHERE (("groups".user_id = 1046015453)) LIMIT 1
Build Load (2.4ms)   SELECT * FROM "builds" WHERE ("builds".customer_id = 807266524)
User Exists (11.5ms)   SELECT "users".id FROM "users" INNER JOIN groups ON users.id = groups.user_id WHERE ("users"."id" = 1046015453) AND (("groups".customer_id = 192574979)) LIMIT 1

builds table:
:id => 192574979;
:customer_id => 807266524;

So, I've forked this on github and if I can figure out what to change I'll push an update for review.

Alternativly, the "Arrays in if_attribute" ticket would probably allow the whole issue to be avoided. Since I could then rewrite the Authorization rule as....


authorization do
  role :customer_account do
    has_permission_on :builds do
      to :read
      if_attribute :customer_id => in {user.customers.map(&:id)}
    end
  end
end

Comments and changes to this ticket

  • Andy Koch

    Andy Koch December 9th, 2008 @ 12:02 AM

    I've tracked the issue to this line...

    
    attr_value = object.send(attr)
    

    in this method...

    
    def validate? (attr_validator, object = nil, hash = nil)
      object ||= attr_validator.object
      return false unless object
      (hash || @conditions_hash).all? do |attr, value|
      begin
        attr_value = object.send(attr)
        rescue ArgumentError, NoMethodError => e
          raise AuthorizationUsageError, "Error when calling #{attr} on " +
          "#{object.inspect} for validating attribute: #{e}"
        end
    ....
    

    The "object.send(attr)" is correctly calling the join attr, which when viewed via debugger looks correct. But the somewhere deep in the Rails code it's not correctly applying the foreign_key.

    As I'm not too keen on digging into the Rails code I think I might take a hack at the Array issue as I suspect that to be an alternate solution.

  • Steffen Bartsch

    Steffen Bartsch December 9th, 2008 @ 09:45 AM

    Interesting model. As you suppose, it might just be that Rails is limited here. Does it work for you without d_a? I will try to produce a stripped-down test case this evening. Definitely an interesting case.

  • Andy Koch

    Andy Koch December 9th, 2008 @ 03:26 PM

    I'm not sure what you mean by "d_a".

    However, what does work for me... If the "if_attribute" clause is removed

    
    authorization do
      role :customer_account do
        has_permission_on :builds do
          to :read
        end
      end
    end
    

    this works, but users may now read builds from any customer.

    Another case works perfectly,

    
    authorization do
      role :customer_account do
        has_permission_on :customers do
          to :read
          if_attribute :members => contains{user}
        end
      end
    end
    

    which uses a traditional HMT join and works like clock-work.

  • Steffen Bartsch

    Steffen Bartsch December 9th, 2008 @ 04:38 PM

    d_a referred to declarative_authorization.

    What I meant to asked: Does the :accessors association really work for you? In my tests, :primary_key only worked for the direct has_many association (:groups in your case), but not for a has_many :through association.

    If this isn't working for you either, I will stash my test case for now, until this is worked out in ActiveRecord.

  • Andy Koch

    Andy Koch December 9th, 2008 @ 09:22 PM

    here is a simple test case app, without d_a ;-)

    this one works consistently

    unzip, db:migrate (to sqlite3) and start a console session

    then do

    
    >> Clock.first.conns
    => [#<Item id: 1, name: "that", created_at: "2008-12-06 18:51:21", updated_at: "2008-12-06 18:51:21">]
    

    In my project app with d_a this does not work and the foreign_key issue is present.

  • Steffen Bartsch

    Steffen Bartsch January 27th, 2009 @ 05:33 PM

    • State changed from “new” to “open”

    Sorry, this took so long. There now is a new named_scope backend. I just added a test case showing that this use case now works.

    Andy, if you are still interested in this issue, please have a look, if the test case fulfills your expectations.

  • Andy Koch

    Andy Koch January 27th, 2009 @ 05:35 PM

    Thanks for the follow-up Steffen,

    I'll take a look at it today.

  • Steffen Bartsch

    Steffen Bartsch March 24th, 2009 @ 08:50 PM

    • State changed from “open” to “resolved”
  • Michael Swan

    Michael Swan August 18th, 2010 @ 11:44 PM

    • Milestone order changed from “0” to “0”

    I have had this problem occur in my Rails 2.3.5 project. I tried 2.3.8 as well, but still this problem occurs. Is there any chance that the current version of rails has been altered back to produce the same problem?

  • Steffen Bartsch

    Steffen Bartsch August 19th, 2010 @ 08:31 AM

    You probably have a different error there. I'd suggest that you turn to the mailing list, this tracker isn't used anymore.

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile ยป

By now, decl_auth is using the GitHub issue tracker as well. Please use the one over there: http://github.com/stffn/declarative_authorization/issues

People watching this ticket

Attachments

Pages