| CGI-Builder-Auth documentation | Contained in the CGI-Builder-Auth distribution. |
CGI::Builder::Auth::RealmManager - Manage HTTPD server security realms
use CGI::Builder::Auth::RealmManager;
# open up the database (could also use CGI::Builder::Auth::Realm::connect())
$database = CGI::Builder::Auth::RealmManager->open(-config_file=>'/home/httpd/conf/realms.conf',
-realm=>'members',
-writable=>1);
# List functions
@users = $database->users();
@groups = $database->groups();
# Information about users and groups:
print "Lincoln's groups are @group\n"
if @group = $database->group(-user=>'lincoln');
print "Lincoln is a subscriber\n"
if $database->match_group(-user=>'lincoln',-group=>'subscribers');
print "Lincoln's password is $pass\n"
if $pass = $database->passwd(-user=>'lincoln');
print "Intruder alert.\n"
unless $database->match_passwd(-user=>'lincoln',
-password=>'xyzzy');
$lincoln_info = $database->get_fields(-user=>'lincoln');
print "Lincoln's full name is $lincoln_info->{Name}\n";
print "The subscribers are @members.\n"
if @members = $database->members(-group=>'subscribers');
# Database updates
print "Added Fred.\n"
if $database->set_passwd(-user=>'fred',
-password=>'sssh!',
-fields=>{Name=>'Fred Smith',
Nationality=>'French'});
print "Assigned Fred to 'subscribers' and 'VIPs'\n"
if $database->set_group(-user=>'fred',
-group=>['subscribers','VIPs']);
print "Changed Fred's nationality.\n"
if $database->set_fields(-user=>'fred',
-fields=>{Nationality=>'German'});
print "Fred is now gone.\n"
if $database->delete_user(-user=>'fred');
print "VIP group is now gone.\n"
if $database->delete_group(-group=>'VIPs');
print "Uh oh. An error occurred: ",$database->errstr,"\n"
if $database->errstr;
Pay no attention to that man behind the curtain! Move along, nothing to see here!
This module was originally part of the HTTPD-User-Manage collection, which is available on CPAN. If you want to use it, go download that package. This module is used as part of the internal implementation of CGI::Builder::Auth. The original documentation is preserved here in this release for historical purposes. The software has been hacked and this documentation is not guaranteed to be correct. The module may disappear from the CGI::Builder::Auth distribution in a future release. Do not use it directly or rely on it.
CGI::Builder::Auth::RealmManager provides a high-level, unified view onto the many access control databases used by Apache, Netscape, NCSA httpd, CERN and other Web servers. It works hand-in-hand with CGI::Builder::Auth::Realm, which provides access to a standard configuration file for describing security database setups. Internally, CGI::Builder::Auth::Realm uses Doug MacEachern's HTTPD-Tools modules for database access and locking.
Important note: Do not use these modules to adjust the Unix password or group files. They do not have the same format as the Web access databases.
CGI::Builder::Auth::RealmManager provides the following class and instance methods.
$db = CGI::Builder::Auth::RealmManager->new(-realm => $realm_def,
-writable => 1,
-mode => 0644,
-server => 'ncsa');
The new() class method creates a new connection to the database indicated by the indicated CGI::Builder::Auth::RealmDef object. Ordinarily it will be more convenient to use the open() method (below) or CGI::Builder::Auth::RealmDef::connect(). Only the -realm argument is mandatory. The others are optional and will default to reasonable values.
If successful, the function result is an CGI::Builder::Auth::RealmManager object, which you can treat as a handle to the database.
Arguments:
-realm Realm definition. See CGI::Builder::Auth::Realm(3). -writable If true, open database for writing. -mode Override file creation mode. -server Override server type.
$db = CGI::Builder::Auth::RealmManager->open(-realm => 'subscribers',
-config_file => '/home/httpd/conf/realms.conf',
-writable => 1,
-mode => 0644,
-server => 'ncsa');
The open() class method creates a new connection to the database given the indicated configuration file and realm name. Internally it first creates an CGI::Builder::Auth::Realm object, then queries it for the named realm, passing this result to new(). This is probably the easiest way to create a new connection.
Only the -realm and -config_file arguments are mandatory. The others are optional and will default to reasonable values.
If successful, the function result is an CGI::Builder::Auth::RealmManager object, which you can treat as a handle to the database.
Arguments:
-config_file Path to realm configuration file. See CGI::Builder::Auth::Realm(3). -realm Realm name. -writable If true, open database for writing. -config An alias for -config_file. -mode Override file creation mode. -server Override server type.
$db->close()
When you are done with the database you should close() it. This will commit changes and tidy up.
@users = $db->users();
Return all users known to this database as a (potentially very long) list.
@groups = $db->groups()
Return all groups known to this database as a (potentially very long) list.
@groups = $db->group(-user=>'username'); $boolean = $db->group(-user=>'username',-group=>'groupname');
This method returns information about the groups that a user belongs to. Called with the name of the user only, it returns a list of all the groups the user is a member of. Called with both a user and a group name, it returns a boolean value indicating whether the user belongs to the group.
Arguments:
-user Name of the user -group Name of the group -name An alias for -user -grp An alias for -group
You can also call this method with the positional parameters (user,group), as in:
@groups = $db->group('username');
$boolean = $db->match_group(-user=>'username',-group=>'groupname');
This method is identical to the group() function, except that it requires a group name to be provided.
$password = $db->passwd(-user=>'username'); $boolean = $db->passwd(-user=>'username',-password=>'password');
Called with a user name this method returns his encrypted password. Called with a user name and an unencrypted password, this routine returns a boolean indicating whether this password matches the stored password.
Arguments:
-user Name of the user -password Password to check against (optional) -name Alias for -user -passwd Alias for -password
You can also call this routine using positional arguments, where the first argument is the user name and the second is the password to try:
print "You're OK" if $db->password('Fred','Open sesame');
$password = $db->password(-user=>'username'); $boolean = $db->password(-user=>'username',-password=>'password');
An alias for passwd(), just to make things interesting.
$boolean = $db->match_passwd(-user=>'username',-password=>'password');
This method is identical to the two-argument form of passwd(), except that it requires a trial password to be provided.
$boolean = $db->match(-user=>'username',-password=>'password');
This method is an alias for match_passwd(), just to make things interesting.
$fields = $db->get_fields(-user=>'username',
-fields=>\@list_of_fields);
The user database can contain additional information about the user in the form of name/value pairs. This method provides access to these "additional fields."
get_fields() accepts a user name and optionally a list of the fields to retrieve. If no field list is provided, it will retrieve all fields defined in the security realm configuration file (see CGI::Builder::Auth::Realm (3)). In practice, it is rarely necessary to limit the fields that are retrieved unless you are accessing a SQL database table containing an unusually large number of fields.
Arguments:
-user Name of user -fields List reference to fields to retrieve (optional) -name Alias for -user -field Alias for -fields
The return value is a hash reference. You can retrieve the actual values like this:
$fields = $db->get_fields(-user=>'fred');
print $fields->{'Full_Name'};
$resultcode = $db->set_passwd(-user=>'username',
-password=>'password',
-fields=>\%extra_fields);
set_passwd() sets the user's password and/or additional field information. If the user does not already exist in the database, he is created. The method requires the username and one or more of the new password and a hash reference to additional user fields. If either the password or the additional fields are absent, they will be unchanged.
When setting field values, the old and new values are merged. To delete a previous field value, you must explicitly set it to undef in the hash reference. Otherwise the previous value will be retained.
A result code of true indicates a successful update. The method will fail unless the database is opened for writing.
Arguments:
-user Name of the user to update
-password New password
-fields Hash ref to the fields to update
-name Alias for -user
-passwd Alias for -password
-gcos Alias for -fields
$resultcode = $db->set_password(-user=>'username',
-password=>'password',
-fields=>\%extra_fields);
This is an alias for set_passwd(), just to make life interesting.
$resultcode = $db->set_fields(-user=>'username',
-fields=>\%extra_fields);
set_fields() allows you to adjust the extra field information about the designated user. Its functionality is identical to set_passwd(), but the name is a little more appropriate. This method requires a user name and a hash reference containing new field values.
When setting field values, the old and new values are merged. To delete a previous field value, you must explicitly set it to undef in the hash reference. Otherwise the previous value will be retained.
Arguments:
-user Name of the user to update
-fields Hash ref to the fields to update
-name Alias for -user
-gcos Alias for -fields
A true result code indicates that the database was successfully updated. The database must be writable for this method to succeed.
$resultcode = $db->set_group(-user=>'username',
-group=>\@list_of_groups);
This method allows you to set the list of groups that a user belongs to without changing any other information about him or her. It expects a user name and a list reference pointing to the groups to assign the user to. The user will be removed from any groups he previously participated in.
Arguments:
-user Name of the user to update
-group List of groups to assign user to
-name Alias for -user
-grp Alias for -group
A true result code indicates that the database was successfully updated. The database must be writable for this method to succeed.
$resultcode = $db->delete_user(-user=>'username');
Delete the user and all his associated information from the database. If there are any empty groups after this deletion, they are removed as well. This operation is irreversible.
Arguments:
-user Name of the user to remove
-name Alias for -user
A true result code indicates that the database was successfully updated. The database must be writable for this method to succeed.
You may also call this method with a single positional argument:
$resultcode = $db->delete_user('username');
$resultcode = $db->delete_group(-group=>'groupname');
Delete the group from the database. Users who participate in the deleted group are not deleted. However, they may find themselves orphaned (not participating in any groups).
Arguments:
-group Name of the user to remove
-grp Alias for -group
A true result code indicates that the database was successfully updated. The database must be writable for this method to succeed.
You may also call this method with a single positional argument:
$resultcode = $db->delete_group('groupname');
$error = $db->errstr();
This method returns a string describing the last error encountered by RealmManager.pm. It is not reset by successful function calls, so its contents are only valid after a method returns a false result code.
CGI::Builder::Auth::Realm(3) CGI::Builder::Auth::UserAdmin(3) CGI::Builder::Auth::GroupAdmin(3), CGI::Builder::Auth::Authen(3)
Lincoln Stein <lstein@cshl.org>
Copyright (c) 1997, Lincoln D. Stein
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| CGI-Builder-Auth documentation | Contained in the CGI-Builder-Auth distribution. |
package CGI::Builder::Auth::RealmManager; use strict; require Exporter; use vars qw(@ISA @EXPORT $VERSION $ERROR); @ISA = 'Exporter'; @EXPORT = qw(rearrange); $ERROR = ''; $VERSION = 1.33; use Carp; require CGI::Builder::Auth::Realm; sub open { my $class = shift; my ($realm,$config,$rest) = rearrange([ 'REALM',['CONFIG_FILE','CONFIG']],@_); croak "Must provide the path to a config file" unless -r $config; my $realms = new CGI::Builder::Auth::Realm(-config_file=>$config,%$rest); return undef unless $realms; my $r = $realms->realm($realm); return undef unless $r; return $realms->dbm(-realm=>$r,%$rest); } sub new { my $class = shift; my ($realm,$mode,$writable,$server) = rearrange([ 'REALM', 'MODE', ['WRITE','WRITABLE'], 'SERVER' ],@_); croak "Must provide a valid realm object" unless $realm && ref($realm); my $self = { 'realm'=>$realm, 'mode'=>$mode || 0644, 'writable'=>$writable, 'server'=>$server, }; bless $self,$class; return undef unless $self->open_passwd; return undef unless $self->open_group; return $self; } sub open_passwd { my $self = shift; my $realm = $self->{realm}; # Create the right kind of CGI::Builder::Auth::UserAdmin and CGI::Builder::Auth::GroupAdmin objects. my %params; $params{DB} = $realm->userdb; $params{Encrypt} = $realm->crypt; $params{Server} = $self->{server}; $params{Flags} = $self->{writable} ? 'rwc' : 'r'; $params{Mode} = $self->{mode}; $params{Locking} = $self->{writable}; if ($realm->crypt() =~ /MD5/) { $params{Encrypt} = 'MD5'; $self->{digest}++; } my $userType = $realm->usertype; CASE: { do { $params{DBType} = 'Text'; next; } if $userType=~/text|file/i; do { $params{DBType} = 'DBM'; $params{DBMF} = "\U$userType\E"; $params{DBMF} = 'NDBM' if $params{DBMF} eq 'DBM'; next; } if $userType =~ /^(NDBM|GDBM|DB|DBM|SDBM|ODBM)$/; do { my $p = $realm->SQLdata; $params{DB} = $p->{database}; $params{Host} = $p->{host} eq 'localhost' ? '' : $p->{host}; $params{DBType} = 'SQL'; $params{Driver} = $realm->driver; # # do what Lincoln didn't: $params{User} = $p->{dblogin}; $params{Auth} = $p->{dbpassword}; $params{DEBUG} = 0; # $params{UserTable} = $p->{usertable}; $params{NameField} = $p->{userfield}; $params{PasswordField} = $p->{passwdfield}; next; } if $userType=~/sql/i; } my $return = eval <<'END'; use CGI::Builder::Auth::UserAdmin; $self->{userDB} = new CGI::Builder::Auth::UserAdmin(%params); END ; print STDERR $ERROR = $@ unless $return; return $return; } sub errstr { return $ERROR; } sub open_group { my $self = shift; my $realm = $self->{realm}; return 1 unless $realm->groupdb; my %params; $params{DB} = $realm->groupdb; $params{Server} = $self->{server}; $params{Flags} = $self->{writable} ? 'rwc' : 'r'; $params{Mode} = $self->{mode}; $params{Locking} = $self->{writable}; my $groupType = $realm->grouptype; CASE: { do { $params{DBType} = 'Text'; next } if $groupType=~/text|file/i; do { $params{DBType} = 'DBM'; $params{DBMF} = "\U$groupType\E"; $params{DBMF} = 'NDBM' if $params{DBMF} eq 'DBM'; next } if $groupType =~ /^(NDBM|GDBM|DB|DBM|SDBM|ODBM)$/; do { my $p = $realm->SQLdata; $params{DB} = $p->{database}; $params{Host} = $p->{host} eq 'localhost' ? '' : $p->{host}; $params{DBType} = 'SQL'; $params{Driver} = $realm->driver; # # do what Lincoln didn't: $params{User} = $p->{dblogin}; $params{Auth} = $p->{dbpassword}; $params{DEBUG} = 0; # $params{GroupTable} = $p->{grouptable}; $params{NameField} = $p->{groupuserfield} || $p->{userfield}; $params{GroupField} = $p->{groupfield}; $params{UserTable} = $p->{usertable}; # needed for obscure reasons next; } if $groupType=~/sql/i; } my $return = eval<<'END'; use CGI::Builder::Auth::GroupAdmin 1.50; $self->{groupDB} = new CGI::Builder::Auth::GroupAdmin(%params); END ; $ERROR = $@ unless $return; return $return; } sub users { my $self = shift; return $self->{userDB}->list(); } # Return true if a user is in a particular group sub match_group { my $self = shift; my ($user,$group) = rearrange([['USER','NAME'],['GROUP','GRP']],@_); croak "Must provide a user name" unless $user; croak "Must provide a group name" unless $group; return undef unless $self->{groupDB}; # Slightly different if we're using a DBM file. # Result of inconsistencies in CGI::Builder::Auth::GroupAdmin my %users; grep ($users{$_}++,$self->{groupDB}->list($group)); return $users{$user}; } sub open_writable { my $self = shift; return 1 if $self->{writable}; $self->{writable}++; if ($self->{userDB}) { $self->{userDB}->commit(); $self->{userDB}->close(); unless ($self->open_passwd()) { $ERROR = "Unable to open user file for writing"; return undef; } } if ($self->{groupDB}) { $self->{groupDB}->commit(); $self->{groupDB}->close(); unless ( $self->open_group() ) { $ERROR = "Unable to open group file for writing"; return undef; } } 1; } sub set_passwd { my $self = shift; my ($user,$passwd,$otherfields) = rearrange([[qw(USER NAME)],[qw(PASSWORD PASSWD)],[qw(OTHER GCOS FIELDS VALUES)] ],@_); croak "Must provide a user ID" unless $user; croak "Must provide a password or field values" unless $passwd || $otherfields; return undef unless $self->{userDB}; # reopen if necessary return undef unless $self->open_writable(); #special passwords for the digest method $passwd = "$user:$self->{realm}:$passwd" if $passwd && $self->{digest}; my @other = (); my $result; if (defined($otherfields)) { @other = ref($otherfields) eq 'ARRAY' ? @$otherfields : ($otherfields) ; } if ($self->{userDB}->exists($user)) { # nasty hack here to avoid problems in the way that UserAdmin does its # updates (first it deletes, then it adds!) my($crypt) = ''; unless ($passwd) { ($crypt,$self->{userDB}->{ENCRYPT}) = ($self->{userDB}->{ENCRYPT},'none'); $passwd = $self->passwd($user); } @other = $self->get_fields($user) unless @other; ($result,$ERROR) = $self->{userDB}->update($user,$passwd,@other); $self->{userDB}->{ENCRYPT} = $crypt if $crypt; return $result unless $result; } else { ($result,$ERROR) = $self->{userDB}->add($user,$passwd,@other); return $result unless $result; } ($result,$ERROR) = $self->{userDB}->commit(); return $result; } sub set_password { &set_passwd; } sub set_fields { my $self = shift; my ($user,$fields) = rearrange([[qw(USER NAME)],[qw(OTHER GCOS FIELD FIELDS VALUES)] ],@_); croak "Must provide a user ID" unless $user; croak "Must provide field values" unless $fields; my $current = $self->get_fields(-user=>$user); foreach (keys %$fields) { $current->{$_} = $fields->{$_}; } return $self->set_passwd(-user=>$user,-fields=>$current); } # return true if passwords match sub match_passwd { my $self = shift; my ($user,$passwd) = rearrange([[qw(USER NAME)],[qw(PASSWD PASSWORD)]],@_); croak "Must provide a user ID" unless $user; croak "Must provide a password" unless $passwd; return undef unless $self->{userDB}->exists($user); $passwd = "$user:$self->{realm}:$passwd" if $self->{digest}; my $stored_passwd = $self->{userDB}->password($user); if ($self->{userDB}->{ENCRYPT} eq 'crypt') { return crypt($passwd,$stored_passwd) eq $stored_passwd; } else { return $self->{userDB}->encrypt($passwd) eq $stored_passwd; } } # shortcut for match_passwd sub match { &match_passwd; } sub passwd { my $self = shift; my ($user,$passwd) = rearrange([[qw(USER NAME)],[qw(PASSWORD PASSWD)]],@_); croak "Must provide a user ID" unless $user; if ($passwd) { return $self->match_passwd('-user'=>$user,'-passwd'=>$passwd) }; return undef unless $self->{userDB}->exists($user); my (@pw) = split(/:/,$self->{userDB}->password($user)); return $pw[1] if $self->{digest}; return $pw[0]; } sub password { &passwd; } sub delete_user { my $self = shift; my ($user) = rearrange([[qw(USER NAME)]],@_); croak "Must provide a user ID" unless $user; return undef unless $self->open_writable(); $self->{userDB}->delete($user) if $self->{userDB}; return unless $self->{groupDB}; my $group; foreach $group ($self->{groupDB}->list) { $self->{groupDB}->delete($user,$group); } my $result; ($result,$ERROR) = $self->{groupDB}->commit(); return $result unless $result; ($result,$ERROR) = $self->{userDB}->commit(); return $result; } # With one argument returns the groups that the user is in. # With two arguments returns true if user is in the group sub group { my $self = shift; my ($user,$group) = rearrange([[qw(USER NAME)],[qw(GROUP GRP)]],@_); croak "Must provide a user ID" unless $user; if ($group) { return $self->match_group('-user'=>$user,'-group'=>$group) }; return () unless my $db = $self->{groupDB}; # Shortcut to avoid doing and undoing unnecessary work. if (ref($db)=~/DBM::apache/) { # check for Apache's weird combined user/group database format return $self->{groupDB}->{DB} eq $self->{userDB}->{DB} ? split(',',(split(':',$db->{'_HASH'}->{$user}))[1]) : split(',',$db->{'_HASH'}->{$user}); } my ($g,%groups); foreach $g ($self->{groupDB}->list) { my %user; grep($user{$_}++,$self->{groupDB}->list($g)); $groups{$g}++ if $user{$user}; } return keys %groups; } sub groups { my $self = shift; return () unless $self->{groupDB}; return $self->{groupDB}->list(); } sub members { my $self = shift; my ($group) = rearrange([[qw(GROUP GRP)]],@_); $group || croak "Must provide a group name"; return () unless $self->{groupDB}; return $self->{groupDB}->list($group); } sub set_group { my $self = shift; my ($user,$groups) = rearrange([[qw(USER NAME)],[qw(GROUP GRP)]],@_); croak "Must provide a user ID" unless $user; my $db; # reopen if necessary return undef unless $self->open_writable(); return unless $db = $self->{groupDB}; my (@groups) = ref($groups) ? @$groups : ($groups); # Shortcut to avoid doing and undoing work. if (ref($db)=~/DBM::apache/) { $db->{'_HASH'}->{$user}=join(',',@groups); $self->remove_dangling_groups(); return 1; } # otherwise we do it the "correct" way my (%current,%new); grep ($current{$_}++,$self->group($user)); grep ($new{$_}++,@groups); my (@to_remove) = grep (!$new{$_},keys %current); my (@to_add) = grep (!$current{$_},keys %new); foreach (@to_remove) { $db->delete($user,$_); } foreach (@to_add) { $db->add($user,$_); } $self->remove_dangling_groups(); my $result; ($result,$ERROR) = $db->commit(); return $result; } sub delete_group { my $self = shift; my ($group) = rearrange([[qw(GROUP GRP)]],@_); $group || croak "Must provide a group name"; return 1 unless $self->{groupDB}; return undef unless $self->open_writable(); $self->{groupDB}->remove($group); my $result; ($result,$ERROR) = $self->{groupDB}->commit(); return $result; } sub remove_dangling_groups { my $self = shift; my $grp; foreach $grp ($self->groups) { next unless $grp; $self->delete_group($grp) unless $self->members('-group'=>$grp); } } # Fetch field names from a SQL database. # Only those fields that are returned by fields() are accessible. # The return value is an associative array in which the keys are the # field names and the values are the field types (s=string, i=integer, f=real). sub fields { my $realm = shift->{realm}; my $fields; return () unless $fields = $realm->fields; my @f = split(/\s+/,$fields); my %f; foreach (@f) { my($name,$type) = split(':',$_,2); $f{$name} = $type || 's'; # string by default } return %f; } # Fetch the named fields from an SQL database. # Input is a user ID and a reference to a list of field names. All fields will be # returned if no list specified. # The return value is a hash of the fields, or a reference to the hash in a scalar # context. sub get_fields { my $self = shift; my ($user,$fields) = rearrange([[qw(USER NAME)],[qw(FIELDS FIELD VALUE VALUES)]],@_); croak "Must provide a user ID" unless $user; my (%ok) = $self->fields; my (@fields); if (defined($fields)) { @fields = grep ($ok{$_},@$fields); } else { @fields = keys %ok; } $self->{userDB}->fetch($user,@fields); } sub error { return $ERROR; } sub close { my $self = shift; do { $self->{userDB}->commit; $self->{userDB}->close() } if $self->{userDB}; do { $self->{groupDB}->commit; $self->{groupDB}->close() } if $self->{groupDB}; } sub DESTROY { my $self = shift; $self->close; } # -------- exported utility routine ---------- sub rearrange { my($order,@param) = @_; return () unless @param; return @param unless (defined($param[0]) && substr($param[0],0,1) eq '-'); my $i; for ($i=0;$i<@param;$i+=2) { $param[$i]=~s/^\-//; # get rid of initial - if present $param[$i]=~tr/a-z/A-Z/; # parameters are upper case } # make sure param has even number of elements push(@param,'') if ((@param) && ($#param % 2 == 0)); my(%param) = @param; # convert into associative array my(@return_array); local($^W) = 0; my($key)=''; foreach $key (@$order) { my($value); if (ref($key) eq 'ARRAY') { foreach (@$key) { last if defined($value); $value = $param{$_}; delete $param{$_}; } } else { $value = $param{$key}; delete $param{$key}; } push(@return_array,$value); } push (@return_array,{%param}) if %param; return (@return_array); } sub realm { return shift->{realm}; } 1; __END__