summaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/Server.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl/lib/Wallet/Server.pm')
-rw-r--r--perl/lib/Wallet/Server.pm1096
1 files changed, 1096 insertions, 0 deletions
diff --git a/perl/lib/Wallet/Server.pm b/perl/lib/Wallet/Server.pm
new file mode 100644
index 0000000..95fd4e6
--- /dev/null
+++ b/perl/lib/Wallet/Server.pm
@@ -0,0 +1,1096 @@
+# Wallet::Server -- Wallet system server implementation.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2007, 2008, 2010, 2011, 2013, 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Server;
+require 5.006;
+
+use strict;
+use warnings;
+use vars qw(%MAPPING $VERSION);
+
+use Wallet::ACL;
+use Wallet::Config;
+use Wallet::Schema;
+
+# 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.11';
+
+##############################################################################
+# Utility methods
+##############################################################################
+
+# Create a new wallet server object. A new server should be created for each
+# user who is making changes to the wallet. Takes the principal and host who
+# are sending wallet requests. Opens a connection to the database that will
+# be used for all of the wallet metadata based on the wallet configuration
+# information. We also instantiate the administrative ACL, which we'll use
+# for various things. Throw an exception if anything goes wrong.
+sub new {
+ my ($class, $user, $host) = @_;
+ my $schema = Wallet::Schema->connect;
+ my $acl = Wallet::ACL->new ('ADMIN', $schema);
+ my $self = {
+ schema => $schema,
+ user => $user,
+ host => $host,
+ admin => $acl,
+ };
+ bless ($self, $class);
+ return $self;
+}
+
+# Returns the database handle (used mostly for testing).
+sub dbh {
+ my ($self) = @_;
+ return $self->{schema}->storage->dbh;
+}
+
+# Returns the DBIx::Class-based database schema object.
+sub schema {
+ my ($self) = @_;
+ return $self->{schema};
+}
+
+# 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};
+}
+
+# Disconnect the database handle on object destruction to avoid warnings.
+sub DESTROY {
+ my ($self) = @_;
+
+ if ($self->{schema}) {
+ $self->{schema}->storage->dbh->disconnect;
+ }
+}
+
+##############################################################################
+# Object methods
+##############################################################################
+
+# Given an object type, return the mapping to a class by querying the
+# database, or undef if no mapping exists. Also load the relevant module.
+sub type_mapping {
+ my ($self, $type) = @_;
+ my $class;
+ eval {
+ my $guard = $self->{schema}->txn_scope_guard;
+ my %search = (ty_name => $type);
+ my $type_rec = $self->{schema}->resultset('Type')->find (\%search);
+ $class = $type_rec->ty_class;
+ $guard->commit;
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ if (defined $class) {
+ eval "require $class";
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ }
+ return $class;
+}
+
+# Given an object which doesn't currently exist, check whether a default_owner
+# function is defined and, if so, if it returns an ACL for that object. If
+# so, create the ACL and check if the current user is authorized by that ACL.
+# Returns true if so, false if not, setting the internal error as appropriate.
+#
+# This leaves those new ACLs in the database, which may not be the best
+# behavior, but it's the simplest given the current Wallet::ACL API. This
+# should probably be revisited later.
+sub create_check {
+ my ($self, $type, $name) = @_;
+ my $user = $self->{user};
+ my $host = $self->{host};
+ my $schema = $self->{schema};
+ unless (defined (&Wallet::Config::default_owner)) {
+ $self->error ("$user not authorized to create ${type}:${name}");
+ return;
+ }
+ my ($aname, @acl) = Wallet::Config::default_owner ($type, $name);
+ unless (defined $aname) {
+ $self->error ("$user not authorized to create ${type}:${name}");
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($aname, $schema) };
+ if ($@) {
+ $acl = eval { Wallet::ACL->create ($aname, $schema, $user, $host) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ for my $entry (@acl) {
+ unless ($acl->add ($entry->[0], $entry->[1], $user, $host)) {
+ $self->error ($acl->error);
+ return;
+ }
+ }
+ } else {
+ my @entries = $acl->list;
+ if (not @entries and $acl->error) {
+ $self->error ($acl->error);
+ return;
+ }
+ @entries = sort { $$a[0] cmp $$b[0] && $$a[1] cmp $$b[1] } @entries;
+ @acl = sort { $$a[0] cmp $$b[0] && $$a[1] cmp $$b[1] } @acl;
+ my $okay = 1;
+ if (@entries != @acl) {
+ $okay = 0;
+ } else {
+ for my $i (0 .. $#entries) {
+ $okay = 0 unless ($entries[$i][0] eq $acl[$i][0]);
+ $okay = 0 unless ($entries[$i][1] eq $acl[$i][1]);
+ }
+ }
+ unless ($okay) {
+ $self->error ("ACL $aname exists and doesn't match default");
+ return;
+ }
+ }
+ if ($acl->check ($user)) {
+ return $aname;
+ } else {
+ $self->error ("$user not authorized to create ${type}:${name}");
+ return;
+ }
+}
+
+# Create an object and returns it. This function is called by both create and
+# autocreate and assumes that permissions and names have already been checked.
+# On error, returns undef and sets the internal error.
+sub create_object {
+ my ($self, $type, $name) = @_;
+ my $class = $self->type_mapping ($type);
+ unless ($class) {
+ $self->error ("unknown object type $type");
+ return;
+ }
+ my $schema = $self->{schema};
+ my $user = $self->{user};
+ my $host = $self->{host};
+ my $object = eval { $class->create ($type, $name, $schema, $user, $host) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ return $object;
+}
+
+# Create a new object and returns that object. This method can only be called
+# by wallet administrators. autocreate should be used by regular users who
+# may benefit from default ACLs. On error, returns undef and sets the
+# internal error.
+sub create {
+ my ($self, $type, $name) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ my $id = $type . ':' . $name;
+ $self->error ("$self->{user} not authorized to create $id");
+ return;
+ }
+ if (defined (&Wallet::Config::verify_name)) {
+ my $error = Wallet::Config::verify_name ($type, $name, $self->{user});
+ if ($error) {
+ $self->error ("${type}:${name} rejected: $error");
+ return;
+ }
+ }
+ return unless $self->create_object ($type, $name);
+ return 1;
+}
+
+# Attempt to auto-create an object based on default ACLs. This method is
+# called by the wallet client when trying to get an object that doesn't
+# already exist. On error, returns undef and sets the internal error.
+sub autocreate {
+ my ($self, $type, $name) = @_;
+ if (defined (&Wallet::Config::verify_name)) {
+ my $error = Wallet::Config::verify_name ($type, $name, $self->{user});
+ if ($error) {
+ $self->error ("${type}:${name} rejected: $error");
+ return;
+ }
+ }
+ my $acl = $self->create_check ($type, $name);
+ return unless $acl;
+ my $object = $self->create_object ($type, $name);
+ return unless $object;
+ unless ($object->owner ($acl, $self->{user}, $self->{host})) {
+ $self->error ($object->error);
+ return;
+ }
+ return 1;
+}
+
+# Given the name and type of an object, returns a Perl object representing it
+# or returns undef and sets the internal error.
+sub retrieve {
+ my ($self, $type, $name) = @_;
+ my $class = $self->type_mapping ($type);
+ unless ($class) {
+ $self->error ("unknown object type $type");
+ return;
+ }
+ my $object = eval { $class->new ($type, $name, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ } else {
+ return $object;
+ }
+}
+
+# Sets the internal error variable to the correct message for permission
+# denied on an object.
+sub object_error {
+ my ($self, $object, $action) = @_;
+ my $user = $self->{user};
+ my $id = $object->type . ':' . $object->name;
+ if ($action eq 'getattr') {
+ $action = "get attributes for";
+ } elsif ($action eq 'setattr') {
+ $action = "set attributes for";
+ } elsif ($action !~ /^(create|get|store|show|destroy)\z/) {
+ $action = "set $action for";
+ }
+ $self->error ("$self->{user} not authorized to $action $id");
+}
+
+# Given an object and an action, checks if the current user has access to
+# perform that object. If so, returns true. If not, returns undef and sets
+# the internal error message. Note that we do not allow any special access to
+# admins for get and store; if they want to do that with objects, they need to
+# set the ACL accordingly.
+sub acl_verify {
+ my ($self, $object, $action) = @_;
+ my %actions = map { $_ => 1 }
+ qw(get store show destroy flags setattr getattr comment);
+ unless ($actions{$action}) {
+ $self->error ("unknown action $action");
+ return;
+ }
+ if ($action ne 'get' and $action ne 'store') {
+ return 1 if $self->{admin}->check ($self->{user});
+ }
+ my $id;
+ if ($action eq 'getattr') {
+ $id = $object->acl ('show');
+ } elsif ($action eq 'setattr') {
+ $id = $object->acl ('store');
+ } elsif ($action ne 'comment') {
+ $id = $object->acl ($action);
+ }
+ if (! defined ($id) and $action ne 'flags') {
+ $id = $object->owner;
+ }
+ unless (defined $id) {
+ $self->object_error ($object, $action);
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $status = $acl->check ($self->{user});
+ if ($status == 1) {
+ return 1;
+ } elsif (not defined $status) {
+ $self->error ($acl->error);
+ return;
+ } else {
+ $self->object_error ($object, $action);
+ return;
+ }
+}
+
+# Retrieves or sets an ACL on an object.
+sub acl {
+ my ($self, $type, $name, $acl, $id) = @_;
+ undef $self->{error};
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->object_error ($object, 'ACL');
+ return;
+ }
+ my $result;
+ if (defined $id) {
+ $result = $object->acl ($acl, $id, $self->{user}, $self->{host});
+ } else {
+ $result = $object->acl ($acl);
+ }
+ if (not defined ($result) and $object->error) {
+ $self->error ($object->error);
+ }
+ return $result;
+}
+
+# Retrieves or sets an attribute on an object.
+sub attr {
+ my ($self, $type, $name, $attr, @values) = @_;
+ undef $self->{error};
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ my $user = $self->{user};
+ my $host = $self->{host};
+ if (@values) {
+ return unless $self->acl_verify ($object, 'setattr');
+ if (@values == 1 and $values[0] eq '') {
+ @values = ();
+ }
+ my $result = $object->attr ($attr, [ @values ], $user, $host);
+ $self->error ($object->error) unless $result;
+ return $result;
+ } else {
+ return unless $self->acl_verify ($object, 'getattr');
+ my @result = $object->attr ($attr);
+ if (not @result and $object->error) {
+ $self->error ($object->error);
+ return;
+ } else {
+ return @result;
+ }
+ }
+}
+
+# Retrieves or sets the comment of an object.
+sub comment {
+ my ($self, $type, $name, $comment) = @_;
+ undef $self->{error};
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ my $result;
+ if (defined $comment) {
+ return unless $self->acl_verify ($object, 'comment');
+ $result = $object->comment ($comment, $self->{user}, $self->{host});
+ } else {
+ return unless $self->acl_verify ($object, 'show');
+ $result = $object->comment;
+ }
+ if (not defined ($result) and $object->error) {
+ $self->error ($object->error);
+ }
+ return $result;
+}
+
+# Retrieves or sets the expiration of an object.
+sub expires {
+ my ($self, $type, $name, $expires) = @_;
+ undef $self->{error};
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->object_error ($object, 'expires');
+ return;
+ }
+ my $result;
+ if (defined $expires) {
+ $result = $object->expires ($expires, $self->{user}, $self->{host});
+ } else {
+ $result = $object->expires;
+ }
+ if (not defined ($result) and $object->error) {
+ $self->error ($object->error);
+ }
+ return $result;
+}
+
+# Retrieves or sets the owner of an object.
+sub owner {
+ my ($self, $type, $name, $owner) = @_;
+ undef $self->{error};
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->object_error ($object, 'owner');
+ return;
+ }
+ my $result;
+ if (defined $owner) {
+ $result = $object->owner ($owner, $self->{user}, $self->{host});
+ } else {
+ $result = $object->owner;
+ }
+ if (not defined ($result) and $object->error) {
+ $self->error ($object->error);
+ }
+ return $result;
+}
+
+# Checks for the existence of an object. Returns 1 if it does, 0 if it
+# doesn't, and undef if there was an error in checking the existence of the
+# object.
+sub check {
+ my ($self, $type, $name) = @_;
+ my $object = $self->retrieve ($type, $name);
+ if (not defined $object) {
+ if ($self->error =~ /^cannot find/) {
+ return 0;
+ } else {
+ return;
+ }
+ }
+ return 1;
+}
+
+# Retrieve the information associated with an object, or returns undef and
+# sets the internal error if the retrieval fails or if the user isn't
+# authorized. If the object doesn't exist, attempts dynamic creation of the
+# object using the default ACL mappings (if any).
+sub get {
+ my ($self, $type, $name) = @_;
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ return unless $self->acl_verify ($object, 'get');
+ my $result = $object->get ($self->{user}, $self->{host});
+ $self->error ($object->error) unless defined $result;
+ return $result;
+}
+
+# Store new data in an object, or returns undef and sets the internal error if
+# the object can't be found or if the user isn't authorized. Also don't
+# permit storing undef, although storing the empty string is fine. If the
+# object doesn't exist, attempts dynamic creation of the object using the
+# default ACL mappings (if any).
+sub store {
+ my ($self, $type, $name, $data) = @_;
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ return unless $self->acl_verify ($object, 'store');
+ if (not defined ($data)) {
+ $self->{error} = "no data supplied to store";
+ return;
+ }
+ my $result = $object->store ($data, $self->{user}, $self->{host});
+ $self->error ($object->error) unless defined $result;
+ return $result;
+}
+
+# Return a human-readable description of the object's metadata, or returns
+# undef and sets the internal error if the object can't be found or if the
+# user isn't authorized.
+sub show {
+ my ($self, $type, $name) = @_;
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ return unless $self->acl_verify ($object, 'show');
+ my $result = $object->show;
+ $self->error ($object->error) unless defined $result;
+ return $result;
+}
+
+# Return a human-readable description of the object history, or returns undef
+# and sets the internal error if the object can't be found or if the user
+# isn't authorized.
+sub history {
+ my ($self, $type, $name) = @_;
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ return unless $self->acl_verify ($object, 'show');
+ my $result = $object->history;
+ $self->error ($object->error) unless defined $result;
+ return $result;
+}
+
+# Destroys the object, or returns undef and sets the internal error if the
+# object can't be found or if the user isn't authorized.
+sub destroy {
+ my ($self, $type, $name) = @_;
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ return unless $self->acl_verify ($object, 'destroy');
+ my $result = $object->destroy ($self->{user}, $self->{host});
+ $self->error ($object->error) unless defined $result;
+ return $result;
+}
+
+##############################################################################
+# Object flag methods
+##############################################################################
+
+# Clear a flag on an object. Takes the object and the flag. Returns true on
+# success or undef and sets the internal error on failure.
+sub flag_clear {
+ my ($self, $type, $name, $flag) = @_;
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ return unless $self->acl_verify ($object, 'flags');
+ my $result = $object->flag_clear ($flag, $self->{user}, $self->{host});
+ $self->error ($object->error) unless defined $result;
+ return $result;
+}
+
+# Set a flag on an object. Takes the object and the flag. Returns true on
+# success or undef and sets the internal error on failure.
+sub flag_set {
+ my ($self, $type, $name, $flag) = @_;
+ my $object = $self->retrieve ($type, $name);
+ return unless defined $object;
+ return unless $self->acl_verify ($object, 'flags');
+ my $result = $object->flag_set ($flag, $self->{user}, $self->{host});
+ $self->error ($object->error) unless defined $result;
+ return $result;
+}
+
+##############################################################################
+# ACL methods
+##############################################################################
+
+# Checks for the existence of an ACL. Returns 1 if it does, 0 if it doesn't,
+# and undef if there was an error in checking the existence of the object.
+sub acl_check {
+ my ($self, $id) = @_;
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ if ($@ =~ /^ACL .* not found/) {
+ return 0;
+ } else {
+ $self->error ($@);
+ return;
+ }
+ }
+ return 1;
+}
+
+# Create a new empty ACL in the database. Returns true on success and undef
+# on failure, setting the internal error.
+sub acl_create {
+ my ($self, $name) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->error ("$self->{user} not authorized to create ACL");
+ return;
+ }
+ my $user = $self->{user};
+ my $host = $self->{host};
+ if (defined (&Wallet::Config::verify_acl_name)) {
+ my $error = Wallet::Config::verify_acl_name ($name, $user);
+ if ($error) {
+ $self->error ("$name rejected: $error");
+ return;
+ }
+ }
+ my $schema = $self->{schema};
+ my $acl = eval { Wallet::ACL->create ($name, $schema, $user, $host) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ } else {
+ return 1;
+ }
+}
+
+# Sets the internal error variable to the correct message for permission
+# denied on an ACL.
+sub acl_error {
+ my ($self, $acl, $action) = @_;
+ my $user = $self->{user};
+ if ($action eq 'add') {
+ $action = 'add to';
+ } elsif ($action eq 'remove') {
+ $action = 'remove from';
+ } elsif ($action eq 'history') {
+ $action = 'see history of';
+ }
+ $self->error ("$self->{user} not authorized to $action ACL $acl");
+}
+
+# Display the history of an ACL or return undef and set the internal error.
+sub acl_history {
+ my ($self, $id) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->acl_error ($id, 'history');
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $result = $acl->history;
+ if (not defined $result) {
+ $self->error ($acl->error);
+ return;
+ }
+ return $result;
+}
+
+# Display the membership of an ACL or return undef and set the internal error.
+sub acl_show {
+ my ($self, $id) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->acl_error ($id, 'show');
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ my $result = $acl->show;
+ if (not defined $result) {
+ $self->error ($acl->error);
+ return;
+ }
+ return $result;
+}
+
+# Change the human-readable name of an ACL or return undef and set the
+# internal error.
+sub acl_rename {
+ my ($self, $id, $name) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->acl_error ($id, 'rename');
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ if ($acl->name eq 'ADMIN') {
+ $self->error ('cannot rename the ADMIN ACL');
+ return;
+ }
+ if (defined (&Wallet::Config::verify_acl_name)) {
+ my $error = Wallet::Config::verify_acl_name ($name, $self->{user});
+ if ($error) {
+ $self->error ("$name rejected: $error");
+ return;
+ }
+ }
+ unless ($acl->rename ($name, $self->{user}, $self->{host})) {
+ $self->error ($acl->error);
+ return;
+ }
+ return 1;
+}
+
+# Destroy an ACL, deleting it out of the database. Returns true on success.
+# On failure, returns undef, setting the internal error.
+sub acl_destroy {
+ my ($self, $id) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->acl_error ($id, 'destroy');
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ if ($acl->name eq 'ADMIN') {
+ $self->error ('cannot destroy the ADMIN ACL');
+ return;
+ }
+ unless ($acl->destroy ($self->{user}, $self->{host})) {
+ $self->error ($acl->error);
+ return;
+ }
+ return 1;
+}
+
+# Add an ACL entry to an ACL. Returns true on success. On failure, returns
+# undef, setting the internal error.
+sub acl_add {
+ my ($self, $id, $scheme, $identifier) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->acl_error ($id, 'add');
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ unless ($acl->add ($scheme, $identifier, $self->{user}, $self->{host})) {
+ $self->error ($acl->error);
+ return;
+ }
+ return 1;
+}
+
+# Remove an ACL entry to an ACL. Returns true on success. On failure,
+# returns undef, setting the internal error.
+sub acl_remove {
+ my ($self, $id, $scheme, $identifier) = @_;
+ unless ($self->{admin}->check ($self->{user})) {
+ $self->acl_error ($id, 'remove');
+ return;
+ }
+ my $acl = eval { Wallet::ACL->new ($id, $self->{schema}) };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ if ($acl->name eq 'ADMIN') {
+ my @e = $acl->list;
+ if (not @e and $acl->error) {
+ $self->error ($acl->error);
+ return;
+ } elsif (@e == 1 && $e[0][0] eq $scheme && $e[0][1] eq $identifier) {
+ $self->error ('cannot remove last ADMIN ACL entry');
+ return;
+ }
+ }
+ my $user = $self->{user};
+ my $host = $self->{host};
+ unless ($acl->remove ($scheme, $identifier, $user, $host)) {
+ $self->error ($acl->error);
+ return;
+ }
+ return 1;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=head1 NAME
+
+Wallet::Server - Wallet system server implementation
+
+=for stopwords
+keytabs metadata backend HOSTNAME ACL timestamp ACL's nul Allbery
+backend-specific wallet-backend verifier
+
+=head1 SYNOPSIS
+
+ use Wallet::Server;
+ my $server = Wallet::Server->new ($user, $host);
+ $server->create ('keytab', 'host/example.com@EXAMPLE.COM');
+
+=head1 DESCRIPTION
+
+Wallet::Server is the top-level class that implements the wallet server.
+The wallet is a system for storing, generating, and retrieving secure
+information such as Kerberos keytabs. The server maintains metadata about
+the objects, checks access against ACLs, and dispatches requests for
+objects to backend implementations for that object type.
+
+Wallet::Server is normally instantiated and used by B<wallet-backend>, a
+thin wrapper around this object that determines the authenticated remote
+user and gets user input and then calls the appropriate method of this
+object.
+
+To use this object, several configuration variables must be set (at least
+the database configuration). For information on those variables and how
+to set them, see L<Wallet::Config>.
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item new(PRINCIPAL, HOSTNAME)
+
+Creates a new wallet server object for actions from the user PRINCIPAL
+connecting from HOSTNAME. PRINCIPAL and HOSTNAME will be used for logging
+history information for all subsequent operations. new() opens the
+database, using the database configuration as set by Wallet::Config and
+ensures that the C<ADMIN> ACL exists. That ACL will be used to authorize
+privileged operations.
+
+On any error, this method throws an exception.
+
+=back
+
+=head1 INSTANCE METHODS
+
+For all methods that can fail, the caller should call error() after a
+failure to get the error message.
+
+=over 4
+
+=item acl(TYPE, NAME, ACL [, ID])
+
+Gets or sets the ACL type ACL to ID for the object identified by TYPE and
+NAME. ACL should be one of C<get>, C<store>, C<show>, C<destroy>, or
+C<flags>. If ID is not given, returns the current setting of that ACL as
+the name of the ACL or undef if that ACL isn't set or on failure. To
+distinguish between an ACL that isn't set and a failure to retrieve the
+ACL, the caller should call error() after an undef return. If error()
+also returns undef, that ACL wasn't set; otherwise, error() will return
+the error message.
+
+If ID is given, sets the specified ACL to ID, which can be either the name
+of an ACL or a numeric ACL ID. To clear the ACL, pass in an empty string
+as the ID. To set or clear an ACL, the current user must be authorized by
+the ADMIN ACL. Returns true for success and false for failure.
+
+ACL settings are checked before the owner and override the owner setting.
+
+=item acl_add(ID, SCHEME, IDENTIFIER)
+
+Adds an ACL entry with scheme SCHEME and identifier IDENTIFIER to the ACL
+identified by ID. ID may be either the ACL name or the numeric ACL ID.
+SCHEME must be a valid ACL scheme for which the wallet system has an ACL
+verifier implementation. To add an entry to an ACL, the current user must
+be authorized by the ADMIN ACL. Returns true for success and false for
+failure.
+
+=item acl_create(NAME)
+
+Create a new ACL with the specified NAME, which must not be all-numeric.
+The newly created ACL will be empty. To create an ACL, the current user
+must be authorized by the ADMIN ACL. Returns true on success and false on
+failure.
+
+=item acl_destroy(ID)
+
+Destroys the ACL identified by ID, which may be either the ACL name or its
+numeric ID. This call will fail if the ACL is still referenced by any
+object. The ADMIN ACL may not be destroyed. To destroy an ACL, the
+current user must be authorized by the ADMIN ACL. Returns true on success
+and false on failure.
+
+=item acl_history(ID)
+
+Returns the history of the ACL identified by ID, which may be either the
+ACL name or its numeric ID. To see the history of an ACL, the current
+user must be authorized by the ADMIN ACL. Each change that modifies the
+ACL (not counting changes in the name of the ACL) will be represented by
+two lines. The first line will have a timestamp of the change followed by
+a description of the change, and the second line will give the user who
+made the change and the host from which the change was made. Returns
+undef on failure.
+
+=item acl_remove(ID, SCHEME, IDENTIFIER)
+
+Removes from the ACL identified by ID the entry matching SCHEME and
+IDENTIFIER. ID may be either the name of the ACL or its numeric ID. The
+last entry in the ADMIN ACL cannot be removed. To remove an entry from an
+ACL, the current user must be authorized by the ADMIN ACL. Returns true
+on success and false on failure.
+
+=item acl_rename(OLD, NEW)
+
+Renames the ACL identified by OLD to NEW. This changes the human-readable
+name, not the underlying numeric ID, so the ACL's associations with
+objects will be unchanged. The ADMIN ACL may not be renamed. OLD may be
+either the current name or the numeric ID. NEW must not be all-numeric.
+To rename an ACL, the current user must be authorized by the ADMIN ACL.
+Returns true on success and false on failure.
+
+=item acl_show(ID)
+
+Returns a human-readable description, including membership, of the ACL
+identified by ID, which may be either the ACL name or its numeric ID. To
+show an ACL, the current user must be authorized by the ADMIN ACL
+(although be aware that anyone with show access to an object can see the
+membership of ACLs associated with that object through the show() method).
+Returns the human-readable description on success and undef on failure.
+
+=item attr(TYPE, NAME, ATTRIBUTE [, VALUE ...])
+
+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.
+
+If VALUE is not 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. To retrieve an attribute setting, the
+user must be authorized by the ADMIN ACL, the show ACL if set, or the
+owner ACL if the show ACL is not set.
+
+If VALUE is given, sets the given ATTRIBUTE values to VALUE, which is one
+or more attribute values. Pass the empty string as the only VALUE to
+clear the attribute values. Returns true on success and false on failure.
+To set an attribute value, the user must be authorized by the ADMIN ACL,
+the store ACL if set, or the owner ACL if the store ACL is not set.
+
+=item autocreate(TYPE, NAME)
+
+Creates a new object of type TYPE and name NAME. TYPE must be a
+recognized type for which the wallet system has a backend implementation.
+Returns true on success and false on failure.
+
+To create an object using this method, the current user must be authorized
+by the default owner as determined by the wallet configuration. For more
+information on how to map new objects to default owners, see
+Wallet::Config(3). Wallet administrators should use the create() method
+to create objects.
+
+=item check(TYPE, NAME)
+
+Check whether an object of type TYPE and name NAME exists. Returns 1 if
+it does, 0 if it doesn't, and undef if some error occurred while checking
+for the existence of the object.
+
+=item comment(TYPE, NAME, [COMMENT])
+
+Gets or sets the comment for the object identified by TYPE and NAME. If
+COMMENT is not given, returns the current comment or undef if no comment
+is set or on an error. To distinguish between an expiration that isn't
+set and a failure to retrieve the expiration, the caller should call
+error() after an undef return. If error() also returns undef, no comment
+was set; otherwise, error() will return the error message.
+
+If COMMENT is given, sets the comment to COMMENT. Pass in the empty
+string for COMMENT to clear the comment. To set a comment, the current
+user must be the object owner or be on the ADMIN ACL. Returns true for
+success and false for failure.
+
+=item create(TYPE, NAME)
+
+Creates a new object of type TYPE and name NAME. TYPE must be a
+recognized type for which the wallet system has a backend implementation.
+Returns true on success and false on failure.
+
+To create an object using this method, the current user must be authorized
+by the ADMIN ACL. Use autocreate() to create objects based on the default
+owner as determined by the wallet configuration.
+
+=item destroy(TYPE, NAME)
+
+Destroys the object identified by TYPE and NAME. This destroys any data
+that the wallet had saved about the object, may remove the underlying
+object from other external systems, and destroys the wallet database entry
+for the object. To destroy an object, the current user must be a member
+of the ADMIN ACL, authorized by the destroy ACL, or authorized by the
+owner ACL; however, if the destroy ACL is set, the owner ACL will not be
+checked. Returns true on success and false on failure.
+
+=item dbh()
+
+Returns the database handle of a Wallet::Server object. This is used
+mostly for testing; normally, clients should perform all actions through
+the Wallet::Server object to ensure that authorization and history logging
+is done properly.
+
+=item 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.
+
+=item expires(TYPE, NAME [, EXPIRES])
+
+Gets or sets the expiration for the object identified by TYPE and NAME.
+If EXPIRES is not given, returns the current expiration or undef if no
+expiration is set or on an error. To distinguish between an expiration
+that isn't set and a failure to retrieve the expiration, the caller should
+call error() after an undef return. If error() also returns undef, the
+expiration wasn't set; otherwise, error() will return the error message.
+
+If EXPIRES is given, sets the expiration to EXPIRES. 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. To set an expiration, the current user must be authorized by the
+ADMIN ACL. Returns true for success and false for failure.
+
+=item flag_clear(TYPE, NAME, FLAG)
+
+Clears the flag FLAG on the object identified by TYPE and NAME. To clear
+a flag, the current user must be authorized by the ADMIN ACL or the flags
+ACL on the object.
+
+=item flag_set(TYPE, NAME, FLAG)
+
+Sets the flag FLAG on the object identified by TYPE and NAME. To set a
+flag, the current user must be authorized by the ADMIN ACL or the flags
+ACL on the object.
+
+=item get(TYPE, NAME)
+
+Returns the data associated with the object identified by TYPE and NAME.
+Depending on the object TYPE, this may generate new data and invalidate
+any existing data or it may return data previously stored or generated.
+Note that this data may be binary and may contain nul characters. To get
+an object, the current user must either be authorized by the owner ACL or
+authorized by the get ACL; however, if the get ACL is set, the owner ACL
+will not be checked. Being a member of the ADMIN ACL does not provide any
+special privileges to get objects.
+
+Returns undef on failure. The caller should be careful to distinguish
+between undef and the empty string, which is valid object data.
+
+=item history(TYPE, NAME)
+
+Returns (as a string) the human-readable history of the object identified
+by TYPE and NAME, or undef on error. To see the object history, the
+current user must be a member of the ADMIN ACL, authorized by the show
+ACL, or authorized by the owner ACL; however, if the show ACL is set, the
+owner ACL will not be checked.
+
+=item owner(TYPE, NAME [, OWNER])
+
+Gets or sets the owner for the object identified by TYPE and NAME. If
+OWNER is not given, returns the current owner as the name of the ACL or
+undef if no owner is set or on an error. To distinguish between an owner
+that isn't set and a failure to retrieve the owner, the caller should call
+error() after an undef return. If error() also returns undef, that ACL
+wasn't set; otherwise, error() will return the error message.
+
+If OWNER is given, sets the owner to OWNER, which may be either the name
+of an ACL or a numeric ACL ID. To set an owner, the current user must be
+authorized by the ADMIN ACL. Returns true for success and false for
+failure.
+
+The owner of an object is permitted to get, store, and show that object,
+but cannot destroy or set flags on that object without being listed on
+those ACLs as well.
+
+=item schema()
+
+Returns the DBIx::Class schema object.
+
+=item show(TYPE, NAME)
+
+Returns (as a string) a human-readable representation of the metadata
+stored for the object identified by TYPE and NAME, or undef on error.
+Included is the metadata and entries of any ACLs associated with the
+object. To show an object, the current user must be a member of the ADMIN
+ACL, authorized by the show ACL, or authorized by the owner ACL; however,
+if the show ACL is set, the owner ACL will not be checked.
+
+=item store(TYPE, NAME, DATA)
+
+Stores DATA for the object identified with TYPE and NAME for later
+retrieval with get. Not all object types support this. Note that DATA
+may be binary and may contain nul characters. To store an object, the
+current user must either be authorized by the owner ACL or authorized by
+the store ACL; however, if the store ACL is set, the owner ACL is not
+checked. Being a member of the ADMIN ACL does not provide any special
+privileges to store objects. Returns true on success and false on
+failure.
+
+=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