[rspec-users] [rails] An authorization question
zach.dennis at gmail.com
Tue Mar 3 00:15:39 EST 2009
On Mon, Mar 2, 2009 at 11:35 PM, Stephen Eley <sfeley at gmail.com> wrote:
> On Mon, Mar 2, 2009 at 5:16 PM, Zach Dennis <zach.dennis at gmail.com> wrote:
>> Forgot to mention what we did do. We ended up with the following...
>> def index
>> if user.has_role?("admin")
>> elsif user.has_role?("associate")
>> raise AccessDenied
> That seems sort of backwards to me. These aren't the user's invoices,
> right? They're just invoices which the user happens to be allowed to
> see? Chaining it this way makes it look like the invoices *belong* to
> the role, and seems put the user up front instead of the invoices.
I agree, it could be better.
> You also have conditional branching with hardcoded values, making the
> controller brittle, and some redundancy with the controller asking the
> model for a value and then passing the value right back to the model.
I would like to push down the conditional logic to a lower part of the
app and there probably is a way that I haven't found yet with the
technique I'm using. Right now I'm exploring some trade-offs. Put more
emphasis on the responsibility of a role or on the responsibility of
the model? I've been down the model route, and would like to see where
the role route takes me. :)
> Can a model have more than one role? If it can, you have a problem
> here because the 'if' will only ever *act* on one role. If it can't,
> life gets simple:
> def index
> @invoices = Invoice.by_role(user)
> rescue AccessDenied
> flash[:warning] = "Nope. Sorry. Nice try."
> redirect_to :back
> [Invoice model]
> def by_role(user)
> case user.role
> when "admin"
> when "associate"
> raise AccessDenied
> ...That could probably still be made more elegant. I'm not a huge fan
> of case statements, and I have in my head some idea that you could
> have named scopes for each role and use "send" to pick the right
> scope. But that could be overdesigning it, and in any case I don't
> really know what each role has to return in your business case.
I've been down this path many times. It has worked well when the
privilege/role set was limited and fairly simple, but seems to leave
models convoluted for anything else. That's what sparked me to explore
concretely identifying the Role in my app, and allowing it to make
decisions that are unique to it, rather than sprinkling them
throughout the models.
> The important takeaway here is that the Invoice is responsible for
> figuring out what to return, based on user data passed to it at
> runtime; the User doesn't have to have special logic to know how to
> query every other model in the system.
To clarify, the User doesn't know how-to query anything. All it knows
is if it can fulfill a Role. If it can it returns the appropriate role
object. Each role is responsible for knowing what it can access, but
it doesn't do the nitty gritty work. It calls methods on other models.
For the Invoice example, the associate role calls
Invoice.by_area(user.area), whereas the Admin role calls Invoice.all
in each of their respective #invoices methods.
Some of what has piqued my interest in exploring apps that place more
emphasis on Roles and Privileges is that it it's difficult to
understand what it means to be an admin when what it means to be able
to to be an "admin" or "supervisor" or a "team supervisor" or an
"employee" is sprinkled throughout the app. So far I have enjoyed
having the responsibility of an admin in one spot, even though that
Admin object doesn't deal with the details. It just knows how-to make
a decision and then it tells another object to do it. It's kind of
like inserting a thin-layer of roles and privileges between the
application layer and the domain layer. I'd say these sit on the top
end of the domain layer.
As you pointed out earlier, the API for #in_role could use a little love.
More information about the rspec-users