Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

User authorization and design of larger web/intranet applications.

by techcode (Hermit)
on Oct 10, 2005 at 00:12 UTC ( [id://498660]=perlquestion: print w/replies, xml ) Need Help??

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

Hello Monks!

I'm about to start my college project. It will be some sort of Intranet/B2B application. On one side you have company that can add products, check orders, manage dealers ...etc. and on other side you have the dealers.

The method that I'm using at this point (for existing applications) - is to have an base class which is inherited by all modules that contain Runmodes (CGI::Application term). Among things like setting up a DB connection and configuring some of the plug ins (sessions, templates path ...) in cgiapp_prerun (method which is called after all configuration but before the specific runmode is executed) I call a method named authorize.

# Inside base module sub cgiapp_prerun { my ($self, $run_mode) = @_; unless( $self->authorize() ){ # Error $self->prerun_mode('UNAUTHORIZED'); } } sub authorize { my $self = shift; return $self->session->param('logged-in'); }
Each of modules that inherits from this one can override the authorize method in case additional checks are to be made (say if user has admin rights). Obviously methods are grouped in modules by logical relationship and they basically make one object/class on it's own.

How does this looks to you folks? What methods are you using?

I will also need to implement a bit more complicated checking as b2b application will be just part of the intranet application (kind of module/plug-in for it). So some grouping of users will need to take place, as I imagine not everyone will be able to admin everything (some admins will be admins for entire intranet, some for b2b and some for some other module).

As I haven't yet worked on projects of this size, I would appreciate any recommendations or help of any kind.

Thanks.

Have you tried freelancing? Check out Scriptlance - I work there.
  • Comment on User authorization and design of larger web/intranet applications.
  • Download Code

Replies are listed 'Best First'.
Re: User authorization and design of larger web/intranet applications.
by ph713 (Pilgrim) on Oct 10, 2005 at 02:18 UTC

    In web applications I've done in the past that had a database on the backend and needed user authentication and session management, my general scheme has been something like this: (we'll call the example web app "Acme")

    • In the database, in addition to a presumed "users" table (containing user name, user info, auth details (md5 passwd or something), etc), make a "sessions" table. It should probably key on a column called "sessionid" (which would be like a char(40) or something: long enough to hold a base64-encoded secure hash), and have as other columns "username", "ip_addr", "login_time", "last_access_time", etc...
    • Make a module called AcmeWeb::Session. Make it a part of your project standards that every CGI must use AcmeWeb::Session;, and that they must, before doing any other processing, do something like my $sess = AcmeWeb::Session->new();.
    • AcmeWeb::Session->new()'s logic goes something like this:
      my $dbh = DBI->connect(....); if(my $sidcookie = browsercookie('acme_sid')) { if(acme_sid is in the sessions table in the database) { if(the ipaddr matches current, and last_access_time doesnt indic +ate the session timed out already) { update_db_last_access_time; return $session_object; } } } if(username/password cgi parameters exist) { check user authentication against database - if it flies, create a + new session table entry (hash random numbers for the session id) and + then... send set-cookie: header to browser with the new acme_sid cookie return $session_object } print_login_page (which submits the above user/pass cgi params) exit(0); (if we made it here, don't return control to the calling sc +ript)
    • The new AcmeWeb::Session object, from the calling script's point of view, should contain accessors for the user info (full name, preferences, etc), and should contain an accessor for the database handle that AcmeWeb::Session already opened (in order to do authentication), so that the script can re-use the same handle.

    Just a broad overview of the process. I end up doing things considerably differently depending on a lot of project-specific factors, and there's a lot of details being skipped over here, esp wrt to secure programming practices in the auth/session code, but this is the general idea it always seems to boil down to.

Re: User authorization and design of larger web/intranet applications.
by pg (Canon) on Oct 10, 2005 at 01:57 UTC

    This actually brings up a very interesting OO design question. For functionality that seems to be common among classes, whether it should be method(s) in the inherit structure, or should be a seperate class on its own.

    Obviously the answer is "depend", lots of times "depends" on your perception. Let's look at two particular cases we have here: 1) authorization, and 2) DB connection.

    If I design, I will have seperate classes for each of them.

    For authorization, if the class should not be accessed, then it should not be "touched" at all. If the authorization functionality is a method of the class itself, obviously the class will be touched in order to to determine whether it should be touched, which is logically a loop hole. So a seperate class should be employed.

    For the DB connection, this is a typical good opportunity for class factory. This class factory produces classes that wrap DB connections. For other classes in your application that access the DB , there is no need for them to know the details such as how to get DB connection, or what is the maximum number of connections allowed etc, even not through inheritance. All they care is to ask the class factory for a class that represents DB connection.

      For authorization, if the class should not be accessed, then it should not be "touched" at all. If the authorization functionality is a method of the class itself, obviously the class will be touched in order to to determine whether it should be touched, which is logically a loop hole. So a separate class should be employed.
      I guess you don't know and haven't used CGI::Application?

      Anyway, the way it usually works is that you inherit from CGI::Application, and have methods in it that are runmodes (screens or however you would call it).

      With it's plug-in CGI::Application::Dispatch that I'm using whole thing becomes nice M-V-C. Based on the path (such as /index.pl/Module/method - I'm using URL rewriting so it's actually /Module/method.perl) it creates creates the Module (can control it's prefix of name-space such as AppName::Runmodex::Module). And calls method.

      Of course there are many things that are done before the method is actually called ... But in the end, one way or the other, that class is initialised ...

      For the DB connection, this is a typical good opportunity for class factory. This class factory produces classes that wrap DB connections. For other classes in your application that access the DB , there is no need for them to know the details such as how to get DB connection, or what is the maximum number of connections allowed etc, even not through inheritance. All they care is to ask the class factory for a class that represents DB connection.
      I'm using something like that : 491418. Altho I added support for multiple connections (to different DB's) and things like that. So in the end, anywhere in the application I just ask for $self->param('DB') and get DBIx::Handy object. Or I can just make new one (in case I'm in some other module so I wouldn't pass that param around the whole thing).

      Have you tried freelancing? Check out Scriptlance - I work there.
Re: User authorization and design of larger web/intranet applications.
by nothingmuch (Priest) on Oct 10, 2005 at 07:37 UTC
    On the conceptual side, look into role based authorization, and make sure authorization and authentication are not too tightly connected. This is an easy distinction to make, and will keep things more more flexible in the future.

    Here's how role based authz works: each user is a member of any number of groups, each representing a role that the user can take part in (administrator, regular user, moose hugger), and each page or area in the app simply has guards before each action (the way you manage that is dependent on your web app framework - i don't know CGI::Application) which require a certain role. (under more complicated schemes groups could contain or inherit from each other).

    The session info contains the ID of the logged in user, and the check for roles should be dynamic (so that the session doesn't have to be fried if the user's role changes).

    The code that actually requires authentication should say something like

    $authorization_obj->assert_role($user_id, "role_name"); # note that th +is object knows nothing about usernames/passwords - it assumes the us +er is a known user
    and then an exception should be thrown, going to an error handler instead of the normal code.

    An error handler around the actions should check to see if there is a login error, and redirect the user to the login page.

    If you want finer granularity, you can add exception handlers with higher locality to your application areas, but try very hard to keep things simple - if too much complexity finds it's way into the authentication system people will be sad.

    After you've made sure parts are locked out to users who shouldn't be touching them, you can start hiding irrelevant parts from the user in order to keep the interface cleaner. For this you should have a check_role method in addition to an assert_role method that doesn't throw exceptions, but just returns a true/false value.

    On the authentication side - your login handler should just check credentials and mark the logged in ID in the session. But this should have no implication whatsoever on what the user can do!

    In your code you are on the right track on making sure authorization happens all the time, but return $self->session->param('logged-in'); is just not an abstract enough check. In addition, throwing an exception will assure that the code after the exception will not occur, up to the caller's closest eval boundry - this is an easy way to be sure you're not accidentally running code that shouldn't be run.

    -nuffin
    zz zZ Z Z #!perl
Re: User authorization and design of larger web/intranet applications.
by EvanCarroll (Chaplain) on Oct 10, 2005 at 01:05 UTC
    Use Mason.

    Create /auth/autohandler, regular user authentication code.
    Create /auth/admin/autohandler, admin use authentication code.
    If authentication fails bump them out to /index.html

    Thats how the current system I'm creating now works.


    Evan Carroll
    www.EvanCarroll.com
      That isn't an option because of several reasons. Among them: I haven't worked with Mason before and don't have time to learn it and unavailability of mod_perl.
        Mason does run without mod_perl, but I wouldn't recommend it ;) Nor would anyone else using Mason most likely ;)
Re: User authorization and design of larger web/intranet applications.
by MrCromeDome (Deacon) on Oct 10, 2005 at 14:00 UTC

    cgiapp_prerun() is not the best place to do things such as creating a database connection or initializing most plugins. You'd be better off doing those things in the cgiapp_init() method of your base class. For module-specific configuration, use the setup() method. cgiapp_prerun(), however, is a good place for authen/authz checking.

    Speaking of authen/authz, I'm sure you are aware of Cees' excellent CGI::Application::Plugin::Authentication plugin. Authorization has been planned, but as far as I know he is still working on it. Check the CGI::Application mailing lists for more information.

    And, of course, you can look for CGI::App help at #cgiapp on irc.perl.org :) (I know you have)

    Good luck!
    MrCromeDome
Re: User authorization and design of larger web/intranet applications.
by johnnywang (Priest) on Oct 10, 2005 at 17:48 UTC
    Just want to say that I do exactly what you're doing with C::A, i.e., put DB connection in setup(), and authentication in cgi_prerun(). For authorization, my applications usually only have a few roles, so I usually just check some flags similar to authentication (e.g., is the admin flag set? the sub classes can decide which flag they want.)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://498660]
Front-paged by Aristotle
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (8)
As of 2025-06-19 08:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?
    erzuuliAnonymous Monks are no longer allowed to use Super Search, due to an excessive use of this resource by robots.