Apache2-SiteControl-1.05/0000755000076400007640000000000010470673673013674 5ustar tkaytkayApache2-SiteControl-1.05/docs/0000755000076400007640000000000010470673673014624 5ustar tkaytkayApache2-SiteControl-1.05/docs/apache0000644000076400007640000000327210361323142015752 0ustar tkaytkayApache configuration variables, and their default values/meanings: AccessControllerDebug: default 0 - Turn on/off debugging of access controller. Set to 1 to enable. AccessControllerLocks: default /tmp - The location of the lock files for the session information AccessControllerManagerFactory: default SiteControl::ManagerFactory - The name of the module to use as the permission manager factory. This package includes SiteControl::ManagerFactory, which is an abstract base. It must be overridden and supplied by the user. AccessControllerSessions: default /tmp - The location of the session files AccessControllerUserFactory: default SiteControl::UserFactory - The module that will take the user information and create some kind of user object. AuthName: default "default" - The auth name to use with apache for the set of auth parameters. See docs for apache. RadiusAccessControllerHost: default localhost - If using radius, the name of the host that is running the radius server RadiusAccessControllerSecret: default unknown - If using radius, the secret key that allows communication SiteControlMethod: default SiteControl::Radius - The module that is used to verify credentials. See SiteControl::Radius for ideas on writing your own. UserObjectSavePassword: default 0 - Should the user's password be saved in the server side session file. This is necessary for webapps that must log into other servers as that user (i.e. imap). UserObjectPasswordCipher: default CAST5 - The cipher to use for encrypting the user passwords in the session files UserObjectPasswordKey: - The key to use with the above cipher when encrypting passwords. Apache2-SiteControl-1.05/docs/README0000644000076400007640000000017310361323142015463 0ustar tkaytkayCode documentation can be found embedded in the source. This directory contains a quick reference for apache options only. Apache2-SiteControl-1.05/lib/0000755000076400007640000000000010470673673014442 5ustar tkaytkayApache2-SiteControl-1.05/lib/Apache2/0000755000076400007640000000000010470673673015705 5ustar tkaytkayApache2-SiteControl-1.05/lib/Apache2/SiteControl/0000755000076400007640000000000010470673673020152 5ustar tkaytkayApache2-SiteControl-1.05/lib/Apache2/SiteControl/Radius.pm0000644000076400007640000000501310406634363021726 0ustar tkaytkaypackage Apache2::SiteControl::Radius; use 5.008; use strict; use warnings; use Carp; use Authen::Radius; #use Apache2::Connection; #use Apache2::RequestRec; #use APR::SockAddr; sub check_credentials { my $r = shift; # Apache request object my $username = shift; my $password = shift; my $host = $r->dir_config("RadiusSiteControlHost") || "localhost"; my $secret = $r->dir_config("RadiusSiteControlSecret") || "unknown"; my $radius; # Get my IP address to pass as the # Source IP and NAS IP Address # TODO: Only works with apache 2...uncommented for now #my $c = $r->connection; #my $sockaddr = $c->local_addr if defined($c); my $nas_ip_address = undef; # $sockaddr->ip_get if defined($sockaddr); $r->log_error("WARNING: Shared secret is not set. Use RadiusSiteControlSecret in httpd.conf") if $secret eq "unknown"; $radius = new Authen::Radius(Host => $host, Secret => $secret); if(!$radius) { $r->log_error("Could not contact radius server!"); return 0; } if($radius->check_pwd($username, $password, $nas_ip_address)) { return 1; } $r->log_error("User $username failed authentication:" . $radius->strerror); return 0; } 1; __END__ =head1 NAME Apache2::SiteControl::Radius - Raduis authentication module for SiteControl =head1 SYNOPSIS In Apache/mod_perl's configuration: =over 4 PerlModule Apache2::SiteControl ... PerlSetVar SiteControlMethod Apache2::SiteControl::Radius ... ... PerlSetVar RadiusSiteControlHost "localhost" PerlSetVar RadiusSiteControlSecret "mysecret" ... ... PerlSetVar RadiusSiteControlHost "localhost" PerlSetVar RadiusSiteControlSecret "mysecret" ... =back =head1 DESCRIPTION Apache2::SiteControl::Radius uses Authen::Radius to do the actual authentication of login attempts for the SiteControl system. See the SiteControl documentation for a complete apache configuration example. The synopsis above shows the configuration parameters for the radius module only, which is not a stand-alone thing. The proper variables for the apache configuration of this modules are shown in the synopsis above. You must set the radius host and shared secret in all sections that will use the SiteControl system for authentication. =head1 SEE ALSO Apache2::SiteControl =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE =cut Apache2-SiteControl-1.05/lib/Apache2/SiteControl/UserFactory.pm0000644000076400007640000003046510470666252022761 0ustar tkaytkaypackage Apache2::SiteControl::UserFactory; use 5.008; use strict; use warnings; use Carp; use Data::Dumper; use Apache2::SiteControl::User; use Crypt::CBC; our $engine; our $encryption_key; sub init_engine { my $cipher = shift; my $key = shift; if(!defined($engine)) { $engine = Crypt::CBC->new({ key => $key, cipher => $cipher }); } } # Params: Apache request, username, password, other credentials... sub makeUser { my $this = shift; my $r = shift; my $username = shift; my $password = shift; my @other_cred = @_; my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp"; my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp"; my $mapdir = $r->dir_config("SiteControlUsermap") || ""; my $debug = $r->dir_config("SiteControlDebug") || 0; my $savePassword = $r->dir_config("UserObjectSavePassword") || 0; my $cipher = $r->dir_config("UserObjectPasswordCipher") || "CAST5"; my $key = $r->dir_config("UserObjectPasswordKey") || $encryption_key || "A not very secure key because the admin forgot to set it."; my $saveOther = $r->dir_config("UserObjectSaveOtherCredentials") || 0; my $factory = $r->dir_config("SiteControlUserFactory") || "Apache2::SiteControl::UserFactory"; my $user = undef; my %session; my $usermap; my $session_removed = 0; $r->log_error("encryption engine using key: $key") if $debug; init_engine($cipher, $key) if($savePassword); # Proper steps: # 1. Check to see if session already exists for user. If so, delete it. # 2. Create new session for user and populate it. # 3. Return the new user object. $r->log_error("Making user object for $username.") if $debug; eval { if($mapdir && -l "$mapdir/$username") { $r->log_error("$username is logging in, and already had a session. Removing old session."); $session_removed = 1; my $sid = readlink "$mapdir/$username"; unlink "$mapdir/$username"; # Remove the link unlink "$sid"; # Remove the session file } tie %session, 'Apache::Session::File', undef, { Directory => $sessiondir, LockDirectory => $lockdir }; # Remember the username to session mapping. $r->log_error("Making symlink from $sessiondir/$session{_session_id} to $mapdir/$username") if($mapdir); symlink "$sessiondir/" . $session{_session_id}, "$mapdir/$username" if($mapdir); $user = new Apache2::SiteControl::User($username, $session{_session_id}, $factory); $session{username} = $username; $session{manager} = $factory; $session{attr_password} = $engine->encrypt($password) if($savePassword); $session{attr_session_removed} = $session_removed; if(@other_cred && $saveOther) { my $i = 2; for my $c (@other_cred) { $r->log_error("Saving extra credential_$i with value $c") if $debug; $session{"attr_credential_$i"} = $c; $i++; } } $r->log_error("Created user: " . Dumper($user)) if $debug; }; if($@) { $r->log_error("Problem making new user object: $@"); return undef; } # Note: this is a half-baked user, but the controller only needs the session # id. It might be better to return the result of findUser instead. return $user; } # Params: apache request, session_key sub findUser { my $this = shift; my $r = shift; my $ses_key = shift; my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp"; my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp"; my $debug = $r->dir_config("SiteControlDebug") || 0; my $savePassword = $r->dir_config("UserObjectSavePassword") || 0; my $cipher = $r->dir_config("UserObjectPasswordCipher") || "CAST5"; my $key = $r->dir_config("UserObjectPasswordKey") || $encryption_key || "A not very secure key because the admin forgot to set it."; my %session; my $user; $r->log_error("encryption engine using key: $key") if $debug; init_engine($cipher, $key) if($savePassword); eval { tie %session, 'Apache::Session::File', $ses_key, { Directory => $sessiondir, LockDirectory => $lockdir }; # FIXME: Document the possible problems with changing user factories when # persistent sessions already exist. $user = new Apache2::SiteControl::User($session{username}, $ses_key, $session{manager}); for my $key (keys %session) { next if $key !~ /^attr_/; my $k2 = $key; $k2 =~ s/^attr_//; if($k2 eq 'password') { $user->{attributes}{$k2} = $engine->decrypt($session{$key}); } else { $user->{attributes}{$k2} = $session{$key}; } } $r->log_error("Restored user: " . Dumper($user)) if $debug; }; if($@) { # This method should fail for new logins (or login after logout), so # failing to find the user is not considered a "real" error $r->log_error("Failed to find a user with cookie $ses_key.") if $debug; return undef; } return $user; } # Apache request, user object (not name) sub invalidate { my $this = shift; my $r = shift; my $userobj = shift; my $debug = $r->dir_config("SiteControlDebug") || 0; my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp"; my $mapdir = $r->dir_config("SiteControlUsermap") || ""; my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp"; my %session; if(!$userobj->isa("Apache2::SiteControl::User") || !defined($userobj->{sessionid})) { $r->log_error("Invalid user object passed to saveAttribute. Cannot remove user."); return 0; } $r->log_error("Logging out user: " . $userobj->getUsername) if $debug; eval { unlink "$mapdir/" . $userobj->getUsername; # Remove the MSD link tie %session, 'Apache::Session::File', $userobj->{sessionid}, { Directory => $sessiondir, LockDirectory => $lockdir }; tied(%session)->delete; $r->log_error("Done with logout.") if $debug; }; if($@) { $r->log_error("Could not delete user (logout): $@"); } } # Apache request, user object, attribute name sub saveAttribute { my $this = shift; my $r = shift; my $userobj = shift; my $name = shift; my $debug = $r->dir_config("SiteControlDebug") || 0; my $sessiondir = $r->dir_config("SiteControlSessions") || "/tmp"; my $lockdir = $r->dir_config("SiteControlLocks") || "/tmp"; my %session; if(!$userobj->isa("Apache2::SiteControl::User") || !defined($userobj->{sessionid})) { $r->log_error("Invalid user object passed to saveAttribute. Attribute not saved."); return 0; } eval { tie %session, 'Apache::Session::File', $userobj->{sessionid}, { Directory => $sessiondir, LockDirectory => $lockdir }; $r->log_error("Saving attribute $name = " . $userobj->getAttribute($name) . "using Apache::Session::File.") if $debug; $session{"attr_$name"} = $userobj->getAttribute($name); untie %session; }; if($@) { $r->log_error("Failed to save user attribute: $@"); return 0; } } 1; __END__ =head1 NAME Apache2::SiteControl::UserFactory - User factory/persistence =head1 DESCRIPTION This class is responsible for creating user objects (see Apache2::SiteControl::User) and managing the interfacing of those objects with a persistent session store. The default implementation uses Apache::Session::File to store the various attributes of the user to disk. If you want to do your own user management, then you should leave the User class alone, and subclass only this factory. The following methods are required: =over 3 =item makeUser($$) This method is called with the Apache Request object, username, password, and all other credential_# fields from the login form. It must create and return an instance of Apache2::SiteControl::User (using new...See User), and store that information (along with the session key stored in cookie format in the request) in some sort of permanent storage. This method is called in response to a login, so it should invalidate any existing session for the given user name (so that a user can be logged in only once). This method must return the key to use as the browser session key, or undef if it could not create the user. =item findUser($$) This method is passed the apache request and the session key (which you defined in makeUser). This method is called every time a "logged in" user makes a request. In other words the user objects are not persistent in memory (each request gets a new "copy" of the state). This method uses the session key (which was stored in a browser cookie) to figure out what user to restore. The implementation is required to look up the user by the session key, recreate a Apache2::SiteControl::User object and return it. It must restore all user attributes that have been saved via saveAttribute (below). =item invalidate($$) This method is passed the apache request object and a previously created user object. It should delete the user object from permanent store so that future request to find that user fails unless makeUser has been called to recreate it. The session ID (which you made up in makeUser) is available from $user->{sessionid}. =item saveAttribute($$$) This method is automatically called whenever a user has a new attribute value. The incoming arguments are the apache request, the user object, and the name of the attribute to save (you can read it with $user->getAttribute($name)). This method must save the attribute in a such a way that later calls to findUser will be able to restore the attribute to the user object that is created. The session id you created for this user (in makeUser) is available in $user->{sessionid}. =back =head1 Apache Config Directives The following is a list of configuration variables that can be set with apache's PerlSetVar to configure the behavior of this class: =over 3 =item SiteControlDebug (default 0): Debug mode =item SiteControlLocks (default /tmp): Where the locks are stored =item SiteControlSessions (default /tmp): Where the session data is stored =item SiteControlUsermap (default none): Where the usernames are mapped to session files. Required if you want multiple session detection. If unset a single userid can be used to log in multiple times simultaneously. =item SiteControlUserFactory (default: Apache2::SiteControl::UserFactory) An implementation like this module. =item UserObjectSaveOtherCredentials (default: 0) Indicates that other form data from the login screen (credential_2, credential_3, etc.) should be saved in the session data. The keys will be credential_2, etc. name of the user factory to use when making user objects. These are useful if your web application has other login choices (i.e. service, database, etc.) that you need to know about at login. =item UserObjectSavePassword (default 0) Indicates that the password should be saved in the local session data, so that it is available to other parts of the web app (and not just the auth system). This might be necessary if you are logging the user in and out of services on the back end (like in webmail and database apps). =item UserObjectPasswordCipher (default CAST5) The CBC cipher used for encrypting the user passwords in the session files (See Crypt::CBC for info on allowed ciphers...this value is passed directly to Crypt::CBC->new). If you are saving user passwords, they will be encrypted when stored in the apache session files. This gives a little bit of added security, and makes the apache config the only sensitive file (since that is where you configure the key itself) instead of every random session file that is laying around on disk. There is a global variable in this package called $encryption_key, which will be used if this variable is not set. The suggested method is to set the encryption key during server startup using a random value (i.e. from /dev/random), so that all server forks will inherit the value. =item UserObjectPasswordKey The key to use for encryption of the passwords in the session files. See UserObjectPasswordCipher above. =back =head1 SEE ALSO Apache2::SiteControl::User, Apache::SiteControl::PermissionManager, Apache2::SiteControl::Rule, Apache::SiteControl =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE Apache2::SiteControl is covered by the GPL. =cut Apache2-SiteControl-1.05/lib/Apache2/SiteControl/Rule.pm0000644000076400007640000000367010406634370021413 0ustar tkaytkaypackage Apache2::SiteControl::Rule; use 5.008; use strict; use warnings; use Carp; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $this = { }; bless ($this, $class); return $this; } sub grants($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; return 0; } sub denies($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; return "Abstract rule denies everything. Do not use."; } 1; __END__ =head1 NAME Apache2::SiteControl::Rule - Permission manager access rule. =head2 DESCRIPTION Each rule is a custom-written class that implements some aspect of your site's access logic. Rules can choose to grant or deny a request. package sample::Test; use strict; use warnings; use Carp; use Apache2::SiteControl::Rule; use base qw(Apache2::SiteControl::Rule); sub grants($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; if($action eq "edit" && $resource->isa("sample::Record")) { return 1 if($user{name} eq "root"); } return 0; } sub denies($$$$) { return 0; } 1; The PermissionManager will only give permission if I one rule grants permission, I rule denies it. It is important that your rules never grant or deny a request they do not understand, so it is a good idea to use type checking to prevent strangeness. B if you expect different rules to accept different resource types or user types, since each rule is used on every access request. =head1 EXPORT None by default. =head1 SEE ALSO Apache2::SiteControl::UserFactory, Apache::SiteControl::ManagerFactory, Apache2::SiteControl::PermissionManager, Apache::SiteControl =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE =cut Apache2-SiteControl-1.05/lib/Apache2/SiteControl/GrantAllRule.pm0000644000076400007640000000347510406634374023047 0ustar tkaytkaypackage Apache2::SiteControl::GrantAllRule; use 5.008; use strict; use warnings; use Carp; use Carp::Assert; use Apache2::SiteControl::Rule; use base qw(Apache2::SiteControl::Rule); sub new { my $proto = shift; my $class = ref($proto) || $proto; my $this = { }; bless ($this, $class); return $this; } sub grants($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; return "Default is to allow"; } sub denies($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; return 0; } 1; __END__ =head1 NAME Apache2::SiteControl::GrantAllRule - A rule that grants permission to do everything. =head1 SYNOPSIS In your instance of a ManagerFactory: =over 4 use Apache2::SiteControl::GrantAllRule; ... sub getPermissionManager { ... $manager->addRule(new Apache2::SiteControl::GrantAllRule); ... return $manager; } =back =head1 DESCRIPTION Apache2::SiteControl::GrantAllRule is a pre-built rule that grants access for all permission requests. This rule can be used to help implement a system that has a default policy of allowing access, and to which you add rules that deny access for specific cases. Note that the loose type checking of Perl makes this inherently dangerous, since a typo is likely to fail to deny access. It is recommended that you take the opposite approach with your rules, since a typo will err on the side of denying access. The former is a security hole, the latter is a bug that people will complain about (so you can fix it). =head1 SEE ALSO Apache2::SiteControl::ManagerFactory, Apache::SiteControl::PermissionManager, Apache2::SiteControl::Rule =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE =cut Apache2-SiteControl-1.05/lib/Apache2/SiteControl/User.pm0000644000076400007640000001062710406634341021420 0ustar tkaytkaypackage Apache2::SiteControl::User; use 5.008; use strict; use warnings; use Carp; # This object represents a transient view of a persistent user. The UserManager # is responsible for loading/saving these things. sub new($$$$) { my $proto = shift; my $username = shift; my $sessionid = shift; my $usermanager = shift; my $class = ref($proto) || $proto; my $this = { username => $username, sessionid => $sessionid, manager => $usermanager, attributes => {} }; bless ($this, $class); return $this; } sub getUsername { my $this = shift; return $this->{username}; } # user, request, name, value sub setAttribute { my $this = shift; my $r = shift; my $name = shift; my $value = shift; $this->{attributes}{$name} = $value; eval("$this->{manager}" . '->saveAttribute($r, $this, $name)'); if($@) { $r->log_error("ERROR! FAILED TO SAVE ATTRIBUTE. SESSION WILL NOT WORK: $@"); } } sub getAttribute { my $this = shift; my $name = shift; return $this->{attributes}{$name} if defined($this->{attributes}{$name}); return undef; } # user object, apache request sub logout { my $this = shift; my $r = shift; if(!defined($this) || !defined($r)) { croak "INVALID CALL TO LOGOUT. You forgot to use OO syntax, or you forgot to pass the request object."; } eval("$this->{manager}" . '->invalidate($r, $this)'); if($@) { $r->log_error("Logout failed: $@"); } } 1; __END__ =head1 NAME Apache2::SiteControl::User - User representations =head2 SYNOPSIS my $user = Apache2::SiteControl->getCurrentUser($r); # $r is the apache request object # Checking out the user's name: if($user->getUsername eq 'sam') { ... } ... # Working with attributes (session persistent data) my $ssn = $user->getAttribute('ssn'); $user->setAttribute($r, 'ssn', '333-555-6666'); # Removing/invalidating session for the user $user->logout($r); =head2 DESCRIPTION The SiteControl system has a base concept of a user which includes the user's name, persistent attributes (which are persistent via session), and support for user logout. It is assumed that you will be working from mod_perl, and some of the methods require an Apache request object. The request object is used by some methods to coordinate access to the actual session information in the underlying system (for storing attributes and implementing logout). User objects are created by a factory (by default Apache2::SiteControl::UserFactory), so if you subclass User, you must understand the complete interaction between the factory (which is responsible for interfacing with persistence), the SiteControl, etc. The default implementation of User and UserFactory use AuthCookie to manage the sessions, and Apache::Session::File to store the various details about a user to disk. If you are using Apache2::SiteControl::User and Apache::SiteControl::UserFactory (the default and recommended), then you should configure the following parameters in your apache configuration file: # This is where the session data files will be stored SiteControlSessions directory_name # This is where the locks will be stored SiteControlLocks directory_name These two directories should be different, and should be readable and writable by the apache daemon only. They must exist before trying to use SiteControl. =head1 METHODS =over 8 =item B Get the name that the current user used to log in. =item B Get the value of a previously stored attribute. Returns undef is there is no value. =item B Add an attribute (scalar data only) to the current session. The current apache request object is required (in order to figure out the session). Future versions may support more complex storage in the session. This attribute will stay associated with this user until they log out. =item B Log the user out. If you do not pass the current apache request, then this method will log an error to the apache error logs, and the user's session will continue to exist. =back =head1 SEE ALSO Apache2::SiteControl::UserFactory, Apache::SiteControl::ManagerFactory, Apache2::SiteControl::PermissionManager, Apache::SiteControl =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE This modules is covered by the GNU public license. =cut Apache2-SiteControl-1.05/lib/Apache2/SiteControl/ManagerFactory.pm0000644000076400007640000000217410406634343023404 0ustar tkaytkaypackage Apache2::SiteControl::ManagerFactory; use 5.008; use strict; use warnings; use Carp; use Carp::Assert; sub getPermissionManager { croak "Attempt to call abstract method getPermissionManager"; } 1; __END__ =head1 NAME Apache2::SiteControl::ManagerFactory - An abstract base class to use as a pattern for custom PermissionManager production. =head1 DESCRIPTION This package is a simple abstract base class. Use it as the base for creating your instances of permission managers. For example, package MyManagerFactory; use strict; use Apache2::SiteControl::ManagerFactory; use base qw(Apache2::SiteControl::ManagerFactory); our $manager; sub getPermissionManager { return $manager if(defined($manager) && $manager->isa(Apache2::SiteControl::ManagerFactory)); $manager = new Apache2::SiteControl::PermissionManager; $manager->addRule(new XYZRule); $manager->addRule(new SomeOtherRule); return $manager; } 1; =head1 SEE ALSO Apache2::SiteControl::PermissionManager, Apache::SiteControl::Rule =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE =cut Apache2-SiteControl-1.05/lib/Apache2/SiteControl/PermissionManager.pm0000644000076400007640000001224210406634346024125 0ustar tkaytkaypackage Apache2::SiteControl::PermissionManager; use 5.008; use strict; use warnings; use Carp; sub new { my $proto = shift; my $class = ref($proto) || $proto; my $this = { rules => [] }; bless ($this, $class); return $this; } sub addRule($$) { my $this = shift; my $rule = shift; push @{$this->{rules}}, $rule; return 1; } sub can($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; my $rule; my ($granted, $denied) = (0,0); for $rule (@{$this->{rules}}) { $granted = 1 if($rule->grants($user, $action, $resource)); $denied = 1 if($rule->denies($user, $action, $resource)); } return ($granted && !$denied); } 1; __END__ =head1 NAME Apache2::SiteControl::PermissionManager - Rule-based permission management =head1 SYNOPSIS use Apache2::SiteControl::PermissionManager; $manager = new Apache2::SiteControl::PermissionManager(); $rule1 = new SomeSubclassOfSiteControl(); $manager->addRule($rule1); ... $user = new SomeUserTypeYouDefineThatMakesSenseToRules; if($manager->can($user, $action, $resource)) { # OK to do action } # For example if($manager->can($user, "read", "/etc/shadow")) { open DATA, " Decide how you want to represent a user. (i.e. Apache2::SiteControl::User) =item B<2.> Decide the critical sections of your code that need to be protected, and decide what to do if the user doesn't pass muster. For example if a screen should just hide fields, then the application code needs to reflect that. =item B<3.> Create a permission manager instance for your application. Typically use a singleton pattern (there need be only one manager). In the SiteControl system, this is done by a ManagerFactory that you write. =item B<4.> Surround sensitive sections of code with something like: if($manager->can($user, "view salary", $payrollRecord)) { # show salary fields } else # hide salary fields } =item B<5.> Create rules that spell out the behavior you want and add them to your application's permission manager. The basic idea is that a rule can grant permission, or deny it. If it neither grants or denies, then the manager will take the safe route and say that the action cannot be taken. Part of the code for the rule for protecting salaries might look like: package SalaryViewRule; use Apache2::SiteControl::Rule; use Apache2::SiteControl::User; use base qw(Apache2::SiteControl::Rule); sub grants { $this = shift; $user = shift; $action = shift; $resource = shift; # Do not grant on requests we don't understand. return 0 if(!$user->isa("Apache2::SiteControl::User") || !$this->isa("Apache2::SiteControl::Rule")); if($action eq "view salary" && $resource->isa("Payroll::Record")) { if($user->getUsername() eq $resource->getEmployeeName()) { return "user can view their own salary"; } } return 0; } Then in your subclass of ManagerFactory: use SalaryViewRule; ... $viewRule = new SalaryViewRule; $manager->addRule($viewRule); =back =head1 METHODS =over 8 =item B(I, I, I) This is the primary method of the PermissionManager. It asks if the specified user can do the specified action on the specified resource. For example, $manager->can($user, "eat", "cake"); would return true if the user is allowed to eat cake. Note that this gives you quite a bit of flexibility, but at the expense of strong type safety. It is suggested that all of your rules do type checking to insure that a rule is properly applied. =back =head1 SEE ALSO Apache2::SiteControl::Rule, Apache::SiteControl::ManagerFactory, Apache2::SiteControl::UserFactory, Apache::SiteControl =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE =cut Apache2-SiteControl-1.05/lib/Apache2/SiteControl.pm0000644000076400007640000003075210470666304020510 0ustar tkaytkaypackage Apache2::SiteControl; use 5.008; use strict; use warnings; use Carp; use Apache2::AuthCookie; use Apache::Session::File; our $VERSION = "1.05"; use base qw(Apache2::AuthCookie); our %managers = (); sub getCurrentUser { my $this = shift; my $r = shift; my $debug = $r->dir_config("SiteControlDebug") || 0; my $factory = $r->dir_config("SiteControlUserFactory") || "Apache2::SiteControl::UserFactory"; my $auth_type = $r->auth_type; my $auth_name = $r->auth_name; my ($ses_key) = ($r->headers_in->{"Cookie"} || "") =~ /$auth_type\_$auth_name=([^;]+)/; $r->log_error("Session cookie: " . ($ses_key ? $ses_key:"UNSET")) if $debug; $r->log_error("Loading module $factory") if $debug; eval "require $factory" or $r->log_error("Could not load $factory: $@"); $r->log_error("Using user factory $factory") if $debug; my $username = $r->user(); return undef if(!$username); $r->log_error("user name is $username") if $debug; my $user = undef; $factory = '$user' . " = $factory" . '->findUser($r, $ses_key)'; $r->log_error("Evaluating: $factory") if $debug; eval($factory) or $r->log_error("Eval failed: $@"); $r->log_error("Got user object: $user") if $debug && defined($user); return defined($user) ? $user : 0; } sub getPermissionManager { my $this = shift; my $r = shift; my $debug = $r->dir_config("SiteControlDebug") || 0; my $name = $r->dir_config("AuthName") || "default"; $r->log_error("AuthName is not set! Using 'default'.") if $name eq "default"; return $managers{$name} if(defined($managers{$name}) && $managers{$name}); $r->log_error("Building manager") if $debug; my $factory = $r->dir_config("SiteControlManagerFactory"); $r->log_error("Manager Factory not set!") if !defined($factory); return undef if !defined($factory); $r->log_error("Loading module $factory") if $debug; eval "require $factory" or $r->log_error("Could not load $factory: $@"); $factory = '$managers{$name}' . " = $factory" . '->getPermissionManager()'; $r->log_error("Building a manager using: $factory") if $debug; eval($factory) or $r->log_error("Evaluation failed: $@"); return $managers{$name}; } # This is the method that receives the login form data and decides if the # user is allowed to log in. sub authen_cred { my $this = shift; # Package name (same as AuthName directive) my $r = shift; # Apache request object my @cred = @_; # Credentials from login form my $debug = $r->dir_config("SiteControlDebug") || 0; my $checker = $r->dir_config("SiteControlMethod") || "Apache2::SiteControl::Radius"; my $factory = $r->dir_config("SiteControlUserFactory") || "Apache2::SiteControl::UserFactory"; my $user = undef; my $ok; # Load the user authentication module eval "require $checker" or $r->log_error("Could not load $checker: $@"); eval "require $factory" or $r->log_error("Could not load $factory: $@"); eval '$ok = ' . ${checker} . '::check_credentials($r, @cred)' or $r->log_error("authentication error code: $@"); if($ok) { eval('$user = ' . "$factory" . '->makeUser($r, @cred)'); if($@) { $r->log_error("Error reported during call to ${factory}->makeUser: $@"); } } return $user->{sessionid} if defined($user); return undef; } # This sub is called for every request that is under the control of # SiteControl. It is responsible for verifying that the user id (session # key) is valid and that the user is ok. # It returns a user name if all is well, and undef if not. sub authen_ses_key { my ($this, $r, $session_key) = @_; my $debug = $r->dir_config("SiteControlDebug") || 0; my $factory = $r->dir_config("SiteControlUserFactory") || "Apache2::SiteControl::UserFactory"; my $user = undef; eval "require $factory" or $r->log_error("Could not load $factory: $@"); $r->log_error("Attempting auth using session key $session_key") if $debug; eval { eval('$user = ' . "$factory" . '->findUser($r, $session_key)'); if($@) { $r->log_error("Error reported during call to ${factory}->findUser: $@"); } }; if($@) { $r->log_error("User tried access with invalid/nonexistent session: $@"); return undef; } return $user->getUsername if defined($user); return undef; } 1; __END__ =head1 NAME Apache2::SiteControl - Perl web site authentication/authorization system =head1 SYNOPSIS See samples/site for complete example. Note, this module is intended for mod_perl. See Apache2::SiteControl for mod_perl2. =head1 DESCRIPTION Apache2::SiteControl is a set of perl object-oriented classes that implement a fine-grained security control system for a web-based application. The intent is to provide a clear, easy-to-integrate system that does not require the policies to be written into your application components. It attempts to separate the concerns of how to show and manipulate data from the concerns of who is allowed to view and manipulate data and why. For example, say your web application is written in HTML::Mason. Your individual "screens" are composed of Mason modules, and you would like to keep those as clean as possible, but decisions have to be made about what to allow as the component is processed. SiteControl attempts to make that as easy as possible. =head2 DEVELOPER'S VIEWPOINT - EXAMPLE In this document we use HTML::Mason to create examples of how to use the control mechanisms, but any mod_perl based system should be supportable. A good mason component tries to do most of the perl processing in a separate block, so that simple substitutions can be made in HTML in the rest of the page. This makes it much easier for web developers and perl developers to co-exist on a project. The SiteControl system tries to make it possible to continue to follow this model. You obtain a user object and permission manager from the SiteControl system. These are intended to be opaque data types to the page designer, and are defined elsewhere (see USERS). The actual web page component should carry these objects around without implementing anything in the way of policy. For example, your mason component might look like this: ... % if($manager->can($currentUser, "edit", $table)) {

...

% } else {

x is <% $table->{x} %> % } <%init> my $currentUser = Apache2::SiteControl->getCurrentUser($r); my $manager = Apache2::SiteControl->getPermissionManager($r); ... application specific stuff... i.e. my $table = ... Notice that the component does not bother looking at the user object, and there is no policy code...just a request for permission: if($manager->can($currentUser, "do something to", $resource)) Of course the developer needs to know I about the underlying system. For example, the action string "do something to" is rather arbitrary. These can be anything, and must be specified as rule actions. It is recommended that you use some form of Perl constants for these instead of strings, but that is up to you. The resource is intended to be less opaque. This is likely the object that the page developer wants to muck with, and so probably knows the internals of that object a bit better. This is the crossover point from what SiteControl can figure out on its own to information you have to supply. The default behavior is for the manager to deny any request. In order for a request to be approved, someone has to write a rule that joins together the user, action, and resource and makes a decision about the permissibility of the action. If all you want is login and user tracking (but no permission manager), then it is safe to ignore the permission manager altogether. =head1 USERS Users and Rules are the central components of the SiteControl system. The user object must be Apache2::SiteControl::User (or a subclass). See Apache2::SiteControl::User for a description of what it supports (session storage, logout, etc.). The glue to SiteControl is the UserFactory, which you can define or accept the default of Apache2::SiteControl::UserFactory (recommended). Whenever a login attempt succeeds, the factory returns an object that represents a valid, logged-in user. See Apache2::SiteControl::UserFactory for more information. =head2 PERMISSION MANAGER Each site will have a permission manager. There is usually no need for you to subclass Apache2::SiteControl::PermissionManager, but you do need to create one and populate it with your access rules. You do this by creating a factory class, which looks something like this: package samples::site::MyPermissionFactory; use Apache2::SiteControl::PermissionManager; use Apache2::SiteControl::GrantAllRule; use samples::site::EditControlRule; use base qw(Apache2::SiteControl::ManagerFactory); our $manager; sub getPermissionManager { return $manager if defined($manager); $manager = new Apache2::SiteControl::PermissionManager; $manager->addRule(new Apache2::SiteControl::GrantAllRule); $manager->addRule(new samples::site::EditControlRule); return $manager; } 1; The primary goal of your factory is to produce an instance of a permission manager that knows the rules for permitting access to your site. This is an easy process that involves calling the constructor (via new) and then calling addRule one or more times. =head2 RULES The PermissionManager is the object that the site developers ask about what is allowed and what is not. As you saw in the previous section, you create a manager, and add some rules. Each rule is a custom-written class that implements some aspect of your site's access logic. Rules can choose to grant or deny a request. The following is a pretty complex example that demonstrates the features of a rule. Most rules with either specifically grant permission, or deny it. Most will not deal with both possibilities. In this example we are assuming that the user is implemented as an object that has attributes which can be retrieved with a getAttribute method (of course, you would have to have implemented that as well). The basic action that this rule handles is called "beat up", so the site makes calls like: if($referee->can($userA, "beat up", $userB)) { ... } In terms of English, we would describe the rule "If A is taller than B, then we say that A can beat up B. If A is less skilled than B, then we say that A cannot beat up B". The rule looks like this: package samples::FightRules; use strict; use warnings; use Carp; use Apache2::SiteControl::Rule; use base qw(Apache2::SiteControl::Rule); sub grants($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; if($action eq "beat up" && $resource->isa("Apache2::SiteControl::User")) { my ($h1, $h2); $h1 = $user->getAttribute("height"); $h2 = $resource->getAttribute("height"); return 1 if(defined($h1) && defined($h2) && $h1 > $h2); } return 0; } sub denies($$$$) { my $this = shift; my $user = shift; my $action = shift; my $resource = shift; if($action eq "beat up" && $resource->isa("Apache2::SiteControl::User")) { my ($s1, $s2); $s1 = $user->getAttribute("skill"); $s2 = $resource->getAttribute("skill"); return 1 if(defined($s1) && defined($s2) && $s1 < $s2); } return 0; } 1; The PermissionManager will only give permission if I one rule grants permission, I no rule denies it. I think it is clearer to separate rules like the previous one into separate rule classes altogether. A HeightMakesMightRule and a DefenseSkillRule. Splitting into two rules makes things clearer, and there is no limit to the number of rules that the PermissionManager can check. It is important that your rules never grant or deny a request they do not understand, so it is a good idea to use type checking to prevent strangeness. B if you expect different rules to accept different resource types or user types, since each rule is used on every access request. =head1 EXPORT None by default. =head1 SEE ALSO Apache2::SiteControl::UserFactory, Apache::SiteControl::ManagerFactory, Apache2::SiteControl::PermissionManager, Apache::SiteControl::Rule =head1 AUTHOR This module was written by Tony Kay, Etkay@uoregon.eduE. =head1 COPYRIGHT AND LICENSE This modules is covered by the GNU public license. =cut Apache2-SiteControl-1.05/META.yml0000644000076400007640000000076010470673673015150 0ustar tkaytkay# http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: Apache2-SiteControl version: 1.05 version_from: installdirs: site requires: Apache2::AuthCookie: 3.08 Apache2::Request: 2.05 Apache::Session::File: 1.54 Crypt::CAST5: 0.04 Crypt::CBC: 2.14 distribution_type: module generated_by: ExtUtils::MakeMaker version 6.17 Apache2-SiteControl-1.05/README.UPGRADING_TO_1.00000644000076400007640000000127210361323143017034 0ustar tkaytkayVersion 1.0 has a significant change from previous versions. AccessController is dropped in favor of a module actually called SiteControl. This makes the whole system work better in the Perl sense, and make version checking and installation smoother with CPAN. To upgrade: 1) Edit all of your modules that depend on Apache2::SiteControl::AccessController, and change them to use Apache2::SiteControl. 2) Make the same change in all of your pages. i.e.: $user = Apache2::SiteControl::AccessController->getPermissionManager($r) becomes: $user = Apache2::SiteControl->getPermissionManager($r) i.e. perl -pi'.bak' 's/SiteControl::AccessController/SiteControl/' *.html Apache2-SiteControl-1.05/README0000644000076400007640000000661110361323143014537 0ustar tkaytkayApache Notes ============ This package allows you to create a complex site authorization system where specific actions on resources are given custom rules. It extends Apache::AuthCookie to track sessions. It has been tested with Apache 1 and 2, and seems to work well. There are sample sites in the samples directory. The differences in the two apache environments require slightly different setups, so be sure to use the proper sample for your configuration. Use Apache2::SiteControl for apache 2.x. Description =========== There are two levels of control in Apache::SiteControl. The first is managed by Apache::SiteControl, and determines if a valid user has logged in. If so, it makes a user object available to the request processor. This is done using Apache::SiteControl::UserFactory (a good base implementation is already written). The underlying code associates this user with a session, and manages the browser interaction. The second level of control is supplied by an application level PermissionManager. The user objects are passed to this object, along with the requested action and an opaque resource (of any type). Rules are installed in the PM that determine if a specific action is allowed for a given user and resource. if($manager->can($currentUser, "change", $dnsrecord)) { ... } where the PM applies the various installed rules (user-defined) and returns true if the action is allowed, false otherwise. In this example, one of the rules might detect that the resource (dnsrecord) is a row from a DNS tracking table. It might then check to see if the currentUser is associated with DNS management and return true if they are, false otherwise. The top level application could then use this in a pretty abstract way. For example, if there is a generic section of code that allows users to modify a row from a table, the same code could be used: if($manager->can($currentUser, "change", $thisRecord)) { ... } The application doesn't have to figure out what thisRecord is...the rules can sense them. This allows the top-level application to be written in very generic terms, and rules to be written based on the actual logic involved. A rule might include code like this: sub grants { $this = shift; $user = shift; $action = shift; $resource = shift; if($action eq "change") { if($resource->isa("DNS::Record")) { if($resource->getContactEMail() eq $user->getAttribute("email")) { return "permission granted by DNSRule"; } } } return 0; } which would detect the proper types that it knows how to handle, and do a check that would indicate if permission is to be granted. Comments on Rules How would you make a system that allows everything, unless something is specifically denied? Have a GrantAll rule that always grants permission. Add rules that never grant, but deny on specific cases. How to make a system that denies everything except things that have been checked out: Write rules that grant on your specific cases. The default is to deny permission if no rules have anything else to say about the request. A rule can take several approaches: Relative rule: It grants but never denies. Or it denies, but never grants. Absolute rule: If it grants, then it does not deny. If it does not grant, then it denies. Read the manual pages for more information. Apache2-SiteControl-1.05/Makefile.PL0000644000076400007640000000142210470673662015643 0ustar tkaytkayuse ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( NAME => 'Apache2::SiteControl', VERSION => '1.05', PREREQ_PM => { Apache2::AuthCookie => 3.08, Apache::Session::File => 1.54, Apache2::Request => 2.05, Crypt::CBC => 2.14, Crypt::CAST5 => 0.04, # Authen::Radius => 0.10, optional }, # e.g., Module::Name => 1.1 ABSTRACT => 'An object-oriented, fine-grained site access control facility', AUTHOR => 'Tony Kay ', ); Apache2-SiteControl-1.05/Changes0000644000076400007640000000563510470666340015171 0ustar tkaytkayAug 16, 2006: Version 1.05 - Added proper code for logout (MSD was not quite complete...) Aug 14, 2006: Version 1.04 - Major performance improvement in multiple session detection. New variable (SiteControlUsermap) must be set in order to enable multiple session detection now. Mar 17, 2006: Version 1.03 - Removed Radius from dependency list July 8, 2005: Version 1.0 - Added code so that session encryption key can be set by external code. This allows a random, in-memory key to be used instead of a static one that can be compromised by reading a config file. - INCOMPATIPLE NAME CHANGE: Changed AccessController to Apache::SiteControl. This makes more sense in the CPAN sense, and will help with installation and version checking. See README.UPGRADING_TO_1.0. - The sample code should work, but it is untested. (Apache2::SiteControl is not used by the author. It is maintained by request from other users who do use it.) June 14, 2005: Version 0.46 - Added encryption when saving passwords to session files. Adds a little security, but still needs to be better. May 5, 2005: Version 0.42 - Fixed bug in saving extra user credentials to session May 1, 2005: Version 0.42 - Changed name to Apache::SiteControl - Removed radius bugfix from 0.41...needs more debugging May 1, 2005: - Fixed bug when passing extended credentials to check_credentials. Apr 29, 2005: Version 0.41 - Added the ability to pass extra "credentials" from the login page. This will allow you to choose alternatives (such as imap server for a webmail client, etc.) - Patched Radius.pm to include NAS IP Apr 20, 2005: Version 0.41 - Added ability to store password in user object. This is enabled with an Apache directive. Mar 1, 2005: Version 0.3.1 - Changed call to user factory so that all credentials are passed. This makes it possible to embed the password in the user object for things like pass-through logins. - New option to allow caching of passwords in the server side session (off by default for security reasons...see docs/apache) June 7, 2004: Version 0.3 - Now works with Apache 2/mod_perl 2 in Apache compat mode - Wrote sample sites for both apache 1 and 2. - Updated docs to reflect all the changes June 2, 2004 - Updated concepts of User, UserFactory - User now has methods for managing the session. You can add data to the session simply by calling $user->setAttribute(...), and you can get it back by calling $user->getAttribute(...) - Logouts are now done by simply calling $user->logout - UserFactory is now tightly integrated with Apache::Session::File, and the factory is now responsible for all persistence management. - Much less code need be written to start using the package. Mostly done in httpd.conf now. (You still need to write rules, and a permission factory of course). Apache2-SiteControl-1.05/sample/0000755000076400007640000000000010470673673015155 5ustar tkaytkayApache2-SiteControl-1.05/sample/samplelogin.pl0000755000076400007640000000302110361323142020001 0ustar tkaytkay#!/usr/bin/perl use strict; my $r = Apache2->request; $r->status(200); # If they tried going to a protected resource, find out what it was so we can # forward them there on success. my $uri = $r->prev->uri if($r->prev); # if there are args, append that to the uri my $args = $r->prev->args if($r->prev); if ($uri && $args) { $uri .= "?$args"; } $uri = "/sample/site/index.html" if !$uri; my $reason = $r->prev->subprocess_env("AuthCookieReason") if($r->prev); my $form = < Enter Login and Password

This is a secure document

Failure reason: '$reason'. Please enter your login and password to authenticate.

Login:
Password:
HERE $r->no_cache(1); my $x = length($form); $r->content_type("text/html"); $r->header_out("Content-length","$x"); $r->header_out("Pragma", "no-cache"); $r->send_http_header; $r->print ($form); Apache2-SiteControl-1.05/sample/index.html0000644000076400007640000000026210361323142017130 0ustar tkaytkay Sample system

Click here to login

Apache2-SiteControl-1.05/sample/apache_modperlinit.pl0000755000076400007640000000050110361323142021316 0ustar tkaytkay# This is the location of the site_control library. Not needed it you installed # Apache::SiteControl in your system paths. use lib qw(/home/tkay/src/site_control2/lib); # The location of the extra modules used in the sample. Modify to match your # extract directory use lib qw(/home/tkay/src/site_control2/sample); 1; Apache2-SiteControl-1.05/sample/SimpleAuth.pm0000644000076400007640000000050110361323142017540 0ustar tkaytkaypackage SimpleAuth; use 5.008; use strict; use warnings; sub check_credentials { my $r = shift; # Apache request object my $username = shift; my $password = shift; return 1 if($username eq 'admin' && $password eq 'test'); return 1 if($username eq 'user' && $password eq 'test'); return 0; } 1; Apache2-SiteControl-1.05/sample/EditControlRule.pm0000644000076400007640000000155710361323142020557 0ustar tkaytkaypackage EditControlRule; use Apache2::SiteControl::Rule; @ISA = qw(Apache2::SiteControl::Rule); # This rule is going to be used in a system that automatically grants # permission for everything (via the GrantAllRule). So this rule will # only worry about what to deny, and the grants method can return whatever. # Note that writing a deny-based system is inherently more dangerous and # buggy because of the lack of type-safety. Typos in the HTML components can # cause a rule to fail to deny an invalid request, which is typically less # desirable than failing to grant a request. The former is a security hole that # might get missed; the latter is a bug that gets quickly reported. sub grants($$$$) { return 0; } sub denies($$$$) { my ($this, $user, $action, $resource) = @_; return 1 if($action eq "edit" && $user->getUsername ne "admin"); return 0; } 1; Apache2-SiteControl-1.05/sample/site/0000755000076400007640000000000010470673673016121 5ustar tkaytkayApache2-SiteControl-1.05/sample/site/logout.html0000644000076400007640000000065710361323142020306 0ustar tkaytkay LOGOUT % if($success) {

You have been logged out.

% } else {

Attempt to log out failed. You were not logged in.

% } <%init> my $user = Apache2::SiteControl->getCurrentUser($r); my $success = 0; if(defined($user) && $user->isa("Apache2::SiteControl::User")) { $user->logout($r); $success = 1; } Apache2-SiteControl-1.05/sample/site/index.html0000644000076400007640000000351710361323142020102 0ustar tkaytkay Test Page

This is a sample page.

Logout

% if($manager->can($currentUser, "edit", $preferences)) {
Name:<% $currentUser %>
Favorite color:
Age: ">

% } else {
Name:<% $currentUser %>
Favorite color: <% $preferences->{"favoriteColor"} %>
Age: <% $preferences->{"age"} %>
% } <%init> my $currentUser = Apache2::SiteControl->getCurrentUser($r); my $manager = Apache2::SiteControl->getPermissionManager($r); # This would be where you would access your back-end data store to get # data...we will simulate this will literals: my $preferences = { favoriteColor => 'Red', age => 20 }; <%once> sub colorList($) { my $prefs = shift; my $result = ""; for my $color ("Red", "Green", "Blue", "Purple", "Pink") { if($color eq $prefs->{"favoriteColor"}) { $result .= "