http://qs321.pair.com?node_id=370263

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

First, thanks for all the replies to Structuring multiple CGI::Application modules. This rewrite is an attempt to incorporate the suggestions from that node. Note that I have studied the referenced related nodes and documentation. Unfortunately, this is also my first attempt at OOP which is probably the reason for my current problem. Either that or I've stumbled over yet another aspect of C::A that eludes me.

The problem is that although the login sub is inherited/invoked, the program seems to exit from there. By what mechanism does the app return to the original runmode invoked by the initial cgi script ('show_survey')? Do I have to store the initial value as a webapp param (if so, where? the MyApp or MyAuth module?)and then execute that runmode from the login sub? I'm pleased with the progress so far. Hopefully this is just something silly I'm overlooking. Thanks for your continued patience.

Here is the revised code:

cgi script

#!/usr/local/bin/perl use MyApp; my $webapp = MyApp->new(); $webapp->start_mode('show_survey'); $webapp->run;

authentication C::A

package MyAuth; use base 'CGI::Application'; use strict; use warnings; sub cgiapp_init { my $self = shift; # set the name of the run mode CGI param $self->mode_param('rm'); # shared run modes $self->run_modes([ 'login', 'AUTOLOAD' ]); } sub cgiapp_prerun { my $self = shift; my $runmode = shift; my $authorized = $self->param('authorized'); unless ($authorized) { $self->prerun_mode( 'login' ); } } sub login { my $self = shift; my ($error) = @_; my $output = "<p><H3>sub MyAuth::login entered</H3></p>"; my %params; $params{'ERROR'} = $error if defined($error); # load/process login template # for test purposes assume user authenticates successfully $self->param('authorized', 1); $output .= $self->dump_html(); return $output; } sub AUTOLOAD { my $self = shift; my $output = "<p><H3>sub MyAuth::AUTOLOAD entered</H3></p>"; $output .= "<p><H3>### ERROR - Invalid runmode '" . $self->mode_param('rm') . "'</H3></p>"; return $output; } 1;

survey C::A

package MyApp; use base 'MyAuth'; use strict; use warnings; sub setup { my $self = shift; my $output = "<p><H3>sub MyApp::setup entered</H3></p>"; $self->start_mode('show_survey'); $self->run_modes([ 'show_survey', 'save_survey' ]); return $output; } sub show_survey { my $self = shift; my $output = "<p><H3>sub MyApp::show_survey entered</H3></p>"; return $output; } sub save_survey { my $self = shift; my $output = "<p><H3>sub MyApp::save_survey entered</H3></p>"; return $output; } 1;

Replies are listed 'Best First'.
Re: Structuring multiple CGI::Application modules II
by dragonchild (Archbishop) on Jun 28, 2004 at 17:37 UTC
    I think you've got your design backwards. You're trying to go to the home page first. Generally, the steps are:
    1. User goes to the website.
    2. User is presented with a login page.
    3. User enters credentials (username/password, generally).
    4. User hits submit.
    5. Server validates credentials
      1. If invalid, server re-sends login page with useful-ish error message
      2. If valid, server sends home page. Server also sets cookie to indicate user has logged in successfully
    6. All other pages check for existence of cookie. If cookie isn't there, redirect to login page.

    This means that your cookie-checking method is in the base class. Your pages are in the child classes. cgiapp_prerun() will call your cookie-checking method to determine if it should redirect to the login page or not. The runmode method won't even be executed unless the cookie is there and has been verified.

    Generally, one has one C::A child to do logging in, logging out, password changes, etc. Then, you have a few other C::A children that do actual content generation. So, you'd actually have two CGI scripts - one for login and one for show_survey. Both would inherit from your baseclass.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

      Thanks for the feedback. Evidently I haven't explained what I'd like to do well enough. The code fragments provided so far do not follow the design I want or are missing pieces that I need to understand the implementation.

      I want interchangeable C::As. Following your suggestions, The ideal 'base' module would provide optional Apache::Session support and private logging functions.

      There could be several 'login' C::As. I can think of at least three different authentication methods. The 'content' C::As don't know or care what type authentication is being used, only that the user is authorized.

      The problem is, the 'login' C::As must know what 'content' C::A to redirect to. I'm assuming that this will have to be done through either C::A parameters or CGI data passed through the CGI script that invokes the 'login' C::A. What would be the appropriate place to perform the redirect to the 'content' C::A? Create a 'content' runmode in the 'login' C::A and force that runmode once the login succeeds? It would seem that I should go back to my original code from my first post and move the redirect from teardown() to the end of my validate() sub as you did in your Re: Re: Re: Why CGI::Application? example. The only difference is that mine will be a redirect to a different C::A whereas yours was internal.

      I don't think the 'content' C::As needs to redirect to 'login'. Giving a "User not authenticated" message is sufficient.

      Based on these specs I don't think cgi_prerun or cgi_init methods are really necessary in my C::As, at least not for redirection.

        The redirect can also be external. Just issue a 302 to the appropriate controller CGI script. Remember - C::A is just a fancy way of handling multiple requests in the same CGI script. If you read the rest of my replies in Why CGI::Application?, you'll see that I had some 4-5 different CGI scripts, each handling a different type of content, with a master script handling login functionality. So, do something like:
        1. Go to the login runmode.
        2. User enters credentials.
        3. The validate function does "The Right Thing"(tm). (Probably determined by a CGI parameter.)
        4. It then issues a 302 to the appropriate content-handling CGI script. This would be determined by a CGI parameter.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        I shouldn't have to say this, but any code, unless otherwise stated, is untested