views:

112

answers:

1

I'm using Catalyst with Catalyst::Plugin::Authentication and Catalyst::Plugin::Authorization::Roles and am wondering if there is a better approach to adding an attribute to a model that I'm not seeing.

Each user is permitted to access one or more companies, but there is always one primary (current) company at a time. The permitted list is stored in the database, and database access is primarily through DBIC.

My first inclination is to say that it's the user that has a current company, and thus put it as part of the user model: give the user package a "sub company { … }" to get/set the user's current company. The database check is fairly easy; just use "$self->search_related" (a DBIC method, inherited by the user model).

The problems I run in to are:

  • The current company needs to persist between requests, but I'd rather not store it to the database (it should only persist for this session). The natural place is the session…
  • There is a role, akin to Unix's root, that allows you to act as any company, ignoring the list in the database. Checking this role can be done through the database, but everywhere else in the app uses $c->assert_user_role and friends.

I've heard its best to keep the models as Catalyst-independent as possible. It also seems pretty weird to have a model manipulating $c->session.

Of course, I could move those checks to the controllers, and have the model accept whatever the controller sends, but that's violating DRY pretty heavily, and just begging for a security issue if I forget one of the checks somewhere.

Any suggestions? Or do I just shrug and go ahead and do it in the model?

Thanks, and apologies for the title, I couldn't come up with a good one.

+2  A: 

The key is to create an instance of the model class for each request, and then pass in the parts of the request you need. In this case, you probably want to pass in a base resultset. Your model will make all the database calls via $self->resultset->..., and it will "just work" for the current user. (If the current user is root, then you just pass in $schema->resultset("Foo"). If the current user is someone else, then pass in $schema->resultset("Foo")->stuff_that_can_be_seen_by($c->user). Your model then no longer cares.)

I have some slides about this, but they are very outdated:

http://www.jrock.us/doqueue-grr/slide95c.html#end

(See the stuff immediately before and after, also.)

Note that restricted resultsets and web ACLs are orthogonal. You want to make the model as tight as possible (so that your app can't accidentally do something you don't want it to, even if the code says to), but various web-only details will still need to be encoded in ACLs. ("You are not allowed to view this page." is different from "You can only delete your own objects, not everyone's". The ACL handles the first case, the restricted resultset handles the second. Even if you write $rs->delete, since the resultset is restricted, you didn't delete everything in the database. You only deleted the things that you have permission to delete. Convenient!)

jrockway
Unfortunately, stuff_that_can_be_seen_by() is non-trivial, and is not a resultset. But, your slideshow does give another answer: split the model into two parts, the Catalyst-independent part and the Catalyst-dependent part, and then passing all the non-trivial stuff isn't so bad, because its only in one spot.
derobert