in reply to Simple Authorization?

I would, and have for one project, set-up a DBIC family/schema exactly as normal and related in the various DBIC (and Authn/Authz Cat) docs; MyApp::Schema::User, MyApp::Schema::Role, and MyApp::Schema::UserRole. And then you just add a method to the user class. E.g. (semi-tested):

# In MyApp::Schema::Class =item B<has_role> Takes a role object, an id, or a name. =cut sub has_role : method { my $self = shift; my $role = shift || return; if ( blessed $role ) { return first { $role->id == $_->id } $self->roles; } elsif ( $role =~ /\A\d+\z/ ) { return first { $role == $_->id } $self->roles; } else { return first { $role eq $_->name } $self->roles; } return; }

I don't think that the snippet above relies on this but I find it handy for the the Role to stringify to its name instead of its id.

# in MyApp::Schema::Role use overload '""' => sub { return +shift->name; }, fallback => 1;

Then you can-

if ( $user->has_role("admin") ) { # authorized for admin stuff... }

Remember, the whole point of the MyApp::Schema class in Cat apps is to have schema code that is not married to Catalyst. So any of the examples along those lines are fine for a non-Cat application.

Update: I would do this with the modern namespacing. It allows for much more complex behavior while keeping the code quite clean.

MyApp::Schema::Result::User MyApp::Schema::Result::Role MyApp::Schema::Result::UserRole

Replies are listed 'Best First'.
Re^2: Simple Authorization?
by pileofrogs (Priest) on Mar 18, 2009 at 19:01 UTC

    I'm looking for something that I can also easily apply to a non-Catalyst app. If what you've said included that information, I didn't understand it.

    Said another way, I have two apps. One uses Catalyst, the other does not. They both will use the same users and authorization schemes. So, a user of type 'admin' in app 1 is guaranteed to be a user of type admin in app 2. I want a module that will let me add has_role to my user object in Cat, as above but also lets me call a simple has_role function or similer in a non-Catalyst app.

    Maybe that's implied in DBIC? I haven't used DBIC beyond the Catalyst tutorial.

      It would work perfectly well without Cat, with, as a command line tool, in CGI::Application, as the data source of a standalone CGI service. Anything. This is the best practice way of doing schemas in Cat precisely because the schema should not be tied to the web application. It's entirely reusable this way.

      I don't have time right now to cook up a full tutorial so this isn't complete (a better example would have working code to do it all in SQLite or something) but it's pretty close. I know DBIC can be hard to hash out but the info is out there and the list is friendly as long as you don't ask about "DBIx" when MST's around. :)

      1. You need a user table, a role table, and user_role join table to work with this particular approach; there are other ways to do it but this is flexible and what I'd call best practice (compared to a role column in the user table for example which is easy to implement but has huge limitations you'll hit quickly and require spaghetti to solve).
      2. The minimum necessary table layout is approximately-
        CREATE TABLE `user` ( `id` int unsigned NOT NULL auto_increment, `username` VARCHAR(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `role` ( `id` int unsigned NOT NULL auto_increment, `name` VARCHAR(25) NOT NULL UNIQUE, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `user_role` ( `user` int unsigned NOT NULL, `role` int unsigned NOT NULL, PRIMARY KEY (`user`,`role`), FOREIGN KEY (`user`) REFERENCES user(id), FOREIGN KEY (`role`) REFERENCES role(id) ) ENGINE=InnoDB;
      3. Create your DBIC schema. There is a good stub here: Re: Switch from DBI to DBIx::Class: thoughts?
      4. In your DBIC schema files, if you followed the above example, you will find these classes/pms were created-
        MyApp::Schema::Result::User MyApp::Schema::Result::Role MyApp::Schema::Result::UserRole
      5. Add to MyApp/Schema/Result/User.pm beneath the "DO NOT MODIFY this" line. If you do so, you can regenerate your schema files automatically while preserving any code you've added-
        # Created by DBIx::Class::Schema::Loader v0.04999_06 @ 2009-02-28 19:1 +4:54 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:iS0oJWk28gjL7QD50cdRqQ no warnings "uninitialized"; use Scalar::Util "blessed"; use List::Util "first"; __PACKAGE__->many_to_many( roles => "user_roles", "role" ); sub has_role : method { my $self = shift; my $role = shift || return; if ( blessed $role ) { return first { $role->id == $_->id } $self->roles; } elsif ( $role =~ /\A\d+\z/ ) { return first { $role == $_->id } $self->roles; } else { return first { $role eq $_->name } $self->roles; } return; }
      6. (Optional, I think, but makes some things much more DWIM) Add to MyApp/Schema/Result/Role.pm beneath the "DO NOT MODIFY this" line-
        use overload '""' => sub { return +shift->name; }, fallback => 1;
      7. Now, you connect the schema like you would anywhere. Catalyst uses its Model space to abstract this a bit. Since we're not in Cat, we'll do it manually-
        my $attr = { RaiseError => 1, AutoCommit => 1, ChopBlanks => 1, }; my $schema = MyApp::Schema->connect ( "dbi:mysql:db_name", $user, $pass, $attr ); # Just grab one- my $user = $schema->resultset("User") ->search({},{ order_by => 'RAND()' })->first; print "User ", $user->username, " has these roles: ", join(", ", $user->roles) || "none!", "\n";

      And you're good to go. Wherever. :)

        Wow.. I am not worthy of such an answer as this...

        ++

        I'm a bit fuzzy on step 7. What do I need to use/load so that MyApp::Schema->connect means something?

        Truly, I am not worthy..

        Please Ignore...

        I found the MyApp::Schema module sitting there right in front of me. Duh..

        Carry on...