diff options
Diffstat (limited to 'perl/lib/Wallet/Object/Base.pm')
-rw-r--r-- | perl/lib/Wallet/Object/Base.pm | 1052 |
1 files changed, 1052 insertions, 0 deletions
diff --git a/perl/lib/Wallet/Object/Base.pm b/perl/lib/Wallet/Object/Base.pm new file mode 100644 index 0000000..a6a78bf --- /dev/null +++ b/perl/lib/Wallet/Object/Base.pm @@ -0,0 +1,1052 @@ +# Wallet::Object::Base -- Parent class for any object stored in the wallet. +# +# Written by Russ Allbery <eagle@eyrie.org> +# Copyright 2007, 2008, 2010, 2011, 2014 +# The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::Object::Base; +require 5.006; + +use strict; +use warnings; +use vars qw($VERSION); + +use DateTime; +use Date::Parse qw(str2time); +use DBI; +use Text::Wrap qw(wrap); +use Wallet::ACL; + +# This version should be increased on any code change to this module. Always +# use two digits for the minor version with a leading zero if necessary so +# that it will sort properly. +$VERSION = '0.08'; + +############################################################################## +# Constructors +############################################################################## + +# Initialize an object from the database. Verifies that the object already +# exists with the given type, and if it does, returns a new blessed object of +# the specified class. Stores the database handle to use, the name, and the +# type in the object. If the object doesn't exist, returns undef. This will +# probably be usable as-is by most object types. +sub new { + my ($class, $type, $name, $schema) = @_; + my %search = (ob_type => $type, + ob_name => $name); + my $object = $schema->resultset('Object')->find (\%search); + die "cannot find ${type}:${name}\n" + unless ($object and $object->ob_name eq $name); + my $self = { + schema => $schema, + name => $name, + type => $type, + }; + bless ($self, $class); + return $self; +} + +# Create a new object in the database of the specified name and type, setting +# the ob_created_* fields accordingly, and returns a new blessed object of the +# specified class. Stores the database handle to use, the name, and the type +# in the object. Subclasses may need to override this to do additional setup. +sub create { + my ($class, $type, $name, $schema, $user, $host, $time) = @_; + $time ||= time; + die "invalid object type\n" unless $type; + die "invalid object name\n" unless $name; + my $guard = $schema->txn_scope_guard; + eval { + my $date = DateTime->from_epoch (epoch => $time); + my %record = (ob_type => $type, + ob_name => $name, + ob_created_by => $user, + ob_created_from => $host, + ob_created_on => $date); + $schema->resultset('Object')->create (\%record); + %record = (oh_type => $type, + oh_name => $name, + oh_action => 'create', + oh_by => $user, + oh_from => $host, + oh_on => $date); + $schema->resultset('ObjectHistory')->create (\%record); + $guard->commit; + }; + if ($@) { + die "cannot create object ${type}:${name}: $@\n"; + } + my $self = { + schema => $schema, + name => $name, + type => $type, + }; + bless ($self, $class); + return $self; +} + +############################################################################## +# Utility functions +############################################################################## + +# Set or return the error stashed in the object. +sub error { + my ($self, @error) = @_; + if (@error) { + my $error = join ('', @error); + chomp $error; + 1 while ($error =~ s/ at \S+ line \d+\.?\z//); + $self->{error} = $error; + } + return $self->{error}; +} + +# Returns the type of the object. +sub type { + my ($self) = @_; + return $self->{type}; +} + +# Returns the name of the object. +sub name { + my ($self) = @_; + return $self->{name}; +} + +# Record a global object action for this object. Takes the action (which must +# be one of get or store), and the trace information: user, host, and time. +# Returns true on success and false on failure, setting error appropriately. +# +# This function commits its transaction when complete and should not be called +# inside another transaction. +sub log_action { + my ($self, $action, $user, $host, $time) = @_; + unless ($action =~ /^(get|store)\z/) { + $self->error ("invalid history action $action"); + return; + } + + # We have two traces to record, one in the object_history table and one in + # the object record itself. Commit both changes as a transaction. We + # assume that AutoCommit is turned off. + my $guard = $self->{schema}->txn_scope_guard; + eval { + my $date = DateTime->from_epoch (epoch => $time); + my %record = (oh_type => $self->{type}, + oh_name => $self->{name}, + oh_action => $action, + oh_by => $user, + oh_from => $host, + oh_on => $date); + $self->{schema}->resultset('ObjectHistory')->create (\%record); + + # Add in more timestamps based on the action type. + my %search = (ob_type => $self->{type}, + ob_name => $self->{name}); + my $object = $self->{schema}->resultset('Object')->find (\%search); + if ($action eq 'get') { + $object->ob_downloaded_by ($user); + $object->ob_downloaded_from ($host); + $object->ob_downloaded_on ($date); + } elsif ($action eq 'store') { + $object->ob_stored_by ($user); + $object->ob_stored_from ($host); + $object->ob_stored_on ($date); + } + $object->update; + $guard->commit; + }; + if ($@) { + my $id = $self->{type} . ':' . $self->{name}; + $self->error ("cannot update history for $id: $@"); + return; + } + return 1; +} + +# Record a setting change for this object. Takes the field, the old value, +# the new value, and the trace information (user, host, and time). The field +# may have the special value "type_data <field>" in which case the value after +# the whitespace is used as the type_field value. +# +# This function does not commit and does not catch exceptions. It should +# normally be called as part of a larger transaction that implements the +# setting change and should be committed with that change. +sub log_set { + my ($self, $field, $old, $new, $user, $host, $time) = @_; + my $type_field; + if ($field =~ /^type_data\s+/) { + ($field, $type_field) = split (' ', $field, 2); + } + my %fields = map { $_ => 1 } + qw(owner acl_get acl_store acl_show acl_destroy acl_flags expires + comment flags type_data); + unless ($fields{$field}) { + die "invalid history field $field"; + } + + my $date = DateTime->from_epoch (epoch => $time); + my %record = (oh_type => $self->{type}, + oh_name => $self->{name}, + oh_action => 'set', + oh_field => $field, + oh_type_field => $type_field, + oh_old => $old, + oh_new => $new, + oh_by => $user, + oh_from => $host, + oh_on => $date); + $self->{schema}->resultset('ObjectHistory')->create (\%record); +} + +############################################################################## +# Get/set values +############################################################################## + +# Set a particular attribute. Takes the attribute to set and its new value. +# Returns undef on failure and true on success. +sub _set_internal { + my ($self, $attr, $value, $user, $host, $time) = @_; + if ($attr !~ /^[a-z_]+\z/) { + $self->error ("invalid attribute $attr"); + return; + } + $time ||= time; + my $name = $self->{name}; + my $type = $self->{type}; + if ($self->flag_check ('locked')) { + $self->error ("cannot modify ${type}:${name}: object is locked"); + return; + } + + my $guard = $self->{schema}->txn_scope_guard; + eval { + my %search = (ob_type => $type, + ob_name => $name); + my $object = $self->{schema}->resultset('Object')->find (\%search); + my $column = "ob_$attr"; + my $old = $object->$column; + my $new = $value; + $object->update ({ $column => $value }); + + if (ref ($old) && $old->isa ('DateTime')) { + $old->set_time_zone ('local'); + $old = $old->ymd . q{ } . $old->hms; + } + if (ref ($new) && $new->isa ('DateTime')) { + $new->set_time_zone ('local'); + $new = $new->ymd . q{ } . $new->hms; + } + $self->log_set ($attr, $old, $new, $user, $host, $time); + $guard->commit; + }; + if ($@) { + my $id = $self->{type} . ':' . $self->{name}; + $self->error ("cannot set $attr on $id: $@"); + return; + } + return 1; +} + +# Get a particular attribute. Returns the attribute value or undef if the +# value isn't set or on a database error. The two cases can be distinguished +# by whether $self->{error} is set. +sub _get_internal { + my ($self, $attr) = @_; + undef $self->{error}; + if ($attr !~ /^[a-z_]+\z/) { + $self->error ("invalid attribute $attr"); + return; + } + $attr = 'ob_' . $attr; + my $name = $self->{name}; + my $type = $self->{type}; + my $value; + eval { + my %search = (ob_type => $type, + ob_name => $name); + my $object = $self->{schema}->resultset('Object')->find (\%search); + $value = $object->$attr; + }; + if ($@) { + $self->error ($@); + return; + } + return $value; +} + +# Get or set an ACL on an object. Takes the type of ACL and, if setting, the +# new ACL identifier. If setting it, trace information must also be provided. +sub acl { + my ($self, $type, $id, $user, $host, $time) = @_; + if ($type !~ /^(get|store|show|destroy|flags)\z/) { + $self->error ("invalid ACL type $type"); + return; + } + my $attr = "acl_$type"; + if ($id) { + my $acl; + eval { $acl = Wallet::ACL->new ($id, $self->{schema}) }; + if ($@) { + $self->error ($@); + return; + } + return $self->_set_internal ($attr, $acl->id, $user, $host, $time); + } elsif (defined $id) { + return $self->_set_internal ($attr, undef, $user, $host, $time); + } else { + my $id = $self->_get_internal ($attr); + return unless defined $id; + my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) }; + if ($@) { + $self->error ($@); + return; + } + return $acl->name; + } +} + +# Get or set an attribute on an object. Takes the name of the attribute and, +# if setting, the values and trace information. The values must be provided +# as a reference to an array, even if there is only one value. +# +# Attributes are used by backends for backend-specific information (such as +# enctypes for a keytab). The default implementation rejects all attribute +# names as unknown. +sub attr { + my ($self, $attr, $values, $user, $host, $time) = @_; + $self->error ("unknown attribute $attr"); + return; +} + +# Format the object attributes for inclusion in show(). The default +# implementation just returns the empty string. +sub attr_show { + my ($self) = @_; + return ''; +} + +# Get or set the comment value of an object. If setting it, trace information +# must also be provided. +sub comment { + my ($self, $comment, $user, $host, $time) = @_; + if ($comment) { + return $self->_set_internal ('comment', $comment, $user, $host, $time); + } elsif (defined $comment) { + return $self->_set_internal ('comment', undef, $user, $host, $time); + } else { + return $self->_get_internal ('comment'); + } +} + +# Get or set the expires value of an object. Expects an expiration time in +# seconds since epoch. If setting the expiration, trace information must also +# be provided. +sub expires { + my ($self, $expires, $user, $host, $time) = @_; + if ($expires) { + my $seconds = str2time ($expires); + unless (defined $seconds) { + $self->error ("malformed expiration time $expires"); + return; + } + my $date = DateTime->from_epoch (epoch => $seconds); + return $self->_set_internal ('expires', $date, $user, $host, $time); + } elsif (defined $expires) { + return $self->_set_internal ('expires', undef, $user, $host, $time); + } else { + my $date = $self->_get_internal ('expires'); + if (defined $date) { + $date->set_time_zone ('local'); + return $date->ymd . q{ } . $date->hms; + } else { + return; + } + } +} + +# Get or set the owner of an object. If setting it, trace information must +# also be provided. +sub owner { + my ($self, $owner, $user, $host, $time) = @_; + if ($owner) { + my $acl; + eval { $acl = Wallet::ACL->new ($owner, $self->{schema}) }; + if ($@) { + $self->error ($@); + return; + } + return $self->_set_internal ('owner', $acl->id, $user, $host, $time); + } elsif (defined $owner) { + return $self->_set_internal ('owner', undef, $user, $host, $time); + } else { + my $id = $self->_get_internal ('owner'); + return unless defined $id; + my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) }; + if ($@) { + $self->error ($@); + return; + } + return $acl->name; + } +} + +############################################################################## +# Flags +############################################################################## + +# Check whether a flag is set on the object. Returns true if set, 0 if not +# set, and undef on error. +sub flag_check { + my ($self, $flag) = @_; + my $name = $self->{name}; + my $type = $self->{type}; + my $schema = $self->{schema}; + my $value; + eval { + my %search = (fl_type => $type, + fl_name => $name, + fl_flag => $flag); + my $flag = $schema->resultset('Flag')->find (\%search); + if (not defined $flag) { + $value = 0; + } else { + $value = $flag->fl_flag; + } + }; + if ($@) { + $self->error ("cannot check flag $flag for ${type}:${name}: $@"); + return; + } else { + return ($value) ? 1 : 0; + } +} + +# Clear a flag on an object. Takes the flag and trace information. Returns +# true on success and undef on failure. +sub flag_clear { + my ($self, $flag, $user, $host, $time) = @_; + $time ||= time; + my $name = $self->{name}; + my $type = $self->{type}; + my $schema = $self->{schema}; + my $guard = $schema->txn_scope_guard; + eval { + my %search = (fl_type => $type, + fl_name => $name, + fl_flag => $flag); + my $flag = $schema->resultset('Flag')->find (\%search); + unless (defined $flag) { + die "flag not set\n"; + } + $flag->delete; + $self->log_set ('flags', $flag->fl_flag, undef, $user, $host, $time); + $guard->commit; + }; + if ($@) { + $self->error ("cannot clear flag $flag on ${type}:${name}: $@"); + return; + } + return 1; +} + +# List the flags on an object. Returns a list of flag names, which may be +# empty. On error, returns the empty list. The caller should call error() in +# this case to determine if an error occurred. +sub flag_list { + my ($self) = @_; + undef $self->{error}; + my @flags; + eval { + my %search = (fl_type => $self->{type}, + fl_name => $self->{name}); + my %attrs = (order_by => 'fl_flag'); + my @flags_rs = $self->{schema}->resultset('Flag')->search (\%search, + \%attrs); + for my $flag (@flags_rs) { + push (@flags, $flag->fl_flag); + } + }; + if ($@) { + my $id = $self->{type} . ':' . $self->{name}; + $self->error ("cannot retrieve flags for $id: $@"); + return; + } else { + return @flags; + } +} + +# Set a flag on an object. Takes the flag and trace information. Returns +# true on success and undef on failure. +sub flag_set { + my ($self, $flag, $user, $host, $time) = @_; + $time ||= time; + my $name = $self->{name}; + my $type = $self->{type}; + my $schema = $self->{schema}; + my $guard = $schema->txn_scope_guard; + eval { + my %search = (fl_type => $type, + fl_name => $name, + fl_flag => $flag); + my $flag = $schema->resultset('Flag')->find (\%search); + if (defined $flag) { + die "flag already set\n"; + } + $flag = $schema->resultset('Flag')->create (\%search); + $self->log_set ('flags', undef, $flag->fl_flag, $user, $host, $time); + $guard->commit; + }; + if ($@) { + $self->error ("cannot set flag $flag on ${type}:${name}: $@"); + return; + } + return 1; +} + +############################################################################## +# History +############################################################################## + +# Expand a given ACL id to add its name, for readability. Returns the +# original id alone if there was a problem finding the name. +sub format_acl_id { + my ($self, $id) = @_; + my $name = $id; + + my %search = (ac_id => $id); + my $acl_rs = $self->{schema}->resultset('Acl')->find (\%search); + if (defined $acl_rs) { + $name = $acl_rs->ac_name . " ($id)"; + } + + return $name; +} + +# Return the formatted history for a given object or undef on error. +# Currently always returns the complete history, but eventually will need to +# provide some way of showing only recent entries. +sub history { + my ($self) = @_; + my $output = ''; + eval { + my %search = (oh_type => $self->{type}, + oh_name => $self->{name}); + my %attrs = (order_by => 'oh_id'); + my @history = $self->{schema}->resultset('ObjectHistory') + ->search (\%search, \%attrs); + + for my $history_rs (@history) { + my $date = $history_rs->oh_on; + $date->set_time_zone ('local'); + $output .= sprintf ("%s %s ", $date->ymd, $date->hms); + + my $old = $history_rs->oh_old; + my $new = $history_rs->oh_new; + my $action = $history_rs->oh_action; + my $field = $history_rs->oh_field; + + if ($action eq 'set' and $field eq 'flags') { + if (defined ($new)) { + $output .= "set flag $new"; + } elsif (defined ($old)) { + $output .= "clear flag $old"; + } + } elsif ($action eq 'set' and $field eq 'type_data') { + my $attr = $history_rs->oh_type_field; + if (defined ($old) and defined ($new)) { + $output .= "set attribute $attr to $new (was $old)"; + } elsif (defined ($old)) { + $output .= "remove $old from attribute $attr"; + } elsif (defined ($new)) { + $output .= "add $new to attribute $attr"; + } + } elsif ($action eq 'set' + and ($field eq 'owner' or $field =~ /^acl_/)) { + $old = $self->format_acl_id ($old) if defined ($old); + $new = $self->format_acl_id ($new) if defined ($new); + if (defined ($old) and defined ($new)) { + $output .= "set $field to $new (was $old)"; + } elsif (defined ($new)) { + $output .= "set $field to $new"; + } elsif (defined ($old)) { + $output .= "unset $field (was $old)"; + } + } elsif ($action eq 'set') { + if (defined ($old) and defined ($new)) { + $output .= "set $field to $new (was $old)"; + } elsif (defined ($new)) { + $output .= "set $field to $new"; + } elsif (defined ($old)) { + $output .= "unset $field (was $old)"; + } + } else { + $output .= $action; + } + $output .= sprintf ("\n by %s from %s\n", $history_rs->oh_by, + $history_rs->oh_from); + } + }; + if ($@) { + my $id = $self->{type} . ':' . $self->{name}; + $self->error ("cannot read history for $id: $@"); + return; + } + return $output; +} + +############################################################################## +# Object manipulation +############################################################################## + +# The get methods must always be overridden by the subclass. +sub get { die "Do not instantiate Wallet::Object::Base directly\n"; } + +# Provide a default store implementation that returns an immutable object +# error so that auto-generated types don't have to provide their own. +sub store { + my ($self, $data, $user, $host, $time) = @_; + my $id = $self->{type} . ':' . $self->{name}; + if ($self->flag_check ('locked')) { + $self->error ("cannot store $id: object is locked"); + return; + } + $self->error ("cannot store $id: object type is immutable"); + return; +} + +# The default show function. This may be adequate for many types; types that +# have additional data should call this method, grab the results, and then add +# their data on to the end. +sub show { + my ($self) = @_; + my $name = $self->{name}; + my $type = $self->{type}; + my @attrs = ([ ob_type => 'Type' ], + [ ob_name => 'Name' ], + [ ob_owner => 'Owner' ], + [ ob_acl_get => 'Get ACL' ], + [ ob_acl_store => 'Store ACL' ], + [ ob_acl_show => 'Show ACL' ], + [ ob_acl_destroy => 'Destroy ACL' ], + [ ob_acl_flags => 'Flags ACL' ], + [ ob_expires => 'Expires' ], + [ ob_comment => 'Comment' ], + [ ob_created_by => 'Created by' ], + [ ob_created_from => 'Created from' ], + [ ob_created_on => 'Created on' ], + [ ob_stored_by => 'Stored by' ], + [ ob_stored_from => 'Stored from' ], + [ ob_stored_on => 'Stored on' ], + [ ob_downloaded_by => 'Downloaded by' ], + [ ob_downloaded_from => 'Downloaded from' ], + [ ob_downloaded_on => 'Downloaded on' ]); + my $fields = join (', ', map { $_->[0] } @attrs); + my @data; + my $object_rs; + eval { + my %search = (ob_type => $type, + ob_name => $name); + $object_rs = $self->{schema}->resultset('Object')->find (\%search); + }; + if ($@) { + $self->error ("cannot retrieve data for ${type}:${name}: $@"); + return; + } + my $output = ''; + my @acls; + + # Format the results. We use a hack to insert the flags before the first + # trace field since they're not a field in the object in their own right. + # The comment should be word-wrapped at 80 columns. + for my $i (0 .. $#attrs) { + my $field = $attrs[$i][0]; + my $fieldtext = $attrs[$i][1]; + my $value = $object_rs->$field; + next unless defined($value); + + if ($field eq 'ob_comment' && length ($value) > 79 - 17) { + local $Text::Wrap::columns = 80; + local $Text::Wrap::unexpand = 0; + $value = wrap (' ' x 17, ' ' x 17, $value); + $value =~ s/^ {17}//; + } elsif ($field eq 'ob_created_by') { + my @flags = $self->flag_list; + if (not @flags and $self->error) { + return; + } + if (@flags) { + $output .= sprintf ("%15s: %s\n", 'Flags', "@flags"); + } + my $attr_output = $self->attr_show; + if (not defined $attr_output) { + return; + } + $output .= $attr_output; + } elsif (ref ($value) && $value->isa ('DateTime')) { + $value->set_time_zone ('local'); + $value = sprintf ("%s %s", $value->ymd, $value->hms); + } elsif ($field =~ /^ob_(owner|acl_)/) { + my $acl = eval { Wallet::ACL->new ($value, $self->{schema}) }; + if ($acl and not $@) { + $value = $acl->name || $value; + push (@acls, [ $acl, $value ]); + } + } + $output .= sprintf ("%15s: %s\n", $fieldtext, $value); + } + if (@acls) { + my %seen; + @acls = grep { !$seen{$_->[1]}++ } @acls; + for my $acl (@acls) { + $output .= "\n" . $acl->[0]->show; + } + } + return $output; +} + +# The default destroy function only destroys the database metadata. Generally +# subclasses need to override this to destroy whatever additional information +# is stored about this object. +sub destroy { + my ($self, $user, $host, $time) = @_; + $time ||= time; + my $name = $self->{name}; + my $type = $self->{type}; + if ($self->flag_check ('locked')) { + $self->error ("cannot destroy ${type}:${name}: object is locked"); + return; + } + my $guard = $self->{schema}->txn_scope_guard; + eval { + + # Remove any flags that may exist for the record. + my %search = (fl_type => $type, + fl_name => $name); + $self->{schema}->resultset('Flag')->search (\%search)->delete; + + # Remove any object records + %search = (ob_type => $type, + ob_name => $name); + $self->{schema}->resultset('Object')->search (\%search)->delete; + + # And create a new history object for the destroy action. + my $date = DateTime->from_epoch (epoch => $time); + my %record = (oh_type => $type, + oh_name => $name, + oh_action => 'destroy', + oh_by => $user, + oh_from => $host, + oh_on => $date); + $self->{schema}->resultset('ObjectHistory')->create (\%record); + $guard->commit; + }; + if ($@) { + $self->error ("cannot destroy ${type}:${name}: $@"); + return; + } + return 1; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=head1 NAME + +Wallet::Object::Base - Generic parent class for wallet objects + +=for stopwords +DBH HOSTNAME DATETIME ACL backend metadata timestamp Allbery wallet-backend +backend-specific subclasses + +=head1 SYNOPSIS + + package Wallet::Object::Simple; + @ISA = qw(Wallet::Object::Base); + sub get { + my ($self, $user, $host, $time) = @_; + $self->log_action ('get', $user, $host, $time) or return; + return "Some secure data"; + } + +=head1 DESCRIPTION + +Wallet::Object::Base is the generic parent class for wallet objects (data +types that can be stored in the wallet system). It provides default +functions and behavior, including handling generic object settings. All +handlers for objects stored in the wallet should inherit from it. It is +not used directly. + +=head1 PUBLIC CLASS METHODS + +The following methods are called by the rest of the wallet system and +should be implemented by all objects stored in the wallet. They should be +called with the desired wallet object class as the first argument +(generally using the Wallet::Object::Type->new syntax). + +=over 4 + +=item new(TYPE, NAME, DBH) + +Creates a new object with the given object type and name, based on data +already in the database. This method will only succeed if an object of +the given TYPE and NAME is already present in the wallet database. If no +such object exits, throws an exception. Otherwise, returns an object +blessed into the class used for the new() call (so subclasses can leave +this method alone and not override it). + +Takes a Wallet::Schema object, which is stored in the object and used +for any further operations. + +=item create(TYPE, NAME, DBH, PRINCIPAL, HOSTNAME [, DATETIME]) + +Similar to new() but instead creates a new entry in the database. This +method will throw an exception if an entry for that type and name already +exists in the database or if creating the database record fails. +Otherwise, a new database entry will be created with that type and name, +no owner, no ACLs, no expiration, no flags, and with created by, from, and +on set to the PRINCIPAL, HOSTNAME, and DATETIME parameters. If DATETIME +isn't given, the current time is used. The database handle is treated as +with new(). + +=back + +=head1 PUBLIC INSTANCE METHODS + +The following methods may be called on instantiated wallet objects. +Normally, the only methods that a subclass will need to override are +get(), store(), show(), and destroy(). + +If the locked flag is set on an object, no actions may be performed on +that object except for the flag methods and show(). All other actions +will be rejected with an error saying the object is locked. + +=over 4 + +=item acl(TYPE [, ACL, PRINCIPAL, HOSTNAME [, DATETIME]]) + +Sets or retrieves a given object ACL as a numeric ACL ID. TYPE must be +one of C<get>, C<store>, C<show>, C<destroy>, or C<flags>, corresponding +to the ACLs kept on an object. If no other arguments are given, returns +the current ACL setting as an ACL ID or undef if that ACL isn't set. If +other arguments are given, change that ACL to ACL and return true on +success and false on failure. Pass in the empty string for ACL to clear +the ACL. The other arguments are used for logging and history and should +indicate the user and host from which the change is made and the time of +the change. + +=item attr(ATTRIBUTE [, VALUES, PRINCIPAL, HOSTNAME [, DATETIME]]) + +Sets or retrieves a given object attribute. Attributes are used to store +backend-specific information for a particular object type and ATTRIBUTE +must be an attribute type known to the underlying object implementation. +The default implementation of this method rejects all attributes as +unknown. + +If no other arguments besides ATTRIBUTE are given, returns the values of +that attribute, if any, as a list. On error, returns the empty list. To +distinguish between an error and an empty return, call error() afterward. +It is guaranteed to return undef unless there was an error. + +If other arguments are given, sets the given ATTRIBUTE values to VALUES, +which must be a reference to an array (even if only one value is being +set). Pass a reference to an empty array to clear the attribute values. +The other arguments are used for logging and history and should indicate +the user and host from which the change is made and the time of the +change. Returns true on success and false on failure. + +=item attr_show() + +Returns a formatted text description of the type-specific attributes of +the object, or undef on error. The default implementation of this method +always returns the empty string. If there are any type-specific +attributes set, this method should return that metadata, formatted as key: +value pairs with the keys right-aligned in the first 15 characters, +followed by a space, a colon, and the value. + +=item comment([COMMENT, PRINCIPAL, HOSTNAME [, DATETIME]]) + +Sets or retrieves the comment associated with an object. If no arguments +are given, returns the current comment or undef if no comment is set. If +arguments are given, change the comment to COMMENT and return true on +success and false on failure. Pass in the empty string for COMMENT to +clear the comment. + +The other arguments are used for logging and history and should indicate +the user and host from which the change is made and the time of the +change. + +=item destroy(PRINCIPAL, HOSTNAME [, DATETIME]) + +Destroys the object by removing all record of it from the database. The +Wallet::Object::Base implementation handles the generic database work, but +any subclass should override this method to do any deletion of files or +entries in external databases and any other database entries and then call +the parent method to handle the generic database cleanup. Returns true on +success and false on failure. The arguments are used for logging and +history and should indicate the user and host from which the change is +made and the time of the change. + +=item error([ERROR ...]) + +Returns the error of the last failing operation or undef if no operations +have failed. Callers should call this function to get the error message +after an undef return from any other instance method. + +For the convenience of child classes, this method can also be called with +one or more error strings. If so, those strings are concatenated +together, trailing newlines are removed, any text of the form S<C< at \S+ +line \d+\.?>> at the end of the message is stripped off, and the result is +stored as the error. Only child classes should call this method with an +error string. + +=item expires([EXPIRES, PRINCIPAL, HOSTNAME [, DATETIME]]) + +Sets or retrieves the expiration date of an object. If no arguments are +given, returns the current expiration or undef if no expiration is set. +If arguments are given, change the expiration to EXPIRES and return true +on success and false on failure. EXPIRES must be in the format +C<YYYY-MM-DD HH:MM:SS>, although the time portion may be omitted. Pass in +the empty string for EXPIRES to clear the expiration date. + +The other arguments are used for logging and history and should indicate +the user and host from which the change is made and the time of the +change. + +=item flag_check(FLAG) + +Check whether the given flag is set on an object. Returns true if set, +C<0> if not set, and undef on error. + +=item flag_clear(FLAG, PRINCIPAL, HOSTNAME [, DATETIME]) + +Clears FLAG on an object. Returns true on success and false on failure. +The other arguments are used for logging and history and should indicate +the user and host from which the change is made and the time of the +change. + +=item flag_list() + +List the flags set on an object. If no flags are set, returns the empty +list. On failure, returns an empty list. To distinguish between the +empty response and an error, the caller should call error() after an empty +return. It is guaranteed to return undef if there was no error. + +=item flag_set(FLAG, PRINCIPAL, HOSTNAME [, DATETIME]) + +Sets FLAG on an object. Returns true on success and false on failure. +The other arguments are used for logging and history and should indicate +the user and host from which the change is made and the time of the +change. + +=item get(PRINCIPAL, HOSTNAME [, DATETIME]) + +An object implementation must override this method with one that returns +either the data of the object or undef on some error, using the provided +arguments to update history information. The Wallet::Object::Base +implementation just throws an exception. + +=item history() + +Returns the formatted history for the object. There will be two lines for +each action on the object. The first line has the timestamp of the action +and the action, and the second line gives the user who performed the +action and the host from which they performed it (based on the trace +information passed into the other object methods). + +=item name() + +Returns the object's name. + +=item owner([OWNER, PRINCIPAL, HOSTNAME [, DATETIME]]) + +Sets or retrieves the owner of an object as a numeric ACL ID. If no +arguments are given, returns the current owner ACL ID or undef if none is +set. If arguments are given, change the owner to OWNER and return true on +success and false on failure. Pass in the empty string for OWNER to clear +the owner. The other arguments are used for logging and history and +should indicate the user and host from which the change is made and the +time of the change. + +=item show() + +Returns a formatted text description of the object suitable for human +display, or undef on error. All of the base metadata about the object, +formatted as key: value pairs with the keys aligned in the first 15 +characters followed by a space, a colon, and the value. The attr_show() +method of the object is also called and any formatted output it returns +will be included. If any ACLs or an owner are set, after this data there +is a blank line and then the information for each unique ACL, separated by +blank lines. + +=item store(DATA, PRINCIPAL, HOSTNAME [, DATETIME]) + +Store user-supplied data into the given object. This may not be supported +by all backends (for instance, backends that automatically generate the +data will not support this). The default implementation rejects all +store() calls with an error message saying that the object is immutable. + +=item type() + +Returns the object's type. + +=back + +=head1 UTILITY METHODS + +The following instance methods should not be called externally but are +provided for subclasses to call to implement some generic actions. + +=over 4 + +=item log_action (ACTION, PRINCIPAL, HOSTNAME, DATETIME) + +Updates the history tables and trace information appropriately for ACTION, +which should be either C<get> or C<store>. No other changes are made to +the database, just updates of the history table and trace fields with the +provided data about who performed the action and when. + +This function commits its transaction when complete and therefore should +not be called inside another transaction. Normally it's called as a +separate transaction after the data is successfully stored or retrieved. + +=item log_set (FIELD, OLD, NEW, PRINCIPAL, HOSTNAME, DATETIME) + +Updates the history tables for the change in a setting value for an +object. FIELD should be one of C<owner>, C<acl_get>, C<acl_store>, +C<acl_show>, C<acl_destroy>, C<acl_flags>, C<expires>, C<flags>, or a +value starting with C<type_data> followed by a space and a type-specific +field name. The last form is the most common form used by a subclass. +OLD is the previous value of the field or undef if the field was unset, +and NEW is the new value of the field or undef if the field should be +unset. + +This function does not commit and does not catch database exceptions. It +should normally be called as part of a larger transaction that implements +the change in the setting. + +=back + +=head1 SEE ALSO + +wallet-backend(8) + +This module is part of the wallet system. The current version is +available from L<http://www.eyrie.org/~eagle/software/wallet/>. + +=head1 AUTHOR + +Russ Allbery <eagle@eyrie.org> + +=cut |