pileofrogs has asked for the wisdom of the Perl Monks concerning the following question:

Not Authentication, mind you.

Greeting Monks!

Is there a simple authorization module out there that anyone can recommend? I want to do simple role-based authorization (EG. you are a member of the Admin group, therefore you may Push the Big Red Button) in a Catalyst app and in a non-catalyst app. I want the two apps to be able to share the groups database, so something with an interface like Authen::Simple but with role-based authorization. EG, I would use it like this:

# ... if ( !authenticate($user,$pass) ) { die "You are not $user! Imposter!\n"; } if ( !authorized($user,'admin') ) { die "You may be $user, but you aren't allowed here!\n"; }

...i'm looking for a module to give me the 'authorized' function.

Thanks!
--Pileofrogs

Replies are listed 'Best First'.
Re: Simple Authorization?
by Your Mother (Archbishop) on Mar 18, 2009 at 18:30 UTC

    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

      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. :)

Re: Simple Authorization?
by trwww (Priest) on Mar 18, 2009 at 18:15 UTC

    Hello,

    At YAPC::NA 2007 I did a talk involving authentication and authorization using both Catalyst and CGI::Application.

    There is a link to the slides and code at http://waveright.homeip.net/yapc07/.

    Hope you find it helpful,

Re: Simple Authorization?
by locked_user sundialsvc4 (Abbot) on Mar 18, 2009 at 21:48 UTC

    Why not use LDAP? (a.k.a. Open Directory?)

    As you know, the Apache server has easy built-in support for LDAP-based rules to govern access to any portion of a website. Once the user has passed that level of authentication, this information can be reliably obtained by the application and used for authorization. Not surprisingly, there are hundreds of CPAN modules already out there...

    The overwhelming practical advantage of this scheme is that it can be centrally managed, enterprise-wide, from just one console and in one uniform way. Instead of doing “one thing one way for one app,” you do “one thing one way for all of them at once.”

      Basically, I don't want to maintain an LDAP server.

        Well then, so be it.

        Is there any other existing authentication infrastructure that is already employed elsewhere, that you can cabbage onto? Is it likely that one day there might be a dozen or so other applications out there, all of them needing the “do it just one way” capability that you described for the existing two apps?

        One thing that can be said about companies that have adopted LDAP:   they almost-uniformly didn't start out that way. They started from a cacaphony of “individually home-grown” applications and, at very-considerable expense mind you, imposed LDAP upon all of them... wishing sorely that they had done so much sooner. Perhaps this observation truly does not apply to you...

Re: Simple Authorization?
by scorpio17 (Canon) on Mar 18, 2009 at 19:59 UTC

    For non-catalyst apps, I'd recommend CGI::Application::Plugin::Authorization. It kind of assumes you're also using CGI::Application::Plugin::Authentication.