aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/wallet.pod10
-rw-r--r--perl/Wallet/Server.pm91
-rwxr-xr-xperl/t/server.t94
-rwxr-xr-xserver/wallet-backend27
-rw-r--r--tests/server/backend-t.in37
5 files changed, 150 insertions, 109 deletions
diff --git a/client/wallet.pod b/client/wallet.pod
index 8d117e2..10c44ba 100644
--- a/client/wallet.pod
+++ b/client/wallet.pod
@@ -200,6 +200,16 @@ caution when removing entries from the C<ADMIN> ACL.
Display the name, numeric ID, and entries of the ACL <id>.
+=item autocreate <type> <name>
+
+Create a new object of type <type> with name <name>. The user must be
+listed in the default ACL for an object with that type and name, and the
+object will be created with that default ACL set as the object owner.
+
+Normally, there's no need to run this command directly. It's
+automatically run when trying to get or store an object that doesn't
+already exist.
+
=item check <type> <name>
Check whether an object of type <type> and name <name> already exists. If
diff --git a/perl/Wallet/Server.pm b/perl/Wallet/Server.pm
index b52f1aa..e698b39 100644
--- a/perl/Wallet/Server.pm
+++ b/perl/Wallet/Server.pm
@@ -174,9 +174,10 @@ sub create_check {
}
}
-# Create a new object and returns that object. On error, returns undef and
-# sets the internal error.
-sub create {
+# 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) {
@@ -186,28 +187,57 @@ sub create {
my $dbh = $self->{dbh};
my $user = $self->{user};
my $host = $self->{host};
+ my $object = eval { $class->create ($type, $name, $dbh, $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, $user);
+ 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);
- unless ($acl) {
- return unless $self->{admin}->check ($user);
- }
- my $object = eval { $class->create ($type, $name, $dbh, $user, $host) };
- if ($@) {
- $self->error ($@);
- return;
- } else {
- if ($acl and not $object->owner ($acl, $user, $host)) {
- $self->error ($object->error);
+ 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;
}
- return 1;
}
+ 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
@@ -407,11 +437,6 @@ sub check {
sub get {
my ($self, $type, $name) = @_;
my $object = $self->retrieve ($type, $name);
- if (not defined $object and $self->error =~ /^cannot find/) {
- if ($self->create ($type, $name)) {
- $object = $self->retrieve ($type, $name);
- }
- }
return unless defined $object;
return unless $self->acl_check ($object, 'get');
my $result = $object->get ($self->{user}, $self->{host});
@@ -427,11 +452,6 @@ sub get {
sub store {
my ($self, $type, $name, $data) = @_;
my $object = $self->retrieve ($type, $name);
- if (not defined $object and $self->error =~ /^cannot find/) {
- if ($self->create ($type, $name)) {
- $object = $self->retrieve ($type, $name);
- }
- }
return unless defined $object;
return unless $self->acl_check ($object, 'store');
if (not defined ($data)) {
@@ -842,6 +862,18 @@ 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
@@ -854,10 +886,9 @@ 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, the current user must either be authorized by the ADMIN
-ACL or 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).
+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)
diff --git a/perl/t/server.t b/perl/t/server.t
index f732af3..423127f 100755
--- a/perl/t/server.t
+++ b/perl/t/server.t
@@ -8,7 +8,7 @@
#
# See LICENSE for licensing terms.
-use Test::More tests => 338;
+use Test::More tests => 341;
use POSIX qw(strftime);
use Wallet::Admin;
@@ -742,12 +742,10 @@ is ($server->attr ('base', 'service/both', 'foo', 'foo'), undef,
is ($server->error, 'unknown attribute foo', ' but calls the method');
is ($server->destroy ('base', 'service/both'), 1, ' and we can destroy it');
is ($server->get ('base', 'service/both'), undef, ' and now cannot get it');
-is ($server->error, "$user2 not authorized to create base:service/both",
- ' because it is gone');
+is ($server->error, 'cannot find base:service/both', ' because it is gone');
is ($server->store ('base', 'service/both', 'stuff'), undef,
' or store it');
-is ($server->error, "$user2 not authorized to create base:service/both",
- ' because it is gone');
+is ($server->error, 'cannot find base:service/both', ' because it is gone');
# Test default ACLs on object creation.
#
@@ -786,9 +784,14 @@ package main;
# We're still user2, so we should now be able to create service/default. Make
# sure we can and that the ACLs all look good.
-is ($server->create ('base', 'service/default'), 1,
- 'Creating an object with the default ACL works');
-is ($server->create ('base', 'service/foo'), undef, ' but not any object');
+is ($server->create ('base', 'service/default'), undef,
+ 'Creating an object with the default ACL fails');
+is ($server->error, "$user2 not authorized to create base:service/default",
+ ' due to lack of authorization');
+is ($server->autocreate ('base', 'service/default'), 1,
+ ' but autocreation succeeds');
+is ($server->autocreate ('base', 'service/foo'), undef,
+ ' but not any object');
is ($server->error, "$user2 not authorized to create base:service/foo",
' with the right error');
$show = $server->show ('base', 'service/default');
@@ -812,11 +815,11 @@ EOO
}
# Try the other basic cases in default_owner.
-is ($server->create ('base', 'service/default-both'), undef,
+is ($server->autocreate ('base', 'service/default-both'), undef,
'Creating an object with an ACL mismatch fails');
is ($server->error, "ACL both exists and doesn't match default",
' with the right error');
-is ($server->create ('base', 'service/default-2'), 1,
+is ($server->autocreate ('base', 'service/default-2'), 1,
'Creating an object with an existing ACL works');
$show = $server->show ('base', 'service/default-2');
$show =~ s/(Created on:) [\d-]+ [\d:]+$/$1 0/m;
@@ -833,58 +836,22 @@ Members of ACL user2 (id: 3) are:
EOO
is ($show, $expected, ' and the created object and ACL are correct');
-# Test auto-creation on get and store.
+# Auto-creation does not work on get or store; this is done by the client.
$result = eval { $server->get ('base', 'service/default-get') };
-is ($result, undef, 'Auto-creation on get...');
-is ($@, "Do not instantiate Wallet::Object::Base directly\n", ' ...works');
-$show = $server->show ('base', 'service/default-get');
-$show =~ s/(Created on:) [\d-]+ [\d:]+$/$1 0/m;
-$expected = <<"EOO";
- Type: base
- Name: service/default-get
- Owner: user2
- Created by: $user2
- Created from: $host
- Created on: 0
-
-Members of ACL user2 (id: 3) are:
- krb5 $user2
-EOO
-is ($show, $expected, ' and the created object and ACL are correct');
-is ($server->get ('base', 'service/foo'), undef,
- ' but auto-creation of something else fails');
-is ($server->error, "$user2 not authorized to create base:service/foo",
- ' with the right error');
+is ($result, undef, 'Auto-creation on get fails');
+is ($@, '', ' does not die');
+is ($server->error, 'cannot find base:service/default-get',
+ ' and fails with the right error');
is ($server->store ('base', 'service/default-store', 'stuff'), undef,
- 'Auto-creation on store...');
-is ($server->error,
- "cannot store base:service/default-store: object type is immutable",
- ' ...works');
-$show = $server->show ('base', 'service/default-store');
-$show =~ s/(Created on:) [\d-]+ [\d:]+$/$1 0/m;
-$expected = <<"EOO";
- Type: base
- Name: service/default-store
- Owner: user2
- Created by: $user2
- Created from: $host
- Created on: 0
-
-Members of ACL user2 (id: 3) are:
- krb5 $user2
-EOO
-is ($show, $expected, ' and the created object and ACL are correct');
-is ($server->store ('base', 'service/foo', 'stuff'), undef,
- ' but auto-creation of something else fails');
-is ($server->error, "$user2 not authorized to create base:service/foo",
+ 'Auto-creation on store fails');
+is ($server->error, 'cannot find base:service/default-store',
' with the right error');
# Switch back to admin to test auto-creation.
$server = eval { Wallet::Server->new ($admin, $host) };
is ($@, '', 'Switching users back to admin works');
-$result = eval { $server->get ('base', 'service/default-admin') };
-is ($result, undef, 'Auto-creation on get...');
-is ($@, "Do not instantiate Wallet::Object::Base directly\n", ' ...works');
+is ($server->autocreate ('base', 'service/default-admin'), 1,
+ 'Autocreation works for admin');
$show = $server->show ('base', 'service/default-admin');
$show =~ s/(Created on:) [\d-]+ [\d:]+$/$1 0/m;
$expected = <<"EOO";
@@ -931,13 +898,28 @@ if ($server->create ('base', 'host/default.example.edu')) {
} else {
is ($server->error, '', ' as does creating host/default.example.edu');
}
+is ($server->destroy ('base', 'service/default-admin'), 1,
+ ' and destroying default-admin works');
+is ($server->destroy ('base', 'host/default.example.edu'), 1,
+ ' and destroying host/default.example.edu works');
is ($server->create ('base', 'host/default'), undef,
' but an unqualified host fails');
is ($server->error, 'base:host/default rejected: host default must be fully'
. ' qualified (add .example.edu)', ' with the right error');
+is ($server->create ('base', 'host/default.stanford.edu'), undef,
+ ' and a host in the wrong domain fails');
+is ($server->error, 'base:host/default.stanford.edu rejected: host'
+ . ' default.stanford.edu not in .example.edu domain',
+ ' with the right error');
+is ($server->autocreate ('base', 'service/default-admin'), 1,
+ 'Creating default/admin succeeds');
+is ($server->autocreate ('base', 'host/default'), undef,
+ ' but an unqualified host fails');
+is ($server->error, 'base:host/default rejected: host default must be fully'
+ . ' qualified (add .example.edu)', ' with the right error');
is ($server->acl_show ('auto-host'), undef, ' and the ACL is not present');
is ($server->error, 'ACL auto-host not found', ' with the right error');
-is ($server->create ('base', 'host/default.stanford.edu'), undef,
+is ($server->autocreate ('base', 'host/default.stanford.edu'), undef,
' and a host in the wrong domain fails');
is ($server->error, 'base:host/default.stanford.edu rejected: host'
. ' default.stanford.edu not in .example.edu domain',
diff --git a/server/wallet-backend b/server/wallet-backend
index 967f9b4..4a0b5cf 100755
--- a/server/wallet-backend
+++ b/server/wallet-backend
@@ -178,6 +178,9 @@ sub command {
} else {
error "unknown command acl $action";
}
+ } elsif ($command eq 'autocreate') {
+ check_args (2, 2, [], @args);
+ $server->autocreate (@args) or failure ($server->error, @_);
} elsif ($command eq 'check') {
check_args (2, 2, [], @args);
my $status = $server->check (@args);
@@ -328,14 +331,16 @@ B<wallet-backend> takes no traditional options.
=head1 COMMANDS
Most commands are only available to wallet administrators (users on the
-C<ADMIN> ACL). The exceptions are C<get>, C<store>, C<show>, C<destroy>,
-C<flag clear>, C<flag set>, C<getattr>, C<setattr>, and C<history>. All
-of those commands have their own ACLs except C<getattr> and C<history>,
-which use the C<show> ACL, and C<setattr>, which uses the C<store> ACL.
-If the appropriate ACL is set, it alone is checked to see if the user has
-access. Otherwise, C<get>, C<store>, C<show>, C<getattr>, C<setattr>, and
-C<history> access is permitted if the user is authorized by the owner ACL
-of the object.
+C<ADMIN> ACL). The exceptions are C<autocreate>, C<get>, C<store>,
+C<show>, C<destroy>, C<flag clear>, C<flag set>, C<getattr>, C<setattr>,
+and C<history>. All of those commands have their own ACLs except
+C<getattr> and C<history>, which use the C<show> ACL, and C<setattr>,
+which uses the C<store> ACL. If the appropriate ACL is set, it alone is
+checked to see if the user has access. Otherwise, C<get>, C<store>,
+C<show>, C<getattr>, C<setattr>, and C<history> access is permitted if the
+user is authorized by the owner ACL of the object. C<autocreate> is
+permitted if the user is listed in the default ACL for an object for that
+name.
Administrators can run any command on any object or ACL except for C<get>
and C<store>. For C<get> and C<store>, they must still be authorized by
@@ -391,6 +396,12 @@ caution when removing entries from the C<ADMIN> ACL.
Display the name, numeric ID, and entries of the ACL <id>.
+=item autocreate <type> <name>
+
+Create a new object of type <type> with name <name>. The user must be
+listed in the default ACL for an object with that type and name, and the
+object will be created with that default ACL set as the object owner.
+
=item check <type> <name>
Check whether an object of type <type> and name <name> already exists. If
diff --git a/tests/server/backend-t.in b/tests/server/backend-t.in
index 3047ebd..0b65c07 100644
--- a/tests/server/backend-t.in
+++ b/tests/server/backend-t.in
@@ -10,7 +10,7 @@
use strict;
use IO::String;
-use Test::More tests => 1245;
+use Test::More tests => 1263;
# Create a dummy class for Wallet::Server that prints what method was called
# with its arguments and returns data for testing.
@@ -93,6 +93,12 @@ sub attr {
}
}
+sub autocreate {
+ shift;
+ print "autocreate @_\n";
+ return ($_[0] eq 'error') ? undef : 1
+}
+
sub check {
shift;
print "check @_\n";
@@ -206,19 +212,20 @@ is ($OUTPUT, "error for admin (1.2.3.4): unknown command flag foo\n",
is ($out, "$new\n", ' and nothing ran');
# Check too few, too many, and bad arguments for every command.
-my %commands = (check => [2, 2],
- create => [2, 2],
- destroy => [2, 2],
- expires => [2, 4],
- get => [2, 2],
- getacl => [3, 3],
- getattr => [3, 3],
- history => [2, 2],
- owner => [2, 3],
- setacl => [4, 4],
- setattr => [4, 9],
- show => [2, 2],
- store => [3, 3]);
+my %commands = (autocreate => [2, 2],
+ check => [2, 2],
+ create => [2, 2],
+ destroy => [2, 2],
+ expires => [2, 4],
+ get => [2, 2],
+ getacl => [3, 3],
+ getattr => [3, 3],
+ history => [2, 2],
+ owner => [2, 3],
+ setacl => [4, 4],
+ setattr => [4, 9],
+ show => [2, 2],
+ store => [3, 3]);
my %acl_commands = (add => [3, 3],
create => [1, 1],
destroy => [1, 1],
@@ -315,7 +322,7 @@ for my $command (sort keys %flag_commands) {
# Now, test that we ran the right functions and passed the correct arguments.
my $error = 1;
-for my $command (qw/create destroy setacl setattr store/) {
+for my $command (qw/autocreate create destroy setacl setattr store/) {
my $method = { setacl => 'acl', setattr => 'attr' }->{$command};
$method ||= $command;
my @extra = ('foo') x ($commands{$command}[0] - 2);