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

Apologies if this question is not well-formed; to some extent, if I knew what the precise question was, I'd already have the answer.

Anyway, I'm working in a system that has a number of CGI::Application classes, and I'm looking at converting some older non-CGI::App code into additional ones. Some of the new run modes will require a particular kind of authentication/login and should use an interstitial login phase before proceeding to the desired content. I don't want to bundle all those run modes into one monolithic class - I'd like them divided up by what part of the system they deal with. But I don't see a good way to provide the login phase to each class. At the moment, I'm planning a mixin class to provide the login-related run_modes, but something about that feels dirty.

Any suggestions?

  • Comment on juggling multiple CGI::Application subclasses

Replies are listed 'Best First'.
Re: juggling multiple CGI::Application subclasses
by Corion (Patriarch) on Nov 06, 2007 at 08:57 UTC

    I don't use CGI::Application, but my approach would be to make the legacy code into CGI::Application::Pluggable instances and use a hacked ->cgiapp_prerun method to handle the login tangent depending on the target. "If it's in a module, it's not a hack".

    That still leaves you with The One Class though, but maybe you can turn that inside out by making your authorization step into a plugin and using that plugin from the several legacy apps.

    Disclaimer: I use basic authentication and TinyAuth to handle access to my apps, so I'm not really qualified to comment on how to put authentication into your application.

Re: juggling multiple CGI::Application subclasses
by dragonchild (Archbishop) on Nov 06, 2007 at 14:59 UTC
    You're thinking about this backwards. Instead of worrying about your class hierarchy from to top-down (base-to-child), worry about it from the perspective that you're developing at - from the bottom-up (child-to-base). Remember - CgiApp doesn't care about your base classes - it only cares about the leaves of your class tree. So, work with those as your "base" level. Then, build your classes-that-encapsulate-common-functionality from there.

    So, create a base class called My::App::WithLogin that inherits from My::App::Base which provides a cgiapp_prerun that checks for being logged in. Then, have your areas that require authn to use that. If they require a different kind of login, create another base class that inherits form My::App::WithLogin called My::App::WithSpecialLogin.

    Remember - nothing says that all your children have to inherit from the same base class. You can have a hierarchy of base classes for the leaves that are all at the same conceptual level.


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: juggling multiple CGI::Application subclasses
by shmem (Chancellor) on Nov 06, 2007 at 08:39 UTC
    First things coming to mind are Aspect and attributes, to bolt an auth phase through your runmodes.

    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: juggling multiple CGI::Application subclasses
by friedo (Prior) on Nov 06, 2007 at 14:56 UTC
    I use a base class which can check the appropriate permissions/credentials in the cgiapp_prerun phase. Here's an example where I check to see if a user has an admin bit -- all the administration modules in this app inherit from this. If the user doesn't have the right credentials, then the runmode is reset to unauthorized, which just displays a "permission denied" screen. You can use the same technique for interrupting the session with a login screen -- although for this app I use mod_auth_tkt for that purpose instead.
    package Alex2::CGI::Admin; use strict; use warnings; use base 'Alex2::CGI'; sub setup { my $self = shift; $self->run_modes( [ 'main' ] ); $self->start_mode( 'main' ); } sub cgiapp_prerun { my $self = shift; $self->SUPER::cgiapp_prerun( @_ ); $self->run_modes( [ 'unauthorized' ] ); unless( $self->user->admin or $self->user->group_admin ) { $self->prerun_mode( 'unauthorized' ); } # group admins can only use certain tools if ( $self->user->group_admin and !$self->allows_group_admin ) { $self->prerun_mode( 'unauthorized' ); } } ....
Re: juggling multiple CGI::Application subclasses
by Cubes (Pilgrim) on Nov 06, 2007 at 13:03 UTC

    Our CGI::Application app has a couple of main sections, so I made a base class for each that contains all of the housekeeping I need for that area's subclasses: setup, teardown, cgiapp_init, cgiapp_pre/postrun, etc. Our auth stuff is in cgiapp_prerun.

    In each subclass, I then "use base" the appropriate area's parent class, which gets me the auth & housekeeping stuff, override any functions (setup, for example) that need to be tweaked for the subclass, and add all the meat for the particular area of the app that subclass handles.

    (Sorry I don't have time to go into a lot of detail, but hopefully this will get you pointed in a useful direction. I'll be out of town from tomorrow through the weekend, but I'd be happy to provide more info when I return.)

    Edit: I should add that when I do the auth checks in cgiapp_prerun, I simply redirect to the login page if necessary, stashing away the intended URL in the database so the user can be redirected there after they sign in. This way I can keep the login handling routines themselves in their own tidy little module.

Re: juggling multiple CGI::Application subclasses
by saberworks (Curate) on Nov 07, 2007 at 18:33 UTC
    I am probably more familiar than most with the exact code you're talking about ;)

    Anyway, I ported/adapted CGI::Application to PHP for a project I was working on and I came to a similar conclusion as others in this thread, but I'll try to explain it a little more clearly.

    First I have CGI::Application. I subclassed that with something like: MyApplication

    From there, I have 3 different areas that require different levels of authentication.

    1: public (no auth)
    2: registered users (auth against users table or something)
    3: administrators (auth against users table + admin table or something)

    So I have three different "base" classes that all my regular CGI::Applications inherit from.

    MyApplication::Public
    MyApplication::Registered
    MyApplication::Admin

    Each of these classes have inside them the prerun modes required to authenticate the user. If the prerun mode can't authenticate, it bumps them to the login page. After login is successful, it forwards them back to page they originally tried to access.

    Then, all my various CGI::Application modules inherit from one of those 3 modules. The added benefit is that it's really easy to see at a glance what type of permission a user needs to access run modes in a specific module.