[rspec-users] [rails] An authorization question

Zach Dennis zach.dennis at gmail.com
Sat Feb 28 16:34:51 EST 2009


On Sat, Feb 28, 2009 at 2:54 PM, Chris Flipse <cflipse at gmail.com> wrote:
> I've actually been okay with it at the unit testing / rspec level -- I've
> had it stubbed as you describe for a while.
>
> The pain point came in as I was trying to setup data for Cucumber ... Which,
> listening to my tests, tells me that the current structure is bad.  I was
> more curious to see how others are handling that sort of situation.
>
> I want to get *away* from the global variable, I'm just not entirely sure
> what the target should be.  There doesn't seem to be a whole lot of talk
> about actual implementation specifics around model level authorization.

Disclaimer, this entire post has to deal with model level
authorization and a technique I've been using and evolving in Rails
projects. It has nothing to do with your issue of global state for
what User is logged in. Onto the fun stuff IMO....

I've been wanting to write a thorough blog post on thoughts and
reflections on a very related topic, but since you've already kicked
off the conservation I'll try to add to the discussion. :)

For nearly a year I've been utilizing the concepts of Roles and also
Privileges. They are close at heart, but different needs have proven
both of them to be successful at different times.

Here's an example of being reliant on a role:

  # raises if the user cannot fulfill the role
  supervisor = user.in_role(:supervisor)
  supervisor.do_supervisory_thing

And the supervisor role looks like:

  class Roles::Supervisor < Roles::Base
    def do_supervisory_thing
      # inside the implementation you
      # can refer to the source of the role
      # using the method #source
    end
  end

The privilege functionality is similar:

  # this will raise access denied if user doesn't have the privilege
  payer = user.with_privilege(:make_payment)
  payer.pay!(invoice)

And its implementation looks like:

  class Privileges::MakePayment < Privileges::Base
    def pay!(invoice)
      Payment.create! :invoice => invoice
    end
  end

This abstracts out the behaviour surrounding privileges and roles out
into their own component. In most cases the methods are very small,
and they merely create real models, or do other things. The big win
for me has been that they create a concrete representation of
something tied to a role or a privilege. I don't know how much more
intention-revealing I can get. :)

Right now I am leaving things a little verbose in the controller
actions, e.g. user.in_role(...) or user.with_privilege(...), but I
like it because no one has to guess what is being used. The less
verbose route would be to omit #in_role or #with_privilege and just
say:

  user.pay!(invoice)

The user would be in charge of searching its roles/privileges for one
that responded to #pay!. I am considering moving to this route, there
are some edge cases that would need to be solved.

If you're interested, the roles project is on github:
http://github.com/zdennis/roles/tree/master

I just through up a wiki page for using them with Rails if you want to
test it out: http://wiki.github.com/zdennis/roles/rails





>
>
>
> On Sat, Feb 28, 2009 at 2:16 PM, David Chelimsky <dchelimsky at gmail.com>
> wrote:
>>
>> On Sat, Feb 28, 2009 at 12:51 PM, Chris Flipse <cflipse at gmail.com> wrote:
>> >
>> >
>> > On Sat, Feb 28, 2009 at 1:38 PM, David Chelimsky <dchelimsky at gmail.com>
>> > wrote:
>> >>
>> >> On Sat, Feb 28, 2009 at 11:52 AM, Chris Flipse <cflipse at gmail.com>
>> >> wrote:
>> >> > I've been going back over some legacy code, backfilling tests, and
>> >> > I'm
>> >> > encountering something that is causing no small amount of pain.  This
>> >> > is
>> >> > in
>> >> > a mature Rails app, that's  lived and migrated from 1.1 through to
>> >> > 2.1,
>> >> > so
>> >> > there's a lot of ancient cruft built up in the corners that I've been
>> >> > trying
>> >> > to clean up.
>> >> >
>> >> > My question/pain point revolves around authorization.  In at least
>> >> > two
>> >> > different models in the system  -- areas that are core to the
>> >> > functionality
>> >> > -- there are models that run through a state transition.  Only
>> >> > certain
>> >> > users
>> >> > are allowed to make those transitions, however.  You're basic "only
>> >> > an
>> >> > admin
>> >> > can publish an article" kind of restrictions.
>> >> >
>> >> > These models show up across most of the app -- several different
>> >> > controllers.  As such, long, long ago, someone patched updated the
>> >> > site
>> >> > authentication code to assign a User.current singleton inside the
>> >> > login_required filter.
>> >>
>> >> Unless I'm missing something, this seems like the problem is wider
>> >> than testability.
>> >>
>> >> Let's say I log in. Right now I am User.current. Now you log in, and
>> >> become User.current. Now I got to view some resource that I am not
>> >> permitted to see, but I get to see it because you are permitted and
>> >> YOU are the User.current.
>> >>
>> >> Am I missing something?
>> >
>> > The login filter is called at the beginning of every request, from
>> > application controller.  It resets the value, nilling it out if you're
>> > not
>> > logged in, at the start of each request.  So long as Rails remains
>> > single
>> > threaded, the scenario you describe isn't possible.  One process, one
>> > request at a time.  No bleed there.
>> >
>> > Of course, they're supposedly working on making it not-so single
>> > threaded,
>> > so that may eventually become a problem.  All the more reason to find
>> > something that doesn't suck.
>>
>> :)
>>
>> >> > This is then used by several models, sometimes to
>> >> > populate an updated_by stamp, sometimes it's actually used within a
>> >> > models
>> >> > validations(!), and it's definately used within some of the
>> >> > state-transition
>> >> > guards.
>> >> >
>> >> > Now, this is really just a global variable by another name, and it's
>> >> > pretty
>> >> > well embedded after two years.  I've come upon a whole bunch of
>> >> > different
>> >> > pain points in trying to setup data (real data) within the cucumber
>> >> > steps
>> >> > I've been backfilling.  Lacking any support of injection, I end up
>> >> > doing
>> >> > a
>> >> > lot of juggling of the User.current value
>>
>> You can stub this in your code examples:
>>
>>  User.stub!(:current).and_return(mock_model(User, :authorized? => true))
>>
>> or
>>
>>  User.stub!(:current).and_return(mock_model(User, :authorized? => false))
>>
>> etc.
>>
>> Replace "authorized? => true/false" with whatever is necessary for the
>> authorization to pass or fail as needed in each example.
>>
>> Stubs are cleared out after each example, so you don't have to use any
>> additional injection techniques.
>>
>> HTH,
>> David
>>
>> >>just to get some test data
>> >> > built
>> >> > and in the right set of states ... and while I can bury the temporary
>> >> > reassignments necessary inside a block, it still feels like it's an
>> >> > intractable mess.
>> >> >
>> >> > I know *why* this was originally done -- to avoid having to pass User
>> >> > objects around all the time, and it does _appear_ to keep the API
>> >> > clean
>> >> > --
>> >> > but the hidden dependancy isn't really clean.
>> >> >
>> >> > So, does anyone have any suggestions of how to easily manage model
>> >> > level
>> >> > user authorization?
>>
>>
>>
>>



-- 
Zach Dennis
http://www.continuousthinking.com
http://www.mutuallyhuman.com


More information about the rspec-users mailing list