I've recently browsed through some MUSH, MOO and MUD languages, and stumbled onto the description of an interesting feature, namely having access levels within the language definition. Those access levels prevent code from being executed for a user if that user dosen't have the correct access level.

I thought to myself that this would be an interesting/funny feature to have when implementing a web interface in Zope-style (Python) or in IOWA (Ruby). These "application servers" expose objects through HTML, properties/fields become strings and the object methods become links on the page. Of course, there would be other ways to determine which methods should be exposed to which user (level), but the idea of declaring the access level together with the object method struck me as an easy way to keep the information together.

How did I implement my mock up system ? I wanted to put a keyword into the same line as the sub declaration, and the only way to get that was (as I found after some digging) through attributes. Luckily, Damian Conway provided me with an easy way to handle attributes. The attributes are a proposed Perl 6 feature which he backported to Perl 5.6 (or so I'm told), and go (for example) between the name of a sub and the code block in the declaration :

sub myFoo : myAttr { # This is sub myFoo with attribute myAttr };

When using Attribute::Handlers, you supply code that gets called whenever such a declaration is encountered by the Perl parser. This gives you Lisp-style macrology and allows you to rewrite (or rather, as I did, wrap) the attributed code. Other possible uses include tieing an attributed scalar and other weird stuff.

After I got my declaration parsed without errors, I wanted to actually act on the information I got. And again, Damian Conway comes to my rescue, this time with Hook::LexWrap. This ingenious module allows you to easily wrap your own code around any subroutine you know the name of. You can specify code to be called before and/or after any call to that routine and you can even inspect and change the parameters and return values.

Armed with these tools, it was the work of two (short) evenings to implement this system. The accesslevel attribute is introduced for an "application", and the argument to that attribute describes the maximum level a user must have to be allowed to call a method. I use a ranking system where lower access levels mean higher privileges (root has ID 0, as on unix), since this allows adding new levels more easily IMO. So I install my own wrapper around the attributed method of my "application", and check the (implied) current user level against the access level of that method and log an error if the user level is too big. Nice and easy.

Since this is all so nice and easy, there are also some warts. First, if an invocation is rejected, an exception should be raised, since there is no sensible return value to return. Second, the security is only mediocre. The security won't blow up if you manage to call a restricted method from within an unrestricted method (this still raises an access violation), but if you manage to run other, unguarded Perl code, all bets are off.

Enough talking, let's see the code !

The code implements the "security features" and a crude "application" consisting of three pages and two commands. The pages are shown through three commands, show_front, show_inside and show_admin, which show the frontpage (accessible to everybody), the inside of the "website" (accessible only to registered users) and the administration page (accessible only by ROOT). Then there are also the help and quit commands, which show help respective quit the input loop. And there is the backdoor command su [num], which allows you to change your access level on the fly.
But now for the code :

#!/usr/bin/perl -w use strict; # For our crude help system, see below use vars qw/%accessibleCommands/; { # The first package, implementing the "accesslevel" attribute. # All objects which implement security must inherit # from this package package Security; use Attribute::Handlers; use Hook::LexWrap; use vars qw/$verbose/; $verbose = 0; # This is called for each use of ":accessLevel(0)" # in a definition of a subroutine sub accessLevel :ATTR(CODE) { my ($package, $symbol, $referent, $attr, $data, $phase) = @_; my $name = *{$symbol}{NAME}; print "Adding $name => $data\n" if $verbose; $::accessibleCommands{$name} = $data; # Install our access level checker : wrap *{$symbol}, pre => sub { my ($self,@args) = @_; if ($self->{"userlevel"} <= $data) { # We will call the original routine $self->log_action($name); #$self->log_action($name,@args); # Why does this crash IndigoPerl 5.6.1, build 626 + ?? } else { $self->log_accessViolation($name,$data); # Due to lack of a better value. $_[-1] = "Access violation in $name() : Level $da +ta too large !"; # Maybe an exception should be raised instead } }; print STDERR ref($referent), " $name ($referent) was just declared ", "and ascribed the ${attr} attribute ", "with data ($data)\n", "in phase $phase\n" if $verbose; }; sub new { my $self = { userlevel => 666, username => "Hieronymous Monk", url => "<none>", }; my $classname = shift; $classname = ref($classname)||$classname; bless $self, $classname; return $self; }; sub log_accessViolation { my ($session,$symbol,$level) = @_; print "ACCESS VIOLATION on $symbol: user: ",$session->{'username'}," +/",$session->{'userlevel'}," tried ",$session->{'url'},"/$level\n" }; sub log_action { my ($session,@params) = @_; print "ACTION: user: ",$session->{username},"/",$session->{'userleve +l'},":",join("", @params); }; sub log_error { my ($session,$message) = @_; print "ERROR: user: ",$session->{'username'},"/",$session->{'userlev +el'}," $message"; }; 1; } { # This is the "application", inheriting from # the Security package package secureApp; no warnings 'redefine'; # because that's what we'll do plenty use base 'Security'; use UNIVERSAL; use constant ANONYMOUS => 666; use constant REGISTERED => 1; use constant ROOT => 0; sub export_show_inside :accessLevel(1) { my $self = shift; print "Showing the inside.\n"; # return "the inside"; # Return values are not (yet) well thought out }; sub export_show_frontpage :accessLevel(666) { my $self = shift; print "Showing the front page.\n"; }; sub export_show_admin :accessLevel(0) { my $self = shift; print "Showing the admin page.\n"; }; sub export_su :accessLevel(666) { my ($self,$newlevel) = @_; $self->{"userlevel"} = $newlevel; print "You become a new " . ((rand > 0.5) ? "wo" : "" ) . "man.\n"; }; sub export_quit :accessLevel(666) { my ($self) = @_; exit 0; }; sub export_help :accessLevel(666) { my ($self) = @_; # A really crude help system which dosen't account # for multiple classes etc. foreach (sort (keys %::accessibleCommands)) { print "$1 : $::accessibleCommands{$_}\n" if /^export_(.*)/; }; }; # The main (and blind) dispatch loop sub handle { my ($self,$command,@params) = @_; my $handler = $self->can( "export_$command" ); if ($handler) { #print "$command(", join( ",", @params ), ");\n"; $self->{"url"} = "export_$command"; &$handler( $self,@params ); } else { $self->log_error("Invalid command requested: '$command'\n"); }; }; sub new { my $self = new Security; my $classname = shift; $classname = ref($classname)|| $classname; bless $self,$classname; print "Current user level : ",$self->{userlevel},"\n"; return $self; }; 1; }; my $app = new secureApp; print "Type 'help' for commands\n"; print $app->{'userlevel'},">"; while (<>) { chomp; my ($command, @params) = split / /; $app->handle($command,@params) if /./; print $app->{'userlevel'},">"; };
perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web

In reply to An Access Level system with Attribute::Handlers and Hook::LexWrap by Corion

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.